package co.touchlab.android.threading.tasks;

import android.app.Application;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import co.touchlab.android.threading.utils.UiThreadContext;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

/**
 * Relatively simple queue implementation.  See TaskQueueActual for detail on implementation.
 * <p/>
 * Created by kgalligan on 7/5/14.
 */
public class TaskQueue
{
    private static Map<String, TaskQueue> queueMap = new HashMap<String, TaskQueue>();
    private static final String DEFAULT_QUEUE = "__DEFAULT";

    /**
     * Get a direct reference to your queue.  Call on main thread.
     * @param name
     * @return
     */
    public static synchronized TaskQueue loadQueue(String name)
    {
        TaskQueue taskQueueActual = queueMap.get(name);
        if(taskQueueActual == null)
        {
            taskQueueActual = new TaskQueue();
            queueMap.put(name, taskQueueActual);
        }

        return taskQueueActual;
    }

    /**
     * The default queue
     * @return
     */
    public static TaskQueue loadQueueDefault()
    {
        return loadQueue(DEFAULT_QUEUE);
    }


    /**
     * Puts a task on the queue. Use for local ops. Faster responses.
     *
     * @param context
     * @param task
     */
    public static void execute(Context context, String queue, Task task)
    {
        TaskQueue queueActual = loadQueue(queue);
        queueActual.execute(context, task);
    }

    //*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~
    //*~*~*~*~*~*~*~*~*~*~ PER INSTANCE *~*~*~*~*~*~*~*~*~*~*~*~*~
    //*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~

    private final Handler handler;
    private final PollRunnable pollRunnable = new PollRunnable();
    private final PostExeRunnable postExeRunnable = new PostExeRunnable();
    private final ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory()
    {
        @Override
        public Thread newThread(Runnable r)
        {
            return new Thread(r);
        }
    });
    private Queue<Task> tasks = new LinkedList<Task>();
    private Task currentTask;
    private Application application;

    public TaskQueue()
    {
        handler = new Handler(Looper.getMainLooper());
    }

    /**
     * Puts a task on the queue.  Call on main thread only.
     *
     * @param context
     * @param task
     */
    public void execute(final Context context, final Task task)
    {
        if(UiThreadContext.isInUiThread())
        {
            callExecute(context, task);
        }
        else
        {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    callExecute(context, task);
                }
            });
        }
    }

    private void callExecute(Context context, Task task)
    {
        UiThreadContext.assertUiThread();

        if(application == null)
            application = (Application) context.getApplicationContext();

        tasks.add(task);

        resetPollRunnable();
    }

    private void resetPollRunnable()
    {
        handler.removeCallbacks(pollRunnable);
        handler.post(pollRunnable);
    }

    private class PollRunnable implements Runnable
    {
        @Override
        public void run()
        {
            UiThreadContext.assertUiThread();

            if(currentTask == null)
            {
                Task task = tasks.poll();
                if (task != null)
                {
                    currentTask = task;
                    executorService.execute(new ExeTask(task));
                }
            }
        }
    }

    private class ExeTask implements Runnable
    {
        private Task task;

        private ExeTask(Task task)
        {
            this.task = task;
        }

        @Override
        public void run()
        {
            UiThreadContext.assertBackgroundThread();

            try
            {
                task.run(application);
            }
            catch (Throwable e)
            {
                boolean handled = task.handleError(e);
                if (!handled)
                    handler.post(new ThrowRunnable(e));
            }
            finally
            {
                handler.post(postExeRunnable);
            }
        }
    }

    private class ThrowRunnable implements Runnable
    {
        private Throwable cause;

        private ThrowRunnable(Throwable cause)
        {
            this.cause = cause;
        }

        @Override
        public void run()
        {
            if(cause instanceof RuntimeException)
                throw (RuntimeException)cause;
            else if(cause instanceof Error)
                throw (Error)cause;
            else
                throw new RuntimeException(cause);
        }
    }

    private class PostExeRunnable implements Runnable
    {
        @Override
        public void run()
        {
            UiThreadContext.assertUiThread();

            try
            {
                if(currentTask != null)
                {
                    Task task = currentTask;
                    currentTask = null;
                    task.onComplete(application);
                }
            }
            finally
            {
                resetPollRunnable();
            }
        }
    }

    /**
     * Query existing tasks.  Call on main thread only.
     *
     * @param queueQuery
     */
    public void query(QueueQuery queueQuery)
    {
        UiThreadContext.assertUiThread();

        for (Task task : tasks)
        {
            queueQuery.query(task);
        }

        if(currentTask != null)
            queueQuery.query(currentTask);
    }

    public interface QueueQuery
    {
        void query(Task task);
    }
}

@@ -3595,32 +3595,33 @@
 %0A%0A            if
+ 
 (currentTask == 
@@ -3617,17 +3617,17 @@
 entTask 
-=
+!
 = null)%0A
@@ -3630,38 +3630,45 @@
 ll)%0A            
-%7B%0A
     
+return;%0A%0A
             Task
@@ -3697,28 +3697,24 @@
             
-    
 if (task != 
@@ -3727,32 +3727,26 @@
             
-    %7B%0A  
+%7B%0A
             
@@ -3741,34 +3741,32 @@
                 
-  
 currentTask = ta
@@ -3765,28 +3765,24 @@
 ask = task;%0A
-    
             
@@ -3829,34 +3829,16 @@
 task));%0A
-                %7D%0A
         

Check if current task exists