using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class Character : MonoBehaviour
{
    public string nextScene;
	Animator characterAnimator;

    #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")]
    private LayerMask Ignore;

    [Tooltip("Current Inventory of the player")]
    public Inventory Inventory;

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

    #endregion Inspector Fields

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

    #region Unity Functions

    private void Start()
    {
        if (Inventory != null)
            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 Bear = Instantiate(prefab, this.gameObject.transform);

        characterAnimator = GetComponentInChildren<Animator>();
    }

    

    #endregion Unity Functions

    #region Class Implementation

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


    public Vector3 yValue(float time, float heightMultiplier)
    {
        float y = Mathf.Sin((Mathf.PI*time)) * heightMultiplier;
        return new Vector3(0, y, 0);
    }

	IEnumerator JumpCoroutine(Block Target, Transform Current, float time, float heightMax)
	{
		float elapsedTime = 0;
		Vector3 startPosition = Current.position;
		time *= 0.8f;
        characterAnimator.Play("Jump Inplace");
        yield return new WaitForSeconds(0.15f);
        while (elapsedTime < time)
		{
            transform.position = Vector3.Lerp(startPosition, Target.VisualPosition, (elapsedTime / time));
			transform.position += yValue((elapsedTime / time), heightMax);
			yield return new WaitForEndOfFrame();
			elapsedTime += Time.deltaTime; 
		}
		transform.position = Target.VisualPosition;
	}

    IEnumerator MoveCoroutine(Block Target, Transform Current, float time, float heightMax)
    {
        float elapsedTime = 0;
        Vector3 startPosition = Current.position;
        time *= 0.8f;
		characterAnimator.Play("Walk");
		yield return new WaitForSeconds(0.05f);
        while (elapsedTime < time)
        {
			
            transform.position = Vector3.Lerp(startPosition, Target.VisualPosition, (elapsedTime / time));
			yield return new WaitForEndOfFrame();
            elapsedTime += Time.deltaTime; 
        }
        transform.position = Target.VisualPosition;
    }

	IEnumerator MoveDownCoroutine(Block Target, Transform Current, float time, float heightMax)
	{
		float elapsedTime = 0;
		Vector3 startPosition = Current.position;
		time *= 0.8f;
		characterAnimator.Play("Walk");
		while (elapsedTime < time)
		{
			transform.position = Vector3.Lerp(startPosition, Target.VisualPosition, (elapsedTime / time));
			transform.position += yValue((elapsedTime / time), heightMax);
			yield return new WaitForEndOfFrame();
			elapsedTime += Time.deltaTime; 
		}
		transform.position = Target.VisualPosition;
	}

    IEnumerator rotateCoroutine(Direction direction, Transform Current, float time, float heightMax)
    {
        float elapsedTime = 0;
        time *= 0.8f;
        Vector3 endDirection = direction.ToVector(Current);
        Vector3 startDirection = Current.forward;
        Vector3 startPosition = transform.position;
        while (elapsedTime < time)
        {
			characterAnimator.Play("Jump");
            transform.forward = Vector3.Slerp(startDirection, endDirection, (elapsedTime / time));
            yield return new WaitForEndOfFrame();
            elapsedTime += Time.deltaTime;
        }
        transform.forward = endDirection;
    }


    /// <summary>
    /// Moves one block in specefied direction, Can walk off obstacles
    /// </summary>
    /// <param name="direction">direction to walk</param>
    /// <remarks>Technically is same as JumpLong(1) but kept seperate to avoid confusion</remarks>
    public void Move(Direction direction, float speed)
    { 
        //setting up variables
        Vector3 position = _currentBlock.position + direction.ToVector(transform); // position wanted
        Block hit; //output of block detection
        Block moveTo = _currentBlock; //block we'll actually move to

        //if move is obstucted no where to move
        if (Block.isBlockAtPosition(position + Vector3.up, 1, ~Ignore))
            return;


        //If block at Position is walkable set it to moveTo
		if (Block.isBlockAtPosition(position, 1, ~Ignore, out hit) && hit.isWalkable(~Ignore)){
            moveTo = hit;
			_currentBlock = moveTo;
			StartCoroutine(MoveCoroutine(_currentBlock, transform, speed, 0.3f));		}
        //else if block down one is walkable
		else if (Block.isBlockAtPosition(position + Vector3.down, 1, ~Ignore, out hit) && hit.isWalkable(~Ignore)){
            moveTo = hit;
			_currentBlock = moveTo;
			StartCoroutine(MoveDownCoroutine(_currentBlock, transform, speed, 0.3f));
		}
        //set current block && move
        //CurrentBlock = moveTo;
        //StartCoroutine(MoveCoroutine(CurrentBlock, transform, speed, 0.3f));
        //transform.position = CurrentBlock.VisualPosition;
    }

    /// <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)
        {
            collectable.OnCollect(this);
        }
		if (other.gameObject.name == "collect_sphere"){
			other.gameObject.SetActive(false);
			//player.collected +=1;

		}
        if (other.gameObject.name == "End Portal")
        {
            other.GetComponent<Collider>().enabled = false;
            SceneManager.LoadScene(nextScene);
        }
    }

    /// <summary>
    /// Rotates to point in specific direction based on current direction
    /// </summary>
    /// <param name="direction">Local direction to point</param>
    public void Rotate(Direction direction, float speed)
    {
        StartCoroutine(rotateCoroutine(direction, transform, speed, 0.15f));
        //transform.forward = direction.ToVector(transform);
    }

    /// <summary>
    /// Jumps in specefied direction, picks between Long Jump and Jumping up
    /// </summary>
    /// <param name="direction">Direction to Jump</param>
    public void Jump(Direction direction, float speed)
    {
        //if there is a block infront JumpUp else LongJump
        if (Block.isBlockAtPosition(_currentBlock.position + direction.ToVector(transform) + Vector3.up, 1, ~Ignore))
            JumpUp(direction, speed);
        else
            JumpLong(direction, speed);
    }

    #endregion Class Implementation

    #region Private Functions

    /// <summary>
    /// Jumps up obstacle of specific height. Jumps as high as possible
    /// </summary>
    /// <param name="direction">Direction of obstacle</param>
    /// <param name="height">max height</param>
    private void JumpUp(Direction direction, float speed, int height = 1)
    {
        //setting up variables
        Vector3 position; // position wanted
        Block hit; //output of block detection
        Block moveTo = _currentBlock; //block we'll actually move to

        //Check blocks in going up then move to the heighest walkable one
        for (int i = 0; i <= height; i++)
        {
            //next position up the tower
            position = _currentBlock.position + direction.ToVector(transform) + (Vector3.up * i);

            //if block is walkable set it to last known position
            if (Block.isBlockAtPosition(position, 1, ~Ignore, out hit) && hit.isWalkable(~Ignore))
                moveTo = hit;
        }
        
        //set current block && move
        _currentBlock = moveTo;
		StartCoroutine(JumpCoroutine(_currentBlock, transform, speed, 0.5f));
        //transform.position = CurrentBlock.VisualPosition;
    }

    /// <summary>
    /// Long jumps forward a specified distance. Can Jump gaps. Stops at obstruction
    /// </summary>
    /// <param name="direction">Direction to Jump</param>
    /// <param name="Distance">Max distance</param>
    private void JumpLong(Direction direction, float speed, int Distance = 2)
    {
        //setting up variables
        Vector3 position; // position wanted
        Block hit; //output of block detection
        Block moveTo = _currentBlock; //block we'll actually move to

        //Check blocks in front until we hit an obstruction or went the distance
        for (int i = 1; i <= Distance; i++)
        {
            //Next position to MoveTo
            position = _currentBlock.position + (direction.ToVector(transform) * i);

            //if jump is obstructed, stop and go to last known block
            if (Block.isBlockAtPosition(position + Vector3.up, 1, ~Ignore))
                break;


            //If block at Position is walkable set it to last known position
            if (Block.isBlockAtPosition(position, 1, ~Ignore, out hit) && hit.isWalkable(~Ignore))
                moveTo = hit;
            //else if block down one is walkable
            else if (Block.isBlockAtPosition(position + Vector3.down, 1, ~Ignore, out hit) && hit.isWalkable(~Ignore))
                moveTo = hit;

        }//end for
        
        //Set Current Block and move
        _currentBlock = moveTo;
		StartCoroutine(JumpCoroutine(_currentBlock, transform, speed, 0.5f));
        //transform.position = CurrentBlock.VisualPosition;
    }

    #endregion Private Functions

}