using System;
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.Stages;
using System.Collections.Generic;
using MikuMikuDance.XNA.ShadowMap;
using BulletX.BulletCollision.CollisionDispatch;
using BulletX.BulletCollision.BroadphaseCollision;
using BulletX.BulletDynamics.ConstraintSolver;
using BulletX.BulletDynamics.Dynamics;
using BulletX.BulletCollision.CollisionShapes;
using BulletX.LinerMath;
//using BulletCLI;

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>
    /// MikuMikuDance for XNAのスタートクラス
    /// </summary>
    public class MikuMikuDanceXNA : DrawableGameComponent
    {
        const int MaxSubStep = 10;
        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でうっかり削除されないように)
        volatile ICollisionConfiguration config=null;
        volatile CollisionDispatcher dispatcher = null;
        volatile IBroadphaseInterface pairCache = null;
        volatile 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>
        /// <remarks>物理演算完成までデフォルトはfalse</remarks>
        public bool UsePhysic { get; set; }
        
#if TRACE
        /// <summary>
        /// デバッグ用のタイム計測オブジェクト格納場所
        /// </summary>
        public ITimeRular TimeRular = null;
#endif

        /// <summary>
        /// 物理演算の地面
        /// </summary>
        public RigidBody Ground { get { return groundBody; } }

        /// <summary>
        /// MikuMikuDance for XNAに必要となる環境を満たしているかチェックし、基本設定を行う
        /// </summary>
        /// <param name="gdManager">GraphicsDeviceManager</param>
        /// <param name="UseAntiAlias">アンチエイリアス使用フラグ</param>
        /// <returns>今のところtrueを返す</returns>
        /// <remarks>シェーダ要件を満たしていない場合はXNAのダイアログが出てアプリケーションが終了する。アンチエイリアスはx2を使用</remarks>
        public static bool CheckDevice(GraphicsDeviceManager gdManager, bool UseAntiAlias)
        {
            //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
        }

        //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();
            //Ground = new MMDGroundObject(0.0f, Physic);
        }
        /// <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>
        /// モデルをアセットから読み込む
        /// </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, Matrix defaultTransform)
        {
            MMDModel result = new MMDModel(game);
            result.Name = assetName;
            result.World = defaultTransform;
            result.ModelSetup(Content.Load<MMDModelData>(assetName), this, game.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>
        /// 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((float)gameTime.ElapsedGameTime.TotalSeconds);
            base.Update(gameTime);
        }
        /// <summary>
        /// 登録済み物理エンジンのアップデート
        /// </summary>
        /// <param name="timeStep">GameTimeオブジェクト</param>
        public void UpdatePhisics(float timeStep)
        {
            if (timeStep != 0.0f && UsePhysic)
            {
                if (TimeRular != null)
                    TimeRular.BeginMark(1, "Physics", Color.Red);
                Physic.stepSimulation(timeStep, MaxSubStep);
                if (TimeRular != null)
                    TimeRular.EndMark(1, "Physics");
            }
        }

        private void CreatePhysicSystem()
        {
            //物理エンジンの作成
            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);
            /*groundBody.Friction = 0.5f;
            groundBody.Restitution = 0.5f;*/
            Physic.addRigidBody(groundBody, (short)CollisionFilterGroups.AllFilter, (short)CollisionFilterGroups.AllFilter);//(short)(-1 ^ 2));
            UsePhysic = true;
            
        }
        /// <summary>
        /// MMD関連オブジェクトの描画
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Draw(GameTime gameTime)
        {
            //モデル用のグラフィックスデバイスのセットアップ
            MMDModel.GraphicsSetup(Game.GraphicsDevice, false);
            //モデルの描画
            if (DrawModelEvent != null)
                DrawModelEvent(Game.GraphicsDevice, gameTime.TotalGameTime.Ticks, false);
            //アクセサリ用のグラフィックスデバイスのセットアップ
            MMDAccessory.GraphicsSetup(Game.GraphicsDevice, false);
            //アクセサリの描画
            if (DrawAccessoryEvent != null)
                DrawAccessoryEvent(Game.GraphicsDevice, gameTime.TotalGameTime.Ticks, false);
        }
        /// <summary>
        /// MMD関連オブジェクトのシャドウマップ描画
        /// </summary>
        public void DrawShadowMap(GameTime gameTime)
        {
            //モデルのグラフィックスデバイスのセットアップ
            MMDModel.GraphicsSetup(Game.GraphicsDevice, true);
            //モデルの描画
            if (DrawModelEvent != null)
                DrawModelEvent(Game.GraphicsDevice, gameTime.TotalGameTime.Ticks, true);
            //アクセサリ用のグラフィックスデバイスのセットアップ
            MMDAccessory.GraphicsSetup(Game.GraphicsDevice, true);
            //アクセサリの描画
            if (DrawAccessoryEvent != null)
                DrawAccessoryEvent(Game.GraphicsDevice, gameTime.TotalGameTime.Ticks, true);
        }

        
    }
}
