using System.Linq;
using UnityEngine;
using UniGLTF;
using System;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif


namespace VRM
{
#if UNITY_EDITOR
    [Serializable]
    public struct PropItem
    {
        public ShaderUtil.ShaderPropertyType PropertyType;
        public Vector4 DefaultValues;
    }
#endif

    [Serializable]
    public class MaterialItem
    {
        public Material Material { get; private set; }
#if UNITY_EDITOR
        public Dictionary<string, PropItem> PropMap = new Dictionary<string, PropItem>();

        public string[] PropNames
        {
            get;
            private set;
        }
#endif

        public static MaterialItem Create(Material material)
        {
            var item = new MaterialItem
            {
                Material = material
            };
#if UNITY_EDITOR

            var propNames = new List<string>();
            for (int i = 0; i < ShaderUtil.GetPropertyCount(material.shader); ++i)
            {
                var propType = ShaderUtil.GetPropertyType(material.shader, i);
                var name = ShaderUtil.GetPropertyName(material.shader, i);

                switch (propType)
                {
                    case ShaderUtil.ShaderPropertyType.Color:
                        // 色
                        item.PropMap.Add(name, new PropItem
                        {
                            PropertyType = propType,
                            DefaultValues = material.GetColor(name),
                        });
                        propNames.Add(name);
                        break;

                    case ShaderUtil.ShaderPropertyType.TexEnv:
                        // テクスチャ
                        {
                            name += "_ST";
                            item.PropMap.Add(name, new PropItem
                            {
                                PropertyType = propType,
                                DefaultValues = material.GetVector(name),
                            });
                            propNames.Add(name);
                        }
                        // 縦横分離用
                        {
                            var st_name = name + "_S";
                            item.PropMap.Add(st_name, new PropItem
                            {
                                PropertyType = propType,
                                DefaultValues = material.GetVector(name),
                            });
                            propNames.Add(st_name);
                        }
                        {
                            var st_name = name + "_T";
                            item.PropMap.Add(st_name, new PropItem
                            {
                                PropertyType = propType,
                                DefaultValues = material.GetVector(name),
                            });
                            propNames.Add(st_name);
                        }
                        break;
                }
            }
            item.PropNames = propNames.ToArray();
#endif
            return item;
        }
    }

    [Serializable]
    public class MeshPreviewItem
    {
        public string Path
        {
            get;
            private set;
        }

        public SkinnedMeshRenderer SkinnedMeshRenderer
        {
            get;
            private set;
        }

        public Mesh Mesh
        {
            get;
            private set;
        }

        public string[] BlendShapeNames
        {
            get;
            private set;
        }

        public int BlendShapeCount
        {
            get { return BlendShapeNames.Length; }
        }

        public Material[] Materials
        {
            get;
            private set;
        }

        Transform m_transform;
        public Vector3 Position
        {
            get { return m_transform.position; }
        }
        public Quaternion Rotation
        {
            get { return m_transform.rotation; }
        }

        MeshPreviewItem(string path, Transform transform, Material[] materials)
        {
            Path = path;
            m_transform = transform;
            Materials = materials;
        }

        public void Bake(IEnumerable<BlendShapeBinding> values, float weight)
        {
            if (SkinnedMeshRenderer == null) return;

            // Update baked mesh
            if (values != null)
            {
                // clear
                for (int i = 0; i < BlendShapeCount; ++i)
                {
                    SkinnedMeshRenderer.SetBlendShapeWeight(i, 0);
                }

                foreach (var x in values)
                {
                    if (x.RelativePath == Path)
                    {
                        if (x.Index >= 0 && x.Index < SkinnedMeshRenderer.sharedMesh.blendShapeCount)
                        {
                            SkinnedMeshRenderer.SetBlendShapeWeight(x.Index, x.Weight * weight);
                        }
                        else
                        {
                            Debug.LogWarningFormat("Out of range {0}: 0 <= {1} < {2}",
                                SkinnedMeshRenderer.name,
                                x.Index,
                                SkinnedMeshRenderer.sharedMesh.blendShapeCount);
                        }
                    }
                }
            }
            SkinnedMeshRenderer.BakeMesh(Mesh);
        }

        public static MeshPreviewItem Create(Transform t, Transform root,
            Func<Material, Material> getOrCreateMaterial)
        {
            //Debug.Log("create");

            var meshFilter = t.GetComponent<MeshFilter>();
            var meshRenderer = t.GetComponent<MeshRenderer>();
            var skinnedMeshRenderer = t.GetComponent<SkinnedMeshRenderer>();
            if (meshFilter != null && meshRenderer != null)
            {
                // copy
                meshRenderer.sharedMaterials = meshRenderer.sharedMaterials.Select(x => getOrCreateMaterial(x)).ToArray();
                return new MeshPreviewItem(t.RelativePathFrom(root), t, meshRenderer.sharedMaterials)
                {
                    Mesh = meshFilter.sharedMesh
                };
            }
            else if (skinnedMeshRenderer != null)
            {
                // copy
                skinnedMeshRenderer.sharedMaterials = skinnedMeshRenderer.sharedMaterials.Select(x => getOrCreateMaterial(x)).ToArray();
                if (skinnedMeshRenderer.sharedMesh.blendShapeCount > 0)
                {
                    // bake required
                    var sharedMesh = skinnedMeshRenderer.sharedMesh;
                    return new MeshPreviewItem(t.RelativePathFrom(root), t, skinnedMeshRenderer.sharedMaterials)
                    {
                        SkinnedMeshRenderer = skinnedMeshRenderer,
                        Mesh = new Mesh(), // for bake
                        BlendShapeNames = Enumerable.Range(0, sharedMesh.blendShapeCount).Select(x => sharedMesh.GetBlendShapeName(x)).ToArray()
                    };
                }
                else
                {
                    return new MeshPreviewItem(t.RelativePathFrom(root), t, skinnedMeshRenderer.sharedMaterials)
                    {
                        Mesh = skinnedMeshRenderer.sharedMesh,
                    };
                }
            }
            else
            {
                return null;
            }
        }
    }
}
