using System.Collections;
using System.Collections.Generic;
using System.IO;
using System;

using UnityEngine;
using Animrig;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Animrig.Unity
{
    [ExecuteInEditMode]
    [AddComponentMenu("Animationrig/Animationrig State")]
    public class AnimrigState : MonoBehaviour
    {
        // property
        public AnimationrigState State { get; protected set; } = null;
        public Animationrig Data { get; protected set; } = null;
        public Preset Preset { get; protected set; } = null;

        [SerializeField] protected TextAsset fAnimrigTextAsset = null;
        public TextAsset AnimrigTextAsset
        {
            get
            {
                return fAnimrigTextAsset;
            }
            set
            {
                if (fAnimrigTextAsset == value) return;
                fAnimrigTextAsset = value;
                SetupAnimrig();
            }
        }
        [SerializeField] protected TextAsset fPresetTextAsset = null;
        public TextAsset PresetTextAsset
        {
            get
            {
                return fPresetTextAsset;
            }
            set
            {
                if (fPresetTextAsset == value) return;
                fPresetTextAsset = value;
                SetupPreset();
            }
        }

        [Tooltip("default:true. Do in unity update. If you control manually, set false.")]
        [SerializeField] public bool AutoUpdate = true;
        [Tooltip("default:true. Call object update only once per frame.")]
        [SerializeField] public bool UpdateOnce = true;
        [SerializeField] public List<GameObject> RendererObjects = new List<GameObject>(4);
        // for timeline.
        //[SerializeField] public List<int> Tracks = new List<int>(4);
        private int pTrack0, pTrack1, pTrack2, pTrack3;
        [Tooltip("Startup animations.")]
        [SerializeField] public int Track0 = -1;
        [SerializeField] public int Track1 = -1;
        [SerializeField] public int Track2 = -1;
        [SerializeField] public int Track3 = -1;

        [SerializeField] public bool AutoFadeEnable = true;
        [SerializeField] public float AutoFadeDuration = 1.0f;
        [SerializeField] public Animrig.Easing AutoFadeEasing = Animrig.Easing.OutInQuad;
        [SerializeField] public float AutoFadeEasingPower = 1.0f;

        public bool IsRebuild { get; protected set; } = false;
        
        protected void EntryTrack(int trackindex, ref int previndex, int animindex, float duration = 0.5f, bool reload = true)
        {
            if (!reload)
                if (animindex == previndex) return;
            previndex = animindex;        

            if (State == null) return;

            // editor
            if (!Application.isPlaying) return;

            if (!AutoFadeEnable) duration = 0.0f;
            if ((animindex < 0) || (animindex >= State.Data.AnimationCount))
            {
                State.ClearTrackFade(trackindex, duration, AutoFadeEasing, AutoFadeEasingPower);
            }
            else
            {
                State.EntryTrackFade(trackindex, animindex, duration, AutoFadeEasing, AutoFadeEasingPower);
            }


        }

        // for timeline signal-track.
        public void SignalEntryTrack(string op)
        {
            if (State == null) return;
            if (!Application.isPlaying) return;

            Easing inStringToEasing(string s)
            {
                return (Easing)Enum.Parse(typeof(Easing), s, true);
            }
            var track = -1;
            var name = "";
            var duration = AutoFadeDuration;
            var emode = AutoFadeEasing;
            var epower = AutoFadeEasingPower;
            if (!AutoFadeEnable) duration = 0.0f;

            var sl = Misc.DivideString(',', op);
            foreach (var s in sl)
            {
                var s1 = "";
                var s2 = "";
                var p = s.IndexOf(':');
                if (p > -1)
                {
                    s1 = Misc.Slice(s, 0, p);
                    s2 = Misc.Slice(s, p + 1, 4096);
                }
                if (s1 == "track")
                {
                    track = Misc.ToIntDef(s2, -1);
                }
                else if (s1 == "name")
                {
                    name = s2;
                }
                else if (s1 == "duration")
                {
                    duration = Misc.ToFloatDef(s2, 0.0f);
                    duration = Math.Min(Math.Max(duration, 0.0f), 1000);
                }
                else if (s1 == "easing")
                {
                    try
                    {
                        emode = inStringToEasing(s2);
                    }
                    catch
                    {
                        //
                    }
                }
                else if (s1 == "easingpow")
                {
                    epower = Misc.ToFloatDef(s2, 1.0f);
                    epower = Math.Min(Math.Max(epower, 0.0f), 2.0f);
                }
            }

            var animindex = State.Data.Search(name);
            if (animindex == -1)
            {
                State.ClearTrackFade(track, duration, emode, epower);
            }
            else
            {
                State.EntryTrackFade(track, animindex, duration, emode, epower);
            }
        }
       
        protected void SetupAnimrig()
        {
            //while (fTracks.Count < 8) fTracks.Add(-1);

            State = null;
            Data = null;

            if (fAnimrigTextAsset == null)
            {
                SetRendererCount();
                return;
            }

            // load data
            {
                Data = new Animationrig();
                bool b;
                using (var ms = new MemoryStream(fAnimrigTextAsset.bytes))
                {
                    ms.Position = 0;
                    b = Data.LoadFromBinary(ms);
                }
                if (!b)
                {
                    // error
                    Data = null;
                    SetRendererCount();
                    new Exception("AnimationrigUnityCompo : can not load animationrig data.");
                }
            }

            // setup state
            State = new AnimationrigState(Data);
            SetRendererCount();
        }
        protected List<string> LoadStringListFromTextAsset(TextAsset ta)
        {
            var sl = new List<string>();
            string s = "";
            foreach (var c in ta.text)
            {
                if (c == '\r') continue;
                if (c == '\n')
                {
                    sl.Add(s);
                    s = "";
                    continue;
                }
                s = s + c;
            }
            if (s != "") sl.Add(s);

            return sl;
        }
        protected void SetupPreset()
        {
            Preset = null;

            if (fPresetTextAsset == null) return;

            {
                Preset = new Preset();
                var sl = LoadStringListFromTextAsset(fPresetTextAsset);
                Preset.MakeFromStringList(sl);
                // binary mode. not necessary on unity.
                /* 
                bool b;
                using (var ms = new MemoryStream(fPresetTextAsset.bytes))
                {
                    ms.Position = 0;
                    b = Preset.LoadFromStream(ms);
                }
                if (!b)
                {
                    Preset = null;
                    new Exception("AnimationrigUnityCompo : can not load preset data.");
                }
                */
            }
        }
        protected void SetRendererCount()
        {
            var count = 2;
            if (Data != null) count = Math.Max(Data.ObjectCount, count);

            if (RendererObjects.Count == count) return;

            if (RendererObjects.Count < count)
            {
                for (var i = 0; i < count - RendererObjects.Count; i++)
                {
                    RendererObjects.Add(null);
                }
            }
            if (RendererObjects.Count > count)
            {
                RendererObjects.RemoveRange(count, RendererObjects.Count - count);
            }
        }

        public AnimrigState()
            : base()
        {
            Clear();
        }
        public void Clear()
        {
            fAnimrigTextAsset = null;
            fPresetTextAsset = null;

            SetupAnimrig();
            SetupPreset();

            SetRendererCount();
        }
        public void Rebuild()
        {
            SetupAnimrig();
            SetupPreset();
           
            IsRebuild = true;
        }
		public void UpdateState(double deltatime, bool updateonce=true)
		{
			if (Data == null) return;
			if (State == null) return;

			// setup input renderer
			for (var i = 0; i < State.InputCount; i++)
			{
				if (i < RendererObjects.Count)
				{
					var g = RendererObjects[i];
					if (g != null)
					{
						var c = g.GetComponent<IUnityStateRenderer>();
						if (c != null)
						{
							c.UpdateStateRenderer(Data.Objects[i]);
							State.Inputs[i].Renderer = c.StateRenderer;
							continue;
						}
					}
				}

				State.Inputs[i].Renderer = null;
			}

			// update
			State.Update(deltatime,updateonce);

			// update gameobject
			for (var i = 0; i < State.InputCount; i++)
			{
				if (i < RendererObjects.Count)
				{
					var g = RendererObjects[i];
					if (g != null)
					{
						var c = g.GetComponent<IUnityStateRenderer>();
						if (c != null)
						{
							c.UpdateGameObject();
						}
					}
				}
			}
		}

        // unity
        private void Awake()
        {
            void DoStartupAnimations()
            {
                if (State == null) return;

                EntryTrack(0, ref pTrack0, Track0, 0.0f, true);
                EntryTrack(1, ref pTrack1, Track1, 0.0f, true);
                EntryTrack(2, ref pTrack2, Track2, 0.0f, true);
                EntryTrack(3, ref pTrack3, Track3, 0.0f, true);
            }

            Rebuild();
            DoStartupAnimations();
        }
        void Update()
        {
            void DoChangeAnimation()
            {
                EntryTrack(0, ref pTrack0, Track0, AutoFadeDuration, false);
                EntryTrack(1, ref pTrack1, Track1, AutoFadeDuration, false);
                EntryTrack(2, ref pTrack2, Track2, AutoFadeDuration, false);
                EntryTrack(3, ref pTrack3, Track3, AutoFadeDuration, false);
            }

            if (!Application.isPlaying) return;

            DoChangeAnimation();

            if (AutoUpdate) UpdateState(Time.deltaTime, UpdateOnce);
        }
    }

    // inspector
#if UNITY_EDITOR

    [CustomEditor(typeof(AnimrigState))]
    public class AnimationrigStateInspector : Editor
    {
        private bool SectionData = false;
        //private Vector2 ScrollData; 
        //private bool SectionState = false;
        //private Vector2 ScrollState;
        private bool SectionPreset = false;
        //private Vector2 ScrollPreset;
        private bool SectionTracks = true;
        //private Vector2 ScrollTracks;
        private bool SectionRenderer = true;

        // misc
        private UnityEditor.SerializedProperty sp(string name, int arrayindex=-1)
        {
            if (arrayindex == -1)
            {
                return serializedObject.FindProperty(name);
            }
            else
            {
                return serializedObject.FindProperty(name).GetArrayElementAtIndex(arrayindex);
            }
            
        }

        public void OnEnable()
        {
			
		}
        public override void OnInspectorGUI()
        {
            // base.OnInspectorGUI();

            // bold style
            var boldstyle = new GUIStyle()
            {
                alignment = TextAnchor.MiddleLeft,
                fontStyle = FontStyle.Bold,
            };

            // first rebuild
            var targetclass = target as AnimrigState;
            if (!targetclass.IsRebuild) targetclass.Rebuild();

            serializedObject.Update();

            UnityEngine.Object g;
            g = (TextAsset)EditorGUILayout.ObjectField("Animrig TextAsset", targetclass.AnimrigTextAsset, typeof(TextAsset), true);
            sp("fAnimrigTextAsset").objectReferenceValue = g;
            targetclass.AnimrigTextAsset = g as TextAsset;

            g = (TextAsset)EditorGUILayout.ObjectField("Preset TextAsset", targetclass.PresetTextAsset, typeof(TextAsset), true);
            sp("fPresetTextAsset").objectReferenceValue = g;
            targetclass.PresetTextAsset = g as TextAsset;

            sp("AutoUpdate").boolValue = EditorGUILayout.Toggle(
				new GUIContent("AutoUpdate", "default:true. Called by unity update(). If you call manually, set false."), 
				targetclass.AutoUpdate
			);
			sp("UpdateOnce").boolValue = EditorGUILayout.Toggle(
				new GUIContent("UpdateOnce", "default:true. Call object update only once. If you need accurate animation, set false."),
				targetclass.UpdateOnce
			);

            // renderer objects
            SectionRenderer = EditorGUILayout.Foldout(SectionRenderer, "Renderer");
            if (SectionRenderer)
            {
                EditorGUILayout.LabelField("Renderer GameObjects", boldstyle);
                //var count = targetclass.RendererObjects.Count;
                var count = sp("RendererObjects").arraySize;
                for (var i = 0; i < count; i++)
                {
                    var name = "empty";
                    var typename = "none";
                    if ((targetclass.Data != null) && (i < targetclass.Data.ObjectCount))
                    {
                        name = targetclass.Data.Objects[i].Name;
                        typename = Misc.ObjectTypeToString(targetclass.Data.Objects[i].ObjectType);
                    }
                    var v = sp("RendererObjects", i);
                    if (v != null) v.objectReferenceValue = EditorGUILayout.ObjectField($"{i} {name}({typename})", targetclass.RendererObjects[i], typeof(GameObject), true) as GameObject;
                }
                if (count == 0)
                {
                    var style = new GUIStyle()
                    {
                        alignment = TextAnchor.MiddleCenter,
                        fontStyle = FontStyle.Normal,
                    };
                    EditorGUILayout.LabelField("none",style);
                }
            }
			// renderer object check
			{
				var s = "";
				foreach (var go in targetclass.RendererObjects)
				{
					if (go == null) continue;
					var comp = go.GetComponent<Animrig.Unity.IUnityStateRenderer>();
					if (comp == null)
					{
						if (s != "") s = s + "\r\n";
						s = s + go.name;
					}
				}
				if (s != "")
				{
					EditorGUILayout.HelpBox("Please attach IStateRenderer component to below GameObjects.\r\n" + s, MessageType.Error);
				}
			}
            // Tracks
            SectionTracks = EditorGUILayout.Foldout(SectionTracks, "Tracks");
            if (SectionTracks)
            {
                int[] indexlist;
                string[] namelist;
                {
                    if (targetclass.State == null)
                    {
                        indexlist = new int[32 + 1];
                        namelist = new string[32 + 1];
                        indexlist[0] = -1;
                        namelist[0] = "-1 : none";
                        for (var i = 0; i < 32; i++)
                        {
                            indexlist[i + 1] = i;
                            namelist[i + 1] = $"{i} : empty";
                        }
                    }
                    else
                    {
                        var n = targetclass.Data.AnimationCount;
                        indexlist = new int[n + 1];
                        namelist = new string[n + 1];
                        indexlist[0] = -1;
                        namelist[0] = "-1 : none";
                        for (var i = 0; i < n; i++)
                        {
                            if (i < targetclass.Data.AnimationCount)
                            {
                                indexlist[i + 1] = i;
                                namelist[i + 1] = $"{i} : {targetclass.Data.Animations[i].Name}";
                            }
                            else
                            {
                                indexlist[i + 1] = i;
                                namelist[i + 1] = $"{i} : empty";
                            }
                        }
                    }
                }

                EditorGUILayout.LabelField("Animation tracks", boldstyle);
                {
                    sp("Track0").intValue = EditorGUILayout.IntPopup($"Track{0}", targetclass.Track0, namelist, indexlist);
                    sp("Track1").intValue = EditorGUILayout.IntPopup($"Track{1}", targetclass.Track1, namelist, indexlist);
                    sp("Track2").intValue = EditorGUILayout.IntPopup($"Track{2}", targetclass.Track2, namelist, indexlist);
                    sp("Track3").intValue = EditorGUILayout.IntPopup($"Track{3}", targetclass.Track3, namelist, indexlist);
                }
                // autofade
                {
                    EditorGUILayout.LabelField("AutoFade", boldstyle);

                    sp("AutoFadeEnable").boolValue = EditorGUILayout.Toggle("Enable", targetclass.AutoFadeEnable);
                    sp("AutoFadeDuration").floatValue = Math.Min(Math.Max(EditorGUILayout.FloatField("Duration(sec)", targetclass.AutoFadeDuration), 0), 60);
                    sp("AutoFadeEasing").enumValueIndex = Convert.ToInt32(EditorGUILayout.EnumPopup("Easing", targetclass.AutoFadeEasing));
                    sp("AutoFadeEasingPower").floatValue = Math.Min(Math.Max(EditorGUILayout.Slider("EasingPower", targetclass.AutoFadeEasingPower, 0.0f, 2.0f), 0.0f), 2.0f);
                }
            }

            // animationrig data
            SectionData = EditorGUILayout.Foldout(SectionData, "Animationrig");
            if (SectionData)
            {
                EditorGUILayout.LabelField("Data info", boldstyle);

                var buf = "";
                if (targetclass.Data != null)
                {
                    // animations
                    {
                        buf += $"AnimationCount : {targetclass.Data.AnimationCount}" + "\r\n";
                        var index = 0;
                        foreach (var o in targetclass.Data.Animations)
                        {
                            buf += $"{index} {o.Name}" + "\r\n";
                            index++;
                        }
                    }
                    buf += "\r\n";
                    // objects
                    {
                        buf = buf + $"ObjectCount : {targetclass.Data.ObjectCount}" + "\r\n";
                        var index = 0;
                        foreach (var o in targetclass.Data.Objects)
                        {
                            buf += $"{index} {o.Name} : {Misc.ObjectTypeToString(o.ObjectType)}" + "\r\n";
                            index++;
                        }
                    }
                }
                //ScrollData = EditorGUILayout.BeginScrollView(ScrollData);
                EditorGUILayout.TextArea(buf, GUILayout.Height(200));
                //EditorGUILayout.EndScrollView();
            }

            // animationrig state
            /*
            SectionState = EditorGUILayout.Foldout(SectionState, "AnimationrigState");
            if (SectionState)
            {
                EditorGUILayout.LabelField("State info", boldstyle);

                var buf = "";
                if (targetclass.State != null)
                {
                    buf += $"TrackCount : {targetclass.State.TrackCount}" + "\r\n";
                    var index = 0;
                    foreach (var o in targetclass.State.Tracks)
                    {
                        if (o == null)
                        {
                            buf += "null \r\n";
                        }
                        else
                        {
                            if (o.Animation == null)
                            {
                                buf += "error! \r\n";
                            }
                            else
                            {
                                buf += $"{index} {o.Animation.Name} : frame {o.PlayFrame}" + "\r\n";
                            }
                        }
                        index++;
                    }
                }

                //ScrollPreset = EditorGUILayout.BeginScrollView(ScrollPreset);
                EditorGUILayout.TextArea(buf, GUILayout.Height(200));
                //EditorGUILayout.EndScrollView();
            }
            */

            // preset
            SectionPreset = EditorGUILayout.Foldout(SectionPreset, "Preset");
            if (SectionPreset)
            {
                EditorGUILayout.LabelField("Preset list", boldstyle);

                var style = new GUIStyle()
                {
                    alignment = TextAnchor.MiddleLeft,
                    fontStyle = FontStyle.Normal,
                };
                EditorGUILayout.LabelField("key = value", style);

                var buf = "";
                if (targetclass.Preset != null)
                {
                    buf = buf + $"PresetCount : {targetclass.Preset.list.Count}" + "\r\n";
                    foreach (var o in targetclass.Preset.list)
                    {
                        buf = buf + $"{o.Key} = {o.Value}" + "\r\n";
                    }
                }

                //ScrollPreset = EditorGUILayout.BeginScrollView(ScrollPreset);
                EditorGUILayout.TextArea(buf, GUILayout.Height(200));
                //EditorGUILayout.EndScrollView();
            }

            // buttons
            GUILayout.BeginHorizontal();
            if (GUILayout.Button("reload"))
            {
                targetclass.Rebuild();
			}
            if (GUILayout.Button("clear"))
            {
                targetclass.Clear();
			}
            GUILayout.EndHorizontal();

            // serialize
            serializedObject.ApplyModifiedProperties();
        }
    }

#endif

}

