using System;
using BulletX.BulletCollision.BroadphaseCollision;
using BulletX.BulletCollision.CollisionDispatch;
using BulletX.BulletCollision.CollisionShapes;
using BulletX.BulletDynamics.ConstraintSolver;
using BulletX.BulletDynamics.Dynamics;
using BulletX.LinerMath;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using MikuMikuDance.XNA.Accessory;
using MikuMikuDance.XNA.Debug;
using MikuMikuDance.XNA.Model;
using MikuMikuDance.XNA.Model.ModelData;
using MikuMikuDance.XNA.Motion;
using MikuMikuDance.XNA.Motion.MotionData;
using MikuMikuDance.XNA.ShadowMap;
using MikuMikuDance.XNA.Stages;
using MikuMikuDance.XNA.MultiThread;

namespace MikuMikuDance.XNA
{
    delegate void DrawDelegate(GraphicsDevice graphics, long totalTick, bool IsShadow);
    /// <summary>
    /// Update系イベント用デリゲート
    /// </summary>
    /// <param name="gameTime">GameTimeオブジェクト</param>
    public delegate void UpdateDelegate(GameTime gameTime);
    /// <summary>
    /// FPSモード指定
    /// </summary>
    public enum FPSMode
    {
        /// <summary>
        /// 60FPSモード(Windowsのみ)
        /// </summary>
        FPS60,
        /// <summary>
        /// 30FPSモード(WindowsとXBox両方可能)
        /// </summary>
        FPS30,
    }
    /// <summary>
    /// MikuMikuDance for XNAのスタートクラス
    /// </summary>
    public class MikuMikuDanceXNA : DrawableGameComponent
    {
        //物理演算設定値
        internal static int MaxSubStep = 1;
        internal static float FixedTimeStep = 1f / 60f;

        IMMDCamera m_camera = null;
        IMMDLightManager m_lightmanager = null;
        //DrawManager
        internal DrawManager DrawManager = null;
        //DrawEvent
        internal event DrawDelegate DrawModelEvent;
        internal event DrawDelegate DrawAccessoryEvent;

        /// <summary>
        /// モーショントラックの最大トラック数
        /// </summary>
        public const int MotionTrackCap = 30;

        //物理エンジン用変数(ラッパーがGCでうっかり削除されないように)
        ICollisionConfiguration config = null;
        CollisionDispatcher dispatcher = null;
        IBroadphaseInterface pairCache = null;
        IConstraintSolver solver = null;
        StaticPlaneShape ground = null;
        DefaultMotionState groundMotionState;
        RigidBody groundBody;

#if XBOX360//スレッド用の定数。ベイク処理等に使用
        internal const int NumThread = 4;//使用するスレッド数
#else
        internal readonly int NumThread;
#endif
        
        //properties...
        /// <summary>
        /// MikuMikuDance for XNAで用いるコンテンツマネージャ
        /// </summary>
        public ContentManager Content { get; set; }
        /// <summary>
        /// MikuMikuDance for XNAカメラ
        /// </summary>
        public IMMDCamera Camera { get { return m_camera; } set { if (value == null) throw new System.ApplicationException("nullは代入出来ません"); m_camera = value; } }
        /// <summary>
        /// ライトマネージャ
        /// </summary>
        public IMMDLightManager LightManager { get { return m_lightmanager; } set { if (value == null) throw new System.ApplicationException("nullは代入出来ません"); m_lightmanager = value; } }
        /// <summary>
        /// 物理エンジン
        /// </summary>
        public DiscreteDynamicsWorld Physic { get; internal set; }
        /// <summary>
        /// シャドウマップマネージャ
        /// </summary>
        public IShadowMapManager ShadowMapManager { get; set; }
        /// <summary>
        /// スクリーンマネージャ
        /// </summary>
        public IScreenManager ScreenManager { get; set; }
        /// <summary>
        /// 物理演算を使用フラグ
        /// </summary>
        public bool UsePhysic { get; set; }
        /// <summary>
        /// スレッドマネージャ
        /// </summary>
        public ThreadManager ThreadManager { get; private set; }
        /// <summary>
        /// 物理演算用スレッド
        /// </summary>
        public PhysicsThread PhysicsThread { get; private set; }
#if TRACE
        //MMDX内の計測がマルチスレッド化で衝突を起こすので、ルール検討中……(決まるまで使用不可)
        /// <summary>
        /// デバッグ用のタイム計測オブジェクト格納場所
        /// </summary>
        internal ITimeRuler TimeRular = null;
#endif

        //物理演算用の地面(仮置き)
        private RigidBody Ground { get { return groundBody; } }
        /// <summary>
        /// MikuMikuDance for XNAに必要となる環境を満たしているかチェックし、基本設定を行う
        /// </summary>
        /// <param name="game">Gameオブジェクト(WinForm時はnull)</param>
        /// <param name="gdManager">GraphicsDeviceManager</param>
        /// <param name="UseAntiAlias">アンチエイリアス使用フラグ</param>
        /// <returns>今のところtrueを返す</returns>
        /// <remarks>シェーダ要件を満たしていない場合はXNAのダイアログが出てアプリケーションが終了する。アンチエイリアスはx2を使用</remarks>
        public static bool InitialSetup(Game game, GraphicsDeviceManager gdManager, bool UseAntiAlias)
        {
            return InitialSetup(game, gdManager, UseAntiAlias, FPSMode.FPS60);
        }
        /// <summary>
        /// MikuMikuDance for XNAに必要となる環境を満たしているかチェックし、基本設定を行う
        /// </summary>
        /// <param name="game">Gameオブジェクト(WinForm時はnull)</param>
        /// <param name="gdManager">GraphicsDeviceManager</param>
        /// <param name="UseAntiAlias">アンチエイリアス使用フラグ</param>
        /// <param name="fpsMode">FPSモード</param>
        /// <returns>今のところtrueを返す</returns>
        /// <remarks>シェーダ要件を満たしていない場合はXNAのダイアログが出てアプリケーションが終了する。アンチエイリアスはx2を使用</remarks>
        public static bool InitialSetup(Game game, GraphicsDeviceManager gdManager, bool UseAntiAlias, FPSMode fpsMode)
        {
#if! XBOX
            if (game != null && fpsMode == FPSMode.FPS30)
#endif
            {
                //30FPSモード
                game.TargetElapsedTime = new TimeSpan(333333);
                FixedTimeStep = 1f / 30f;
            }
#if! XBOX
            else
            {
                //60FPSモード
                game.TargetElapsedTime = new TimeSpan(166666);
                FixedTimeStep = 1f / 60f;
            }
#endif
            //MMDXにはシェーダモデル3.0が必要
            gdManager.MinimumVertexShaderProfile = ShaderProfile.VS_3_0;
            gdManager.MinimumPixelShaderProfile = ShaderProfile.PS_3_0;
            if (UseAntiAlias)
            {
                gdManager.PreferMultiSampling = true;
                gdManager.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>(gdManager_PreparingDeviceSettings);
            }
            return true;//今のところ、他に必須環境は無いのでtrueを返す
        }
        //アンチエイリアス設定
        static void gdManager_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
        {
            //PresentationParameters取得
            PresentationParameters pp =
                e.GraphicsDeviceInformation.PresentationParameters;
#if XBOX
            pp.MultiSampleQuality = 0;
            pp.MultiSampleType = MultiSampleType.TwoSamples;
            return;
#else
            int quality = 0;
            GraphicsAdapter adapter = e.GraphicsDeviceInformation.Adapter;
            SurfaceFormat format = adapter.CurrentDisplayMode.Format;
            //2xAAが使えるかテスト
            if (adapter.CheckDeviceMultiSampleType(DeviceType.Hardware,
                format, false, MultiSampleType.TwoSamples, out quality))
            {
                pp.MultiSampleQuality = 0;
                pp.MultiSampleType =
                    MultiSampleType.TwoSamples;
            }
            return;
#endif
        }

#region constructors...
        /// <summary>
        /// MikuMikuDance for XNAのコンストラクタ
        /// </summary>
        /// <param name="game">使用するGameオブジェクト</param>
        public MikuMikuDanceXNA(Game game)
            : base(game)
        {
            //初期設定
            Content = game.Content;
            Camera = new MMDCamera(game);
            game.Components.Add(this);
            game.Components.Add((MMDCamera)Camera);
            LightManager = new MMDLightManager(game);
            game.Components.Add((MMDLightManager)LightManager);
            DrawManager.InitDrawManager(this);
#if WINDOWS
            //論理コア数を取得して、最適スレッド数を取得
            Win32API.SYSTEM_INFO info = new Win32API.SYSTEM_INFO();
            Win32API.GetSystemInfo(ref info);
            NumThread = (int)info.dwNumberOfProcessors;
#endif
            //物理エンジン
            CreatePhysicSystem(null);
        }
        /// <summary>
        /// MikuMikuDance for XNAのコンストラクタ(Gameを使わないバージョン)
        /// </summary>
        /// <param name="content">コンテントマネージャ</param>
        /// <remarks>このコンストラクタはGameを使わないでユーザー自らが環境を構築する際に使用するもの</remarks>
        public MikuMikuDanceXNA(ContentManager content)
            : base(null)
        {
            //初期設定
            Content = content;
            Camera = new MMDCamera(null);
            LightManager = new MMDLightManager(null);
            DrawManager.InitDrawManager(this);
#if WINDOWS
            //論理コア数を取得して、最適スレッド数を取得
            Win32API.SYSTEM_INFO info = new Win32API.SYSTEM_INFO();
            Win32API.GetSystemInfo(ref info);
            NumThread = (int)info.dwNumberOfProcessors;
#endif
            //物理エンジン
            CreatePhysicSystem(null);
        }
        /// <summary>
        /// MikuMikuDance for XNAのコンストラクタ(作成済み物理エンジン指定)
        /// </summary>
        /// <param name="game">使用するGameオブジェクト</param>
        /// <param name="physic">使用するbtDiscreteDynamicsWorldオブジェクト</param>
        public MikuMikuDanceXNA(Game game, DiscreteDynamicsWorld physic)
            :base(game)
        {
            //初期設定
            Content = game.Content;
            Camera = new MMDCamera(game);
            game.Components.Add(this);
            game.Components.Add((MMDCamera)Camera);
            LightManager = new MMDLightManager(game);
            game.Components.Add((MMDLightManager)LightManager);
            DrawManager.InitDrawManager(this);
#if WINDOWS
            //論理コア数を取得して、最適スレッド数を取得
            Win32API.SYSTEM_INFO info = new Win32API.SYSTEM_INFO();
            Win32API.GetSystemInfo(ref info);
            NumThread = (int)info.dwNumberOfProcessors;
#endif
            //物理エンジン
            Physic = physic;
        }
        /// <summary>
        /// MikuMikuDance for XNAのコンストラクタ
        /// </summary>
        /// <param name="game">使用するGameオブジェクト</param>
        /// <param name="threadManager">マルチスレッド処理用スレッドマネージャ</param>
        /// <remarks>ThreadManagerを登録すると、マルチスレッド処理をするようになる</remarks>
        public MikuMikuDanceXNA(Game game, ThreadManager threadManager)
            : base(game)
        {
            //初期設定
            Content = game.Content;
            Camera = new MMDCamera(game);
            game.Components.Add(this);
            game.Components.Add((MMDCamera)Camera);
            LightManager = new MMDLightManager(game);
            game.Components.Add((MMDLightManager)LightManager);
            DrawManager.InitDrawManager(this);
#if WINDOWS
            //論理コア数を取得して、最適スレッド数を取得
            Win32API.SYSTEM_INFO info = new Win32API.SYSTEM_INFO();
            Win32API.GetSystemInfo(ref info);
            NumThread = (int)info.dwNumberOfProcessors;
#endif
            ThreadManager = threadManager;
            //物理エンジン
            CreatePhysicSystem(threadManager);
        }
        /// <summary>
        /// MikuMikuDance for XNAのコンストラクタ(Gameを使わないバージョン)
        /// </summary>
        /// <param name="content">コンテントマネージャ</param>
        /// <param name="threadManager">マルチスレッド処理用スレッドマネージャ</param>
        /// <remarks>このコンストラクタはGameを使わないでユーザー自らが環境を構築する際に使用するもの</remarks>
        public MikuMikuDanceXNA(ContentManager content, ThreadManager threadManager)
            : base(null)
        {
            //初期設定
            Content = content;
            Camera = new MMDCamera(null);
            LightManager = new MMDLightManager(null);
            DrawManager.InitDrawManager(this);
#if WINDOWS
            //論理コア数を取得して、最適スレッド数を取得
            Win32API.SYSTEM_INFO info = new Win32API.SYSTEM_INFO();
            Win32API.GetSystemInfo(ref info);
            NumThread = (int)info.dwNumberOfProcessors;
#endif
            //物理エンジン
            ThreadManager = threadManager;
            CreatePhysicSystem(threadManager);
        }
        /// <summary>
        /// MikuMikuDance for XNAのコンストラクタ(作成済み物理エンジン指定)
        /// </summary>
        /// <param name="game">使用するGameオブジェクト</param>
        /// <param name="physic">使用するbtDiscreteDynamicsWorldオブジェクト</param>
        /// <param name="threadManager">マルチスレッド処理用スレッドマネージャ</param>
        public MikuMikuDanceXNA(Game game, DiscreteDynamicsWorld physic, ThreadManager threadManager)
            : base(game)
        {
            //初期設定
            Content = game.Content;
            Camera = new MMDCamera(game);
            game.Components.Add(this);
            game.Components.Add((MMDCamera)Camera);
            LightManager = new MMDLightManager(game);
            game.Components.Add((MMDLightManager)LightManager);
            DrawManager.InitDrawManager(this);
#if WINDOWS
            //論理コア数を取得して、最適スレッド数を取得
            Win32API.SYSTEM_INFO info = new Win32API.SYSTEM_INFO();
            Win32API.GetSystemInfo(ref info);
            NumThread = (int)info.dwNumberOfProcessors;
#endif
            //物理エンジン
            Physic = physic;
            PhysicsThread = new PhysicsThread(Game, threadManager, Physic, this);
        }
#endregion
        /// <summary>
        /// モデルをアセットから読み込む
        /// </summary>
        /// <param name="assetName">モデルのアセット名</param>
        /// <param name="game">XNA Gameクラス</param>
        /// <param name="defaultTransform">デフォルトトランスフォームマトリックス</param>
        /// <returns>MikuMikuDance for XNAのモデル</returns>
        public MMDModel LoadModel(string assetName, Game game, QuatTransform defaultTransform)
        {
            MMDModel result = new MMDModel(game);
            result.Name = assetName;
            result.Transform = defaultTransform;
            result.ModelSetup(Content.Load<MMDModelData>(assetName), this, game.GraphicsDevice);
            return result;
        }
        /// <summary>
        /// モデルをアセットから読み込む(WinForm用)
        /// </summary>
        /// <param name="assetName">モデルのアセット名</param>
        /// <param name="graphicsDevice">グラフィックスデバイス</param>
        /// <param name="defaultTransform">デフォルトトランスフォームマトリックス</param>
        /// <returns>MikuMikuDance for XNAのモデル</returns>
        /// <remarks>このメソッドはGameを使わないWinForm用です。通常はGameを引数にとるLoadModelを使用して下さい</remarks>
        public MMDModel LoadModel(string assetName, GraphicsDevice graphicsDevice, QuatTransform defaultTransform)
        {
            MMDModel result = new MMDModel(null);
            result.Name = assetName;
            result.Transform = defaultTransform;
            result.ModelSetup(Content.Load<MMDModelData>(assetName), this, graphicsDevice);
            return result;
        }
        /// <summary>
        /// モデルモーションをアセットから読み込む
        /// </summary>
        /// <param name="assetName">モーションのアセット名</param>
        /// <returns>MikuMikuDance for XNAのモデルモーション</returns>
        /// <remarks>カメラ、ライトモーションはLoadStageMotionを使用</remarks>
        public MMDMotion LoadMotion(string assetName)
        {
            MMDMotion result = new MMDMotion();
            result.Initialize(Content.Load<MMDMotionData>(assetName));
            return result;
        }
        /// <summary>
        /// ベイク済みモデルモーションをアセットから読み込む
        /// </summary>
        /// <param name="assetName">モーションのアセット名</param>
        /// <returns>MikuMikuDance for XNAのベイク済みモデルモーション</returns>
        public MMDBakedMotion LoadBakedMotion(string assetName)
        {
            return Content.Load<MMDBakedMotion>(assetName);
        }
        /// <summary>
        /// アクセサリをアセットから読み込む
        /// </summary>
        /// <param name="assetName">アクセサリのアセット名</param>
        /// <param name="game">XNA Gameクラス</param>
        /// <param name="defaultTransform">デフォルトトランスフォームマトリックス</param>
        /// <returns>MikuMikuDance for XNAのアクセサリ</returns>
        public MMDAccessory LoadAccessory(string assetName, Game game, Matrix defaultTransform)
        {
            Microsoft.Xna.Framework.Graphics.Model model = Content.Load<Microsoft.Xna.Framework.Graphics.Model>(assetName);
            return new MMDAccessory(model, this, game, defaultTransform);
        }
        /// <summary>
        /// アクセサリをアセットから読み込む(WinForm用)
        /// </summary>
        /// <param name="assetName">アセット名</param>
        /// <param name="defaultTransform">デフォルトトランスフォームマトリックス</param>
        /// <returns>MikuMikuDance for XNAのアクセサリ</returns>
        public MMDAccessory LoadAccessory(string assetName, Matrix defaultTransform)
        {
            Microsoft.Xna.Framework.Graphics.Model model = Content.Load<Microsoft.Xna.Framework.Graphics.Model>(assetName);
            return new MMDAccessory(model, this, null, defaultTransform);
        }
        /// <summary>
        /// VACをアセットから読み込む
        /// </summary>
        /// <param name="assetName">VACのアセット名</param>
        /// <returns>VACデータ</returns>
        public MMD_VAC LoadVAC(string assetName)
        {
            return Content.Load<MMD_VAC>(assetName);
        }
        /// <summary>
        /// カメラ、ライトモーション(ステージモーション)をアセットから読み込む
        /// </summary>
        /// <param name="assetName">モーションのアセット名</param>
        /// <returns>MikuMikuDance for XNAのステージモーション</returns>
        public MMDStageMotion LoadStageMotion(string assetName)
        {
            MMDStageMotion result = new MMDStageMotion();
            result.Initialize(Content.Load<MMDMotionData>(assetName), this);
            return result;
        }
        /// <summary>
        /// アップデート処理
        /// </summary>
        /// <param name="gameTime">GameTimeObject</param>
        public override void Update(GameTime gameTime)
        {
            //スレッドマネージャが無ければUpdatePhisicsを行う
            if (ThreadManager == null)
            {
                UpdatePhysics((float)gameTime.ElapsedGameTime.TotalSeconds);
            }
            base.Update(gameTime);
        }
        /// <summary>
        /// アップデート処理(WinForm用)
        /// </summary>
        /// <param name="elapsedSecond"></param>
        public void Update(float elapsedSecond)
        {
            if (ThreadManager == null)
            {
                UpdatePhysics(elapsedSecond);
            }
        }
        void UpdatePhysics(float timeStep)
        {
            if (timeStep != 0.0f && UsePhysic)
            {
                Physic.stepSimulation(timeStep, MikuMikuDanceXNA.MaxSubStep);
            }
        }
        private void CreatePhysicSystem(ThreadManager manager)
        {
            //物理エンジンの作成
            config = new DefaultCollisionConfiguration();
            dispatcher = new CollisionDispatcher(config);
            pairCache = new AxisSweep3(new btVector3(-10000, -10000, -10000), new btVector3(10000, 10000, 10000), 5 * 5 * 5 + 1024, null, false);
            solver = new SequentialImpulseConstraintSolver();
            Physic = new DiscreteDynamicsWorld(dispatcher, pairCache, solver, config);
            Physic.Gravity = new btVector3(0, -9.81f * 5.0f, 0);
            //BulletはStaticPlaneが0交差するのを認めてないかもしれない(WikiのHelloWorld解説の古いバージョンにはそう書いてあるが、なぜか消えてる。版上げで消えたかも)
            ground = new StaticPlaneShape(new btVector3(0, 1, 0), 1);
            groundMotionState = new DefaultMotionState(new btTransform(btMatrix3x3.Identity, new btVector3(0, -1, 0)));
            groundBody = new RigidBody(0, groundMotionState, ground, btVector3.Zero);
            Physic.addRigidBody(groundBody, (short)CollisionFilterGroups.AllFilter, (short)CollisionFilterGroups.AllFilter);//(short)(-1 ^ 2));
            UsePhysic = true;
            if (manager != null)//マルチスレッドモード
                PhysicsThread = new PhysicsThread(Game, manager, Physic, this);
        }
        /// <summary>
        /// MMD関連オブジェクトの描画
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Draw(GameTime gameTime)
        {
            //アクセサリ用のグラフィックスデバイスのセットアップ
            MMDAccessory.GraphicsSetup(Game.GraphicsDevice, false);
            //アクセサリの描画
            if (DrawAccessoryEvent != null)
                DrawAccessoryEvent(Game.GraphicsDevice, gameTime.TotalGameTime.Ticks, false);
            //モデル用のグラフィックスデバイスのセットアップ
            MMDModel.GraphicsSetup(Game.GraphicsDevice, false);
            //モデルの描画
            if (DrawModelEvent != null)
                DrawModelEvent(Game.GraphicsDevice, gameTime.TotalGameTime.Ticks, false);
        }
        /// <summary>
        /// MMD関連オブジェクトのシャドウマップ描画
        /// </summary>
        public void DrawShadowMap(GameTime gameTime)
        {
            //アクセサリ用のグラフィックスデバイスのセットアップ
            MMDAccessory.GraphicsSetup(Game.GraphicsDevice, true);
            //アクセサリの描画
            if (DrawAccessoryEvent != null)
                DrawAccessoryEvent(Game.GraphicsDevice, gameTime.TotalGameTime.Ticks, true);
            //モデルのグラフィックスデバイスのセットアップ
            MMDModel.GraphicsSetup(Game.GraphicsDevice, true);
            //モデルの描画
            if (DrawModelEvent != null)
                DrawModelEvent(Game.GraphicsDevice, gameTime.TotalGameTime.Ticks, true);
        }
        /// <summary>
        /// このクラスで保持しているオブジェクトの破棄
        /// </summary>
        /// <param name="disposing">オブジェクトの破棄</param>
        protected override void Dispose(bool disposing)
        {
            if (PhysicsThread != null)
            {
                PhysicsThread.Dispose();
                PhysicsThread = null;
            }
            base.Dispose(disposing);
        }
    }
}
