using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Animrig.Unity
{
	// spine renderer for unity
	public class UnitySpineRenderer : SpinePhysicsRenderer
	{
		public bool OverrideUpdate = false;
		public bool OverrideDefaultMixDuration = true;

		public UnitySpineRenderer()
            : base()
        {
            // on unity, default data scaling x0.01.
            AntiScaling = 100.0f;
        }
		public override void Update(double deltatime, double deltaphysics=-1)
		{
			if (!OverrideUpdate) return;

            if (state == null) return;
            if (skeleton == null) return;

            state.Update((float)deltatime);
            PreparePhysics();
            state.Apply(skeleton);
            skeleton.UpdateWorldTransform();

            // physics
            if (PhysicsGroups.Count > 0)
            {
                if (deltaphysics < 0) deltaphysics = deltatime;
                ApplyPhysics(deltaphysics);
            }
        }
		// spine
		public override void SetMixDuration(float duration)
		{
			if (!OverrideDefaultMixDuration) return;

			base.SetMixDuration(duration);
		}
	}

    // Spine object component
    [AddComponentMenu("Animationrig/Spine Renderer")]
    public class AnimrigRendererSpine : MonoBehaviour, IUnityStateRenderer
    {
		// property
        protected UnitySpineRenderer fRenderer = new UnitySpineRenderer();
        protected bool isRebuild = false;
        public StateRenderer StateRenderer
        {
            get
            {
                return fRenderer;
            }
        }
		[Tooltip("default:false. Call spine update & transform. If you control manually, set true.")]
		[SerializeField] public bool OverrideUpdate = false;
		[Tooltip("default:false. set GameObject.selfactive.")]
		[SerializeField] public bool OverrideEnable = false;
		[Tooltip("default:false. set GameObject.transform.")]
		[SerializeField] public bool OverrideTransform = false;
		[Tooltip("default:false. set SpineObject.Skeleton color.")]
		[SerializeField] public bool OverrideColor = false;
		[Tooltip("default:true. set SpineObject.DefaultMixDuration.")]
		[SerializeField] public bool OverrideDefaultMixDuration = true;
        [Tooltip("default:true. set Order in layer.")]
        [SerializeField] public bool OverridePriority = true;
        [Tooltip("default:true. Physics enable.")]
        [SerializeField] public bool EnablePhysics = true;
        [Tooltip("default:true. Camera transform enable.")]
        [SerializeField] public bool EnableCameraTransform = true;
        [Tooltip("default:100.0. Object scale ratio of Spine.SkeletonJson Scale.")]
		[SerializeField] [Range(1, 100)] public float PixelPerUnit = 100.0f;
		[Tooltip("default:1.0. transform scale ratio. UI space = 1.0 , 3D space = 0.01")]
		[SerializeField] [Range(0.01f, 1.0f)] public float TransformScale = 1.0f;
        [Tooltip("default:true. You can choose the physics force , world position or local position.")]
        [SerializeField] public bool PhysicsForceWorldPosition = true;
        [Tooltip("Apply physics force from the Unity side. The amount of GameObject movement will affect.")]
        [SerializeField] public GameObject PhysicsForceObject = null;
        [Tooltip("physics force ratio.")]
        [SerializeField] [Range(0.0f, 1.0f)] public float PhysicsForceRatio = 0.25f;

        public void UpdateStateRenderer(Animrig.AnimObject obj)
        {
            fRenderer.OverrideDefaultMixDuration = OverrideDefaultMixDuration;
            fRenderer.OverrideUpdate = OverrideUpdate;

            UpdatePhysicsExternalMatrix();

            if (!isRebuild) Rebuild(obj);
        }
        public void Rebuild(Animrig.AnimObject obj)
        {
            if (obj == null) return;
            if (fRenderer == null) return;

            var spineobj = (Animrig.SpineObject)obj;

            var go = gameObject;
            var stateinterface = go.GetComponent<Spine.Unity.IAnimationStateComponent>();
            var skeletoninterface = go.GetComponent<Spine.Unity.ISkeletonAnimation>();

            if ((stateinterface != null) && (skeletoninterface != null))
            {
                fRenderer.state = stateinterface.AnimationState;
                fRenderer.skeleton = skeletoninterface.Skeleton;
            }
            else
            {
                fRenderer.state = null;
                fRenderer.skeleton = null;
            }

            var skeletondata = go.GetComponent<Spine.Unity.ISkeletonComponent>();
            if (skeletondata != null)
            {
                fRenderer.AntiScaling = 1.0f / skeletondata.SkeletonDataAsset.scale;
            }

            fRenderer.SetSkin(spineobj.DefaultSkin);
            fRenderer.SetupPhysics(spineobj.SettingField);

            isRebuild = true;
        }
		public void UpdateGameObject()
		{
			var go = gameObject;

			// transform
			if (OverrideTransform)
			{
				Matrix4 m = fRenderer.TransMatrix;
                // camera
                if (fRenderer.StateInputObjectType != ObjectType.Camera)
                {
                    if (EnableCameraTransform) Animrig.Misc.m4PushMatrix(ref m, ref fRenderer.CameraMatrix);
                }
                // apply transscale
                Misc.m4PushScale(ref m, TransformScale, TransformScale, TransformScale);

				// position
				{
					UnityEngine.Vector3 v;
					v.x = m._41;
					v.y = m._42;
					v.z = m._43;
					// if (!Component3D) v.z = 0.0f;
					go.transform.localPosition = v;
				}
				// scale
				{
					UnityEngine.Vector3 v;
					v.x = (float)Math.Sqrt(m._11 * m._11 + m._12 * m._12 + m._13 * m._13 + m._14 * m._14) * PixelPerUnit;
					v.y = (float)Math.Sqrt(m._21 * m._21 + m._22 * m._22 + m._23 * m._23 + m._24 * m._24) * PixelPerUnit;
					v.z = (float)Math.Sqrt(m._31 * m._31 + m._32 * m._32 + m._33 * m._33 + m._34 * m._34) * PixelPerUnit;
					go.transform.localScale = v;
				}
				// rotate
				{
					UnityEngine.Vector3 forward;
					forward.x = m._31;
					forward.y = m._32;
					forward.z = m._33;

					UnityEngine.Vector3 upwards;
					upwards.x = m._21;
					upwards.y = m._22;
					upwards.z = m._23;

					go.transform.localRotation = UnityEngine.Quaternion.LookRotation(forward, upwards);

					/*
					var q = Misc.m4ExtractRotateQuaternion(ref m);
					UnityEngine.Quaternion uq;
					uq.x = q.x;
					uq.y = q.y;
					uq.z = q.z;
					uq.w = q.w;
					go.transform.localRotation = uq;
					*/
				}
			}

            // Override priority
            if (OverridePriority)
            {
                {
                    var mesh = go.GetComponent<MeshRenderer>();
                    if (mesh != null)
                    {
                        //mesh.sortingLayerID = "default";
                        mesh.sortingOrder = (int)Math.Floor(fRenderer.DrawPriorityDesign);
                    }
                }
            }

            // color
            if (OverrideColor && (fRenderer.skeleton != null))
			{
				fRenderer.skeleton.A = fRenderer.RendererInheritedVisible ? fRenderer.TransColor.a : 0.0f;
				fRenderer.skeleton.R = fRenderer.TransColor.r;
                fRenderer.skeleton.G = fRenderer.TransColor.g;
				fRenderer.skeleton.B = fRenderer.TransColor.b;
                // tint color
                /*
                fRenderer.skeleton.BlackR = fRenderer.TintColor.r;
                fRenderer.skeleton.BlackG = fRenderer.TintColor.g;
                fRenderer.skeleton.BlockB = fRenderer.TintColor.b;
                */
            } 

			// enable
			if (OverrideEnable)
			{
				if (go.activeSelf != fRenderer.RendererInheritedEnable) go.SetActive(fRenderer.RendererInheritedEnable);
			}
		}
        protected void ApplyPhysics(Spine.Unity.ISkeletonAnimation animated)
        {
            if (!EnablePhysics) return;
            if (!isRebuild) return;

            // SkeletonAnimation/Graphic.Update() component code
            /*
            deltaTime *= timeScale;
			skeleton.Update(deltaTime);
			state.Update(deltaTime);
            // *** I want insert Prepare physics code.
			state.Apply(skeleton);

			if (_UpdateLocal != null)       // *** call below event code
				_UpdateLocal(this);

			skeleton.UpdateWorldTransform();

			if (_UpdateWorld != null) {
				_UpdateWorld(this);
				skeleton.UpdateWorldTransform();
			}

			if (_UpdateComplete != null) {
				_UpdateComplete(this);
			}
            */

            if (OverrideUpdate)
            {
                if (fRenderer.PhysicsGroups.Count > 0)
                {
                    double deltatime = Time.deltaTime;
                    fRenderer.PreparePhysics();
                    fRenderer.state.Apply(fRenderer.skeleton);      // *** if Update() can be override, Unnecessary it.
                    fRenderer.skeleton.UpdateWorldTransform();
                    fRenderer.ApplyPhysics(deltatime);
                }
            }
            else
            {
                if (fRenderer.PhysicsGroups.Count > 0)
                {
                    fRenderer.PreparePhysics();
                    fRenderer.state.Apply(fRenderer.skeleton);
                    fRenderer.skeleton.UpdateWorldTransform();

                    double deltatime = Time.deltaTime;
                    fRenderer.ApplyPhysics(deltatime);
                }
            }
        }
        protected void UpdatePhysicsExternalMatrix()
        {
            if (PhysicsForceObject == null) return ;

            fRenderer.PhysicsExternalMatrix = Misc.IdentityMatrix4;

            if (PhysicsForceWorldPosition)
            {
                // world position mode
                var uv3 = PhysicsForceObject.transform.position;
                // inverse matrix
                var uim = PhysicsForceObject.transform.worldToLocalMatrix;
                // disable move
                uim.m03 = 0.0f;
                uim.m13 = 0.0f;
                uim.m23 = 0.0f;
                uv3 = uim.MultiplyPoint3x4(uv3);
                // inverse scale
                float rs = 1.0f / TransformScale;
                rs *= PhysicsForceRatio;
                Misc.m4PushMove(ref fRenderer.PhysicsExternalMatrix, uv3.x * rs, uv3.y * rs, uv3.z * rs);
            }
            else
            {
                // local position mode
                // make local inverse matrix
                /*
                var scale = PhysicsForceObject.transform.localScale;
                if ((scale.x < 0.01f) && (scale.x > -0.01f)) scale.x = 0.01f;
                if ((scale.y < 0.01f) && (scale.y > -0.01f)) scale.y = 0.01f;
                if ((scale.z < 0.01f) && (scale.z > -0.01f)) scale.z = 0.01f;
                var im = Misc.IdentityMatrix4;
                Misc.m4PushScale(ref im, 1.0f / scale.x, 1.0f / scale.y, 1.0f / scale.z);
                var rotate = PhysicsForceObject.transform.eulerAngles;
                Misc.m4PushRotateZ(ref im, -rotate.z / 360);
                Misc.m4PushRotateY(ref im, -rotate.y / 360);
                Misc.m4PushRotateX(ref im, -rotate.z / 360);
                */
                // position
                var uv3 = PhysicsForceObject.transform.localPosition;
                // inverse scale
                float rs = 1.0f / TransformScale;
                rs *= PhysicsForceRatio;
                Misc.m4PushMove(ref fRenderer.PhysicsExternalMatrix, uv3.x * rs, uv3.y * rs, uv3.z * rs);
            }
        }
        // unity
        void OnEnable()
        {
            var spinecompo = gameObject.GetComponent<Spine.Unity.ISkeletonAnimation>();
            if (spinecompo != null)
            {
                spinecompo.UpdateLocal -= ApplyPhysics; // safe
                spinecompo.UpdateLocal += ApplyPhysics;
            }
        }
        void OnDisable()
        {
            var spinecompo = gameObject.GetComponent<Spine.Unity.ISkeletonAnimation>();
            if (spinecompo != null)
            {
                spinecompo.UpdateLocal -= ApplyPhysics;
            }
        }
    }

    // inspector
#if UNITY_EDITOR

    [CustomEditor(typeof(AnimrigRendererSpine))]
    public class AnimationrigSpineRendererInspector : Editor
    {
        public void OnEnable()
        {

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

            var t = target as AnimrigRendererSpine;

			var go = t.gameObject;

			var skeletoncompo = go.GetComponent<Spine.Unity.ISkeletonAnimation>();
            var statecompo = go.GetComponent<Spine.Unity.IAnimationStateComponent>();

            if ((skeletoncompo == null)||(statecompo == null))
            { 
				EditorGUILayout.HelpBox("Please attach SkeletonAnimation/Graphic component to the this GameObject.", MessageType.Error);
            }

            // serializedObject.Update();
            // serializedObject.ApplyModifiedProperties();
        }
    }
#endif

}
