/**
 * Copyright (c) 2009-2011, The HATS Consortium. All rights reserved.
 * This file is licensed under the terms of the Modified BSD License.
 */
package org.abs_models.backend.java.scheduling;

import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;

import org.abs_models.backend.java.lib.runtime.ABSGuard;
import org.abs_models.backend.java.lib.runtime.ABSRuntime;
import org.abs_models.backend.java.lib.runtime.ABSThread;
import org.abs_models.backend.java.lib.runtime.ABSThreadManager;
import org.abs_models.backend.java.lib.runtime.COG;
import org.abs_models.backend.java.lib.runtime.Logging;
import org.abs_models.backend.java.lib.runtime.Task;
import org.abs_models.backend.java.observing.TaskSchedulerView;
import org.abs_models.backend.java.observing.TaskView;

public class DefaultTaskScheduler implements TaskScheduler {
    private static final Logger log = Logging.getLogger(ABSRuntime.class.getName());

    private final List<Task<?>> newTasks = new LinkedList<>();
    private final List<SchedulerThread> suspendedTasks = new LinkedList<>();
    private Task<?> activeTask;
    private volatile SchedulerThread thread;
    private final COG cog;
    private final ABSThreadManager threadManager;

    public DefaultTaskScheduler(COG cog, ABSThreadManager m) {
        this.cog = cog;
        this.threadManager = m;
    }

    @Override
    public synchronized void addTask(Task<?> task) {
        newTasks.add(task);
        if (view != null)
            view.taskAdded(task.getView());
        log.finest(task + " ADDED TO QUEUE");

        if (thread == null) {
            thread = new SchedulerThread();
            thread.start();
        } else {
            notifyAll();
        }
    }

    class SchedulerThread extends ABSThread {
        private Task<?> runningTask;

        public SchedulerThread() {
            super(threadManager);
            setName("ABS Scheduler Thread of " + cog.toString());
            setCOG(cog);
        }

        @Override
        public void run() {
            try {
            loop:
            while (!shutdown) {
                synchronized (DefaultTaskScheduler.this) {
                    activeTask = null;
                    if (newTasks.isEmpty()) {
                        thread = null;
                        DefaultTaskScheduler.this.notifyAll();
                        break loop;
                    }

                    activeTask = newTasks.remove(0);
                    runningTask = activeTask;
                    setName("ABS Scheduler Thread executing " + activeTask.toString());
                }

                View v = view;

                log.finest("Executing " + runningTask);
                try {
                    runningTask.run();
                    v = view;
                    log.finest("Task " + runningTask + " FINISHED");

                } catch (Exception e) {
                    log.finest("EXCEPTION in Task " + runningTask);
                    e.printStackTrace();
                }
            }
            } finally {
                finished();
            }
        }

        // assume called in synchronized block
        public void suspendTask(ABSGuard g) {
            log.finest(runningTask + " on " + g + " SUSPENDING");
            synchronized (DefaultTaskScheduler.this) {
                activeTask = null;
                thread = null;
                if (!newTasks.isEmpty()) {
                    log.finest(runningTask + " on " + g + " Starting new Scheduler Thread");

                    thread = new SchedulerThread();
                    thread.start();
                } else {
                    DefaultTaskScheduler.this.notifyAll();
                }
                log.finest(runningTask + " on " + g + " SUSPENDING");
                suspendedTasks.add(this);
            }

            View v = view;
            if (v != null) {
                v.taskSuspended(runningTask.getView(), g);
            }

            log.finest(runningTask + " AWAITING " + g);
            boolean couldBecomeFalse = g.await();
            if (Thread.interrupted()) {
                return;
            }

            if (!couldBecomeFalse) {
                log.finest(runningTask + " " + g + " READY");
                if (v != null)
                    v.taskReady(runningTask.getView());
            }

            synchronized (DefaultTaskScheduler.this) {
                while (!(g.isTrue() && thread == null)) {
                    try {
                        log.finest(runningTask + " " + g + " WAITING FOR WAKE UP");
                        DefaultTaskScheduler.this.wait();
                        log.finest(runningTask + " WOKE UP...");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        break;
                    }
                }
                thread = this;
                activeTask = runningTask;
                suspendedTasks.remove(this);
            }

            if (v != null)
                v.taskResumed(runningTask.getView(), g);

            log.finest(runningTask + " " + g + " ACTIVE");
        }
    }

    @Override
    public void await(ABSGuard g) {
        thread.suspendTask(g);
    }

    @Override
    public synchronized Task<?> getActiveTask() {
        return activeTask;
    }

    private volatile View view;

    @Override
    public synchronized TaskSchedulerView getView() {
        if (view == null) {
            view = new View();
        }
        return view;
    }

    private class View extends AbstractTaskSchedulerView {
        @Override
        public List<TaskView> getReadyTasks() {
            return null;
        }

        @Override
        public List<TaskView> getSuspendedTasks() {
            return null;
        }

        @Override
        public List<TaskView> getSchedulableTasks() {
            return null;
        }

        @Override
        public TaskView getActiveTask() {
            return DefaultTaskScheduler.this.getActiveTask().getView();
        }

    }

    public static TaskSchedulerFactory getFactory() {
        return new TaskSchedulerFactory() {
            @Override
            public TaskScheduler createTaskScheduler(ABSRuntime runtime, COG cog, ABSThreadManager m, SchedulableTasksFilter filter) {
                return new DefaultTaskScheduler(cog, m);
            }
        };
    }

    @Override
    public COG getCOG() {
        return cog;
    }

}
