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

namespace Dreamteck.Splines
{

    [System.Serializable]
    public class TransformModule : ISerializationCallbackReceiver
    {
        public Vector2 offset
        {
            get { return _offset; }
            set
            {
                if (value != _offset)
                {
                    _offset = value;
                    _hasOffset = _offset != Vector2.zero;
                    if (targetUser != null)
                    {
                        targetUser.Rebuild();
                    }
                }
            }
        }
        public Vector3 rotationOffset
        {
            get { return _rotationOffset; }
            set
            {
                if (value != _rotationOffset)
                {
                    _rotationOffset = value;
                    _hasRotationOffset = _rotationOffset != Vector3.zero;
                    if (targetUser != null)
                    {
                        targetUser.Rebuild();
                    }
                }
            }
        }

        public bool hasOffset
        {
            get { return _hasOffset; }
        }

        public bool hasRotationOffset
        {
            get { return _hasRotationOffset; }
        }

        public Vector3 baseScale
        {
            get { return _baseScale; }
            set
            {
                if (value != _baseScale)
                {
                    _baseScale = value;
                    if (targetUser != null)
                    {
                        targetUser.Rebuild();
                    }
                }
            }
        }

        public bool is2D
        {
            get { return _2dMode; }
            set
            {
                _2dMode = value;
            }
        }

        [SerializeField]
        [HideInInspector]
        private bool _hasOffset = false;
        [SerializeField]
        [HideInInspector]
        private bool _hasRotationOffset = false;

        [SerializeField]
        [HideInInspector]
        private Vector2 _offset;
        [SerializeField]
        [HideInInspector]
        private Vector3 _rotationOffset = Vector3.zero;
        [SerializeField]
        [HideInInspector]
        private Vector3 _baseScale = Vector3.one;
        [SerializeField]
        [HideInInspector]
        private bool _2dMode = false;
        public enum VelocityHandleMode { Zero, Preserve, Align, AlignRealistic }
        public VelocityHandleMode velocityHandleMode = VelocityHandleMode.Zero;
        public SplineSample splineResult
        {
            get
            {
                return _splineResult;
            }
            set
            {
                _splineResult = value;
            }
        }
        private SplineSample _splineResult;

        public bool applyPositionX = true;
        public bool applyPositionY = true;
        public bool applyPositionZ = true;
        public bool applyPosition2D = true;
        public bool retainLocalPosition = false;

        public Spline.Direction direction = Spline.Direction.Forward;
        public bool applyPosition
        {
            get
            {
                if (_2dMode)
                {
                    return applyPosition2D;
                }
                return applyPositionX || applyPositionY || applyPositionZ;
            }
            set
            {
                applyPositionX = applyPositionY = applyPositionZ = applyPosition2D = value;
            }
        }

        public bool applyRotationX = true;
        public bool applyRotationY = true;
        public bool applyRotationZ = true;
        public bool applyRotation2D = true;
        public bool retainLocalRotation = false;
        public bool applyRotation
        {
            get
            {
                if (_2dMode)
                {
                    return applyRotation2D;
                }
                return applyRotationX || applyRotationY || applyRotationZ;
            }
            set
            {
                applyRotationX = applyRotationY = applyRotationZ = applyRotation2D = value;
            }
        }

        public bool applyScaleX = false;
        public bool applyScaleY = false;
        public bool applyScaleZ = false;
        public bool applyScale
        {
            get
            {
                return applyScaleX || applyScaleY || applyScaleZ;
            }
            set
            {
                applyScaleX = applyScaleY = applyScaleZ = value;
            }
        }
        [HideInInspector]
        public SplineUser targetUser = null;

        //These are used to save allocations
        private static Vector3 position = Vector3.zero;
        private static Quaternion rotation = Quaternion.identity;

        public void ApplyTransform(Transform input)
        {
            input.position = GetPosition(input.position);
            input.rotation = GetRotation(input.rotation);
            input.localScale = GetScale(input.localScale);
        }

        public void ApplyRigidbody(Rigidbody input)
        {
#if UNITY_EDITOR
            if (!Application.isPlaying)
            {
                ApplyTransform(input.transform);
                return;
            }
#endif
            input.transform.localScale = GetScale(input.transform.localScale);
            input.MovePosition(GetPosition(input.position));
            input.velocity = HandleVelocity(input.velocity);
            input.MoveRotation(GetRotation(input.rotation));
            Vector3 angularVelocity = input.angularVelocity;
            if (applyRotationX)
            {
                angularVelocity.x = 0f;
            }
            if (applyRotationY)
            {
                angularVelocity.y = 0f;
            }
            if (applyRotationZ || applyRotation2D)
            {
                angularVelocity.z = 0f;
            }
            input.angularVelocity = angularVelocity;
        }

        public void ApplyRigidbody2D(Rigidbody2D input)
        {
#if UNITY_EDITOR
            if (!Application.isPlaying)
            {
                ApplyTransform(input.transform);
                input.transform.rotation = Quaternion.AngleAxis(GetRotation(Quaternion.Euler(0f, 0f, input.rotation)).eulerAngles.z, Vector3.forward);
                return;
            }
#endif
            input.transform.localScale = GetScale(input.transform.localScale);
            input.position = GetPosition(input.position);
            input.velocity = HandleVelocity(input.velocity);
            input.rotation = GetRotation(Quaternion.Euler(0f, 0f, input.rotation)).eulerAngles.z;
            if (applyRotationX)
            {
                input.angularVelocity = 0f;
            }
        }

        Vector3 HandleVelocity(Vector3 velocity)
        {
            Vector3 idealVelocity = Vector3.zero;
            Vector3 direction = Vector3.right;
            switch (velocityHandleMode)
            {
                case VelocityHandleMode.Preserve: idealVelocity = velocity; break;
                case VelocityHandleMode.Align:
                    direction = _splineResult.forward;
                    if (Vector3.Dot(velocity, direction) < 0f) direction *= -1f;
                    idealVelocity = direction * velocity.magnitude; break;
                case VelocityHandleMode.AlignRealistic:
                    direction = _splineResult.forward;
                    if (Vector3.Dot(velocity, direction) < 0f) direction *= -1f;
                    idealVelocity = direction * velocity.magnitude * Vector3.Dot(velocity.normalized, direction); break;
            }
            if (applyPositionX) velocity.x = idealVelocity.x;
            if (applyPositionY) velocity.y = idealVelocity.y;
            if (applyPositionZ) velocity.z = idealVelocity.z;
            return velocity;
        }

        private Vector3 GetPosition(Vector3 inputPosition)
        {
            position = _splineResult.position;
            Vector2 finalOffset = _offset;
            if (finalOffset != Vector2.zero)
            {
                position += _splineResult.right * finalOffset.x * _splineResult.size + _splineResult.up * finalOffset.y * _splineResult.size;
            }
            if (retainLocalPosition)
            {
                Matrix4x4 matrix = Matrix4x4.TRS(position, _splineResult.rotation, Vector3.one);
                Vector3 splineLocalPosition = matrix.inverse.MultiplyPoint3x4(targetUser.transform.position);
                splineLocalPosition.x = applyPositionX ? 0f : splineLocalPosition.x;
                splineLocalPosition.y = applyPositionY ? 0f : splineLocalPosition.y;
                splineLocalPosition.z = applyPositionZ ? 0f : splineLocalPosition.z;
                inputPosition = matrix.MultiplyPoint3x4(splineLocalPosition);
            } else
            {
                if (applyPositionX) inputPosition.x = position.x;
                if (applyPositionY) inputPosition.y = position.y;
                if (applyPositionZ) inputPosition.z = position.z;
            }
            return inputPosition;
        }

        private Quaternion GetRotation(Quaternion inputRotation)
        {
            rotation = Quaternion.LookRotation(_splineResult.forward * (direction == Spline.Direction.Forward ? 1f : -1f), _splineResult.up);
            if (_2dMode)
            {
                if (applyRotation2D)
                {
                    rotation *= Quaternion.Euler(90, -90, 0);
                    inputRotation = Quaternion.AngleAxis(rotation.eulerAngles.z + _rotationOffset.z, Vector3.forward);
                }
                return inputRotation;
            }
            else
            {
                if (_rotationOffset != Vector3.zero)
                {
                    rotation = rotation * Quaternion.Euler(_rotationOffset);
                }
            }

            if (retainLocalRotation)
            {
                Quaternion localRotation = Quaternion.Inverse(rotation) * inputRotation;
                Vector3 targetEuler = localRotation.eulerAngles;
                targetEuler.x = applyRotationX ? 0f : targetEuler.x;
                targetEuler.y = applyRotationY ? 0f : targetEuler.y;
                targetEuler.z = applyRotationZ ? 0f : targetEuler.z;
                inputRotation = rotation * Quaternion.Euler(targetEuler);
            } else
            {
                if (!applyRotationX || !applyRotationY || !applyRotationZ)
                {
                    Vector3 targetEuler = rotation.eulerAngles;
                    Vector3 sourceEuler = inputRotation.eulerAngles;
                    if (!applyRotationX) targetEuler.x = sourceEuler.x;
                    if (!applyRotationY) targetEuler.y = sourceEuler.y;
                    if (!applyRotationZ) targetEuler.z = sourceEuler.z;
                    inputRotation.eulerAngles = targetEuler;
                }
                else 
                {
                    inputRotation = rotation;
                }
            }

            return inputRotation;
        }

        private Vector3 GetScale(Vector3 inputScale)
        {
            if (applyScaleX) inputScale.x = _baseScale.x * _splineResult.size;
            if (applyScaleY) inputScale.y = _baseScale.y * _splineResult.size;
            if (applyScaleZ) inputScale.z = _baseScale.z * _splineResult.size;
            return inputScale;
        }

        public void OnBeforeSerialize()
        {
            
        }

        public void OnAfterDeserialize()
        {
            _hasRotationOffset = _rotationOffset != Vector3.zero;
            _hasOffset = _offset != Vector2.zero;
        }
    }
}