using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using MikuMikuDance.Model.Ver1;
using Microsoft.Xna.Framework.Content.Pipeline;
using MikuMikuDance.XNA.Model.ModelData;
using MikuMikuDance.XNA.Model;
using Microsoft.Xna.Framework;

namespace MikuMikuDance.XNA.ModelReach
{
    static class MMDReachBuilder
    {
        public static NodeContent Build(MMDModel1 model, string filename, ContentIdentity Identity)
        {
            //メッシュのビルド
            MeshContent mesh = MMDMeshBuilderReach.BuildMesh(model, filename, Identity);

            NodeContent nodeContent = new NodeContent();
            //ノードに仕込む用のクラス
            MMDModelIntermediateTag tag = new MMDModelIntermediateTag();
            //ノードに仕込む
            nodeContent.OpaqueData.Add("mmdtag", tag);

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

            //ルートノードにメッシュとボーンを格納
            nodeContent.Identity = Identity;
            if (mesh != null)
            {
                nodeContent.Children.Add(mesh);//メッシュ
            }
            else
                throw new InvalidContentException("MMDReachImporterではメッシュ無しモデルを扱うことは出来ません");
            nodeContent.Children.Add(bone);//ボーン
            
            //tagクラスに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 (tag.Bones[ik.IKBoneIndex].IK == null)
                {
                    tag.Bones[ik.IKBoneIndex].IK = new List<MMDIKData>();
                    tag.Bones[ik.IKBoneIndex].IK.Add(ik);
                    tag.Bones[ik.IKBoneIndex].HasIK = true;
                }
                else
                    tag.Bones[ik.IKBoneIndex].IK.Add(ik);
            }
            
            //物理演算データを格納
            //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);
            tag.Rigids = new MMDRigid[0];
            if (model.RigidBodies != null)
            {
                tag.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;
                    tag.Rigids[i] = rigid;
                }
            }
            tag.Joints = new MMDJoint[0];
            if (model.Joints != null)
            {
                tag.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];
                    tag.Joints[i] = joint;
                }
            }

            return nodeContent;
        }


        private static BoneContent CreateSkeleton(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 static 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 static 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);
                }
            }
        }
    }
}
