using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using System.Threading;

namespace MikuMikuDance.XNA.MultiThread
{
    /// <summary>
    /// ゲームスレッドクラス
    /// </summary>
    public abstract class GameThread<BufferType> : IGameThread
        where BufferType:class, new()
    {
        private volatile int currentBuf;
        private volatile BufferType[] buffers;
        volatile bool StartEndCheck = false;
#if XBOX
        /// <summary>
        /// ハードウェアスレッド番号
        /// </summary>
        protected readonly int HWThreadNum;
#endif
        private volatile AutoResetEvent FrameStart;
        private volatile AutoResetEvent FrameEnd;
        /// <summary>
        /// スレッドマネージャ
        /// </summary>
        protected ThreadManager Manager { get; private set; }
        /// <summary>
        /// スレッド
        /// </summary>
        public Thread ThreadObj { get; private set; }
        /// <summary>
        /// Gameオブジェクト
        /// </summary>
        protected Game Game { get; private set; }

        internal volatile GameTime gameTime;
        /// <summary>
        /// スレッド側で書き込んでいないバッファオブジェクト
        /// </summary>
        public BufferType FreeBuffer { get { return buffers[(currentBuf + 1) % 2]; } }
        /// <summary>
        /// スレッド側で書き込み可能なバッファオブジェクト
        /// </summary>
        protected BufferType WritableBuffer { get { return buffers[currentBuf]; } }
#if XBOX
        /// <summary>
        /// ゲームスレッドコンストラクタ
        /// </summary>
        /// <param name="HWThreadNum">ハードウェアスレッド番号</param>
        /// <param name="game">Gameクラス</param>
        /// <param name="manager">スレッドマネージャ</param>
        public GameThread(int HWThreadNum, Game game, ThreadManager manager)
#else
        /// <summary>
        /// ゲームスレッドコンストラクタ
        /// </summary>
        /// <param name="game">Gameクラス</param>
        /// <param name="manager">スレッドマネージャ</param>
        public GameThread(Game game, ThreadManager manager)
#endif
        {
            Game = game;
            Manager = manager;
            if (manager != null)
                manager.AddThread(this);
            FrameStart = new AutoResetEvent(false);
            FrameEnd = new AutoResetEvent(false);
            buffers = new BufferType[2] { new BufferType(), new BufferType() };
#if XBOX
            this.HWThreadNum = HWThreadNum;
#endif
            Reset();
            ThreadObj = new Thread(new ThreadStart(work));
            ThreadObj.Start();
        }
        /// <summary>
        /// リセット
        /// </summary>
        protected virtual void Reset()
        {
            currentBuf = 0;
            FrameStart.Reset();
            FrameEnd.Reset();
        }
        /// <summary>
        /// スレッドの破棄
        /// </summary>
        public virtual void Dispose()
        {
            if (ThreadObj != null)
            {
                ThreadObj.Abort();
                ThreadObj = null;
                FrameStart.Close();
                FrameEnd.Close();
            }
        }

        private void work()
        {
#if XBOX
            Thread.CurrentThread.SetProcessorAffinity(HWThreadNum);
#endif
            try
            {
                while (true)
                {
                    FrameStart.WaitOne();//同期を取る
                    Thread.MemoryBarrier();
                    if (Manager != null)
                        gameTime = Manager.gameTime;
                    this.Update(gameTime);
                    Thread.MemoryBarrier();
                    FrameEnd.Set();//終了同期取りに伝える
                }
            }
            catch (ThreadAbortException)
            { }
        }
        /// <summary>
        /// ゲームスレッドの更新処理
        /// </summary>
        /// <param name="gameTime">GameTimeオブジェクト</param>
        public abstract void Update(GameTime gameTime);
        
        
        internal virtual void SwapBuffer()
        {
            currentBuf = (currentBuf + 1) % 2;
        }

        /// <summary>
        /// スレッドにフレーム開始を伝える
        /// </summary>
        public virtual void StartFrame()
        {
            SwapBuffer();
            FrameStart.Set();//スレッドに開始を伝える
            StartEndCheck = true;
        }
        /// <summary>
        /// スレッドがフレーム処理を終了するまで待つ
        /// </summary>
        public virtual void Synchronize()
        {
            if (!StartEndCheck)
                return;
            FrameEnd.WaitOne();//スレッドが1フレームの処理を完了するまで待つ
            StartEndCheck = false;
        }

        
    }
}