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

/// <summary>
/// Class which defines blocks around the level
/// </summary>
[RequireComponent(typeof(BoxCollider))]
public class Block : MonoBehaviour
{
    #region Inspector Fields
    [SerializeField]
    [Tooltip("Offset from the top of the block from which a character should stand")]
    private Vector3 VisualOffset = Vector3.zero;

    [SerializeField]
    [Tooltip("Can this type of block be walked on")]
    public bool is_Walkable = true;

    [Tooltip("Is this block underwater?")]
    public bool isWater = false;

    [Tooltip("Is this block at the bottom of a pit?")]
    public bool isPit = false;

    public bool isCrystals = false;
    public bool isRock = false;
    public bool isCollectableSpawnable = false;

    [Header("Spawn Settings")]
    [Tooltip("Can this block be spawned on")]
    public bool isSpawnable = false;

    [Tooltip("Direction Player is poting at when spawned")]
    public Direction SpawnDirection = Direction.Forward;

    #endregion InspectorFields

    #region Private Functions
    /// <summary>
    /// List of current players on this block
    /// </summary>
    public Character CurrentPlayer { get; protected set; }

    private Renderer renderer;
    #endregion Private Functions

    #region ReadOnly Properties
    /// <summary>
    /// Blocks position in global space
    /// </summary>
    public Vector3 position { get { return transform.position; } }

    /// <summary>
    /// Position character should stand in global space
    /// </summary>
    public Vector3 VisualPosition { get { return position + VisualOffset + Vector3.up * 0.5f; } }
    #endregion ReadOnly Properties

    #region Public Functions

    /// <summary>
    /// Is a block valid to walk on
    /// </summary>
    /// <param name="layerMask">Layers to check for when checking for blocks above</param>
    /// <returns></returns>
    public bool isWalkable(LayerMask layerMask)
    {
        //checks if there is no block above this one and that this is tagged as walkable
        return (is_Walkable && !isBlockAtPosition(position + Vector3.up, 1, layerMask));
    }

    /// <summary>
    /// Is called when a player moves onto this block
    /// 
    /// Should be implemented by a derived class
    /// </summary>
    /// <param name="player">Player which moved on to block</param>
    ///<param name="moveDirection">The direction the player moved to get to this block</param>
    public virtual IEnumerator OnWalkedOnByPlayer(Character player, Vector3 moveDirection)
    {
           yield return StartCoroutine(DoPush(player, moveDirection));
    }

    /// <summary>
    /// Is called when a player moves off of block
    /// 
    /// Should be implemented by a derived class
    /// </summary>
    /// <param name="player">Player which moved on to block</param>
    public virtual void OnLeftByPlayer(Character player)
    {
        CurrentPlayer = null;
    }

    /// <summary>
    /// Called to deal with players colliding on this block
    /// </summary>
    /// <param name="newPlayer">Player which is moving into this block</param>
    /// <param name="moveDirection">The direction the player moved to get to this block</param>
    public virtual IEnumerator DoPush(Character newPlayer, Vector3 moveDirection)
    {
        if (CurrentPlayer == null)
        {
            CurrentPlayer = newPlayer;
            yield break;
        }

        Block pushBlock = GetPushLocation(moveDirection, ~CurrentPlayer.Ignore);
        if (pushBlock != this)
        {
            Character oldPlayer = CurrentPlayer;
            CurrentPlayer = newPlayer;
            yield return StartCoroutine(oldPlayer.MoveToBlock(pushBlock, Character.Animation.Hit, 1));
        }
        else
        {
            Block returnBlock = GetPushLocation(-moveDirection, ~newPlayer.Ignore);

            if (returnBlock != this)
                yield return StartCoroutine(newPlayer.MoveToBlock(returnBlock, Character.Animation.Hit, 1));
        }
    }

    public bool isVisible()
    {
        return isPositionVisible(position);
    }

    #endregion Public Functions

    #region Protected Functions

    protected Block GetPushLocation(Vector3 pushDirection, LayerMask ignoreMask)
    {
        //setting up variables
        Vector3 newPosition = position + pushDirection; // position wanted
        Block hit; //output of block detection

        //if move is obstucted no where to move
        if (Block.isBlockAtPosition(newPosition + Vector3.up, 1, ignoreMask))
            return this;


        //If block at Position is walkable set it to moveTo
        if (Block.isBlockAtPosition(newPosition, 1, ignoreMask, out hit) && hit.isWalkable(ignoreMask))
        {
            return hit;
        }
        //else if block down one is walkable
        else if (Block.isBlockAtPosition(newPosition + Vector3.down, 1, ignoreMask, out hit) && hit.isWalkable(ignoreMask))
        {
            return hit;
        }
        return this;
    }

    #endregion Protected Functions

    #region Editor Functions
    private void OnDrawGizmos()
    {
        if (!isSpawnable)
            return;

        Vector3 DrawPosition = VisualPosition + Vector3.up * 0.4f;
        Vector3 Perp = Quaternion.Euler(0, 90, 0) * SpawnDirection.ToVector();

        DebugExtensions.DrawCube(DrawPosition, 0.4f, Color.magenta, 0);

        //Eyes
        Vector3 eyePosition = DrawPosition + SpawnDirection.ToVector() * 0.4f;
        DebugExtensions.DrawCube(eyePosition + Perp * 0.2f, 0.1f, Color.magenta, 0);
        DebugExtensions.DrawCube(eyePosition - Perp * 0.2f, 0.1f, Color.magenta, 0);

        //ears
        Vector3 earPosition = DrawPosition + SpawnDirection.ToVector() * 0.2f + Vector3.up * 0.4f;
        Vector3 earScale = Quaternion.LookRotation(SpawnDirection.ToVector()) * new Vector3(0.1f, 0.1f, 0.05f);
        DebugExtensions.DrawCube(earPosition + Perp * 0.3f, earScale, Color.magenta, 0);
        DebugExtensions.DrawCube(earPosition - Perp * 0.3f, earScale, Color.magenta, 0);

    }
    #endregion Editor Functions


    #region Static Functions

    /// <summary>
    /// Checks if there is a block at supplied position
    /// </summary>
    /// <param name="position">position to check at</param>
    /// <param name="Scale">Scale of block. (should be 1)</param>
    /// <param name="layerMask">Layers to check on</param>
    /// <param name="hit">Block hit</param>
    /// <returns>if a block is at position</returns>
    public static bool isBlockAtPosition(Vector3 position, float Scale, LayerMask layerMask, out Block hit)
    {
        //Turn scale into halfextent and shrink a bit so it doesn't hit bordering blocks
        Vector3 halfExtent = Vector3.one * ((Scale - 0.1f) / 2);

        //Get every collider which is at position
        Collider[] cols = Physics.OverlapBox(position, halfExtent, Quaternion.identity, layerMask);

        //Filter colliders for only GameObjects with an Block component
        Block[] blocks = cols.Where(p => p.GetComponent<Block>() != null).Select(p => p.GetComponent<Block>()).ToArray();

        //Draw cube, for visuals
        DebugExtensions.DrawCube(position, halfExtent, Color.cyan, 1, false);

        //if didn't hit anyblocks return false
        if (blocks.Length == 0)
        {
            hit = null;
            return false;
        }
        else
        {
            //else get the closest block to disered position, (in case we hit mulitple blocks)
            hit = Utility.minBy(blocks, p => Vector3.Distance(p.transform.position, position));
            return true;
        }
    }

    /// <summary>
    /// Checks if there is a block at supplied position
    /// </summary>
    /// <param name="position">position to check at</param>
    /// <param name="Scale">Scale of block. (should be 1)</param>
    /// <param name="layerMask">Layers to check on</param>
    /// <returns>if a block is at position</returns>
    public static bool isBlockAtPosition(Vector3 position, float scale, LayerMask layerMask)
    {
        //Return Overloaded function above
        Block hit;
        return (isBlockAtPosition(position, scale, layerMask, out hit));
    }

    /// <summary>
    /// Gets the block at a position or creates an airblock if there isn't one there
    /// </summary>
    /// <param name="position">position to get block at</param>
    /// <param name="Scale">Scale of block. (should be 1)</param>
    /// <param name="layerMask">Layers to check on</param>
    /// <returns>block at position</returns>
    public static Block GetOrCreateBlockAtPosition(Vector3 position, float Scale, LayerMask layerMask)
    {
        Block retVal;
        if (isBlockAtPosition(position, Scale, layerMask, out retVal))
            return retVal;

        GameObject newBlock = Resources.Load<GameObject>("Cube_Air");
        newBlock.transform.position = position;
        return newBlock.GetComponent<Air>();
    }

    public static bool isPositionVisible(Vector3 position)
    {
        Camera camera = Camera.main;
        Vector2 screenPos = camera.WorldToViewportPoint(position);
        if (screenPos.x > 1 || screenPos.x < 0)
        {
            return false;
        }

        if (screenPos.y > 1 || screenPos.y < 0)
        {
            return false;
        }

        return true;
    }

    #endregion

}