using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using UnityEngine;

namespace DefaultNamespace
{
    public struct OgreBoneWeight
    {
        public int VertexIndex;
        public int BoneIndex;
        public float Weight;
    }

    public struct OgreSubMeshName
    {
        public string Name;
        public int Index;
    }

    /// <summary>
    /// 定义submesh的顶点索引
    /// </summary>
    public struct OgreSubMesh
    {
        public string Material; // 材质名字
        public int FaceCount; // 面数量
        public int[] Triangles;
    }

    public class OgreMeshData
    {
        public Vector3[] Vertices;
        public Vector3[] Normals;
        public Vector2[][] UVs;
        public OgreBoneWeight[] BoneWeights;
        public string SkeletonLink;

        public OgreSubMesh[] SubMesh;
        public OgreSubMeshName[] SubMeshName;

        public void ReadXml(string filename)
        {
            XmlDocument meshDoc = new XmlDocument();
            meshDoc.Load(filename);

            XmlElement rootNode = meshDoc.DocumentElement;
            XmlNode sharedGeometryNode = rootNode.GetElementsByTagName("sharedgeometry")[0];
            XmlNode submeshesNode = rootNode.GetElementsByTagName("submeshes")[0];
            XmlNode submeshnamesNode = rootNode.GetElementsByTagName("submeshnames")[0];

            int vertexCount = int.Parse(sharedGeometryNode.Attributes["vertexcount"].Value);

            Vertices = new Vector3[vertexCount];
            Normals = new Vector3[vertexCount];

            // vertex
            XmlNode vertexBufferNode1 = sharedGeometryNode.ChildNodes[0];
            bool isPositions = bool.Parse(vertexBufferNode1.Attributes["positions"].Value);
            bool isNormals = bool.Parse(vertexBufferNode1.Attributes["normals"].Value);
            for (int i = 0; i < vertexBufferNode1.ChildNodes.Count; i++)
            {
                XmlNode vertexNode = vertexBufferNode1.ChildNodes[i];
                if (isPositions)
                {
                    XmlNode posNode = vertexNode["position"];
                    Vertices[i] = posNode.ReadVector3();
                }

                if (isNormals)
                {
                    XmlNode normalNode = vertexNode["normal"];
                    Normals[i] = normalNode.ReadVector3();
                }
            }

            // uv
            XmlNode vertexBufferNode2 = sharedGeometryNode.ChildNodes[1];
            int coordsNum = int.Parse(vertexBufferNode2.Attributes["texture_coords"].Value);
            UVs = new Vector2[coordsNum][];
            for (int i = 0; i < coordsNum; i++)
                UVs[i] = new Vector2[vertexCount];

            for (int i = 0; i < vertexBufferNode2.ChildNodes.Count; i++)
            {
                XmlNode vertexNode = vertexBufferNode2.ChildNodes[i];
                for (int j = 0; j < vertexNode.ChildNodes.Count; j++)
                {
                    XmlNode texcoordNode = vertexNode.ChildNodes[j];
                    UVs[j][i] = texcoordNode.ReadUV2();
                }
            }

            XmlNode skeletonlinkNode = rootNode.SelectSingleNode("skeletonlink");
            if (skeletonlinkNode != null)
            {
                this.SkeletonLink = ((XmlElement)skeletonlinkNode).GetAttribute("name");
            }

            XmlNode boneassignments = rootNode.SelectSingleNode("boneassignments");
            if (boneassignments is { HasChildNodes: true })
            {
                BoneWeights = new OgreBoneWeight[boneassignments.ChildNodes.Count];
                for (int i = 0; i < boneassignments.ChildNodes.Count; i++)
                {
                    XmlElement item = boneassignments.ChildNodes[i] as XmlElement;
                    int vIdx = int.Parse(item.GetAttribute("vertexindex"));
                    int boneIdx = int.Parse(item.GetAttribute("boneindex"));
                    float weight = float.Parse(item.GetAttribute("weight"));
                    OgreBoneWeight ogreBoneWeight = new OgreBoneWeight
                    {
                        VertexIndex = vIdx,
                        BoneIndex = boneIdx,
                        Weight = weight
                    };
                    this.BoneWeights[i] = ogreBoneWeight;
                }
            }

            // subMesh
            int subMeshCount = submeshesNode.ChildNodes.Count;
            SubMesh = new OgreSubMesh[subMeshCount];
            for (int i = 0; i < subMeshCount; i++)
            {
                XmlNode node = submeshesNode.ChildNodes[i];
                XmlNode facesNode = node.FirstChild;
                string material = node.Attributes["material"].Value;
                int count = int.Parse(facesNode.Attributes["count"].Value);
                SubMesh[i] = new OgreSubMesh
                {
                    Material = material,
                    FaceCount = count,
                    Triangles = new int[count * 3]
                };

                int triangleIdx = 0;
                for (int j = 0; j < facesNode.ChildNodes.Count; j++, triangleIdx += 3)
                {
                    XmlNode faceNode = facesNode.ChildNodes[j];
                    int v1 = int.Parse(faceNode.Attributes["v1"].Value);
                    int v2 = int.Parse(faceNode.Attributes["v2"].Value);
                    int v3 = int.Parse(faceNode.Attributes["v3"].Value);
                    SubMesh[i].Triangles[triangleIdx] = v1;
                    SubMesh[i].Triangles[triangleIdx + 1] = v2;
                    SubMesh[i].Triangles[triangleIdx + 2] = v3;
                }
            }

            // subMeshName
            ReadSubMeshNames(submeshnamesNode);
        }

        private void ReadSubMeshNames(XmlNode subMeshNameNode)
        {
            int count = subMeshNameNode.ChildNodes.Count;
            this.SubMeshName = new OgreSubMeshName[count];
            for (int i = 0; i < count; i++)
            {
                XmlElement item = (XmlElement)subMeshNameNode.ChildNodes[i];
                string name = item.GetAttribute("name");
                int index = int.Parse(item.GetAttribute("index"));
                this.SubMeshName[i] = new OgreSubMeshName
                {
                    Name = name,
                    Index = index
                };
            }
        }
    }

    public class OgreSkeletonData
    {
        public string BlendMode { get; private set; }
        public OgreSkeletonBone[] Bones { get; private set; }
        public OgreBoneHierarchy[] BoneHierarchies { get; private set; }
        public AnimationClip[] Clips { get; private set; }

        public void ReadXml(string filename)
        {
            XmlDocument xml = new XmlDocument();
            xml.Load(filename);

            var root = xml.DocumentElement;
            this.BlendMode = root.GetAttribute("blendmode");

            XmlNode bonesNode = root.ChildNodes[0];
            this.Bones = new OgreSkeletonBone[bonesNode.ChildNodes.Count];
            for (int i = 0; i < bonesNode.ChildNodes.Count; i++)
            {
                OgreSkeletonBone bone = new OgreSkeletonBone();

                XmlElement boneNode = bonesNode.ChildNodes[i] as XmlElement;
                bone.Id = int.Parse(boneNode.GetAttribute("id"));
                bone.Name = boneNode.GetAttribute("name");
                bone.Pos = Vector3.zero;
                bone.Rot = Quaternion.identity;
                bone.Scale = Vector3.one;

                if (boneNode.SelectSingleNode("position") is XmlElement posNode)
                {
                    bone.Pos = posNode.ReadVector3();
                }

                if (boneNode.SelectSingleNode("rotation") is XmlElement rotNode)
                {
                    // 弧度
                    float angle = float.Parse(rotNode.GetAttribute("angle"));
                    float deg = Mathf.Rad2Deg * angle;
                    XmlElement axisNode = (XmlElement)rotNode.FirstChild;
                    Vector3 axis = axisNode.ReadVector3();
                    bone.Rot = Quaternion.AngleAxis(deg, axis).normalized;
                }

                if (boneNode.SelectSingleNode("scale") is XmlElement scaleNode)
                {
                    bone.Scale = scaleNode.ReadVector3();
                }

                this.Bones[i] = bone;
            }

            XmlNode bonehierarchyNode = root.ChildNodes[1];
            this.BoneHierarchies = new OgreBoneHierarchy[bonehierarchyNode.ChildNodes.Count];
            for (int i = 0; i < bonehierarchyNode.ChildNodes.Count; i++)
            {
                XmlElement item = bonehierarchyNode.ChildNodes[i] as XmlElement;
                OgreBoneHierarchy h = new OgreBoneHierarchy();
                h.Bone = item.GetAttribute("bone");
                h.Parent = item.GetAttribute("parent");
                BoneHierarchies[i] = h;
            }

            XmlNode animationsNode = root.SelectSingleNode("animations");
            if (animationsNode != null)
            {
                int animsNum = animationsNode.ChildNodes.Count;
                Clips = new AnimationClip[animsNum];
                for (int i = 0; i < animsNum; i++)
                {
                    XmlElement anim = animationsNode.ChildNodes[i] as XmlElement;
                    string name = anim.GetAttribute("name");
                    float length = float.Parse(anim.GetAttribute("length"));

                    AnimationClip animationClip = new AnimationClip();
                    animationClip.name = name;
                    Clips[i] = animationClip;

                    XmlNode tracksNode = anim.FirstChild;
                    for (int j = 0; j < tracksNode.ChildNodes.Count; j++)
                    {
                        XmlElement trackNode = tracksNode.ChildNodes[j] as XmlElement;
                        string boneName = trackNode.GetAttribute("bone");

                        OgreSkeletonBone boneData = FindBone(boneName);

                        AnimationCurve posX = new AnimationCurve();
                        AnimationCurve posY = new AnimationCurve();
                        AnimationCurve posZ = new AnimationCurve();

                        // 欧拉角
                        AnimationCurve rotX = new AnimationCurve();
                        AnimationCurve rotY = new AnimationCurve();
                        AnimationCurve rotZ = new AnimationCurve();
                        AnimationCurve rotW = new AnimationCurve();

                        XmlNode keyframesNode = trackNode.FirstChild;
                        for (int k = 0; k < keyframesNode.ChildNodes.Count; k++)
                        {
                            XmlElement keyframeNode = keyframesNode.ChildNodes[k] as XmlElement;
                            float time = float.Parse(keyframeNode.GetAttribute("time"));

                            Vector3 vector3 = Vector3.zero;
                            Quaternion q = Quaternion.identity;

                            foreach (XmlNode itemNodeTmp in keyframeNode.ChildNodes)
                            {
                                XmlElement itemNode = itemNodeTmp as XmlElement;
                                switch (itemNode.Name)
                                {
                                    case "translate":
                                        vector3 = itemNode.ReadVector3();

                                        // 位置
                                        vector3 += boneData.Pos;
                                        posX.AddKey(time, vector3.x);
                                        posY.AddKey(time, vector3.y);
                                        posZ.AddKey(time, vector3.z);

                                        break;
                                    case "rotate":
                                        // 保存的是弧度
                                        float angle = float.Parse(itemNode.GetAttribute("angle"));
                                        XmlElement axisNode = (XmlElement)itemNode.FirstChild;
                                        Vector3 axis = axisNode.ReadVector3();

                                        float deg = Mathf.Rad2Deg * angle;
                                        q = Quaternion.AngleAxis(deg, axis).normalized;

                                        // 旋转
                                        q *= boneData.Rot;
                                        rotX.AddKey(time, q.x);
                                        rotY.AddKey(time, q.y);
                                        rotZ.AddKey(time, q.z);
                                        rotW.AddKey(time, q.w);
                                        break;
                                }
                            }
                        }

                        List<string> relativePathList = new List<string>();
                        relativePathList.Add(boneName);
                        FindParents(boneName, relativePathList);
                        relativePathList.Add("skeleton");
                        relativePathList.Reverse();
                        string relativePath = Path.Combine(relativePathList.ToArray()).Replace("\\", "/");

                        animationClip.SetCurve(relativePath, typeof(Transform), "m_LocalPosition.x", posX);
                        animationClip.SetCurve(relativePath, typeof(Transform), "m_LocalPosition.y", posY);
                        animationClip.SetCurve(relativePath, typeof(Transform), "m_LocalPosition.z", posZ);

                        animationClip.SetCurve(relativePath, typeof(Transform), "m_LocalRotation.x", rotX);
                        animationClip.SetCurve(relativePath, typeof(Transform), "m_LocalRotation.y", rotY);
                        animationClip.SetCurve(relativePath, typeof(Transform), "m_LocalRotation.z", rotZ);
                        animationClip.SetCurve(relativePath, typeof(Transform), "m_LocalRotation.w", rotW);

                        // animationClip.SetCurve(relativePath, typeof(Transform), "m_LocalEulerAngles.x", rotX);
                        // animationClip.SetCurve(relativePath, typeof(Transform), "m_LocalEulerAngles.y", rotY);
                        // animationClip.SetCurve(relativePath, typeof(Transform), "m_LocalEulerAngles.z", rotZ);

                        // animationClip.SetCurve(relativePath, typeof(Transform), "localEulerAnglesBaked.x", rotX);
                        // animationClip.SetCurve(relativePath, typeof(Transform), "localEulerAnglesBaked.y", rotY);
                        // animationClip.SetCurve(relativePath, typeof(Transform), "localEulerAnglesBaked.z", rotZ);
                    }

                    animationClip.frameRate = 24f;
                    Debug.Log($"读取动画: {animationClip.name} - {animationClip.length}");
                }
            }

            // 找到骨骼的层级
            void FindParents(string boneName, List<string> result)
            {
                var cur = this.BoneHierarchies.FirstOrDefault(f => f.Bone == boneName);
                if (cur is null)
                    return;

                // 有父
                result.Add(cur.Parent);
                FindParents(cur.Parent, result);
            }

            OgreSkeletonBone FindBone(string name)
            {
                return this.Bones.First(f => f.Name == name);
            }
        }
    }

    public struct OgreSkeletonBone
    {
        public int Id;
        public string Name;
        public Vector3 Pos;
        public Quaternion Rot;
        public Vector3 Scale;
    }

    public class OgreBoneHierarchy
    {
        public string Bone;
        public string Parent;
    }
}