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>
    /// 元インポーターでビルドしていた処理。
    /// インポーターでビルドしていた処理をプロセッサに写した時にそのままにした
    /// </summary>
    class MMDBuilder
    {
        public bool UseMaterialPalette;
        /// <summary>
        /// ビルド処理
        /// </summary>
        /// <param name="filename">ファイル名</param>
        /// <param name="model">MMDモデルデータ</param>
        /// <returns>MMDモデル for XNA中間クラス</returns>
        /// <remarks>XNAのインポータから呼び出される</remarks>
        public TImport Build(string filename, MMD.MMDModel1 model)//, ContentImporterContext context)
        {
            //Identityを設定(コンテンツのロード元の判別等に必要)
            ContentIdentity Identity = new ContentIdentity(filename);
            
            //中間モデル
            MMDModelIntermediate result = new MMDModelIntermediate();

            //頂点番号→表情用の頂点番号の辞書作成
            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だけ見る
                }
            }
            //XNAのメッシュデータ作成
            //MMDモデルからXNA最適化済みメッシュデータを取得
            MMDMeshData mmdMesh = MeshConverter.ConvertForXNA(model, Path.GetDirectoryName(filename), UseMaterialPalette);
            //センター調査
            long FaceVertCount = 0;
            result.PartCenters = new PartCenter[mmdMesh.Materials.Length];
            for (int i = 0; i < mmdMesh.Materials.Length; i++)
            {
                decimal[] center=new decimal[3];
                for (int j = 0; j < 3; j++)
                    center[j] = 0m;
                for (long FaceVert = FaceVertCount; FaceVert < FaceVertCount + mmdMesh.Materials[i].FaceVertCount; FaceVert++)
                {
                    center[0] += (decimal)mmdMesh.Vertexes[mmdMesh.FaceVertexes[FaceVert]].Pos.X;
                    center[1] += (decimal)mmdMesh.Vertexes[mmdMesh.FaceVertexes[FaceVert]].Pos.Y;
                    center[2] += (decimal)mmdMesh.Vertexes[mmdMesh.FaceVertexes[FaceVert]].Pos.Z;
                }
                for (int j = 0; j < 3; j++)
                    center[j] /= mmdMesh.Materials[i].FaceVertCount;
                result.PartCenters[i] = new PartCenter();
                result.PartCenters[i].DefaultCenterPos = new Vector3((float)center[0], (float)center[1], (float)center[2]);
                result.PartCenters[i].IsTranslucent = mmdMesh.Materials[i].IsTranslucent;
                FaceVertCount += mmdMesh.Materials[i].FaceVertCount;
            }
            
            //線用メッシュの作成
            long[] VertMap;
            MMDMeshData mmdMeshBorder = MeshConverter.CreateBorderMesh(mmdMesh, out VertMap);
            //頂点マッピングの逆を作成
            long[] InvVertMap = new long[mmdMeshBorder.Vertexes.LongLength];
            for (long i = 0; i < VertMap.LongLength; i++)
            {
                if (VertMap[i] != -1)
                {
                    InvVertMap[VertMap[i]] = i;
                }
            }
            //メッシュのビルド
            MeshContent meshNormal = MMDMeshBuilder.BuildMesh(mmdMesh, filename, Identity, VertToFaceVert);
            MeshContent meshBorder = MMDMeshBuilder.BuildBorderMesh(mmdMeshBorder, filename, Identity, VertToFaceVert, InvVertMap);

            //ボーンの生成
            List<MMDBoneData> mmdBone;
            BoneContent bone = CreateSkeleton(model, Identity, out mmdBone);
            result.Bones = mmdBone.ToArray();

            //ルートノードを生成して、メッシュとボーンを格納
            NodeContent nodeContent = new NodeContent();
            nodeContent.Identity = Identity;
            if (meshNormal != null)
            {
                nodeContent.Children.Add(meshNormal);//メッシュ
                if (meshBorder != null)//エッジが無いメッシュ対策
                    nodeContent.Children.Add(meshBorder);//境界線
            }
            nodeContent.Children.Add(bone);//ボーン

            //MMD中間モデルにルートノードを格納
            result.RootNode = nodeContent;
            result.Identity = Identity;

            //その他、MMD固有のデータの格納
            //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 ushort[model.IKs[i].IKChildBoneIndex.Length];
                for (byte i2 = 0; i2 < model.IKs[i].IKChildBoneIndex.Length; i2++)
                {
                    ik.IKChildBones[i2] = model.IKs[i].IKChildBoneIndex[i2];
                }
                if (result.Bones[ik.IKBoneIndex].IK == null)
                {
                    result.Bones[ik.IKBoneIndex].IK = new List<MMDIKData>();
                    result.Bones[ik.IKBoneIndex].IK.Add(ik);
                    result.Bones[ik.IKBoneIndex].HasIK = true;
                }
                else
                    result.Bones[ik.IKBoneIndex].IK.Add(ik);
            }
            //表情の格納
            result.Skins = new MMDSkinData[model.Skins.Length];
            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[i] = 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 MMDRigid[0];
            if (model.RigidBodies != null)
            {
                result.Rigids = new MMDRigid[model.RigidBodies.LongLength];
                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[i] = rigid;
                }
            }
            result.Joints = new MMDJoint[0];
            if (model.Joints != null)
            {
                result.Joints = new MMDJoint[model.Joints.LongLength];
                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];
                    MMDMath.CheckMinMax(joint.ConstrainPosition1, joint.ConstrainPosition2);
                    MMDMath.CheckMinMax(joint.ConstrainRotation1, joint.ConstrainRotation2);

                    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[i] = joint;
                }
            }
            
            //ルートノードを返却
            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 = GetAliasName("root", 0, bones);
            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 string GetAliasName(string baseName, int NextNum, List<MMDBoneSetIntermediate> bones)
        {//既存のボーン名と被らないようなエイリアスを作成する
            string boneName = baseName;
            if (NextNum > 0)
                boneName += Convert.ToString(NextNum);
            foreach (var bone in bones)
            {
                if (bone.Bone.Name == boneName)
                    return GetAliasName(baseName, NextNum + 1, bones);
            }
            return boneName;
        }

        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);
                }
            }
        }
    }
}
