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

public class Player : MonoBehaviourSingleton<Player>
{
    public float m_offroadDistanceMin = 3;
    public float m_offroadDistanceMax = 10;
    public float m_backroadDistanceMin = 5;
    public float m_backroadDistanceMax = 15;

    public float m_cameraBasePitch = 16;
    public float m_cameraBaseDistance = 0f;
    public float m_cameraOffroadPitch = 36;
    public float m_cameraOffroadDistance = 3f;

    public UnityStandardAssets.Cameras.AutoCam m_cameraScript;
    Transform m_cameraTarget;

    [System.NonSerialized]
    public float m_maxTraveledDistance;

    [System.NonSerialized]
    public float m_offroadFactor; // -1..0..0..1
    int m_offroadBest = 0;
    [System.NonSerialized]
    public float m_lostFactor; // both offroad and backroad 0..1
    [System.NonSerialized]
    public float m_endroadFactor;
    [System.NonSerialized]
    public float m_laybackFactor;

    float m_pickupInspectTimer;
    const float PickupInspectDuration = 6f;

    private GameObject m_nearItem;
    private GameObject m_holdItem;

    Transform m_lookatItem;
    Vector3 m_lookAtDir = Vector3.forward;
    float m_lookatItemBlend;

    private Transform m_head;
    private Transform m_groundBase;

    public void OnInit()
    {
        m_cameraTarget = transform.Find("CameraTarget");
        m_groundBase = transform.Find("GroundBase");
        m_head = GF.FindChild(transform, "head");
    }

    public void OnUpdate()
    {
        float distance = GetTravelDistance();
        if (distance > m_maxTraveledDistance)
            m_maxTraveledDistance = distance;

        ComputeFactors();

        ApplyFactors();

        if(IsInspectingPickup())
            UpdateInspectingPickup();
    }

    public float GetTravelDistance()
    {
        return transform.position.z;
    }

    public float CalculateOffroadFactor(float min, float max, out int bestRoad)
    {
        Vector3 playerPos = transform.position;
        float playerDistance = playerPos.z;

        float offroadDistance = playerPos.x; // dist from player pos to central road
        bestRoad = 0;

        // if at end of road
        float roadDistance = Road.instance.m_distance;
        if (playerPos.z > roadDistance)
        {
            Vector3 endRoadsOrigin = Vector3.forward * roadDistance;
            Vector3 roadL = (new Vector3(-1, 0, 1)).normalized;
            Vector3 roadR = (new Vector3(1, 0, 1)).normalized;

            // projection of player pos on each road
            float offroadDistanceC = playerPos.z - endRoadsOrigin.z;
            float offroadDistanceL = Vector3.Dot(playerPos - endRoadsOrigin, roadL);
            float offroadDistanceR = Vector3.Dot(playerPos - endRoadsOrigin, roadR);

            // Debug.Log("C=" + offroadDistanceC + " L=" + offroadDistanceL + " R=" + offroadDistanceR);

            if (offroadDistanceL > offroadDistanceC && offroadDistanceL > offroadDistanceR)
            {
                offroadDistance = offroadDistanceR; // dist from pos to left road (90deg)
                bestRoad = -1;
            }
            else if(offroadDistanceR > offroadDistanceC && offroadDistanceR > offroadDistanceL)
            {
                offroadDistance = -offroadDistanceL; // dist from pos to right road (90deg)
                bestRoad = 1;
            }

            // Debug.Log("best road=" + bestRoad);
        }

        float offroadFactor = 0; // -1..0..0..1
        if (offroadDistance < -min)
            offroadFactor = -(-offroadDistance - min) / (max - min);
        else if (offroadDistance > min)
            offroadFactor = (offroadDistance - min) / (max - min);

        offroadFactor = Mathf.Clamp(offroadFactor, -1, 1);

        return offroadFactor;
    }

    public void ComputeFactors()
    {
        m_offroadFactor = CalculateOffroadFactor(m_offroadDistanceMin, m_offroadDistanceMax, out m_offroadBest);
        float offroadFactorAbs = Mathf.Abs(m_offroadFactor);

        float distance = GetTravelDistance();
        float backroadFactor = 0;
        if (distance < m_maxTraveledDistance - m_backroadDistanceMin)
            backroadFactor = ((m_maxTraveledDistance - m_backroadDistanceMin) - distance) / (m_backroadDistanceMax - m_backroadDistanceMin);
        // Debug.Log(backroadFactor);
        backroadFactor = Mathf.Clamp01(Mathf.Abs(backroadFactor));

        // lost too far
        float endroadMin = m_offroadDistanceMin;
        float endroadMax = m_offroadDistanceMax * 2f;
        m_endroadFactor = 0;
        float roadDistance = Road.instance.m_distance;
        float roadTooFarDistance = roadDistance + 10;
        if (distance > roadTooFarDistance + m_offroadDistanceMin)
            m_endroadFactor = Mathf.Clamp01((distance - (roadTooFarDistance + endroadMin)) / (endroadMax - endroadMin));

        m_lostFactor = Mathf.Min(1, offroadFactorAbs + backroadFactor + m_endroadFactor);
    }

    void ApplyFactors()
    {
        float moodFactor = 1.0f - Weather.instance.m_mood.m_cameraPitchFactor;
        float lostFactor = Mathf.Abs(m_lostFactor);
        float comboFactor = Mathf.Clamp01(lostFactor + moodFactor);

        float rot = m_offroadFactor * 90 + m_offroadBest * 45;
        m_cameraTarget.rotation = Quaternion.Euler(0, rot, 0);

        float pitchNormal = m_cameraBasePitch;
        float pitchMood = 40;
        float pitchLost = m_cameraOffroadPitch;
        float pitchBase = GFMath.AngleLerp(pitchNormal, pitchMood, moodFactor);
        float pitchFinal = GFMath.AngleLerp(pitchBase, pitchLost, lostFactor);

        Quaternion camRotation = Quaternion.Euler(pitchFinal, 0, 0);
        Transform camTransform = Camera.main.transform;
//        camTransform.localRotation = camRotation;
//        camTransform.localPosition = Vector3.up * (m_cameraOffroadDistance * lostFactor);

        Vector3 posNormal = Vector3.zero;
        Vector3 posLost = Vector3.up * m_cameraOffroadDistance * lostFactor;
        Vector3 posMood = (Vector3.up * 0.3f + Vector3.forward * 1) * moodFactor;
        Vector3 posBase = Vector3.Lerp(posNormal, posMood, moodFactor);
        Vector3 posFinal = Vector3.Lerp(posBase, posLost, lostFactor);

        camTransform.localRotation = camRotation;
        camTransform.localPosition = posFinal;

        Weather.instance.m_fogExtraFactor = lostFactor + m_endroadFactor * 5f + m_laybackFactor;
    }

    public void SetLaybackFactor(float f)
    {
        //Debug.Log(f);
        m_laybackFactor = f;
        if (f > 8)
        {
            Debug.Log("Quit");
            Application.Quit();
        }
    }

    // ----------------------------------------------------------------------

    private void OnTriggerStay(Collider info)
    {
        //Debug.Log(info.gameObject.name + " tag=" + info.gameObject.tag);
        switch (info.gameObject.tag)
        {
            case "Collectible":
                m_nearItem = info.gameObject;
                break;
            case "CollectibleLookAt":
                m_lookatItem = info.gameObject.transform.parent;
                break;
        }
    }

    public bool IsNearItem()
    {
        return m_nearItem != null;
    }

    public void OnPickup()
    {
        if (!IsNearItem())
            return;

        MemoryItem memoryItem = m_nearItem.GetComponent<MemoryItem>();
        Debug.Log("Pickup memory item " + memoryItem.m_memoryPageIdx);

        // hold item in hand
        var hand = GF.FindChild(transform, "hand_l");
        m_holdItem = m_nearItem;
        m_holdItem.transform.SetParent(hand, false);
        m_holdItem.transform.localPosition = new Vector3(0, 0.15f, 0);

        m_pickupInspectTimer = PickupInspectDuration;

        MemoryBook.instance.OpenBookInspect(memoryItem.m_memoryPageIdx);
    }

    public bool IsInspectingPickup()
    {
        // @TODO consider sound?
        return m_pickupInspectTimer > 0;
    }

    public void UpdateInspectingPickup()
    {
        m_pickupInspectTimer -= Time.deltaTime;
        if (m_pickupInspectTimer < 0)
        {
            m_pickupInspectTimer = 0;
        }

        if(!IsInspectingPickup())
        {
            m_holdItem.transform.parent = null;
            GameObject.Destroy(m_holdItem);
            m_holdItem = null;

            MemoryBook.instance.CloseBook();
        }
    }

    // ----------------------------------------------------------------------

    public void OnSitdown(bool sitdown)
    {
        if (sitdown)
            MemoryBook.instance.OpenBookBrowse();
        else
            MemoryBook.instance.CloseBook();
    }

    // ----------------------------------------------------------------------

    private void FixedUpdate()
    {
        m_nearItem = null;
        m_lookatItem = null;
    }

    private void LateUpdate()
    {
        m_groundBase.rotation = Quaternion.Euler(90, 0, 0);

        LookAtItem();
    }

    // call after anim
    void LookAtItem()
    {
        bool hasTarget = false;
        if (m_lookatItem)
        {
            Vector3 dir = GFMath.GetFlatDir(m_head.position, m_lookatItem.position);
            if (Vector3.Dot(m_lookAtDir, transform.forward) > 0.5f) // >60deg
            {
                m_lookAtDir = dir;
                hasTarget = true;
            }
        }

        if(hasTarget)
            m_lookatItemBlend = Mathf.Min(1, m_lookatItemBlend + Time.deltaTime * 2);
        else
            m_lookatItemBlend = Mathf.Max(0, m_lookatItemBlend - Time.deltaTime * 2);

        if (m_lookatItemBlend > 0)
            m_head.forward = Vector3.Lerp(m_head.forward, -m_lookAtDir, m_lookatItemBlend).normalized;
    }

    public void Teleport(Vector3 pos, bool teleportCamera = true)
    {
        GetComponent<Rigidbody>().position = pos;
        transform.position = pos;

        if (teleportCamera)
            m_cameraScript.transform.position = pos;
    }
}