﻿using System.Linq;
using UnityEngine;
using System;
using System.Collections.Generic;

namespace UniVRM10
{
    [Serializable]
    public sealed class PreviewMeshItem
    {
        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; }
        }

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

        public void Bake(IEnumerable<MorphTargetBinding> 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 * MorphTargetBinding.VRM_TO_UNITY);
                        }
                        else
                        {
                            Debug.LogWarningFormat("Out of range {0}: 0 <= {1} < {2}",
                                SkinnedMeshRenderer.name,
                                x.Index,
                                SkinnedMeshRenderer.sharedMesh.blendShapeCount);
                        }
                    }
                }
            }
            SkinnedMeshRenderer.BakeMesh(Mesh);
        }

        public static PreviewMeshItem 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 PreviewMeshItem(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 PreviewMeshItem(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 PreviewMeshItem(t.RelativePathFrom(root), t, skinnedMeshRenderer.sharedMaterials)
                    {
                        Mesh = skinnedMeshRenderer.sharedMesh,
                    };
                }
            }
            else
            {
                return null;
            }
        }
    }
}
