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

public abstract class Character : MonoBehaviour {

	protected enum Direction { Up, Down, Left, Right };

	[SerializeField] protected GameObject fistLeft;
	[SerializeField] protected GameObject fistRight;
	[SerializeField] protected GameObject legs;
	[SerializeField] protected float armLength = 2.0f;
	[SerializeField] protected float movementSpeed = 0.1f;
	[SerializeField] protected AnimationCurve rollCurve;
	[SerializeField] protected AnimationCurve punchCurve;
	[SerializeField] protected float punchSpeed = 0.2f;
	[SerializeField] protected float chargeSpeed = 1f;
	[SerializeField] protected float inRangeRadius = 2f;
	[SerializeField] protected float coinRadius = 2f;
	[SerializeField] protected float strength;
	[SerializeField] protected float minSpeed;
	[SerializeField] protected float maxSpeed;

	protected Animator animator;

	protected List<GameObject> charsInRange = new List<GameObject>();
	protected List<GameObject> coinsInRange = new List<GameObject>();
	
	protected Vector2 target;

	protected Vector3 fistLeftPosition;
	protected Vector3 fistRightPosition;
	protected Vector3 lastPosition;

	protected bool isAttacking = false;
	protected bool isRolling = false;
	public bool isDead = false;
	public bool isInvuln = false;
	protected bool rollInterrupt = false;
	protected bool isFacingRight = false;
	protected Direction direction = Direction.Down;

	protected bool isChargingLeft;
	protected bool isChargingRight;
	protected bool isPunchingLeft;
	protected bool isPunchingRight;

	protected bool canReturnPunchLeft;
	protected bool canReturnPunchRight;

	public float maxHealth = 100;
	public float health;

	// Use this for initialization
	protected void Start () {
		animator = GetComponent<Animator>();
		fistLeftPosition = fistLeft.transform.localPosition;
		fistRightPosition = fistRight.transform.localPosition;
	}

	protected void Update() {

		if (!isDead) {
			GetCharsInRange();
			GetCoinsInRange();

			float xDif = target.x - transform.position.x;
			float yDif = target.y - transform.position.y;
			int fl = 1;
			int fr = 1;

			if (coinsInRange.Count > 0) {
				Collider[] hitColliders = Physics.OverlapSphere(transform.position, transform.localScale.x * GetComponent<SphereCollider>().radius);
				if (hitColliders.Length > 0) {
					for (int x = 0; x < hitColliders.Length; x += 1) {
						if (coinsInRange.Contains(hitColliders[x].gameObject )&& hitColliders[x].gameObject.GetComponent<Coin>().isActive) {
							CoinPickup(hitColliders[x].gameObject);
							break;
						}
					}
				}
			}

			if (direction != Direction.Up && yDif >= 0 && Mathf.Abs(yDif) >= Mathf.Abs(xDif)) {
				direction = Direction.Up;
				animator.SetTrigger("goUp");
				legs.GetComponent<Animator>().SetTrigger("goUp");
				fistLeft.GetComponent<Fist>().SetPosition("Up");
				fistRight.GetComponent<Fist>().SetPosition("Up");
				fistLeftPosition = fistLeft.transform.localPosition;
				fistRightPosition = fistRight.transform.localPosition;
			} else if (direction != Direction.Down && yDif < 0 && Mathf.Abs(yDif) >= Mathf.Abs(xDif)) {
				direction = Direction.Down;
				animator.SetTrigger("goDown");
				legs.GetComponent<Animator>().SetTrigger("goDown");
				fistLeft.GetComponent<Fist>().SetPosition("Down");
				fistRight.GetComponent<Fist>().SetPosition("Down");
				fistLeftPosition = fistLeft.transform.localPosition;
				fistRightPosition = fistRight.transform.localPosition;
			} else if (direction != Direction.Left && xDif < 0 && Mathf.Abs(yDif) < Mathf.Abs(xDif)) {
				direction = Direction.Left;
				animator.SetTrigger("goLeft");
				legs.GetComponent<Animator>().SetTrigger("goLeft");
				fistLeft.GetComponent<Fist>().SetPosition("Left");
				fistRight.GetComponent<Fist>().SetPosition("Left");
				fistLeftPosition = fistLeft.transform.localPosition;
				fistRightPosition = fistRight.transform.localPosition;
			} else if (direction != Direction.Right && xDif >= 0 && Mathf.Abs(yDif) < Mathf.Abs(xDif)) {
				direction = Direction.Right;
				animator.SetTrigger("goRight");
				legs.GetComponent<Animator>().SetTrigger("goRight");
				fistLeft.GetComponent<Fist>().SetPosition("Right");
				fistRight.GetComponent<Fist>().SetPosition("Right");
				fistLeftPosition = fistLeft.transform.localPosition;
				fistRightPosition = fistRight.transform.localPosition;
			}

			// todo set reverse animation
			if (transform.position == lastPosition) {
				legs.GetComponent<Animator>().SetBool("isMoving", false);
			} else {
				legs.GetComponent<Animator>().SetBool("isMoving", true);
			}

			if (direction == Direction.Up) {
				fl = -1;
				fr = -1;
			} else if (direction == Direction.Down) {
				fl = 1;
				fr = 1;
			} else if (direction == Direction.Left) {
				fl = -1;
				fr = 1;
			} else if (direction == Direction.Right) {
				fl = 1;
				fr = -1;
			}

			GetComponent<SpriteRenderer>().sortingOrder = Mathf.RoundToInt(transform.position.y * 100f) * -1;
			legs.GetComponent<SpriteRenderer>().sortingOrder = Mathf.RoundToInt(transform.position.y * 100f) * -1;
			fistLeft.GetComponent<SpriteRenderer>().sortingOrder = Mathf.RoundToInt(transform.position.y * 100f) * -1 + fl;
			fistRight.GetComponent<SpriteRenderer>().sortingOrder = Mathf.RoundToInt(transform.position.y * 100f) * -1 + fr;
		}
	}

	protected void GetCoinsInRange() {
		coinsInRange.Clear();

		// look for coins
		// compare each coin distance to inRangeRadius
		if (GameManager.instance.GetCoins().Count > 0) {
			for (int i = 0; i < GameManager.instance.GetCoins().Count; i += 1) {
				float d = Vector2.Distance(transform.position, GameManager.instance.GetCoins()[i].transform.position);
				if (d <= coinRadius) {
					coinsInRange.Add(GameManager.instance.GetCoins()[i]);
				}
			}
		}
	}

	protected void GetCharsInRange() {
		// reset charsInRange
		charsInRange.Clear();

		// look for enemies
		// compare each enemy distance to inRangeRadius
		if (GameManager.instance.GetCharacters().Count > 0) {
			for (int i = 0; i < GameManager.instance.GetCharacters().Count; i += 1) {
				if (gameObject.tag != GameManager.instance.GetCharacters()[i].tag && GameManager.instance.GetCharacters()[i] != gameObject && !GameManager.instance.GetCharacters()[i].GetComponent<Character>().isDead) {
					float d = Vector2.Distance(transform.position, GameManager.instance.GetCharacters()[i].transform.position);
					if (d <= inRangeRadius) {
						charsInRange.Add(GameManager.instance.GetCharacters()[i]);
					}
				}
			}
		}
	}

	protected void OnDrawGizmos() {
		if (charsInRange.Count > 0) {
			Gizmos.color = Color.red;
		} else {
			Gizmos.color = Color.white;
		}
		Gizmos.DrawWireSphere(transform.position, inRangeRadius);
	}

	abstract public void CoinPickup(GameObject coin);
	abstract public void YouDied();
	// abstract public void TakeHit(GameObject fist, float force);

	// abstract public IEnumerator ChargePunch(GameObject fist);
	// abstract public IEnumerator ThrowPunch(GameObject fist);
	// abstract public IEnumerator ReturnPunch(GameObject fist);

	protected void ChargePunchLeft() {
		if (!isChargingLeft) {
			print("???");
			isChargingLeft = true;
			StartCoroutine(ChargePunch(fistLeft));
		}
	}

	protected void ChargePunchRight() {
		if (!isChargingRight) {
			isChargingRight = true;
			StartCoroutine(ChargePunch(fistRight));
		}
	}

	protected void ThrowPunchLeft() {
		if (!isPunchingLeft) {
			isChargingLeft = false;
			isPunchingLeft = true;
			StartCoroutine(ThrowPunch(fistLeft));
		}
	}

	protected void ThrowPunchRight() {
		if (!isPunchingRight) {
			isChargingRight = false;
			isPunchingRight = true;
			StartCoroutine(ThrowPunch(fistRight));
		}
	}

	protected void ReturnPunchLeft() {
		if (canReturnPunchLeft) {
			StartCoroutine(ReturnPunch(fistLeft));
		}
	}

	protected void ReturnPunchRight() {
		if (canReturnPunchRight) {
			StartCoroutine(ReturnPunch(fistRight));
		}
	}

	private Collider CheckPunchLand(GameObject fist) {
		Collider hit = null;

		if (charsInRange.Count > 0) {
			Collider[] hitColliders = Physics.OverlapSphere(fist.transform.position, fist.transform.localScale.x * fist.GetComponent<SphereCollider>().radius);
			if (hitColliders.Length > 0) {
				for (int x = 0; x < hitColliders.Length; x += 1) {
					if (charsInRange.Contains(hitColliders[x].gameObject) && !hitColliders[x].GetComponent<Character>().isInvuln) {
						hit = hitColliders[x];
						break;
					}
				}
			}
		}

		return hit;
	}

	public IEnumerator ChargePunch(GameObject fist) {
		yield return new WaitForEndOfFrame();
		if (fist == fistLeft) {
			ThrowPunchLeft();
		} else if (fist == fistRight) {
			ThrowPunchRight();
		}
	}

	public IEnumerator ThrowPunch(GameObject fist) {
		Vector3 location = fist.transform.InverseTransformPoint(target);
		location = Vector3.ClampMagnitude(new Vector3(fist.transform.localPosition.x + location.x * fist.transform.localScale.x, fist.transform.localPosition.y + location.y * fist.transform.localScale.y, fist.transform.localPosition.z), armLength);
		float startTime = Time.time;
		float timeDelta;
		float speed;
		bool returnFist = false;

		while (!returnFist) {
			timeDelta = Time.time - startTime;
			if (timeDelta < punchSpeed) {
				timeDelta = timeDelta / punchSpeed;
			} else {
				timeDelta = 1;
			}

			float moveX = (location.x - fist.transform.localPosition.x) * punchCurve.Evaluate(timeDelta) + fist.transform.localPosition.x;
			float moveY = (location.y - fist.transform.localPosition.y) * punchCurve.Evaluate(timeDelta) + fist.transform.localPosition.y;
			Vector3 newLocation = new Vector3(moveX, moveY, fist.transform.localPosition.z);
			speed = Vector3.Distance(fist.transform.localPosition, newLocation);
			fist.transform.localPosition = newLocation;

			// check for collision
			Collider hit = CheckPunchLand(fist);
			if (hit) {
				returnFist = true;
				hit.GetComponent<Character>().TakeHit(fist, speed, strength);
			} else if (fist.transform.localPosition == location) {
				returnFist = true;
			}

			yield return new WaitForEndOfFrame();
		}

		if (fist == fistLeft) {
			canReturnPunchLeft = true;
			ReturnPunchLeft();
		} else if (fist == fistRight) {
			canReturnPunchRight = true;
			ReturnPunchRight();
		}
	}

	public IEnumerator ReturnPunch(GameObject fist) {
		Vector3 location;

		if (fist == fistLeft) {
			location = fistLeftPosition;
		} else {
			location = fistRightPosition;
		}

		Vector3 ogLocation = location;
		float startTime = Time.time;
		float timeDelta = 0;

		while (fist.transform.localPosition != location && timeDelta != 1) {
			timeDelta = Time.time - startTime;
			if (fist == fistLeft && fistLeftPosition != ogLocation) {
				location = fistLeftPosition;
			} else if (fist == fistRight && fistRightPosition != ogLocation) {
				location = fistRightPosition;
			}

			if (timeDelta < punchSpeed) {
				timeDelta = timeDelta / punchSpeed;

				float moveX = (location.x - fist.transform.localPosition.x) * punchCurve.Evaluate(timeDelta) + fist.transform.localPosition.x;
				float moveY = (location.y - fist.transform.localPosition.y) * punchCurve.Evaluate(timeDelta) + fist.transform.localPosition.y;

				Vector3 newLocation = new Vector3(moveX, moveY, fist.transform.localPosition.z);
				fist.transform.localPosition = newLocation;
			} else {
				timeDelta = 1;
				fist.transform.localPosition = location;
			}

			yield return new WaitForEndOfFrame();
		}

		fist.transform.localPosition = location;

		if (fist == fistLeft) {
			isPunchingLeft = false;
			canReturnPunchLeft = false;
		} else if (fist == fistRight) {
			isPunchingRight = false;
			canReturnPunchRight = false;
		}

		isAttacking = false;
	}

	public void TakeHit(GameObject fist, float force, float str) {
		if (isRolling) {
			rollInterrupt = true;
		}
		// print(force);
		// calculate damage
		health -= str;
		if (health <= 0) {
			health = 0;
			YouDied();
		} else {
			GameManager.instance.PlayPunch();
		}

		Vector3 oldPos = transform.position;
		Vector3 fistPos = fist.transform.position;
		Vector3 difference = oldPos - fistPos;

		StartCoroutine(RollToPosition(new Vector3(transform.position.x + difference.x, transform.position.y + difference.y, transform.position.z)));

		float hpPercent = (maxHealth - health) / maxHealth;
		StartCoroutine(Camera.main.GetComponent<Cam>().ShakeCamera(hpPercent));

		if (CompareTag("Player")) {
			isInvuln = true;
			HealthBar.instance.UpdateHealth(maxHealth, health);
			for (int x = 0; x < str; x += 1) {
				DropCoin();
			}
		}
	}

	public IEnumerator RollToPosition(Vector3 position) {
		float startTime = Time.time;
		float timeDelta;
		isRolling = true;

		while (isRolling) {
			timeDelta = Time.time - startTime;
			if (timeDelta < chargeSpeed) {
				timeDelta = timeDelta / chargeSpeed;
			} else {
				timeDelta = 1;
			}

			float moveX = (position.x - transform.position.x) * rollCurve.Evaluate(timeDelta) + transform.position.x;
			float moveY = (position.y - transform.position.y) * rollCurve.Evaluate(timeDelta) + transform.position.y;
			Vector3 newLocation = new Vector3(moveX, moveY, transform.position.z);
			transform.position = newLocation;

			if (transform.position == position || rollInterrupt) {
				isRolling = false;
				rollInterrupt = false;
			}

			yield return new WaitForEndOfFrame();
		}
	} 

	public void DropCoin() {
		// see if we need to spwan a new coin
		GameObject coin = null;
		GameObject[] _coins = GameObject.FindGameObjectsWithTag("Coin");
        foreach (GameObject _coin in _coins) {
            if ((!_coin.activeSelf || !_coin.GetComponent<Coin>().isActive) && !_coin.GetComponent<Coin>().isFlying) {
				coin = _coin;
            }
        }

		if (coin == null) {
			// spawn a new coin
			coin = GameManager.instance.AddCoin(transform.position);
		}

		coin.GetComponent<Coin>().ThrowMe(transform.position, inRangeRadius);
	}
}
