using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace ECOT.Engine
{
    #region Delegates

    /// <summary>
    /// A threaded task
    /// </summary>
    public delegate void ThreadTaskDelegate();

    /// <summary>
    /// A method to be called when a task finishes
    /// </summary>
    /// <param name="taskId">The task identifier for this task (-1 by default)</param>
#if DEBUG
    public delegate void TaskFinishedDelegate(int taskId, int duration);
#else
    public delegate void TaskFinishedDelegate(int taskId);
#endif

    #endregion

    /// <summary>
    /// A task to thread
    /// </summary>
    public struct ThreadTask
    {
        #region Members

        /// <summary>
        /// The task this thread runs
        /// </summary>
        private ThreadTaskDelegate task;

        /// <summary>
        /// Gets the task this thread runs
        /// </summary>
        public ThreadTaskDelegate Task { get { return task; } }

        /// <summary>
        /// The method called when the task is finished
        /// </summary>
        private TaskFinishedDelegate taskFinished;

        /// <summary>
        /// Gets the task finished method
        /// </summary>
        public TaskFinishedDelegate TaskFinished { get { return taskFinished; } }

        /// <summary>
        /// A task identifier (-1 by default)
        /// </summary>
        private int taskId;

        /// <summary>
        /// Gets the task identifier of this task (-1 by default)
        /// </summary>
        public int TaskId { get { return taskId; } }

#if DEBUG
        public long StartTime;
#endif

        #endregion

        /// <summary>
        /// Initializes a thread task
        /// </summary>
        /// <param name="task">the method to call</param>
        /// <param name="taskFinished">The method to call when finished</param>
        /// <param name="taskId">An identifier for this task</param>
        public ThreadTask(
            ThreadTaskDelegate task,
            TaskFinishedDelegate taskFinished,
            int taskId)
        {
            this.task = task;
            this.taskFinished = taskFinished;
            this.taskId = taskId;
#if DEBUG
            this.StartTime = 0;
#endif
        }

        /// <summary>
        /// Initializes a thread task
        /// </summary>
        /// <param name="task">the method to call</param>
        /// <param name="taskFinished">The method to call when finished</param>
        public ThreadTask(
            ThreadTaskDelegate task,
            TaskFinishedDelegate taskFinished)
            :
            this(task, taskFinished, -1)
        {
        }

        /// <summary>
        /// Initializes a thread task
        /// </summary>
        /// <param name="task">the method to call</param>
        public ThreadTask(ThreadTaskDelegate task)
            : this(task, null, -1)
        {
        }

        /// <summary>
        /// Initializes a thread task
        /// </summary>
        /// <param name="task">the method to call</param>
        public ThreadTask(ThreadTaskDelegate task, int taskId)
            : this(task, null, taskId)
        {
        }
    }

    /// <summary>
    /// A managed thread
    /// </summary>
    public class ManagedThread
    {
        #region Members

        /// <summary>
        /// The thread managed by this object
        /// </summary>
        private Thread thread;

        /// <summary>
        /// The processor this thread should run on
        /// </summary>
        private int processorAffinity;

        /// <summary>
        /// True when this thread should be killed
        /// </summary>
        private bool killThread = false;

        /// <summary>
        /// Tasks to be run by this thread
        /// </summary>
        private Queue<ThreadTask> tasks = new Queue<ThreadTask>();

        private bool isWorking = false;

        #endregion

        #region Methods

        /// <summary>
        /// Initializes the thread with a specified processor affinity
        /// </summary>
        public ManagedThread(int processorAffinity)
        {
            this.processorAffinity = processorAffinity;

            // Create the starter object
            ThreadStart starter = new ThreadStart(taskRunner);
            thread = new Thread(starter);
            thread.Start();
        }

        /// <summary>
        /// Initializes the thread
        /// </summary>
        public ManagedThread()
            : this(-1)
        { }

        /// <summary>
        /// Runs tasks when there are any to run
        /// </summary>
        private void taskRunner()
        {
            #region Set processor
#if XBOX360
            // Ensure processor affinity is within range (1, 3, 4 or 5)
            if (processorAffinity > 0 && processorAffinity < 6 &&
                processorAffinity != 2)
            {
                // Set the processor affinity
                thread.SetProcessorAffinity(new int[] { processorAffinity });
            }
#endif
            #endregion

            #region Run Tasks

            // The task to run
            ThreadTask task;

            while (!killThread)
            {
                if (tasks.Count > 0)
                {
                    this.isWorking = true;

                    // Run the task
                    lock (tasks)
                    {
                        task = tasks.Dequeue();
                    }

#if DEBUG
                    task.StartTime = DateTime.Now.Ticks;
#endif
                    task.Task();
                    if (task.TaskFinished != null)
                    {
#if DEBUG
                        task.TaskFinished(task.TaskId, (int)(DateTime.Now.Ticks - task.StartTime) / 1000);
#else
                        task.TaskFinished(task.TaskId);
#endif
                    }
                }
                else
                {
                    // Wait for more tasks to run
                    this.isWorking = false;
                    Thread.Sleep(0);
                }
            }

            #endregion

            // Clean up
            tasks.Clear();
            tasks = null;
        }

        /// <summary>
        /// Kills this thread, stopping at next safe point
        /// </summary>
        public void Kill()
        {
            killThread = true;
        }

        /// <summary>
        /// Kills the thread immediately
        /// </summary>
        public void KillImmediately()
        {
            killThread = true;
            thread.Abort();
        }

        public void Join()
        {
            thread.Join();
        }

        /// <summary>
        /// Adds a task to this thread
        /// </summary>
        /// <param name="task">The task to perform</param>
        public void AddTask(ThreadTask task)
        {
            lock (tasks)
            {
                tasks.Enqueue(task);
            }
        }

        #endregion

        public bool IsWorking
        {
            get { return this.isWorking; }
        }

        public int TasksCount
        {
            get { return this.tasks.Count; }
        }
    }

    public class ThreadPool
    {
        /// <summary>
        /// The 2 left hardware threads of the Xbox 360, and 2 threads
        /// </summary>
        private ManagedThread[] threads = new ManagedThread[2];

        public ThreadPool()
        {
            this.threads[0] = new ManagedThread(1);
            this.threads[1] = new ManagedThread(5);
        }

        /// <summary>
        /// Adds a task to this thread
        /// </summary>
        /// <param name="task">The task to perform</param>
        public int AddTask(ThreadTask task)
        {
            // Firstly try to assign the task to a idle thread
            for (int i = 0; i < this.threads.Length; i++)
            {
                if (!this.threads[i].IsWorking)
                {
                    this.threads[i].AddTask(task);
                    return i;
                }
            }

            int selThread = 0;

            // If all threads are working, we queue the task in the thread with the less tasks
            for (int i = 1; i < this.threads.Length; i++)
                if (this.threads[i].TasksCount < this.threads[selThread].TasksCount)
                    selThread = i;
            this.threads[selThread].AddTask(task);

            return selThread;
        }

        public void Kill()
        {
            for (int i = 0; i < this.threads.Length; i++)
                this.threads[i].KillImmediately();
        }
    }
}
