using UnityEngine;
using System.Collections;

public enum eBlockDirection { None=-1,	Up,Down,Left,Right,Count }


public class BodyBlockManager : MonoBehaviour 
{
	// matches blockdirection enum
	public static readonly Vector2[] UNIT_DIRECTIONS = new Vector2[]
	{
		Vector2.up,-Vector2.up,- Vector2.right, Vector2.right
	};

	public static readonly eBlockDirection[] DIRECTION_OPPOSITE = new eBlockDirection[]{ eBlockDirection.Down,eBlockDirection.Up,eBlockDirection.Right,eBlockDirection.Left };

	[SerializeField] bool m_multilimb = false;

	[SerializeField] int m_maxBlocks = 4;
	[SerializeField] BodyBlock m_blockPrefab = null;
	[SerializeField] BodyBlock m_rootBlock = null;

	[SerializeField] Sprite m_spriteBody = null;
	[SerializeField] Sprite m_spriteBodySmallL = null;
	[SerializeField] Sprite m_spriteBodySmallR = null;
	[SerializeField] Sprite m_spriteBodySmallU = null;
	[SerializeField] Sprite m_spriteFeet = null;
	[SerializeField] Sprite m_spriteHead = null;
	[SerializeField] Sprite m_spriteHeadSmall = null;
	[SerializeField] Sprite m_spriteNeck = null;
	[SerializeField] Sprite m_spritePantsLR = null;
	[SerializeField] Sprite m_spritePantsU = null;

	[SerializeField] AudioCue m_soundExpand = null;
	[SerializeField] AudioCue m_soundContract = null;
	[SerializeField] AudioCue m_soundOverstretch = null;

	[SerializeField] Rigidbody2D m_armRight = null;
	[SerializeField] Rigidbody2D m_armLeft = null;
	[SerializeField] Rigidbody2D m_armRightShort = null;
	[SerializeField] Rigidbody2D m_armLeftShort = null;


	// not sure if should use grid kidna thing... to know exactly what is where, or just raycast to prevent overlapping bits
	public static readonly int GRID_WIDTH = 64;
	public static readonly int GRID_HEIGHT = 64;

	BodyBlock m_selectedBlock = null;
	int m_currBlocks = 1;
	bool m_playedOverstretch = false; // so over-strecth sound doesn't keep playing while holding the button

	float m_faceIdleTimer = 0;

	// Use this for initialization
	void Start () 
	{/*
		// Create first block (root)
		BodyBlock newBlock = GameObject.Instantiate(m_blockPrefab, pos, Quaternion.identity) as BodyBlock;
		//BodyBlock newBlock = newBlockGO.GetComponent<BodyBlock>();
		newBlock.transform.SetParent(m_selectedBlock.transform);
		*/
		SelectBlock(m_rootBlock);
		UpdateSprites();
		m_leftArmPos = m_armLeft.transform.position;
		m_rightArmPos = m_armRight.transform.position;
	}

	float m_contractButtonHeldTime = 0;
	float m_contractRepeatTimer = 0;
	float m_expandRepeatTimer = 0;
	float m_expandButtonHeldTime = 0;
	eBlockDirection m_expandButtonDir = eBlockDirection.None;


	[SerializeField] float m_expandRepeatMinTime = 0.05f;
	[SerializeField] float m_expandRepeatMaxTime = 0.1f;
	[SerializeField] float m_expandLerpTime = 0.5f;

	void OnExpandButtonHeld(eBlockDirection direction)
	{
		if ( m_expandButtonDir != direction )
		{
			m_expandButtonHeldTime = 0;	
			m_expandRepeatTimer = 0;
		}
		float oldTime = m_expandButtonHeldTime;
		m_expandButtonHeldTime += Time.deltaTime;
		m_expandRepeatTimer -= Time.deltaTime;

		if ( oldTime <= 0 || m_expandRepeatTimer <= 0 )
		{
			m_expandRepeatTimer = Mathf.Lerp(m_expandRepeatMaxTime, m_expandRepeatMinTime, m_expandButtonHeldTime / m_expandLerpTime);
			m_expandButtonDir = direction;
			Expand(direction);
		}

		m_playedOverstretch = true;
	}

	// Update is called once per frame
	void Update () 
	{
		

		bool expandHeld = true;
		if ( Input.GetButton("ExpandLeft") )
		{			
			OnExpandButtonHeld(eBlockDirection.Left);
		}
		else if ( Input.GetButton("ExpandRight") )
		{
			OnExpandButtonHeld(eBlockDirection.Right);
		}
		else if ( Input.GetButton("ExpandUp") )
		{
			OnExpandButtonHeld(eBlockDirection.Up);
		}
		else if ( Input.GetButton("ExpandDown") )
		{
			OnExpandButtonHeld(eBlockDirection.Down);			
		}
		else 
		{
			m_expandButtonDir = eBlockDirection.None;
			m_playedOverstretch = false;
			expandHeld = false;
		}

		if ( Input.GetButton("Contract") && expandHeld == false || Input.GetButtonDown("Contract") )
		{			
			float oldTime = m_contractButtonHeldTime;
			m_contractButtonHeldTime += Time.deltaTime;
			m_contractRepeatTimer -= Time.deltaTime;

			if ( oldTime <= 0 || m_contractRepeatTimer <= 0 )
			{
				m_contractRepeatTimer = Mathf.Lerp(m_expandRepeatMaxTime, m_expandRepeatMinTime, m_contractButtonHeldTime / m_expandLerpTime);

				if ( m_multilimb )
				{
					Contract(m_selectedBlock);
				}
				else 
				{
					// Move the root node onto it's sibling and remove the sibling (relinking with the sibling's sibling)
					BodyBlock toRemove = m_rootBlock.GetFirstSibling();
					if (  toRemove != null )
					{
						eBlockDirection dir = m_rootBlock.GetFirstSiblingDirection();
						Vector3 offset = (Vector3)UNIT_DIRECTIONS[(int)dir] * m_rootBlock.GetBlockSize(dir);

                        if (m_rootBlock.IsCarrying())
                        {
                            m_rootBlock.DropCarrying();
                        }

                        if (toRemove.IsCarrying())
                    	{
                            Carryable switchCarryable = toRemove.GetCarryable();
                            toRemove.DropCarrying();
                            m_rootBlock.StartCarrying(switchCarryable);
                            }

						transform.parent.position += offset;
						//m_rootBlock.transform.position -= offset;

						if ( toRemove != null )
                        {
							m_rootBlock.ClearSiblings();
                        }
                    
						if ( toRemove.GetSiblingCount() > 1 )
						{							
							BodyBlock newNext = toRemove.m_siblings[(int)toRemove.GetNextSibling()];
							m_rootBlock.SetNextSibling(toRemove.GetNextSibling(), newNext);

							// Set the link back to the root
							newNext.m_siblings[(int)DIRECTION_OPPOSITE[(int)toRemove.GetNextSibling()]] = m_rootBlock;
							newNext.transform.position -= offset;
							newNext.transform.SetParent(m_rootBlock.transform);

						}
						else 
						{
							// only 1 node left
							SelectBlock(m_rootBlock);
						}
						m_soundContract.Play();
                        m_currBlocks = Mathf.Max(m_currBlocks - 1, 1);
						toRemove.Remove();
						UpdateSprites();

						UpdateSprites();

					}

				}
			}
		}
		else 
		{
			m_contractButtonHeldTime = 0;
		}

		if ( m_currBlocks > 1 )
		{
			m_faceIdleTimer += Time.deltaTime;
		}
		else 
		{
			m_faceIdleTimer = 0;
		}

	}

    public int GetCurrentBlocks()
    {
        return m_currBlocks;
    }

    public int GetMaxBlocks()
    {
        return m_maxBlocks;
    }

	public void IncreaseMaxBlocks()
	{
		m_maxBlocks++;
	}

	public bool GetHasStandingRoom() // slooow function yay!
	{
		// Check to see if there's ground above any blocks. if so, there's no standing room
		int layermask = LayerMask.GetMask("Ground");
		Vector2 blockcheckSize = m_blockPrefab.m_size * 0.9f;
		Vector2 offsetUp = new Vector2(0,m_blockPrefab.m_size.y);

		BodyBlock block = m_rootBlock;
		while ( block != null )
		{
			if ( Physics2D.BoxCast( (Vector2)block.transform.position + offsetUp, blockcheckSize, 0, Vector2.one, 0, layermask) )
				return false;
			if ( block.GetNextSibling() == eBlockDirection.None )
				break;
			block = block.GetSibling( block.GetNextSibling() );
		}
		return true;

	}



	// expands/selects in direction, and returns true if success (false if can't expand in that direction )
	bool Expand(eBlockDirection direction)
	{
		bool result = false;
		if ( m_selectedBlock.m_siblings[(int)direction] == null )
		{
			//
			// Expand in that direction
			//
			if ( m_currBlocks  < m_maxBlocks ) // check we have enough blocks
			{
				Vector3 pos = m_selectedBlock.transform.position + (Vector3)(m_selectedBlock.GetBlockSize(direction) * BodyBlockManager.UNIT_DIRECTIONS[(int)direction]);
				// TODO: raycast to see if possible or if need to move first

                bool checkGroundBodyBlocking = Physics2D.BoxCast( pos, m_blockPrefab.m_size * 0.9f, 0, Vector2.one, 0, LayerMask.GetMask("Ground", "Player"));

                bool checkCarryableBlocking = false;

                if (direction != eBlockDirection.Up)
				{
                    RaycastHit2D hitinfo = Physics2D.BoxCast( pos, m_blockPrefab.m_size * 0.9f, 0, Vector2.one, 0, LayerMask.GetMask("Carryable"));
                    if (hitinfo)
                    {
                        Carryable carryable = hitinfo.collider.gameObject.GetComponentInParent<Carryable>();
                        if (carryable && carryable.IsCarrying())
                        {
                            checkCarryableBlocking = true;
                        }
                    }                    
                }

                if ( checkGroundBodyBlocking == false && checkCarryableBlocking == false )
				{
					// Create block prefab at position parented under this gameobject
					BodyBlock newBlock = GameObject.Instantiate(m_blockPrefab, pos, Quaternion.identity) as BodyBlock;
					//BodyBlock newBlock = newBlockGO.GetComponent<BodyBlock>();
					newBlock.transform.SetParent(m_selectedBlock.transform);
					m_selectedBlock.SetNextSibling(direction, newBlock);
					newBlock.SetPrevSibling(DIRECTION_OPPOSITE[(int)direction], m_selectedBlock);
					//m_selectedBlock.m_siblings[(int)direction] = newBlock;
					//newBlock.m_siblings[(int)DIRECTION_OPPOSITE[(int)direction]] = m_selectedBlock;

					SelectBlock(newBlock);
					// Maybe need to update physics or something?

					m_soundExpand.Play();
					m_currBlocks++;
					result = true;
					UpdateSprites();
				}

			}
			else 
			{

				if ( m_playedOverstretch == false )
				{
					SystemAudio.Play( m_soundOverstretch );		
					m_playedOverstretch = true;				
				}
			}
		}
		else 
		{
			// Change seletion to the new block
			if ( m_multilimb )
			{
				SelectBlock(m_selectedBlock.m_siblings[(int)direction]);
			}
			else 
			{
				// Contract back
				Contract(m_selectedBlock);
			}

			result = true;
		}
		return result;
	}

	void SelectBlock(BodyBlock block)
	{
		// Change seletion to the new block
		if ( block == null )
			return;
		if ( m_selectedBlock != null )
		{
			m_selectedBlock.Deselect();
		}
		m_selectedBlock = block;
		m_selectedBlock.Select();
	}

	bool Contract(BodyBlock block)
	{
		// Will only contract if leaf block (has only 1 link)

		bool result = false;

		// check there's only 1 sibling, and not root node (can't contract those)
		if ( m_rootBlock != block && block.GetSiblingCount() <= 1 )
		{
			// Get the sibling block
			block.GetParentBlock().ClearNextSibling();
			SelectBlock( block.GetParentBlock() );
			block.Remove();
			m_soundContract.Play();
			m_currBlocks--;
			result = true;
			UpdateSprites();
		}


		return result;
	}

	static readonly Quaternion[] BLOCK_DIRECTION_ROTATIONS = 
	{
		Quaternion.Euler(0,0,0),	// up
		Quaternion.Euler(0,0,180),	// down
		Quaternion.Euler(0,0,90),	// left
		Quaternion.Euler(0,0,-90),	// right
	};
	static readonly int[] ARM_L_SORTORDER = 
	{
		-2,	// up
		-2,	// down
		-2,	// left
		2,	// right
	};
	static readonly int[] ARM_R_SORTORDER = 
	{
		-2,	// up
		-2,	// down
		2,	// left
		-2,	// right
	};

	//static readonly Vector3 ARM_R_OFFSET_SMALL = new Vector3(16,8,0);
	//static readonly Vector3 ARM_L_OFFSET_SMALL = new Vector3(-16,8,0);
	static readonly Vector3 ARM_R_OFFSET = new Vector3(15.5f,9.5f,0);
	static readonly Vector3 ARM_L_OFFSET = new Vector3(-15.5f,9.5f,0);

	Vector3 m_rightArmPos = Vector3.zero;
	Vector3 m_leftArmPos = Vector3.zero;

	void UpdateSprites()
	{
		// Iterate through blocks and set all the sprites

		BodyBlock prevBlock = null;
		BodyBlock block = m_rootBlock;
		eBlockDirection prevDirection = block.GetNextSibling();
		eBlockDirection nextDirection = block.GetNextSibling();
		BodyBlock nextBlock = block.GetSibling(nextDirection);
		if ( m_currBlocks == 1 )
		{
			m_rootBlock.SetSprite(m_spriteHeadSmall);
			block.SetCorners(eBlockDirection.None,eBlockDirection.None);

			// Set arm positions
			m_armRight.gameObject.SetActive(false);
			m_armLeft.gameObject.SetActive(false);
			m_armRightShort.gameObject.SetActive(true);
			m_armLeftShort.gameObject.SetActive(true);

		}
		else if ( m_currBlocks == 2 )
		{
			switch( prevDirection )
			{
				default:
				case eBlockDirection.Up: { block.SetSprite(m_spriteBodySmallU); } break;
				case eBlockDirection.Right: { block.SetSprite(m_spriteBodySmallR); } break;
				case eBlockDirection.Left: { block.SetSprite(m_spriteBodySmallL); } break;
				case eBlockDirection.Down: { block.SetSprite(m_spriteBodySmallU,prevDirection); } break;
			}

			// Set arm positions
			m_armRightShort.gameObject.SetActive(false);
			m_armLeftShort.gameObject.SetActive(false);
			m_armRight.gameObject.SetActive(true);
			m_armLeft.gameObject.SetActive(true);
			m_armRight.transform.position = block.transform.position + (BLOCK_DIRECTION_ROTATIONS[(int)prevDirection] * ARM_R_OFFSET);
			m_armLeft.transform.position = block.transform.position + (BLOCK_DIRECTION_ROTATIONS[(int)prevDirection] * ARM_L_OFFSET);
			m_armRight.GetComponent<HingeJoint2D>().connectedAnchor = (block.transform.position + (BLOCK_DIRECTION_ROTATIONS[(int)prevDirection] * ARM_R_OFFSET) - transform.parent.position);
			m_armLeft.GetComponent<HingeJoint2D>().connectedAnchor = (block.transform.position + (BLOCK_DIRECTION_ROTATIONS[(int)prevDirection] * ARM_L_OFFSET) - transform.parent.position);
			foreach ( SpriteRenderer renderer in m_armLeft.GetComponentsInChildren<SpriteRenderer>() )
				renderer.sortingOrder = ARM_L_SORTORDER[(int)prevDirection];
			foreach ( SpriteRenderer renderer in m_armRight.GetComponentsInChildren<SpriteRenderer>() )
				renderer.sortingOrder = ARM_R_SORTORDER[(int)prevDirection];

			prevBlock = block;
			block = nextBlock;
			nextBlock = block.GetSibling(nextDirection);	

			// do the head
			block.SetSprite(m_spriteHead, nextDirection, true, m_faceIdleTimer);
			block.SetCorners(eBlockDirection.None,eBlockDirection.None);
		}			
		else 
		{

			while ( block != null)
			{
				if ( nextBlock == null )
				{
					// head
					block.SetSprite(m_spriteHead, prevDirection, true, m_faceIdleTimer);
					block.SetCorners(eBlockDirection.None,eBlockDirection.None);
					break;
				}

				if ( block == m_rootBlock )
				{
					// pants
					if ( nextDirection == eBlockDirection.Up )
					{
						block.SetSprite(m_spritePantsU);
						block.SetCorners(eBlockDirection.None,eBlockDirection.None);
					}
					else
					{
						block.SetSprite(m_spritePantsLR); 
						block.SetCorners(eBlockDirection.None,nextDirection);
					}
				}
				else if ( nextBlock.GetNextSibling() == eBlockDirection.None )
				{
					// neck
					block.SetSprite(m_spriteNeck, nextDirection);
					block.SetCorners(prevDirection,eBlockDirection.None);

					// Set arm positions
					m_armRightShort.gameObject.SetActive(false);
					m_armLeftShort.gameObject.SetActive(false);
					m_armRight.gameObject.SetActive(true);
					m_armLeft.gameObject.SetActive(true);
					m_armRight.transform.position = block.transform.position + (BLOCK_DIRECTION_ROTATIONS[(int)nextDirection] * ARM_R_OFFSET);
					m_armLeft.transform.position = block.transform.position + (BLOCK_DIRECTION_ROTATIONS[(int)nextDirection] * ARM_L_OFFSET);
					m_armRight.GetComponent<HingeJoint2D>().connectedAnchor = (block.transform.position + (BLOCK_DIRECTION_ROTATIONS[(int)nextDirection] * ARM_R_OFFSET) - transform.parent.position);
					m_armLeft.GetComponent<HingeJoint2D>().connectedAnchor = (block.transform.position + (BLOCK_DIRECTION_ROTATIONS[(int)nextDirection] * ARM_L_OFFSET) - transform.parent.position);
					//m_armRight.position = m_armRight.transformposition;
					//m_armRight.MovePosition(m_armRight.position);
					//m_armLeft.position = m_armLeft.transform.position;
					//m_armLeft.MovePosition(m_armLeft.position);

					foreach ( SpriteRenderer renderer in m_armLeft.GetComponentsInChildren<SpriteRenderer>() )
						renderer.sortingOrder = ARM_L_SORTORDER[(int)nextDirection];
					foreach ( SpriteRenderer renderer in m_armRight.GetComponentsInChildren<SpriteRenderer>() )
						renderer.sortingOrder = ARM_R_SORTORDER[(int)nextDirection];
				}
				else 
				{
					// body
					block.SetSprite(m_spriteBody );
					block.SetCorners(prevDirection,nextDirection);
				}

				prevBlock = block;
				block = nextBlock;
				prevDirection = nextDirection;	
				if ( block != null )
				{
					nextDirection = block.GetNextSibling();
					nextBlock = block.GetSibling(nextDirection);
				}
			}
		}


	}

}
