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

public class FlockBehaviorWolf : MonoBehaviour {
	public float speed = 0.25f;
	List<GameObject> swarm = new List<GameObject>();
	List<GameObject> detractors;
	public float cohesion = 1.0f;
	public float alignment = 1.0f;
	public float separation = 1.0f;
	public float attractor = 1.0f;
	public float alignmentRadius = 1f;
	public float cohesionRadius = 1f;
	public float separationRadius = 0.5f;
	public float separationRadiusAtSheep = 0.5f;
	public float detractorRadius = 2f;
	public float detractorWeight = 1f;
	public float maxDistanceToSheep = 1f;
	public float minDistanceToSheep = 1f;
	private float speedFactor = 1;
	private Vector2 velocity;
	private List<GameObject> sheep = new List<GameObject>();

	// Use this for initialization
	void Start () {
		List<GameObject> allWolves = new List<GameObject>(GameObject.FindGameObjectsWithTag (this.tag));
		foreach (GameObject aWolf in allWolves) {
			if (aWolf.layer != 8) {
				swarm.Add(aWolf);
			}
		}

		swarm.Remove(gameObject);
		//detractors = new List<GameObject>(GameObject.FindGameObjectsWithTag ("Wolf"));

		//Vector2 velocity = new Vector2(UnityEngine.Random.value, UnityEngine.Random.value);
		//velocity.Normalize ();
		Vector2 velocity = new Vector2(0, 0);
		setVelocity(velocity);
	}

	protected void setVelocity(Vector2 velocity) {
		this.velocity = velocity;
		gameObject.GetComponent<Rigidbody2D>().velocity = speedFactor * speed * velocity ;
	}

	public Vector2 getVelocity() {
		return velocity;
	}
	
	private Vector3 toVector3(Vector2 vector2) {
		return new Vector3(vector2.x, vector2.y, 0);
	}

	private Vector2 computeCohesion() {
		Vector2 center = new Vector2(0, 0);
		int count = 0;
		foreach (GameObject agent in swarm) {
			if (Utils.getDistance(agent, gameObject) < cohesionRadius) {
				center += Utils.toVector2(agent.transform.position);
				count++;
			}
		}
		center /= count;
		Vector2 direction = center - Utils.toVector2(transform.position);
		direction.Normalize();
		return direction;
	}

	private Vector2 computeSeparation(float radius) {
		Vector2 direction = new Vector2(0, 0);
		foreach (GameObject agent in swarm) {
			if (Utils.getDistance(agent, gameObject) < radius) {
				direction += Utils.toVector2(agent.transform.position - transform.position);
			}
		}
		direction = -direction;
		direction.Normalize();
		return direction;
	}

	private Vector2 computeAlignment() {
		Vector2 direction = new Vector2(0, 0);
		foreach (GameObject agent in swarm) {
			if (Utils.getDistance(agent, gameObject) < alignmentRadius) {
				direction += agent.GetComponent<FlockBehaviorWolf>().getVelocity();
			}
		}
		direction.Normalize();
		return direction;
	}

	/*private Vector2 computeDetractor() {
		Vector2 direction = new Vector2(0, 0);
		float minDistance = 0f;
		Vector3 minPosition = new Vector2(0, 0);
		Boolean first = true;
		foreach (GameObject detractor in detractors) {
			float distance = Utils.getDistance (detractor, gameObject);
			if (first || distance < minDistance) {
				minDistance = distance;
				minPosition = detractor.transform.position;
			}
			first = false;
		}
		if (!first && minDistance < detractorRadius) {
			direction = Utils.toVector2(transform.position - minPosition);
			direction.Normalize ();
		}
		return direction;
	}*/

	public Vector2 computeAttractorTarget(GameObject target) {
		var direction = Utils.toVector2(target.transform.position - transform.position);
		direction.Normalize ();
		return direction;
	}
	
	public Vector2 computeAttractor() {
		return computeAttractorTarget(Utils.findNearestNeighbor (gameObject, sheep));
	}
	
	private GameObject getNearestSheep() {
		return Utils.findNearestNeighbor (gameObject, sheep);
	}

	private float getDistanceToNearestSheep() {
		return Utils.getDistance (getNearestSheep(), gameObject);
	}

	protected Vector2 combineDirectionRegular() {
		GameObject nearestSheep = getNearestSheep ();
		float distance = Utils.getDistance (nearestSheep, gameObject);
		if (nearestSheep.GetComponent<feastOnSheep>().readyToDie || distance > maxDistanceToSheep) {
			return Time.deltaTime * 10f * (
				cohesion * computeCohesion() +
				separation * computeSeparation(separationRadius) +
				alignment * computeAlignment() +
				attractor * computeAttractor());
		} else if (distance > minDistanceToSheep) {
			return Time.deltaTime * 10f * (
				0 * computeCohesion() +
				separation * computeSeparation(separationRadiusAtSheep) +
				alignment * computeAlignment() +
				0 * computeAttractor());
		} else {
			return Time.deltaTime * 10f * (
				0 * computeCohesion () +
				separation * computeSeparation (separationRadiusAtSheep) +
				0 * computeAlignment() +
				-attractor * computeAttractor());
		}
	}

	private Vector2 combineDirectionTowards(GameObject target) {
		return Time.deltaTime * 10f * (
			separation * computeSeparation (separationRadiusAtSheep) +
			attractor * computeAttractorTarget(target));
	}

	protected Vector2 combineDirection() {
		GameObject freeToKill = GameObject.FindGameObjectWithTag("FreeToKill");
		if (freeToKill != null) {
			speedFactor = 2f;
			return combineDirectionTowards(freeToKill);
		} else {
			speedFactor = 1f;
			return combineDirectionRegular();
		}
	}

	private void setOrientation(int orientation) {
		transform.eulerAngles = new Vector3(0,orientation > 0 ? 180 : 0,0);
	}

	// Update is called once per frame
	void FixedUpdate () {
		sheep.Clear ();
		List<GameObject> allSheep = new List<GameObject>(GameObject.FindGameObjectsWithTag ("Sheep"));
		foreach (GameObject aSheep in allSheep) {
			if (aSheep.layer != 8) {
				sheep.Add(aSheep);
			}
		}
		
		Vector2 nextVelocity = getVelocity();

		nextVelocity += combineDirection();

		//float height = Camera.main.orthographicSize;
		//float width = (height * Screen.width) / Screen.height;
		float height = 3f;
		float width = 5.3f;
		nextVelocity.y = Utils.reflectAtLimit (nextVelocity.y, transform.position.y, height - 0.5f);
		nextVelocity.x = Utils.reflectAtLimit (nextVelocity.x, transform.position.x, width - 0.5f);
		
		nextVelocity.Normalize();
		setOrientation (Math.Sign (nextVelocity.x));
		setVelocity (nextVelocity);
	}
}
