using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class BoidAgent : MonoBehaviour
{
	public const float SEPARATION_THRESHOLD = 1;

	public GameObject BloodParticlePrefab;
	public Renderer modelRenderer;

	private Vector3 _velocity;// = new Vector2(5, 0);
	public Vector3 Velocity { get { return _velocity; } }
	private Vector3 _calculatedVelocity;
	private Vector3 _wanderTarget = Vector3.zero;
	private Vector3 _faceOutRotation = Vector3.zero;

	private BoidController _controller;
	private BoidController _enemyController;
//	private SpriteRenderer _sprite;
	private Animator _animator;
	private Vector3 _cachedPosition;
	public Vector3 CachedPosition { get { return _cachedPosition; } }
	public BoidController.BehaviourType CurrentBehaviourType
	{
		get
		{
			return _controller.CurBehaviourType;
		}
	}

	public void SetController(BoidController inController, BoidController inEnemyController)
	{
		_controller = inController;
		_enemyController = inEnemyController;

//		_sprite = GetComponentInChildren<SpriteRenderer>();
//		_sprite.color = _controller.teamColor;

		if(modelRenderer)
			modelRenderer.material.color = _controller.teamColor;

		_animator = GetComponentInChildren<Animator>();

		_cachedPosition = transform.position;
	}
	public void DoBehaviours(List<BoidAgent> inFlock)
	{
		Vector3 v1 = Vector3.zero;
		Vector3 v2 = Vector3.zero;
		Vector3 v3 = Vector3.zero;

		//separation from obstacles
		Vector3 v8 = Vector3.zero;
		foreach(Transform obstacle in _controller.ObstacleAgents)
		{
			float distance = (obstacle.position - CachedPosition).sqrMagnitude;
			if (distance > 0 && distance < _controller.SquadBehaviour.SeparationThreshold)
			{
				Vector3 difference = CachedPosition - obstacle.position;
				difference.Normalize();
				difference *= (1 / distance);
				v8 += difference;
			}
		}
		v8 *= _controller.SquadBehaviour.SeparationThreshold;
		//separation behaviour
		if(inFlock.Count > 1)
		{

			foreach(BoidAgent agent in inFlock)
			{
				float distance = (agent.CachedPosition - CachedPosition).sqrMagnitude;
				if(distance > 0 && distance < _controller.SquadBehaviour.SeparationThreshold)
				{
					Vector3 difference = CachedPosition - agent.CachedPosition;
					difference.Normalize();
					difference *= (1 / distance);
					v1 += difference;
				}
			}

			v1 *= _controller.SquadBehaviour.SeparationMultiplier;
			v2 = (_controller.FlockVelocitySum - _velocity) / (inFlock.Count - 1) * _controller.SquadBehaviour.AlignmentMultiplier;
			v3 = (((_controller.FlockPositionSum - _cachedPosition) / (inFlock.Count - 1)) - transform.position) * _controller.SquadBehaviour.CohesionMultiplier;
		}

		//Bounding Behaviour
//		Vector3 v4 = DoBounding() * _controller.SquadBehaviour.BoundingMultiplier;

		//WanderBehaviour
//		Vector3 v5 = DoWander() * _controller.SquadBehaviour.WanderMultiplier;

		//Command Behaviour
		Vector3 v6 = Vector3.zero;
		v6 = new Vector3(_controller.commandInputAxis.x, 0, _controller.commandInputAxis.y) * 50;
		if(v6 != Vector3.zero)
			v6 *= _controller.SquadBehaviour.CommandMultiplier;

		//Flee Behaviour
		Vector3 v7 = Vector3.zero;
		v7 = (_cachedPosition -  _enemyController.FlockPositionSum / _enemyController.Agents.Count) * _controller.SquadBehaviour.FleeMultiplier;
		v7.Normalize();

		//Combine Movement Behaviours
		_calculatedVelocity = _velocity + v1 + v2 + v3 + v6 + v7 + v8;
		_calculatedVelocity = Vector3.ClampMagnitude(_calculatedVelocity, _controller.SquadBehaviour.MaxVelocity);
		float dot = Vector3.Dot((Vector3)_velocity.normalized, (Vector3)_calculatedVelocity.normalized);
		dot = Mathf.InverseLerp(-1, 1, dot);
		dot = Mathf.Lerp(2, 1, dot);
		_calculatedVelocity = Vector3.MoveTowards(_velocity, _calculatedVelocity, _controller.SquadBehaviour.Acceleration * _controller.DeltaTime * dot);

		//Rotation Stuff
		_faceOutRotation = (transform.position - ((_controller.FlockPositionSum - _cachedPosition) / (inFlock.Count - 1))) * _controller.SquadBehaviour.FaceOutMultiplier;

		// Animation
		float behaviourBlend = -1f;
		if(CurrentBehaviourType == BoidController.BehaviourType.Defend)
			behaviourBlend = 0f;
		else if(CurrentBehaviourType == BoidController.BehaviourType.March)
			behaviourBlend = 0.5f;
		else if(CurrentBehaviourType == BoidController.BehaviourType.Charge)
			behaviourBlend = 1f;

		if(_animator != null && behaviourBlend >= 0f)
			_animator.SetFloat("BehaviourType", behaviourBlend);
	}
	public void DoMovement()
	{
		_calculatedVelocity.y = 0f;

		Vector3 newPos = transform.position;
		newPos += (Vector3)_calculatedVelocity * _controller.DeltaTime;

		RaycastHit groundHit;
		Vector3 raycastPos = newPos;
		raycastPos.y += 10f;
		if(Physics.Raycast(raycastPos, Vector3.down, out groundHit, 20f, LayerMask.GetMask("Ground")))
			newPos.y = groundHit.point.y;

		transform.position = newPos;
		_velocity = _calculatedVelocity;

		//Do Rotation
		Vector3 targetRotation = (_velocity + _faceOutRotation);
		targetRotation.y = transform.forward.y;
		transform.forward = Vector3.MoveTowards(transform.forward, targetRotation.normalized, _controller.DeltaTime * _controller.SquadBehaviour.TurnAcceleration);

		DoHardBounding();

		_cachedPosition = transform.position;

	}

	public Vector3 DoBounding()
	{
		Vector3 result = Vector3.zero;
		if (transform.position.x <= _controller.softBoundsMin.x)
		{
			result.x = 10;
		}
		else if(transform.position.x >= _controller.softBoundsMax.x)
		{
			result.x = -10;
		}

		if(transform.position.z <= _controller.softBoundsMin.y)
		{
			result.z = 10;
		}
		else if(transform.position.z >= _controller.softBoundsMax.y)
		{
			result.z = -10;
		}
		return result;
	}
	public void DoHardBounding()
	{
		transform.position = new Vector3(
			Mathf.Clamp(transform.position.x, _controller.hardBoundsMin.x, _controller.hardBoundsMax.x), 
			transform.position.y, 
			Mathf.Clamp(transform.position.z, _controller.hardBoundsMin.z, _controller.hardBoundsMax.z));
	}
	/*public Vector3 DoWander()
	{
		if(Vector3.Distance(transform.position, _wanderTarget) < .05f || _wanderTarget == Vector3.zero)
		{
			//Generate new wander target
			Vector3 dir = new Vector3(Random.value, 0f, Random.value).normalized;
			_wanderTarget = Random.Range(.5f, _controller.BoundMagnitude) * dir;
		}
		//move towards wander target
		return _wanderTarget - transform.position;
	}*/

	public float GetCombatFactor(Vector3 inOpponentPosition, float inCohesionMultiplier, float inVelocityMultiplier, float inRandomMultiplier)
	{
		bool defending = _controller.SquadBehaviour == _controller.DefendBehaviour;
		float cohesionFactor = Map(0, 1, 0, .7f, _controller.CohesionBonus);
		if (!defending)
			cohesionFactor *= .5f;

		float unmappedVelocityFactor = _velocity.magnitude;
		float velocityFactor = Map(0, 1, 0, _controller.ChargeBehaviour.MaxVelocity, unmappedVelocityFactor);

		float randomFactor = Random.value;

		float directionFactor = defending ? 1f : Map(0, 1, -1, 1, Vector3.Dot((Vector3)transform.forward.normalized, (Vector3)inOpponentPosition.normalized));

		return
			(cohesionFactor * inCohesionMultiplier
			+ velocityFactor * inVelocityMultiplier
			+ randomFactor * inRandomMultiplier)
			* directionFactor;
	}

	void OnTriggerEnter(Collider collider)
	{
		if(collider.attachedRigidbody != null)
		{
			BoidAgent otherAgent = collider.attachedRigidbody.GetComponent<BoidAgent>();
			if(otherAgent && CollisionManager.Instance != null && (_controller != otherAgent._controller))
				CollisionManager.Instance.AddPair(this, otherAgent);
		}
	}

	public void Die()
	{
		//		if(_sprite)
		//		{
		//			_sprite.color = _controller.teamDisabledColor;
		//			_sprite.sortingOrder = -1;
		//		}

		//		if(modelRenderer)
		//			modelRenderer.gameObject.SetActive(false);
		//
		//		Collider c = GetComponent<Collider>();
		//		if(c)
		//			c.enabled = false;

		Collider c = GetComponent<Collider>();
		if(c)
			c.enabled = false;
		_controller.DespawnAgent(this);
		transform.DOScale(Vector3.zero, Random.Range(.25f, .75f)).OnComplete(() => Destroy(gameObject));
		Instantiate(BloodParticlePrefab, new Vector3(transform.position.x, transform.position.y + 3.5f, transform.position.z), BloodParticlePrefab.transform.rotation);
	}
	public float Map(float from, float to, float from2, float to2, float value)
	{
		if (value <= from2)
		{
			return from;
		}
		else if (value >= to2)
		{
			return to;
		}
		else {
			return (to - from) * ((value - from2) / (to2 - from2)) + from;
		}
	}
}
