﻿using System;
using System.Collections.Generic;
using System.Linq;
using UniGLTF;
using UniGLTF.MeshUtility;
using UniGLTF.Utils;
using UnityEngine;


namespace VRM
{
    public class VRMFirstPerson : MonoBehaviour
    {
        // If no layer names are set, use the default layer IDs.
        // Otherwise use the two Unity layers called "VRMFirstPersonOnly" and "VRMThirdPersonOnly".
        public static bool TriedSetupLayer = false;
        public static int FIRSTPERSON_ONLY_LAYER = 9;
        public static int THIRDPERSON_ONLY_LAYER = 10;

        [SerializeField]
        public Transform FirstPersonBone;

        [SerializeField]
        public Vector3 FirstPersonOffset;

        [Serializable]
        public struct RendererFirstPersonFlags
        {
            public Renderer Renderer;
            public FirstPersonFlag FirstPersonFlag;
            public Mesh SharedMesh
            {
                get
                {
                    var renderer = Renderer as SkinnedMeshRenderer;
                    if (renderer != null)
                    {
                        return renderer.sharedMesh;
                    }

                    var filter = Renderer.GetComponent<MeshFilter>();
                    if (filter != null)
                    {
                        return filter.sharedMesh;
                    }

                    return null;
                }
            }
        }

        [SerializeField]
        public List<RendererFirstPersonFlags> Renderers = new List<RendererFirstPersonFlags>();

        public void CopyTo(GameObject _dst, Dictionary<Transform, Transform> map)
        {
            var dst = _dst.AddComponent<VRMFirstPerson>();
            dst.FirstPersonBone = map[FirstPersonBone];
            dst.FirstPersonOffset = FirstPersonOffset;
            dst.Renderers = Renderers.Select(x =>
            {
                var mapped = map[x.Renderer.transform];
                var renderer = mapped.GetComponent<Renderer>();
                return new VRMFirstPerson.RendererFirstPersonFlags
                {
                    Renderer = renderer,
                    FirstPersonFlag = x.FirstPersonFlag,
                };
            }).ToList();
        }

        public void SetDefault()
        {
            FirstPersonOffset = new Vector3(0, 0.06f, 0);
            var animator = GetComponent<Animator>();
            if (animator != null)
            {
                FirstPersonBone = animator.GetBoneTransform(HumanBodyBones.Head);
            }
        }

        public void Reset()
        {
            SetDefault();
            TraverseRenderers();
        }

        public void TraverseRenderers(VRMImporterContext context = null)
        {
            Renderers.Clear();

            var rendererComponents = transform.GetComponentsInChildren<Renderer>();
            foreach (var renderer in rendererComponents)
            {
                // renderer が !enabled/!activeSelf なのがロード中なのか否か区別がつかないような気がするので
                // チェックしない。
                // if(!renderer.enabled)
                // {
                //     continue;
                // }
                var flags = new RendererFirstPersonFlags
                {
                    Renderer = renderer,
                    FirstPersonFlag = context == null
                        ? FirstPersonFlag.Auto
                        : GetFirstPersonFlag(context, renderer)
                };
                Renderers.Add(flags);
            }
        }

        static FirstPersonFlag GetFirstPersonFlag(VRMImporterContext context, Renderer r)
        {
            var mesh = r.transform.GetSharedMesh();
            if (mesh == null)
            {
                return FirstPersonFlag.Auto;
            }

            var index = context.Meshes.FindIndex(x => x.Mesh == mesh);
            if (index == -1)
            {
                return FirstPersonFlag.Auto;
            }

            foreach (var x in context.VRM.firstPerson.meshAnnotations)
            {
                if (x.mesh == index)
                {
                    return CachedEnum.ParseOrDefault<FirstPersonFlag>(x.firstPersonFlag, true);
                }
            }

            return FirstPersonFlag.Auto;
        }

        /// <summary>
        /// ヘッドレスモデルを作成した場合に返す
        /// </summary>
        Mesh CreateHeadlessModel(Renderer _renderer, Transform EraseRoot, SetVisibilityFunc setVisibility)
        {
            {
                var renderer = _renderer as SkinnedMeshRenderer;
                if (renderer != null)
                {
                    return CreateHeadlessModelForSkinnedMeshRenderer(renderer, EraseRoot, setVisibility);
                }
            }


            {
                var renderer = _renderer as MeshRenderer;
                if (renderer != null)
                {
                    CreateHeadlessModelForMeshRenderer(renderer, EraseRoot, setVisibility);
                    return null;
                }
            }

            // ここには来ない
            return null;
        }

        public static void SetupLayers()
        {
            if (!TriedSetupLayer)
            {
                TriedSetupLayer = true;
                int layer = LayerMask.NameToLayer("VRMFirstPersonOnly");
                FIRSTPERSON_ONLY_LAYER = (layer == -1) ? FIRSTPERSON_ONLY_LAYER : layer;
                layer = LayerMask.NameToLayer("VRMThirdPersonOnly");
                THIRDPERSON_ONLY_LAYER = (layer == -1) ? THIRDPERSON_ONLY_LAYER : layer;
            }
        }

        private static void CreateHeadlessModelForMeshRenderer(MeshRenderer renderer, Transform eraseRoot, SetVisibilityFunc setVisibility)
        {
            if (renderer.transform.Ancestors().Any(x => x == eraseRoot))
            {
                // 祖先に削除ボーンが居る
                setVisibility(renderer, false, true);
            }
            else
            {
                // 特に変更しない => 両方表示
            }
        }

        /// <summary>
        /// ヘッドレスモデルを作成する。
        ///
        /// 以下の場合は作成しない。
        ///
        /// * 削除対象が無い場合
        /// * 全部削除対象の場合
        ///
        /// </summary>
        private static Mesh CreateHeadlessModelForSkinnedMeshRenderer(SkinnedMeshRenderer renderer, Transform eraseRoot, SetVisibilityFunc setVisibility)
        {
            var bones = renderer.bones;

            var eraseBones = bones.Select((x, i) =>
            {
                // 祖先に削除対象が存在するか
                bool erase = x.Ancestors().Any(y => y == eraseRoot);
                return new
                {
                    i,
                    erase,
                };
            })
            .Where(x => x.erase)
            .Select(x => x.i)
            .ToArray()
            ;
            if (eraseBones.Length == 0)
            {
                // 削除対象が存在しない
                return null;
            }

            // 元のメッシュを三人称に変更(自分からは見えない)
            setVisibility(renderer, false, true);

            // 削除対象のボーンに対するウェイトを保持する三角形を除外して、一人称用のモデルを複製する
            var headlessMesh = BoneMeshEraser.CreateErasedMesh(renderer.sharedMesh, eraseBones);
            if (headlessMesh.triangles.Length == 0)
            {
                // 一人称用のmeshには描画すべき部分が無い(全部削除された)
                UnityEngine.Object.Destroy(headlessMesh);
                return null;
            }

            // 一人称用のモデルのセットアップ
            var go = new GameObject("_headless_" + renderer.name);
            go.layer = FIRSTPERSON_ONLY_LAYER;
            go.transform.SetParent(renderer.transform, false);
            var erased = go.AddComponent<SkinnedMeshRenderer>();
            erased.sharedMesh = headlessMesh;
            erased.sharedMaterials = renderer.sharedMaterials;
            erased.bones = bones;
            erased.rootBone = renderer.rootBone;
            erased.updateWhenOffscreen = true;
            return headlessMesh;
        }

        bool m_done;

        List<Mesh> m_headlessMeshes = new List<Mesh>();

        /// <summary>
        /// Set target renderer visibility
        /// 
        /// https://github.com/vrm-c/UniVRM/issues/633#issuecomment-758454045
        /// 
        /// </summary>
        /// <param name="renderer">Target renderer. Player avatar or other</param>
        /// <param name="firstPerson">visibility in HMD camera</param>
        /// <param name="thirdPerson">other camera visibility</param>
        public delegate void SetVisibilityFunc(Renderer renderer, bool firstPerson, bool thirdPerson);

        [Obsolete("Use SetVisibilityFunc")]
        public delegate void SetVisiblityFunc(Renderer renderer, bool firstPerson, bool thirdPerson);

        /// <summary>
        /// Default implementation.
        /// Threre are 4 cases.
        /// </summary>
        /// <param name="renderer"></param>
        /// <param name="firstPerson"></param>
        /// <param name="thirdPerson"></param>
        public static void SetVisibility(Renderer renderer, bool firstPerson, bool thirdPerson)
        {
            SetupLayers();

            if (firstPerson && thirdPerson)
            {
                // both             
                // do nothing
            }
            else if (firstPerson)
            {
                // only first person
                renderer.gameObject.layer = FIRSTPERSON_ONLY_LAYER;
            }
            else if (thirdPerson)
            {
                // only third person
                renderer.gameObject.layer = THIRDPERSON_ONLY_LAYER;
            }
            else
            {
                // invisible
                renderer.enabled = false;
            }
        }

        [Obsolete("Use SetVisibility")]
        public static void SetVisiblity(Renderer renderer, bool firstPerson, bool thirdPerson) =>
            SetVisibility(renderer, firstPerson, thirdPerson);

        public void Setup()
        {
            // same as v0.63.2
            Setup(true, SetVisibility);
        }

        /// <summary>
        /// from v0.64.0
        /// </summary>
        /// <param name="isSelf"></param>
        public void Setup(bool isSelf, SetVisibilityFunc setVisibility)
        {
            if (m_done) return;
            m_done = true;

            if (isSelf)
            {
                // self avatar
                foreach (var x in Renderers)
                {
                    switch (x.FirstPersonFlag)
                    {
                        case FirstPersonFlag.Auto:
                            {
                                var headlessMesh = CreateHeadlessModel(x.Renderer, FirstPersonBone, setVisibility);
                                if (headlessMesh != null)
                                {
                                    m_headlessMeshes.Add(headlessMesh);
                                }
                            }
                            break;

                        case FirstPersonFlag.FirstPersonOnly:
                            setVisibility(x.Renderer, true, false);
                            break;

                        case FirstPersonFlag.ThirdPersonOnly:
                            setVisibility(x.Renderer, false, true);
                            break;

                        case FirstPersonFlag.Both:
                            setVisibility(x.Renderer, true, true);
                            break;
                    }
                }
            }
            else
            {
                // other avatar
                foreach (var x in Renderers)
                {
                    switch (x.FirstPersonFlag)
                    {
                        case FirstPersonFlag.FirstPersonOnly:
                            setVisibility(x.Renderer, false, false);
                            break;

                        case FirstPersonFlag.Auto:
                        // => Same as Both
                        case FirstPersonFlag.Both:
                        case FirstPersonFlag.ThirdPersonOnly:
                            setVisibility(x.Renderer, true, true);
                            break;
                    }
                }
            }
        }

        void OnDestroy()
        {
            foreach (var mesh in m_headlessMeshes)
            {
                if (mesh != null)
                {
                    // Debug.LogFormat("[VRMFirstPerson] OnDestroy: {0}", mesh);
                    UnityEngine.Object.Destroy(mesh);
                }
            }
            m_headlessMeshes.Clear();
        }
    }
}
