package me.km.scheduler;

import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.LinkedList;
import me.hammerle.snuviscript.code.ISnuviLogger;
import me.hammerle.snuviscript.code.ISnuviScheduler;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;

@OnlyIn(Dist.DEDICATED_SERVER)
public class SnuviScheduler implements ISnuviScheduler
{
    private final ISnuviLogger logger;
    private boolean checker;
    private int counter;
    private final LinkedList<SnuviTask> addQueue = new LinkedList<>();
    private final LinkedList<Integer> removeQueue = new LinkedList<>();
    private final HashMap<Integer, SnuviTask> tasks = new HashMap<>();
    private final AsyncWorker worker = new AsyncWorker();
    private boolean noAdding = false;
    
    public SnuviScheduler(ISnuviLogger logger)
    {
        this.logger = logger;
        counter = 0;
        worker.start();
    }
    
    public void stop()
    {
        noAdding = true;
        long endTime = System.nanoTime() + 60_000_000_000l;
        logger.print("Waiting until end of synchronized tasks ...");
        while(!tasks.isEmpty() && System.nanoTime() < endTime)
        {
            tick();
        }
        logger.print("Waiting until end of asynchronous tasks ...");
        while(worker.hasWork() && System.nanoTime() < endTime)
        {
            try
            {
                Thread.sleep(250);
            }
            catch(InterruptedException ex)
            {
            }
        }
        logger.print("the scheduler is now stopped");
    }
    
    public void cancelTask(int id)
    {
        removeQueue.add(id);
    }
    
    public int scheduleRepeatingTask(Runnable r, long ticks, long rtimer)
    {
        if(noAdding)
        {
            logger.print("scheduling tasks is not allowed on server stop");
            return -1;
        }
        synchronized(addQueue)
        {
            counter++;
            addQueue.add(new SnuviTask(counter, r, ticks, rtimer));
        }
        return counter;
    }
    
    @Override
    public int scheduleTask(Runnable r, long delay)
    { 
        return scheduleRepeatingTask(r, delay, 0);
    }
    
    @Override
    public int scheduleTask(Runnable r)
    { 
        return scheduleTask(r, 0);
    }
    
    public void tick()
    {
        worker.doWork();
        try
        {
            if(!removeQueue.isEmpty())
            {
                removeQueue.forEach(i -> tasks.remove(i));
                removeQueue.clear();
            }
            synchronized(addQueue)
            {
                if(!addQueue.isEmpty())
                {
                    addQueue.forEach(task -> tasks.put(task.getId(), task));
                    addQueue.clear();
                }
            }
            checker = false;
            long l = System.currentTimeMillis();
            tasks.values().removeIf(task -> 
            {
                if(checker)
                {
                    return false;
                }
                if(System.currentTimeMillis() - l > 25)
                {
                    checker = true;
                    logger.print("scheduler has run longer than 25 ms");
                    return false;
                }
                try
                {
                    return task.tick(noAdding);
                }
                catch(Exception ex)
                {
                    logger.print("scheduler exception - task will be removed");
                    ex.printStackTrace();
                    return true;
                }
            });
        }
        catch(ConcurrentModificationException ex)
        {
            logger.print(ex);
        }
    }
    
    public AsyncWorker getWorker()
    {
        return worker;
    }
}
