using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using MikuMikuDance.XNA.Model.ModelData;
using MMD = MikuMikuDance.Model.Ver1;

using TImport = MikuMikuDance.XNA.Model.MMDModelIntermediate;

// TODO: エッジの処理が未実装
// TODO: トゥーンの処理が未実装

namespace MikuMikuDance.XNA.Model
{
    /// <summary>
    /// MikuMikuDanceのモデルデータをXNAにインポートするためのインポータ
    /// </summary>
    [ContentImporter(".pmd", DisplayName = "MikuMikuDance PMD : MikuMikuDance for XNA", CacheImportedData = false, DefaultProcessor = "MMDProcessor")]
    public class MMDImporter : ContentImporter<TImport>
    {
        /// <summary>
        /// インポート処理
        /// </summary>
        /// <param name="filename">ファイル名</param>
        /// <param name="context">コンテントインポータ</param>
        /// <returns>MMDモデル for XNA中間クラス</returns>
        /// <remarks>XNAのインポータから呼び出される</remarks>
        public override TImport Import(string filename, ContentImporterContext context)
        {
            //Identityを設定(コンテンツのロード元の判別等に必要)
            ContentIdentity Identity = new ContentIdentity(filename);
            MMD.MMDModel1 model;
            
            //MMDモデルの読み込み
            //ここで注意点だが
            //MMDは左手座標系で、XNAは右手座標系である
            //よって、全てのZ軸情報が逆である
            MikuMikuDance.Model.MMDModel modelBase;
            try
            {
                modelBase = MikuMikuDance.Model.ModelManager.Read(filename, MikuMikuDance.Model.CoordinateType.LeftHanded);
            }
            catch (Exception e)
            {
                // コンテンツ ID (行番号を含む) を含めて例外を報告します
                throw new InvalidContentException(
                    "Unable to load MMD Model file. Exception:\n" + e.Message,
                    Identity, e);
            }
            //MMDバージョンチェック
            if (modelBase.Version != 1.0f)
                throw new InvalidContentException(
                    "This Importer can read only MMD Model ver 1.0.", Identity);
            //バージョンチェックしたのでMMDVer1にキャスト
            model = (MMD.MMDModel1)modelBase;

            //メッシュビルダー生成。メッシュ名ってモデル名？
            MeshBuilder meshBuilder = MeshBuilder.StartMesh(model.Header.ModelName);
            //頂点や面をチェック
            if (model.Vertexes == null || model.Vertexes.Length == 0)
                throw new InvalidContentException(
                    "There is no Vertex in Model file", Identity);
            if (model.FaceVertexes == null || model.FaceVertexes.Length == 0)
                throw new InvalidContentException(
                    "There is no FaceVertex in Model file", Identity);
            meshBuilder.MergeDuplicatePositions = true;
            
            //ここからメッシュ情報を登録していく
            
            //頂点を登録
            int[] Positions = new int[model.Vertexes.Length];
            for (long i = 0; i < model.Vertexes.LongLength; i++)
            {
                //頂点登録(座標系変換込み)
                Positions[i] = meshBuilder.CreatePosition(new Vector3(model.Vertexes[i].Pos[0], model.Vertexes[i].Pos[1], model.Vertexes[i].Pos[2]));
            }
            
            //テクスチャー座標および法線用の頂点チャンネルを追加
            int texCoordinateIndex = meshBuilder.CreateVertexChannel<Vector2>(VertexChannelNames.TextureCoordinate(0));
            int normalDataIndex = meshBuilder.CreateVertexChannel<Vector3>(VertexChannelNames.Normal(0));
            int weightDataIndex = meshBuilder.CreateVertexChannel<BoneWeightCollection>(VertexChannelNames.Weights(0));
            int ColorDataIndex = meshBuilder.CreateVertexChannel<Vector4>(VertexChannelNames.Color(0));
            //表情用頂点データ(-1なら表情に用いない頂点、0以上ならbaseの頂点番号)
            int FaceDataIndex = meshBuilder.CreateVertexChannel<Vector2>(VertexChannelNames.TextureCoordinate(1));
            long FaceIndex = 0;
            //頂点番号→表情用の頂点番号の辞書作成
            Dictionary<long, long> VertToFaceVert = new Dictionary<long, long>();
            for (int i = 0; i < model.Skins.Length; i++)
            {//base探し
                if (model.Skins[i].SkinType == 0)
                {
                    if (model.Skins[i].SkinVertDatas.LongLength > int.MaxValue)
                        throw new InvalidContentException("base表情の頂点数が多すぎます。MikuMikuDanceXNAはbaseの頂点数を32bit符号付き整数の最大値までしかサポートしていません", Identity);
                    for (long j = 0; j < model.Skins[i].SkinVertDatas.LongLength; j++)
                    {
                        VertToFaceVert.Add(model.Skins[i].SkinVertDatas[j].SkinVertIndex, j);
                    }
                    break;//ここではbaseだけ見る
                }
            }
            //マテリアル
            for (long i = 0; i < model.Materials.LongLength; i++)
            {
                EffectMaterialContent material = new EffectMaterialContent();
                //BasicMaterialContent material = new BasicMaterialContent();
                //名前設定
                material.Name = "material" + i.ToString();
                //色・光沢設定
                
                //未設定データ
                //エッジ、トゥーン
                //model.Vertexes[].EdgeFlag;
                //model.Materials[i].EdgeFlag;
                //model.Materials[i].ToonIndex;
                
                material.OpaqueData.Add("DiffuseColor", new Vector3(model.Materials[i].DiffuseColor[0],
                                model.Materials[i].DiffuseColor[1],
                                model.Materials[i].DiffuseColor[2]));
                material.OpaqueData.Add("EmissiveColor", new Vector3(model.Materials[i].MirrorColor[0],
                                model.Materials[i].MirrorColor[1],
                                model.Materials[i].MirrorColor[2]));
                material.OpaqueData.Add("SpecularColor", new Vector3(model.Materials[i].SpecularCcolor[0],
                                                model.Materials[i].SpecularCcolor[1],
                                                model.Materials[i].SpecularCcolor[2]));
                material.OpaqueData.Add("SpecularPower", model.Materials[i].Specularity);
                material.OpaqueData.Add("Alpha", model.Materials[i].Alpha);
                //エッジ、トゥーンは後で処理を書く……
                //多分シェーダいじれば……
                
                bool HasTexture = false;
                //テクスチャ読み込み
                if (!string.IsNullOrEmpty(model.Materials[i].TextureFileName))
                {
                    string path = model.Materials[i].TextureFileName;
                    if (!Path.IsPathRooted(path))
                    {
                        path = Path.Combine(Path.GetDirectoryName(filename), path);
                    }
                    if (!File.Exists(path))
                        throw new InvalidContentException(
                            "Cannot find texture file:" + path, Identity);
                    //テクスチャのセット
                    if (Path.GetExtension(path).ToLower() == ".sph")
                    {//スフィアマップだ。(厄介物)
                        //中間ディレクトリに拡張子をbmpに変えてコピー(せこっ)
                        if (!Directory.Exists("ext"))
                            Directory.CreateDirectory("ext");
                        string intFilePath = Path.Combine("ext", Path.GetFileNameWithoutExtension(path) + ".bmp");
                        File.Copy(path, intFilePath, true);//上書きありでコピー
                        //スフィアマップ処理(スフィアマップはシェーダ内で処理するので、フラグ代わりのデータを代入)
                        material.OpaqueData.Add("UseSphere", 1.0f);
                        //pathの差し替え
                        path = intFilePath;
                    }
                    else
                    {
                        material.OpaqueData.Add("UseSphere", 0.0f);//スフィアマップを使わない
                    }
                    material.Textures.Add("Texture", new ExternalReference<TextureContent>(path));
                    HasTexture = true;
                }
                else
                {
                    material.OpaqueData.Add("UseSphere", 0.0f);
                }
                for (long k = FaceIndex; k < FaceIndex + model.Materials[i].FaceVertCount; k++)
                {
                    //マテリアルをセット
                    meshBuilder.SetMaterial(material);
                    //頂点index取得
                    int VecIndex = model.FaceVertexes[k];
                    //UV座標を0番にセット
                    if (HasTexture)
                        meshBuilder.SetVertexChannelData(texCoordinateIndex, new Vector2(model.Vertexes[VecIndex].UV[0], model.Vertexes[VecIndex].UV[1]));
                    else
                        meshBuilder.SetVertexChannelData(texCoordinateIndex, Vector2.Zero);
                    //法線セット
                    meshBuilder.SetVertexChannelData(normalDataIndex,
                        new Vector3(model.Vertexes[VecIndex].NormalVector[0],
                                    model.Vertexes[VecIndex].NormalVector[1],
                                    model.Vertexes[VecIndex].NormalVector[2]));
                    //ボーンウェイト
                    BoneWeightCollection boneWeight = new BoneWeightCollection();
                    int boneNum = model.Vertexes[VecIndex].BoneNum[0];
                    if (boneNum >= 0 && boneNum < model.Bones.Length)
                        boneWeight.Add(new BoneWeight(model.Bones[boneNum].BoneName, model.Vertexes[VecIndex].BoneWeight / 100f));
                    boneNum = model.Vertexes[VecIndex].BoneNum[1];
                    if (boneNum >= 0 && boneNum < model.Bones.Length)
                        boneWeight.Add(new BoneWeight(model.Bones[boneNum].BoneName, 1.0f - model.Vertexes[VecIndex].BoneWeight / 100f));
                    
                    meshBuilder.SetVertexChannelData(weightDataIndex, boneWeight);

                    //表情用の頂点番号(base表情列内の番号)をUV座標の1番にセット
                    if (VertToFaceVert.ContainsKey(VecIndex))
                        meshBuilder.SetVertexChannelData(FaceDataIndex, new Vector2(VertToFaceVert[VecIndex], 0));
                    else
                        meshBuilder.SetVertexChannelData(FaceDataIndex, new Vector2(-1, 0));
                    
                    //カラー
                    //MMDはディフューズ色等のマテリアルでカラー設定をするので、ここでは白にしておく
                    meshBuilder.SetVertexChannelData(ColorDataIndex, new Vector4(255, 255, 255, 0));

                    //面を生成
                    meshBuilder.AddTriangleVertex(Positions[VecIndex]);
                }
                FaceIndex += model.Materials[i].FaceVertCount;
            }
            MMDModelIntermediate result = new MMDModelIntermediate();
            //ボーンの生成
            List<MMDBoneData> mmdBone;
            BoneContent bone = CreateSkeleton(model, Identity, out mmdBone);
            result.Bones = mmdBone;
            //メッシュを作成
            MeshContent meshContent = meshBuilder.FinishMesh();
            //メッシュの設定
            meshContent.Identity = Identity;
            meshContent.Name = model.Header.ModelName;
            //IKの格納
            //result.IKs = new List<MMDIKData>();
            for (UInt16 i = 0; i < model.IKs.Length; i++)
            {
                MMDIKData ik = new MMDIKData();
                ik.IKBoneIndex = model.IKs[i].IKBoneIndex;
                ik.IKTargetBoneIndex = model.IKs[i].IKTargetBoneIndex;
                ik.Iteration = model.IKs[i].Iterations;
                ik.ControlWeight = model.IKs[i].AngleLimit;
                ik.IKChildBones = new List<ushort>();
                for (byte i2 = 0; i2 < model.IKs[i].IKChildBoneIndex.Length; i2++)
                {
                    ik.IKChildBones.Add(model.IKs[i].IKChildBoneIndex[i2]);
                }
                result.Bones[ik.IKBoneIndex].IK = ik;
            }
            //表情の格納
            result.Skins = new List<MMDSkinData>();
            for (UInt16 i = 0; i < model.Skins.Length; i++)
            {
                MMDSkinData skin = new MMDSkinData();
                skin.Name = model.Skins[i].SkinName;
                skin.SkinType = (MMDSkinType)model.Skins[i].SkinType;
                skin.SkinVerts = new MMDSkinVertData[model.Skins[i].SkinVertDatas.LongLength];
                for (UInt32 i2 = 0; i2 < model.Skins[i].SkinVertDatas.LongLength; i2++)
                {
                    skin.SkinVerts[i2] = new MMDSkinVertData();
                    skin.SkinVerts[i2].VertIndex = model.Skins[i].SkinVertDatas[i2].SkinVertIndex;
                    skin.SkinVerts[i2].Pos = new float[model.Skins[i].SkinVertDatas[i2].SkinVertPos.Length];
                    for (int i3 = 0; i3 < 3; i3++)
                        skin.SkinVerts[i2].Pos[i3] = model.Skins[i].SkinVertDatas[i2].SkinVertPos[i3];
                }
                result.Skins.Add(skin);
            }
            //表情用に用いるbase内全頂点数を格納
            result.NumVertexForFace = VertToFaceVert.Count;

            //物理演算データを格納
            //if (model.RigidBodies!=null && model.RigidBodies.LongLength > int.MaxValue)
            //    throw new InvalidContentException("物理演算用の剛体数はBulletXの都合により"
            //            + int.MaxValue.ToString() + "が限界です。このモデルは" + model.RigidBodies.LongLength + "個の剛体が含まれてます", Identity);
            //if (model.RigidBodies != null &&  model.Joints.LongLength > int.MaxValue)
            //    throw new InvalidContentException("物理演算用のJoint数はBulletXの都合により"
            //            + int.MaxValue.ToString() + "が限界です。このモデルは" + model.Joints.LongLength + "個の剛体が含まれてます", Identity);
            result.Rigids = new List<MMDRigid>();
            if (model.RigidBodies != null)
            {
                for (UInt32 i = 0; i < model.RigidBodies.LongLength; i++)
                {
                    MMDRigid rigid = new MMDRigid();
                    rigid.AngularDamping = model.RigidBodies[i].AngularDamping;
                    rigid.Friction = model.RigidBodies[i].Friction;
                    rigid.GroupIndex = model.RigidBodies[i].GroupIndex;
                    rigid.GroupTarget = model.RigidBodies[i].GroupTarget;
                    rigid.LinerDamping = model.RigidBodies[i].LinerDamping;
                    rigid.Name = model.RigidBodies[i].Name;
                    rigid.Position = new float[model.RigidBodies[i].Position.Length];
                    for (int j = 0; j < rigid.Position.Length; j++)
                        rigid.Position[j] = model.RigidBodies[i].Position[j];
                    rigid.RelatedBoneIndex = model.RigidBodies[i].RelatedBoneIndex;
                    rigid.Restitution = model.RigidBodies[i].Restitution;
                    rigid.Rotation = new float[model.RigidBodies[i].Rotation.Length];
                    for (int j = 0; j < rigid.Rotation.Length; j++)
                        rigid.Rotation[j] = model.RigidBodies[i].Rotation[j];
                    rigid.ShapeDepth = model.RigidBodies[i].ShapeDepth;
                    rigid.ShapeHeight = model.RigidBodies[i].ShapeHeight;
                    rigid.ShapeType = model.RigidBodies[i].ShapeType;
                    rigid.ShapeWidth = model.RigidBodies[i].ShapeWidth;
                    rigid.Type = model.RigidBodies[i].Type;
                    rigid.Weight = model.RigidBodies[i].Weight;
                    result.Rigids.Add(rigid);
                }
            }
            result.Joints = new List<MMDJoint>();
            if (model.Joints != null)
            {
                for (UInt32 i = 0; i < model.Joints.LongLength; i++)
                {
                    MMDJoint joint = new MMDJoint();
                    joint.ConstrainPosition1 = new float[model.Joints[i].ConstrainPosition1.Length];
                    for (int j = 0; j < joint.ConstrainPosition1.Length; j++)
                        joint.ConstrainPosition1[j] = model.Joints[i].ConstrainPosition1[j];
                    joint.ConstrainPosition2 = new float[model.Joints[i].ConstrainPosition2.Length];
                    for (int j = 0; j < joint.ConstrainPosition2.Length; j++)
                        joint.ConstrainPosition2[j] = model.Joints[i].ConstrainPosition2[j];
                    joint.ConstrainRotation1 = new float[model.Joints[i].ConstrainRotation1.Length];
                    for (int j = 0; j < joint.ConstrainRotation1.Length; j++)
                        joint.ConstrainRotation1[j] = model.Joints[i].ConstrainRotation1[j];
                    joint.ConstrainRotation2 = new float[model.Joints[i].ConstrainRotation2.Length];
                    for (int j = 0; j < joint.ConstrainRotation2.Length; j++)
                        joint.ConstrainRotation2[j] = model.Joints[i].ConstrainRotation2[j];
                    joint.Name = model.Joints[i].Name;
                    joint.Position = new float[model.Joints[i].Position.Length];
                    for (int j = 0; j < joint.Position.Length; j++)
                        joint.Position[j] = model.Joints[i].Position[j];
                    joint.RigidBodyA = model.Joints[i].RigidBodyA;
                    joint.RigidBodyB = model.Joints[i].RigidBodyB;
                    joint.Rotation = new float[model.Joints[i].Rotation.Length];
                    for (int j = 0; j < joint.Rotation.Length; j++)
                        joint.Rotation[j] = model.Joints[i].Rotation[j];
                    joint.SpringPosition = new float[model.Joints[i].SpringPosition.Length];
                    for (int j = 0; j < joint.SpringPosition.Length; j++)
                        joint.SpringPosition[j] = model.Joints[i].SpringPosition[j];
                    joint.SpringRotation = new float[model.Joints[i].SpringRotation.Length];
                    for (int j = 0; j < joint.SpringRotation.Length; j++)
                        joint.SpringRotation[j] = model.Joints[i].SpringRotation[j];
                    result.Joints.Add(joint);
                }
            }
            //ルートノードを生成して、メッシュとボーンを格納
            NodeContent nodeContent = new NodeContent();
            nodeContent.Identity = Identity;
            nodeContent.Children.Add(meshContent);//メッシュ
            nodeContent.Children.Add(bone);//ボーン
            
            //MMD中間モデルにルートノードを格納
            result.RootNode = nodeContent;
            result.Identity = Identity;
            
            //ルートノードを返却
            return result;
        }

        private BoneContent CreateSkeleton(MikuMikuDance.Model.Ver1.MMDModel1 model, ContentIdentity Identity, out List<MMDBoneData> mmdBones)
        {
            //ボーン一覧
            List<MMDBoneSetIntermediate> bones = new List<MMDBoneSetIntermediate>();
            mmdBones = new List<MMDBoneData>();
            //ボーン一覧を生成
            for (UInt16 i = 0; i < model.Bones.Length; i++)
            {
                bones.Add(new MMDBoneSetIntermediate(model.Bones[i]));
                //XNAのボーンで処理されないデータをMMDBoneクラスに入れる
                MMDBoneData mmdBone = new MMDBoneData();
                mmdBone.Name = model.Bones[i].BoneName;
                if (mmdBone.Name == "")
                    throw new InvalidContentException(
                    "All bones need to be named.", Identity);
                mmdBone.BoneType=(MMDBoneType)model.Bones[i].BoneType;
                mmdBone.IKParentBoneIndex = model.Bones[i].IKParentBoneIndex;
                mmdBones.Add(mmdBone);
            }
            //ここでボーンを組み立てる
            //ルートボーンを生成
            BoneContent rootBone = new BoneContent();
            UInt16 rootBoneIndex = UInt16.MaxValue;
            rootBone.Name = "root";
            rootBone.Identity = Identity;
            //rootは(0,0,0)?
            rootBone.Transform = Matrix.CreateFromYawPitchRoll(0, 0, 0) * Matrix.CreateTranslation(0, 0, 0);
            //ルートボーンを元に組み立てる
            try
            {
                BuildSkelton(rootBone, rootBoneIndex, bones);
            }
            catch (StackOverflowException e)
            {
                throw new InvalidContentException("bone tree was looped", Identity, e);
            }

            //ルートボーン内に組み立てられているのでそれを返却
            return rootBone;
        }

        private void BuildSkelton(BoneContent ParentBone, UInt16 ParentBoneIndex, List<MMDBoneSetIntermediate> bones)
        {
            //まずは親ボーン番号を元に検索
            for (UInt16 i = 0; i < bones.Count; i++)
            {
                if (bones[i].BoneParent == ParentBoneIndex)
                {//子ボーンを発見
                    //まずは親ボーンの絶対行列を元に子の行列を修正
                    Matrix parent = ParentBone.AbsoluteTransform;
                    //それを元に子の行列を設定
                    //親の絶対行列*子の行列=子の絶対行列より、子の行列=親の絶対行列逆行列*子の絶対行列
                    bones[i].Bone.Transform = Matrix.Invert(parent) * bones[i].Bone.Transform;
                    //親に登録
                    ParentBone.Children.Add(bones[i].Bone);
                    //再帰的に調査
                    BuildSkelton(bones[i].Bone, i, bones);
                }
            }
        }
    }
}
