package CK.IA;

import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Vector;

public class EnvironmentAgent {
	public static final String CURRENT_PATH = GoalSimulatorConfig.CURRENT_PATH;
	public static final int ENVIROMENT_ID = GoalSimulatorConfig.ENVIRONMENT_ID;
	public static final String OBJ_WAYPOINTS = GoalSimulatorConfig.WAYPOINTS_FILE;
	public static final char slash = GoalSimulatorConfig.DIRECTORY_SEPARATOR;
	/**
	 * Hashtable with entities belonging to a certain team
	 */
	private volatile Hashtable<Integer, HashSet<Entity>> carried_entities_team;
	/**
	 * Hashtable with tuples <id,entity>
	 */
	private volatile Hashtable<Integer, Entity> entities;
	/**
	 * Hashtable with all the environment's goals
	 */
	private volatile HashSet<Goal> goals;
	/**
	 * Hashtable with tuples <id,goal>
	 */
	private volatile Hashtable<Integer, Goal> goals_id;
	/**
	 * is this environment paused?
	 */
	private boolean paused;
	private volatile HashSet<SoldierAgent> soldier_agents;
	/**
	 * Hashset with the soldier agents's ID
	 */
	private volatile HashSet<Integer> soldier_agents_ids;
	private volatile Vector<Integer> soldier_agents_ids_vector;
	private volatile Hashtable<Integer, HashSet<SoldierAgent>> soldier_agents_teams;
	private volatile Hashtable<Integer, Integer> spawn_agents_ids;
	private WaypointNavigation wn;

	public EnvironmentAgent() {
		if (GoalSimulatorConfig.DEBUG)
			System.out.println(this.toString() + " initializing.");
		parseWaypoints();
		if (GoalSimulatorConfig.DEBUG)
			System.out.println(this.toString() + " initializing dynamic data structures");
		soldier_agents_ids = new HashSet<Integer>();
		soldier_agents_ids_vector = new Vector<Integer>();
		soldier_agents = new HashSet<SoldierAgent>();
		soldier_agents_teams = new Hashtable<Integer, HashSet<SoldierAgent>>();
		goals = new HashSet<Goal>();
		goals_id = new Hashtable<Integer, Goal>();
		entities = new Hashtable<Integer, Entity>();
		carried_entities_team = new Hashtable<Integer, HashSet<Entity>>();
		spawn_agents_ids = new Hashtable<Integer, Integer>();
		if (GoalSimulatorConfig.DEBUG)
			System.out.println(this.toString() + " adding goals");
		addAllGoals();
		setPaused(true);
		if (GoalSimulatorConfig.DEBUG)
			System.out.println(this.toString() + " initialized.");
	}

	public void addAllGoals() {
		addRoamGoals(0, GoalSimulatorConfig.NUMBER_GOALS_ROAM_RED);
		addRoamGoals(1, GoalSimulatorConfig.NUMBER_GOALS_ROAM_BLUE);
		Vector3d initial_position = GoalSimulatorConfig.CARRY_GOAL_INITIAL_POSITION_RED;
		addCarryGoals(0, GoalSimulatorConfig.NUMBER_GOALS_CARRY_RED, initial_position);
		initial_position = GoalSimulatorConfig.CARRY_GOAL_INITIAL_POSITION_BLUE;
		addCarryGoals(1, GoalSimulatorConfig.NUMBER_GOALS_CARRY_BLUE, initial_position);
	}

	public void addCarryGoals(int team, int number_of_goals, Vector3d initial_position_) {
		// carry goals, for each team
		for (int i = 0; i < number_of_goals; i++) {
			// random position the goal near their default position
			Vector3d initial_position = initial_position_.clone();
			initial_position.addX(GoalSimulatorConfig.CARRY_GOAL_INITIAL_POSITION_DISPERSION * (Math.random() * 2 - 1));
			initial_position.addZ(GoalSimulatorConfig.CARRY_GOAL_INITIAL_POSITION_DISPERSION * (Math.random() * 2 - 1));
			// choose a random capture point (according to the team)
			int position = (int) (Math.random() * 3);
			Vector3d capture_position;
			if (team == 0) {
				if (position == 0) {
					capture_position = GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_RED_1;
				} else if (position == 1) {
					capture_position = GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_RED_2;
				} else {
					capture_position = GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_RED_3;
				}
			} else {
				if (position == 0) {
					capture_position = GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_BLUE_1;
				} else if (position == 1) {
					capture_position = GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_BLUE_2;
				} else {
					capture_position = GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_BLUE_3;
				}
			}
			// we don't want to change the original position
			capture_position = capture_position.clone();
			capture_position.addX(GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_DISPERSION * (Math.random() * 2 - 1));
			capture_position.addZ(GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_DISPERSION * (Math.random() * 2 - 1));
			// setup the goal's entity
			Entity goal_entity = new Entity(Float.MAX_VALUE, initial_position, GoalSimulatorConfig.CARRY_ENTITY_RADIUS, EntityType.ENTITY, this);
			// create the goal
			Goal goal = new Goal(GoalType.CARRY, goal_entity, GoalSimulatorConfig.GOAL_PRIORITY_CARRY, team,
					GoalStatus.APPROACHING_CAPTURE_POSITION_WITH_PAYLOAD, false, -1);
			// link the goal and his entity
			goal.setCompletePosition(capture_position);
			// add the goal
			this.addGoal(goal);
		}
	}

	public void addEntity(Entity entity) {
		entities.put(entity.getId(), entity);
	}

	public void addGoal(Goal goal) {
		int goal_id = goal.getId();
		Entity entity = goal.getEntity();
		goals_id.put(goal_id, goal);
		goals.add(goal);
		if (entity != null) {
			addEntity(entity);
		}
		// just to track the carried entities for the engine
		if (goal.getType() == GoalType.CARRY) {
			int team = goal.getTeam();
			HashSet<Entity> hash = this.carried_entities_team.get(team);
			if (hash == null) {
				hash = new HashSet<Entity>();
				this.carried_entities_team.put(team, hash);
			}
			hash.add(entity);
		}
	}

	public void addRoamGoals(int team, int number_of_goals) {
		Goal goal;
		Vector3d randomWaypoint;
		for (int i = 0; i < number_of_goals; i++) {
			goal = new Goal(GoalType.ROAM, null, GoalSimulatorConfig.GOAL_PRIORITY_ROAM, team, GoalStatus.ROAMING, false, -1);
			randomWaypoint = wn.getRandomWaypoint();
			randomWaypoint.add(GoalSimulatorConfig.WAYPOINTS_OFFSET);
			goal.setCompletePosition(randomWaypoint);
			this.addGoal(goal);
		}
	}

	public void addSoldierAgent(SoldierAgent agent) {
		int id = agent.getId();
		soldier_agents_ids.add(id);
		soldier_agents.add(agent);
		soldier_agents_ids_vector.add(id);
		int agent_team = agent.getTeam();
		HashSet<SoldierAgent> team = soldier_agents_teams.get(agent_team);
		if (team == null) {
			team = new HashSet<SoldierAgent>();
			soldier_agents_teams.put(agent_team, team);
		}
		team.add(agent);
		addEntity(agent);
	}

	public void addSpawnAgent(SpawnAgent agent) {
		spawn_agents_ids.put(agent.getTeam(), agent.getId());
		addEntity(agent);
	}

	public void damageRandomSoldierAgent() {
		// in each world cycle someone will get hurt, dangerous world
		if (Math.random() < GoalSimulatorConfig.SOLDIER_DAMAGE_PROBABILITY) {
			// who's the lucky one?
			int lucky_id = soldier_agents_ids_vector.get((int) (Math.random() * soldier_agents_ids_vector.size()));
			// the poor guy must be on his feet
			if (!getSoldierAgent(lucky_id).isDead()) {
				// prepare a nice amount of nitroglycerin
				float damage = (float) (Math.random() * GoalSimulatorConfig.SOLDIER_DAMAGE_INTENSITY);
				Message m = new Message(Performative.INFORM, Subject.ID_ENTITY, Property.DAMAGE_FLOAT, lucky_id, lucky_id, ENVIROMENT_ID);
				// add the explosive on the box
				m.setContent(damage);
				// give it to him
				sendMessage(m);
				// System.out.println(lucky_id + " got hurt with " + damage);
			}
		}
	}

	public void executeCycle() {
		if (GoalSimulatorConfig.DEBUG)
			System.out.println(this.toString() + " executeCycle()");
		// update entities
		updateEntities();
		// give a cycle to all entities
		Enumeration<Entity> elements = entities.elements();
		while (elements.hasMoreElements()) {
			Entity nextElement = elements.nextElement();
			nextElement.executeCycle();
		}
		damageRandomSoldierAgent();
	}

	public Hashtable<Integer, Entity> getAllEntities() {
		return entities;
	}

	public HashSet<Goal> getAllGoals() {
		return goals;
	}

	public HashSet<SoldierAgent> getAllSoldierAgents() {
		return soldier_agents;
	}

	public HashSet<Entity> getCarriedEntities(int team) {
		return this.carried_entities_team.get(team);
	}

	public Entity getEntity(int id) {
		return entities.get(id);
	}

	public Goal getGoal(int id) {
		return goals_id.get(id);
	}

	public HashSet<Goal> getGoals(int team) {
		HashSet<Goal> gls = new HashSet<Goal>();
		for (Goal g : goals) {
			if (g.getTeam() == team) {
				gls.add(g);
			}
		}
		return gls;
	}

	public SoldierAgent getSoldierAgent(int id) {
		return (SoldierAgent) this.getEntity(id);
	}

	public HashSet<SoldierAgent> getSoldierAgents(int team) {
		HashSet<SoldierAgent> hs = soldier_agents_teams.get(team);
		if (hs == null) {
			hs = new HashSet<SoldierAgent>();
			soldier_agents_teams.put(team, hs);
		}
		return hs;
	}

	public SpawnAgent getSpawnAgent(int team) {
		return (SpawnAgent) this.getEntity(getSpawnAgentID(team));
	}

	public int getSpawnAgentID(int team) {
		return spawn_agents_ids.get(team);
	}

	public WaypointNavigation getWaypointNavigation() {
		return this.wn;
	}

	public void initializeNavigationalSystem() {
	}

	public boolean isPaused() {
		return paused;
	}

	public void loadObjectives() {
	}

	public void parseWaypoints() {
		System.out.println(this.toString() + " parsing waypoints");
		WavefrontParser wop = new WavefrontParser();
		try {
			wop.parseFile(CURRENT_PATH + slash + OBJ_WAYPOINTS);
			wop.checkCoincidentVertices(GoalSimulatorConfig.WAYPOINT_COINCIDENCE_REPLACEMENT_TOLERANCE, true);
			wop.removeDotLines();
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println(this.toString() + " navigating waypoints");
		wn = new WaypointNavigation(wop.getLines(), wop.getVertices());
	}

	public void processMessage(Message original) {
		boolean message_processed = false;
		if (GoalSimulatorConfig.DEBUG)
			System.out.println(this.toString() + " receiveMessage() " + original.toString());
		int sender_id = original.getSender_id();
		// is the message directed to the environment?
		if (original.getDestination_id() == ENVIROMENT_ID) {
			// retrieve message information
			Performative performative = original.getPerformative();
			Subject subject = original.getSubject();
			Property property = original.getProperty();
			Object content = original.getContent();
			// is the message a request?
			if (performative == Performative.REQUEST) {
				// requested information regarding all soldier agents
				if (subject == Subject.ALL_SOLDIER_AGENTS) {
					// what's wanted is the hashtable of all soldier agents
					if (property == Property.HASHSET) {
						// create the message
						Message answer = new Message(Performative.INFORM, Subject.ALL_SOLDIER_AGENTS, Property.HASHSET, -1, sender_id, ENVIROMENT_ID);
						answer.setContent(this.getAllSoldierAgents());
						// send the message
						sendMessage(answer);
						message_processed = true;
					}
				} // someone wants all entities in the environment
				else if (subject == Subject.ALL_ENTITIES) {
					// as hashtable <id,entity>
					if (property == Property.HASHTABLE_ENTITY_BY_ID) {
						// create the message
						Message answer = new Message(Performative.INFORM, Subject.ALL_ENTITIES, Property.HASHTABLE_ENTITY_BY_ID, -1, sender_id,
								ENVIROMENT_ID);
						answer.setContent(this.getAllEntities());
						// send the message
						sendMessage(answer);
						message_processed = true;
					}
				} // someone wants all goals in the environment
				else if (subject == Subject.ALL_GOALS) {
					// as hashset
					if (property == Property.HASHSET) {
						// create the message
						Message answer = new Message(Performative.INFORM, Subject.ALL_GOALS, Property.HASHSET, -1, sender_id, ENVIROMENT_ID);
						answer.setContent(this.getAllGoals());
						// send the message
						sendMessage(answer);
						message_processed = true;
					}
				}
				// someone wants a path
				else if (subject == Subject.PATH) {
					if (property == Property.LINKED_LIST) {
						Message answer = new Message(Performative.INFORM, Subject.PATH, Property.LINKED_LIST, -1, sender_id, ENVIROMENT_ID);
						Vector3d entity_position = getEntity(sender_id).getPosition();
						Vector3d destination = (Vector3d) content;
						LinkedList<Vector3d> path = wn.getPathAsLinkedList(entity_position, destination);
						answer.setContent(path);
						// send the message
						sendMessage(answer);
						message_processed = true;
					} else if (property == Property.VECTOR) {
						Message answer = new Message(Performative.INFORM, Subject.PATH, Property.VECTOR, -1, sender_id, ENVIROMENT_ID);
						Vector3d entity_position = getEntity(sender_id).getPosition();
						Vector3d destination = (Vector3d) content;
						Vector<Vector3d> path = wn.getPathAsVector(entity_position, destination);
						answer.setContent(path);
						// send the message
						sendMessage(answer);
						message_processed = true;
					}

				}
				// someone wants something about goal bid
				else if (subject == Subject.GOAL_AUCTION_BID) {
					if (property == Property.GOAL_AUCTION_BID) {
						// this is to be asked to the whole team
						// the request always brings the goal auction
						GoalAuctionBid b = (GoalAuctionBid) content;
						GoalAuction ga = b.getGoal_auction();
						Goal goal = ga.getGoal();
						int team = goal.getTeam();
						// let's broadcast to the goal's team (except the sender)
						HashSet<SoldierAgent> soldierAgents = this.getSoldierAgents(team);
						for (SoldierAgent sa : soldierAgents) {
							if (sa.getId() != sender_id) {
								Message m = new Message(Performative.REQUEST, Subject.GOAL_AUCTION_BID, Property.GOAL_AUCTION_BID, -1, sa.getId(),
										sender_id);
								m.setContent(content);
								sendMessage(m);
							}
						}
						message_processed = true;
					}
				}
			} else
			// is the message an inform?
			if (performative == Performative.INFORM) {
				// about an entity
				if (subject == Subject.ID_ENTITY) {
					// regarding the class entity
					if (property == Property.ENTITY) {
						// get the content and update our reference to the
						// entity
						// LOL they're the same but... whatever
						Entity ent = (Entity) content;
						addEntity(ent);
						message_processed = true;
					}
				} // about a goal
				else if (subject == Subject.GOAL) {
					if (property == Property.GOAL) {
						// take the content and update our reference to the goal
						// the same again...
						Goal g = (Goal) content;
						// we may want to check the related entitie's data...
						// check to see if the goal has been assigned early, and
						// inform the agent about it
						Goal goal_here = this.getGoal(g.getId());
						if (goal_here.getId() == g.getId()) {
							// assigned to someone else?
							if (goal_here.isAssigned() && goal_here.getOwner() != g.getOwner()) {
								// do not store the goal and inform the agent
								// that the goal is assigned to someone
								// else
								Message reply = new Message(Performative.INFORM, Subject.GOAL, Property.GOAL, -1, sender_id,
										EnvironmentAgent.ENVIROMENT_ID);
								reply.setContent(goal_here);
								sendMessage(reply);
							} else {
								addGoal(g);
							}
						}
						message_processed = true;
					} else if (property == Property.COMPLETE) {
						// inform all agents of it's accomplishment
						Goal g = (Goal) content;
						if (g.getStatus() == GoalStatus.COMPLETE) {
							Goal goal_here = this.getGoal(g.getId());
							// we may want to see if it's really complete... but as the simulator is currently implemented in a single thread, it is
							goal_here.setStatus(GoalStatus.COMPLETE);
							// no need to broadcast, agents constantly pool the environment for it
							// broadcastToAllAgents(goal_here);
							// process goal according to it's type
							if (goal_here.getType() == GoalType.ROAM) {
								recreateRoamGoal(goal_here);
							} else if (goal_here.getType() == GoalType.CARRY) {
								recreateExchangedCarryGoal(goal_here);
							}
							// heal the agent (i.e., give him a new body)
							Message m = new Message(Performative.INFORM, Subject.ID_ENTITY, Property.HEALTH_FLOAT, sender_id, sender_id,
									ENVIROMENT_ID);
							float health = (float) (GoalSimulatorConfig.SOLDIER_HEALTH_NORMAL * 1.25);
							m.setContent(health);
							sendMessage(m);
							message_processed = true;
						}
					}
				}
			}
			// the message is intended for some entity
		} else {
			// just forward it (we may want to filter it)
			sendMessage(original);
			message_processed = true;
		}
		if (!message_processed) {
			System.out.println("EnvironmentAgent:SOMETHING's MISSING HERE!!!");
		}
	}

	public void recreateExchangedCarryGoal(Goal g) {
		// now we create a new capture position, according to the associated team
		int position = (int) (Math.random() * 3);
		Vector3d capture_position;
		int new_goal_team;
		// goal's team are the red
		if (g.getTeam() == 0) {
			// the goal exchange's to the blue
			new_goal_team = 1;
			// choose a random capture point (according to the team)
			if (position == 0) {
				capture_position = GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_BLUE_1;
			} else if (position == 1) {
				capture_position = GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_BLUE_2;
			} else {
				capture_position = GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_BLUE_3;
			}
		} else
		// goal's team are the blue
		{
			// the goal exchange's to the red
			new_goal_team = 0;
			// choose a random capture point (according to the team)
			if (position == 0) {
				capture_position = GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_RED_1;
			} else if (position == 1) {
				capture_position = GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_RED_2;
			} else {
				capture_position = GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_RED_3;
			}
		}
		// we don't want to change the original position
		capture_position = capture_position.clone();
		// shake the capture's position a little
		capture_position.addX(GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_DISPERSION * (Math.random() * 2 - 1));
		capture_position.addZ(GoalSimulatorConfig.CARRY_GOAL_CAPTURE_POSITION_DISPERSION * (Math.random() * 2 - 1));
		// set the goal's capture point
		g.setCompletePosition(capture_position);
		// exchange the goal's team
		g.setTeam(new_goal_team);
		// setup the goal
		g.setStatus(GoalStatus.APROACHING_PAYLOAD_POSITION);
		g.setOwner(-1);
	}

	public void recreateRoamGoal(Goal g) {
		// now recreate the goal :P
		g.setStatus(GoalStatus.ROAMING);
		g.setOwner(-1);
		// basically we randomly set up a new roam position
		Vector3d randomWaypoint = wn.getRandomWaypoint();
		randomWaypoint.add(GoalSimulatorConfig.WAYPOINTS_OFFSET);
		g.setCompletePosition(randomWaypoint);
	}

	public void sendMessage(Message m) {
		// pass the message to it's receiver
		int id = m.getDestination_id();
		Entity entity = getEntity(id);
		entity.receiveMessage(m);
	}

	public void setPaused(boolean b) {
		// send ALL entities the pause flag
		this.paused = b;
		Enumeration<Entity> elements = entities.elements();
		while (elements.hasMoreElements()) {
			Entity ent = elements.nextElement();
			Message m = new Message(Performative.INFORM, Subject.ID_ENTITY, Property.PAUSED_BOOLEAN, ent.getId(), ent.getId(), ENVIROMENT_ID);
			m.setContent(b);
			sendMessage(m);
		}
	}

	public String toString() {
		return "environment:" + ENVIROMENT_ID;
	}

	/**
	 * Sends a message to all entities requesting their classes.
	 */
	public void updateEntities() {
		if (GoalSimulatorConfig.DEBUG)
			System.out.println(this.toString() + " updateEntities()");
		Enumeration<Entity> elements = entities.elements();
		while (elements.hasMoreElements()) {
			Entity ent = elements.nextElement();
			Message m = new Message(Performative.REQUEST, Subject.ID_ENTITY, Property.ENTITY, ent.getId(), ent.getId(), ENVIROMENT_ID);
			sendMessage(m);
		}
	}
}
