using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using Networking.Server;
using TMPro;

public class Character : MonoBehaviour
{
    public enum Animation { Walk, Run, Jump, Sit, Attack, Hit,None }
    public blockSpawn spawn;
    public int runOrder = 0;

    public string nextScene;
    Animator characterAnimator;
    public bool isTuteLevel = false;

    public bool inWater = false; //Am I in the water?
    public bool respawnNeeded = false; //Am I waiting on a respawn?
    public bool onCrystal = false; 
    public bool underRock = false;
    public bool stuck = false; //Am I still stuck?
    public bool justMoved = false; //Was the logic block I just executed a move command?
    public bool inPit = false;
    Vector3 death = new Vector3(-50, 0, 0);
    #region Inspector Fields

    [SerializeField]
    [Tooltip("Will move to this block at start, else will try and find a block below")]
    private Block _currentBlock;

    [SerializeField]
    [Tooltip("Layers to ignore when checking for blocks")]
    public LayerMask Ignore;

    [Tooltip("Current Inventory of the player")]
    public Inventory Inventory;
    public bool CloneInventoryOnStart = false;

    [Tooltip("How many lives to start out with")]
    public int lives = 3;

    [SerializeField]
    [Tooltip("Character to display")]
    private string CharacterModel = "Bear";

    [SerializeField]
    public ClientData ClientLink;

    [SerializeField]
    private TMPro.TextMeshPro BlockTitlePrefab;

    #endregion Inspector Fields

    #region Read Only
    public Block CurrentBlock { get { return _currentBlock; } }
    public float lastRotation;
    #endregion Read Only

    #region Unity Functions

    private void Start()
    {
        spawn = GameObject.Find("GameManager").GetComponent<blockSpawn>();
        if (Inventory != null && CloneInventoryOnStart)
            Inventory = Inventory.Clone(Inventory);

        //If no starting block find one below it
        if (_currentBlock == null)
            Block.isBlockAtPosition(transform.position + Vector3.down / 2, 1, ~Ignore, out _currentBlock);

        //move to starting block
        transform.position = _currentBlock.VisualPosition;

        //get character string from player replace from "Bear"
        GameObject prefab = Resources.Load(CharacterModel) as GameObject;
        GameObject animal = Instantiate(prefab, this.gameObject.transform);

        characterAnimator = GetComponentInChildren<Animator>();
    }
    private void Update()
    {
        if(lives < 1)
        {
            this.transform.position = death;
            //this.enabled = false;
            //gameObject.SetActive(false);
        }
        GetComponentInChildren<TextMeshPro>().text = runOrder.ToString();
    }


    #endregion Unity Functions

    #region Class Implementation

    public void Initialise(Block startingBlock, Inventory inventory, string Character)
    {
        _currentBlock = startingBlock;
        Inventory = inventory;
        CharacterModel = Character;
    }

    public void DisplayBlock(LogicBlock block)
    {
        if (isTuteLevel == false)
        {
            if (BlockTitlePrefab == null)
                return;

            TMPro.TextMeshPro temp = Instantiate(BlockTitlePrefab.gameObject).GetComponent<TMPro.TextMeshPro>();
            temp.text = block.DisplayName;
            temp.color = ClientLink.Color;

            temp.transform.position = transform.position + (Vector3.one * 0.25f);
            temp.transform.rotation = Quaternion.LookRotation(temp.transform.position - Camera.main.transform.position);
        }
    }

    public IEnumerator MoveToBlock(Block target, Animation animation, float time)
    {
        float startTime = Time.time;
        Vector3 moveDirection = Vector3.ProjectOnPlane(target.position - _currentBlock.position, Vector3.up).normalized;

        _currentBlock.OnLeftByPlayer(this);

        System.Func<float, float> yFunction = null;
        if (animation == Animation.Jump)
            yFunction = (t) => Mathf.Sin((Mathf.PI * t));

        StartAnimation(animation, time);

        yield return StartCoroutine(LerpToBlock(target.VisualPosition, time * 0.8f, yFunction));
        
        _currentBlock= target;
        yield return StartCoroutine (_currentBlock.OnWalkedOnByPlayer(this,moveDirection));

        yield return new WaitForSeconds(time - (Time.time - startTime));

        StopAnimation(animation);
    }



    public IEnumerator RotateInDirection(Direction direction,float angles, Animation animation, float time)
    {
        System.Func<float, float> yFunction = null;
        if (animation == Animation.Jump)
            yFunction = (t) => Mathf.Sin((Mathf.PI * t));

        StartAnimation(animation, time);

        //Debug.Log("Rotating by: " + angles);
        yield return StartCoroutine(Rotate(direction,angles,  time * 0.8f, yFunction));

        StopAnimation(animation);
    }

    


    public IEnumerator AnimateToPosition(Vector3 position, Animation animation, float time)
    {
        System.Func<float, float> yFunction = null;
        if (animation == Animation.Jump)
            yFunction = (t) => Mathf.Sin((Mathf.PI * t));

        StartAnimation(animation, time);

        yield return StartCoroutine(LerpToBlock(position, time * 0.8f, yFunction));

        StopAnimation(animation);
    }

    public void StartAnimation(Animation animation,float speed = 1)
    {
        switch (animation)
        {
            case Animation.Walk:
                characterAnimator.SetBool("isWalking", true);
                break;
            case Animation.Run:
                characterAnimator.SetBool("isRunning", true);
                break;
            case Animation.Sit:
                characterAnimator.SetBool("isSitting", true);
                break;
            case Animation.Jump:
                characterAnimator.SetTrigger("Jump");
                break;
            case Animation.Attack:
                characterAnimator.SetTrigger("Attack");
                break;  
            case Animation.Hit:
                characterAnimator.SetTrigger("Hit");
                break;
            default:
                break;
        }
    }

    public void StopAnimation(Animation animation)
    {
        switch (animation)
        {
            case Animation.Walk:
                characterAnimator.SetBool("isWalking", false);
                break;
            case Animation.Run:
                characterAnimator.SetBool("isRunning", false);
                break;
            case Animation.Sit:
                characterAnimator.SetBool("isSitting", false);
                break;
        }
    }

    public void respawnCharacter()
    {
        respawnCharacter(CurrentBlock);
    }

    public void respawnCharacter(Block respawnPosition)
    {
        //Having found our target block, we move the character there
        if (respawnPosition != null)
        {
            this.transform.position = respawnPosition.VisualPosition;
            this.respawnNeeded = false;
            this._currentBlock = respawnPosition;
            respawnPosition.CurrentPlayer = this;
            this.stuck = false;
            Debug.Log("Moved " + this.name + " to "
                + this.transform.position.x + ", "
                + this.transform.position.y + ", "
                + this.transform.position.z + ", "
                + ", respawnNeeded = " + respawnNeeded
                + ", stuck = " + this.stuck);
        }
        else
        {
            Debug.Log("Failed to find anywhere to put " + this.name);
        }

    }

    /// <summary>
    /// Upon collision with a floating block, collect its
    /// Upon collision with the end portal, end of level
    /// </summary>
    /// <param name="other">name of collided object</param>
    void OnTriggerEnter(Collider other)
    {
        Collectable collectable = other.GetComponentInChildren<Collectable>();

        if (collectable != null)
        {
            //get position from average;
            ClientList list = spawn.clientDataList;
            float average = 0;
            int livePlayerCount = 0;
            foreach (ClientData data in list.ConnectedClients)
            {
                if (data.Lives > 0)
                {
                    average += data.playerCharacter.transform.position.x;
                    livePlayerCount++;
                }
            }
            average /= livePlayerCount;
            float tosend = lives + (transform.position.x - average);
            //Debug.Log(transform.position.x + " - " + average + " =" + (transform.position.x - average));
            //Debug.Log("Value to send: " + tosend);
            spawn.assignLogicBlock(collectable.gameObject, tosend);

            collectable.OnCollect(this);
        }
    }



    #endregion Class Implementation

    #region Private Functions

    private IEnumerator LerpToBlock(Vector3 target, float time, System.Func<float, float> heightOffset = null)
    {

        Vector3 _startPos = transform.position;
        Vector3 _endPos = target;

        Vector3 _newPos;

        float elapsedTime = 0;



        while (elapsedTime / time < 1)
        {

            _newPos = Vector3.Lerp(_startPos, _endPos, (elapsedTime / time));

            if (heightOffset != null)
                _newPos.y += heightOffset(elapsedTime / time);

            transform.position = _newPos;

            yield return new WaitForEndOfFrame();

            elapsedTime += Time.deltaTime;
        }

        _newPos = _endPos;
        if (heightOffset != null)
            _newPos.y += heightOffset(1);

        transform.position = _newPos;
    }

    private IEnumerator Rotate(Direction direction, float angles, float time, System.Func<float, float> heightOffset = null)
    {

        int RotationDir = 0;
        switch (direction)
        {
            case Direction.Forward:
                RotationDir = 0;
                break;
            case Direction.Left:
                RotationDir = -1;
                break;
            case Direction.Right:
                RotationDir = 1;
                break;
            case Direction.Back:
                RotationDir = 2;
                break;
        }

        lastRotation = angles * RotationDir;


        float elapsedTime = 0;
        float anglePerSecond = (angles * RotationDir) / time;
        Vector3 _startPos = transform.position;
        Vector3 startDirection = transform.forward;

        while (elapsedTime < time)
        {
            transform.Rotate(Vector3.up, anglePerSecond * Time.deltaTime);

            if (heightOffset != null)
                transform.position = _startPos + Vector3.up * heightOffset(elapsedTime / time);

            yield return new WaitForEndOfFrame();
            elapsedTime += Time.deltaTime;
        }

        transform.forward = Quaternion.AngleAxis(angles * RotationDir, Vector3.up) * startDirection;
        if (heightOffset != null)
            transform.position = _startPos + Vector3.up * heightOffset(1);
    }



    #endregion Private Functions


}