using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using Framework.Extensions;
using Framework.Entities;

namespace Framework.ActorComponents
{
    /// <summary>
    /// This action exposes a <see cref="Transform"/> as a forward reference for 
    /// another transform.
    /// </summary>
    public class ExposeForwardReference : CoreAction
    {
        private Transform forwardReference;

        /// <summary>
        /// Initializes a new instance of the <see cref="ExposeForwardReference"/> class.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="forwardReference">The forward reference.</param>
        public ExposeForwardReference(CoreBehaviour sender, Transform forwardReference)
            : base(sender)
        {
            this.forwardReference = forwardReference;
        }

        /// <summary>
        /// Gets the reference.
        /// </summary>
        /// <returns></returns>
        public Transform GetReference()
        {
            return this.forwardReference;
        }
    }

    /// <summary>
    /// Controls the relative position of the camera to a target behaviour. The camera is positioned
    /// as though on a fixed stick/boom above the target and moves with them as well as being rotated 
    /// around them by mouse movement.
    /// </summary>
    [AddComponentMenu("Scripts/Framework/Actor Components/Cameras/Third Person Boom")]
    public class ThirdPersonBoom : CoreBehaviour
    {
        public Transform target;
        public new Camera camera;
        public float rotateSpeed = 100f;
        public float defaultDistance = 14f;
        public float viewCorrectionSpeed = 5f;
        public float tilt = 45f;
        public float cameraFocusDistance = 10f;
        public bool keepInView = true;

        private float distance = 14f;

        /// <summary>
        /// Called when this component is awoken after the Unity
        /// Awake is called. 
        /// </summary>
        protected override void InnerAwake()
        {
            Debug.LogWarning("Boom must be before entity component in script execution order. Replace this message with a checking system.");
            // TODO, prevent this from being strongly coupled??
            if (this.target == null)
            {
                this.target = GameObject.Find("Player").transform;
            }
            if (this.camera == null)
            {
                this.camera = GameObject.Find("Camera").GetComponent<Camera>();
            }
        }

        /// <summary>
        /// Called when this component is started after the Unity
        /// Start is called. 
        /// </summary>
        protected override void InnerStart()
        {
            if (this.target != null)
            {
                this.target.gameObject.RequestExpose(this);
                Interact(new ExposeForwardReference(this, this.transform));
            }
        }

        /// <summary>
        /// Called when this component is updated after the Unity
        /// Update is called.
        /// </summary>
        protected override void InnerUpdate()
        {
            float rotateInput = Input.GetAxis("Mouse X");
            this.transform.position = this.target.position;
            // this.transform.up = this.target.up;
            this.transform.rotation = Quaternion.FromToRotation(this.transform.up, this.target.up) * this.transform.rotation;
            //this.transform.localEulerAngles = this.transform.localEulerAngles + (new Vector3(0, rotateInput, 0) * this.rotateSpeed * Time.deltaTime);
            this.transform.Rotate(0, rotateInput * this.rotateSpeed * Time.deltaTime, 0, Space.Self);

            this.camera.transform.position = this.transform.position - ((Quaternion.AngleAxis(this.tilt, this.transform.right) * this.transform.forward) * this.distance);
            this.camera.transform.up = this.transform.up;
            this.camera.transform.rotation = Quaternion.FromToRotation(this.camera.transform.up, this.target.up) * this.camera.transform.rotation;
            //this.camera.transform.rotation = Quaternion.FromToRotation(this.camera.transform.forward, this.camera.transform.position - this.target.position) * this.camera.transform.rotation;
            //this.camera.transform.rotation = this.camera.transform.rotation * Quaternion.Euler(this.tilt, 0, 0);
            this.camera.transform.LookAt(this.transform.position + (this.transform.forward * this.cameraFocusDistance), this.transform.up);

            RaycastHit hit;
            if (this.keepInView && Physics.Raycast(this.target.position + this.target.up * 0.5f, this.camera.transform.position - this.target.position, out hit, this.defaultDistance))
            {
                this.distance = Mathf.Lerp(this.distance, hit.distance, Time.deltaTime * this.viewCorrectionSpeed);

            }
            else
            {
                if (this.distance != this.defaultDistance)
                    this.distance = Mathf.Lerp(this.distance, this.defaultDistance, Time.deltaTime * this.viewCorrectionSpeed);
            }
        }

        protected new void Log(string message)
        {
            Debug.Log(message);
        }
    }

}
