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

public class Attack : MonoBehaviour
{
	[SerializeField] private AttackType attackType;
	[SerializeField] private float damage = 5;
	[SerializeField] private float radius = 5;
	[SerializeField] private float cooldown = 1;
	[SerializeField] private float hitDelay = 0.1f;
	[SerializeField] private float particleDuration = 0.1f;
	[SerializeField] private ParticleSystem myParticleSystem;

	private bool cool = true;
	private FactionInfo factionInfo;
	private List<Attackable> targets = new List<Attackable>();

	private void Start()
	{
		factionInfo = GetComponent<FactionInfo>();
		myParticleSystem.Stop();
	}

	private void Update()
	{
		if (cool)
		{
			DoAttack();
		}
	}

	private void FixedUpdate()
	{
		switch (attackType)
		{
			case AttackType.Melee:
			case AttackType.Ranged:
				GetClosestTarget();
				break;
			case AttackType.AoE:
				GetAllTargets();
				break;
		}
	}

	private void GetClosestTarget()
	{
		GetAllTargets();

		if (targets.Count <= 1)
		{
			return;
		}

		targets.Sort(delegate (Attackable x, Attackable y)
		{
			float squareDistanceX = Vector3.SqrMagnitude(transform.position - x.transform.position);
			float squareDistanceY = Vector3.SqrMagnitude(transform.position - y.transform.position);
			if (x == y || Mathf.Approximately(squareDistanceX, squareDistanceY)) return 0;
			else if (squareDistanceX < squareDistanceY) return -1;
			else if (squareDistanceX > squareDistanceY) return 1;
			else return 0;
		});

		Attackable target = targets[0];
		targets.Clear();
		targets.Add(target);
	}

	private void GetAllTargets()
	{
		targets.Clear();

		if (factionInfo == null)
		{
			throw new Exception("No faction info");
		}

		Collider[] hits = Physics.OverlapSphere(transform.position, radius, GameManager.AttackableLayers);

		foreach (Collider hit in hits)
		{
			if (hit.gameObject == gameObject)
			{
				continue;
			}

			Attackable attackable = hit.GetComponent<Attackable>();
			FactionInfo attackableFaction = hit.GetComponent<FactionInfo>();

			if (attackable != null && attackableFaction != null && factionInfo.IsHostileTarget(attackableFaction))
			{
				targets.Add(attackable);
			}
		}
	}

	private void DoAttack()
	{
		if (targets.Count > 0)
		{
			if (myParticleSystem != null)
			{
				if (attackType == AttackType.Ranged)
				{
					myParticleSystem.transform.LookAt(targets[0].transform.position);
				}

				myParticleSystem.Play();
			}
		}

		foreach (Attackable target in targets)
		{
			if (target == null)
			{
				continue;
			}

			StartCoroutine(DelayHit(targets[0]));
		}

		StartCoroutine(Cooldown());
		StartCoroutine(StopParticles());
	}

	private IEnumerator DelayHit(Attackable target)
	{
		yield return new WaitForSeconds(hitDelay);
		if (target != null)
		{
			target.TakeDamage(damage);
		}
	}

	private IEnumerator Cooldown()
	{
		cool = false;
		yield return new WaitForSeconds(cooldown);
		if (myParticleSystem != null)
		{
			myParticleSystem.Stop();
		}
		cool = true;
	}

	private IEnumerator StopParticles()
	{
		yield return new WaitForSeconds(particleDuration);
		if (myParticleSystem != null)
		{
			myParticleSystem.Stop();
		}
	}

	public bool CanAttack()
	{
		return targets.Count > 0;
	}

	private void OnDrawGizmos()
	{
		Gizmos.color = Color.red;
		Gizmos.DrawWireSphere(transform.position, radius);
	}

	public float GetDamage()
	{
		return damage;
	}

	public float GetRadius()
	{
		return radius;
	}
}

public enum AttackType
{
	Melee,
	Ranged,
	AoE
};
