using System;
using BulletMMD.BulletDynamics.Dynamics;
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;

namespace MikuMikuDance.XNA
{
    /// <summary>
    /// MikuMikuDance for XNAのスタートクラス
    /// </summary>
    public class MikuMikuDanceXNA
    {
        /// <summary>
        /// モーショントラックの最大トラック数
        /// </summary>
        public const int MotionTrackCap = 30;

#if XBOX360//スレッド用の定数。ベイク処理等に使用
        internal const int NumThread = 4;//使用するスレッド数
#else
        internal readonly int NumThread;
#endif
        //スクリーン表示用にフックされたエフェクト
        internal List<Effect> ScreenHook = new List<Effect>();

        //properties...
        /// <summary>
        /// MikuMikuDance for XNAで用いるコンテンツマネージャ
        /// </summary>
        public ContentManager Content { get; set; }
        /// <summary>
        /// MikuMikuDance for XNAカメラ
        /// </summary>
        public MMDCamera Camera { get; protected set; }
        /// <summary>
        /// ライトマネージャ
        /// </summary>
        public MMDLightManager LightManager { get; private set; }
        /// <summary>
        /// 物理エンジン
        /// </summary>
        public btDiscreteDynamicsWorld Physic { get; internal set; }

        /// <summary>
        /// 物理演算を使用フラグ
        /// </summary>
        /// <remarks>物理演算完成までデフォルトはfalse</remarks>
        public bool UsePhysic { get; set; }
        
#if TRACE
        /// <summary>
        /// デバッグ用のタイム計測オブジェクト格納場所
        /// </summary>
        public ITimeRular TimeRular = null;
#endif

        //物理演算用の地面(仮置き)
        private MMDGroundObject Ground { get; set; }
        /// <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)
        {
            //初期設定
            Content = game.Content;
            Camera = new MMDCamera(game);
            game.Components.Add(Camera);
            LightManager = new MMDLightManager(game);
            game.Components.Add(LightManager);
            //アンチエイリアス設定
#if WINDOWS
            //論理コア数を取得して、最適スレッド数を取得
            Win32API.SYSTEM_INFO info = new Win32API.SYSTEM_INFO();
            Win32API.GetSystemInfo(ref info);
            NumThread = (int)info.dwNumberOfProcessors;
#endif
            //物理エンジン
            Physic = 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, btDiscreteDynamicsWorld physic)
        {
            //初期設定
            Content = game.Content;
            Camera = new MMDCamera(game);
            game.Components.Add(Camera);
            LightManager = new MMDLightManager(game);
            game.Components.Add(LightManager);
#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="timeStep">GameTimeオブジェクト</param>
        public void UpdatePhisics(float timeStep)
        {
            if (timeStep != 0.0f && UsePhysic)
            {
                Physic.StepSimulation(timeStep);
            }
        }

        private btDiscreteDynamicsWorld CreatePhysicSystem()
        {
            return null;//現在は物理エンジン未完成のため、nullを返すようにしている
            //物理エンジンの作成
            /*CollisionDispatcher dispatcher = new CollisionDispatcher();
            OverlappingPairCache pairCache = new SimpleBroadphase();
            SequentialImpulseConstraintSolver solver = new SequentialImpulseConstraintSolver();
            
            DiscreteDynamicsWorld result = new DiscreteDynamicsWorld(dispatcher, pairCache, solver);
            result.Gravity = 9.81f * 5.0f * Vector3.Down;
            result.SolverInfo.IterationsCount = 10;
            result.SolverInfo.TimeStep = 15;
            result.Gravity = 9.81f * 5.0f * Vector3.Down;
            return result;*/
        }
        /// <summary>
        /// スクリーン画像をセットする
        /// </summary>
        /// <param name="screen">テクスチャ。nullにするとスクリーンを使用しないことになる</param>
        /// <param name="mode2">mode2にするかどうか(4:3の比でカットするかどうか)</param>
        public void SetScreen(Texture2D screen, bool mode2)
        {
            if (ScreenHook.Count == 0)
                return;
            if (screen == null)
            {
                ScreenHook[0].Parameters["ScreenMode"].SetValue(0);
            }
            else
            {
                ScreenHook[0].Parameters["ScreenMode"].SetValue(mode2 ? 2 : 1);
                ScreenHook[0].Parameters["Screen"].SetValue(screen);
                if (mode2)
                {
                    Vector2 result = new Vector2(screen.Width, screen.Height);
                    if (result.X * 3.0f > result.Y * 4.0f)
                    {
                        result.X =  result.Y * 4.0f / 3.0f / result.X;
                        result.Y = 1.0f;
                    }
                    else
                    {
                        result.Y =  result.X * 3.0f / 4.0f / result.Y;
                        result.X = 1.0f;
                    }
                    ScreenHook[0].Parameters["ScreenTexFix"].SetValue(result);
                }
                else
                {
                    ScreenHook[0].Parameters["ScreenTexFix"].SetValue(Vector2.One);
                }
            }
        }
    }
}
