package Rectangles;

import java.awt.Color;
import java.util.*;

/**
 * @author Vladislav Shchogolev
 * @author Mark Berman
 */
public final class Group3Player6 implements IFCPlayer {

	Rectangles game;
	
	static final double SCORE_THRESHOLD = 0.9;
	static final int 	MAX_ANTICHASE_MOVES = 100;
	static final int	CHASER_MOVE_THRESHOLD = 5;
    static final double RISKINESS = 0.25;	// scale is from 0 to 1
	
    static final String _CNAME = "Apollo Creed";
    static final Color _CCOLOR = Color.BLUE;
	static char[] turnDirs = { _CEAST, _CNORTH, _CWEST, _CSOUTH, _CSTAY };

	static final int EAST = 0;
	static final int NORTH = 1;
	static final int WEST = 2;
	static final int SOUTH = 3;
	static final int STAY = 4;
	
	static Random rand = new Random();
	
	private int myIndex;
	private int size;
	private int numRobots;

	private TeamMember[] members;
	private List teams = new ArrayList();
	private Team offense;
	private Team defense;

	public Robot[] register(Rectangles rectangles) throws Exception {
		Robot[] robots;

		// init
		game = rectangles;
		numRobots = game.numRobots();
		size = game.size();
		myIndex = game.indexOf(this);

		// init team members
		members = new TeamMember[numRobots];
		for (int i = 0; i < numRobots; i++) {
			members[i] = new TeamMember(i);
		}

		Team team = null;
		rand.nextInt();
		robots = new Robot[numRobots];
		for (int i = 0; i < numRobots; i++) {
			/*
			 * Thanks to Mark B.
			 */
		    switch(i) {
		    case 0:
		    case 8:
		    case 16:
			robots[i] = new Robot(rand.nextInt(size/2),
					      rand.nextInt(size/2));
			team = new OffenseTeam(game, myIndex);
			teams.add(team);
			break;
		    case 2:
		    case 10:
		    case 18:
			team = new OffenseTeam(game, myIndex);
			teams.add(team);
			robots[i] = new Robot(rand.nextInt(size/3)+(2*size/3),
					      rand.nextInt(size/3)+(2*(size/3)));
			break;
		    case 4:
		    case 12:
			team = new OffenseTeam(game, myIndex);
			teams.add(team);
			robots[i] = new Robot(rand.nextInt(size/3)+(2*(size/3)),
					      rand.nextInt(size/3));
			break;
		    case 6:
		    case 14:
			team = new OffenseTeam(game, myIndex);
			teams.add(team);
			robots[i] = new Robot(rand.nextInt(size/3),
					      rand.nextInt(size/3)+(2*(size/3)));
			break;
		    default:
			robots[i] = new Robot(robots[i - 1].xpos(), robots[i - 1].ypos());
			break;
		    }
		    
		    members[i].setDirection(rand.nextInt(4));
		    team.addMember(members[i]);
		}
		
		return robots;
	}
    
	public char[] move() throws Exception {

		// process teams
		Team team;
		for (Iterator iter = teams.iterator(); iter.hasNext();) {
			team = (Team) iter.next();
			team.init();
			try {
				team.updateMoves();
			} catch(Exception e) {
				System.out.println("Error in move: " + e.getClass());
				throw new Exception(e);
			}
		}

		// return moves
		int numRobots = game.numRobots();
		char[] moves = new char[numRobots];

		for (int i = 0; i < numRobots; i++) {
			moves[i] = members[i].getMove();
		}

		return moves;
	}

	public double scoreSum() throws Exception {
		double sum = 0;
		MoveResult[] history = game.history();
		if (history.length == 0)
			return 0;
		double[] scores = history[history.length - 1].scores();

		for (int i = 0; i < scores.length; i++) {
			sum += scores[i];
		}

		return sum;
	}

	//	---------------------------------------- Team class

	private static abstract class Team {
		List members = new ArrayList();
		Rectangles game;
		int index;

		public Team(Rectangles r, int i) {
			game = r;
			index = i;
		}

		public void init() throws Exception {
			TeamMember member;
			for (Iterator iter = members.iterator(); iter.hasNext();) {
				member = (TeamMember) iter.next();
				
				member.setRobot(
					game.allRobots()[index][member.getRobotIndex()]);
			}
		}

		protected Robot closestEnemy(Robot me) throws Exception {
			Robot[][] robots = game.allRobots();
			Robot closest = null;
			int dist, min = Integer.MAX_VALUE;

			for (int i = 0; i < robots.length; i++) {
				if (i == index)
					continue;
				for (int j = 0; j < robots[i].length; j++) {
					dist = robotDistance(me, robots[i][j]);
					if (dist < min) {
						min = dist;
						closest = robots[i][j];
					}
				}
			}

			return closest;
		}

		public char getLastMove(Robot r) throws Exception {
			MoveResult[] history = this.game.history();
			MoveResult last = history[history.length - 1];
			return last.moves()[r.playerIndex()][r.ID()];
		}

		/*
		 * Mark A's algorithm modified
		 */
		protected boolean isBeingChased(TeamMember tm) throws Exception {
			Robot closest = closestEnemy(tm.getRobot());

			if (tm.getClosestEnemyBot() == null) {
				tm.setClosestEnemyBot(closest);
				return false;
			}

			if (robotsEqual(tm.getClosestEnemyBot(), closest)) {
				Robot enemy = tm.getClosestEnemyBot();
				if (robotDistance(closest, tm.getRobot()) < 6) {
					if (getLastMove(enemy) == getLastMove(tm.getRobot())) {
						tm.incrementMovesBeingChased();

						// prevent deadlock
						if (tm.getNumMovesBeingChased() > MAX_ANTICHASE_MOVES) {
							tm.setNumMovesBeingChased(0);
							tm.turn(new Random().nextInt(4));
						}
					}

					if (tm.getNumMovesBeingChased() > CHASER_MOVE_THRESHOLD) {
						return true;
					}
				}
			} else {
				tm.setNumMovesBeingChased(0);
				tm.setClosestEnemyBot(closest);
			}

			return false;
		}

		protected int robotDistance(Robot a, Robot b) throws Exception {
			return Math.abs(a.xpos() - b.xpos())
				+ Math.abs(a.ypos() - b.ypos());
		}

		/*
		 * Credited to Mark A.
		 */
		protected boolean robotsEqual(Robot iR1, Robot iR2) throws Exception {
			if (iR1 == null) {
				return (iR2 == null);
			}

			if (iR2 == null) {
				return false;
			}

			return (iR1.playerIndex() == iR2.playerIndex() && 
				iR1.ID() == iR2.ID());
		}

		public void addMember(TeamMember m) {
			members.add(m);
			m.setTeamIndex(members.indexOf(m));
		}

		public List list() {
			return members;
		}

		public boolean canMove(TeamMember m) throws Exception {
			char dir = m.getMove();
			Robot r = m.getRobot();
			switch (dir) {
				case _CNORTH :
					return r.ypos() > 0;
				case _CSOUTH :
					return r.ypos() < this.game.size() - 1;
				case _CEAST :
					return r.xpos() < this.game.size() - 1;
				case _CWEST :
					return r.xpos() > 0;
			}

			return false;
		}

		public abstract void updateMoves() throws Exception;
	}

	//	---------------------------------------- OffenseTeam class

	private static class OffenseTeam extends Team {
		
		private SortedMap samples = new TreeMap();
		private int lastSamplesUpdate = -1;
		
		public OffenseTeam(Rectangles r, int index) throws Exception {
			super(r, index);
		}

		public Vertex getBestTarget(Vertex anchor) throws Exception {

			int ax = anchor.xpos();
			int ay = anchor.ypos();
			int tx, ty, bestTx = 0, bestTy = 0;
			double bestScore = -1, score;
			int maxDelta = (int)(this.game.size() * RISKINESS);
			int vectors[][] = { { 1, 1 }, {1, -1 }, {-1, 1 }, {-1, -1 } };

			for (int k = 0; k < vectors.length; k++) {
				for (int i = 2; i < maxDelta; i++) {
					for (int j = 2; j < maxDelta; j++) {
						tx = ax + i * vectors[k][0];
						ty = ay + j * vectors[k][1];
						score = rectangleScore(ax, ay, tx, ty);
						if (score > bestScore) {
							bestScore = score;
							bestTx = tx;
							bestTy = ty;
						}
					}
				}
			}

			return new Vertex(bestTx, bestTy);
		}

		private double rectangleScore(Vertex a, Vertex b) throws Exception {
			return rectangleScore(a.xpos(), a.ypos(), b.xpos(), b.ypos());
		}

		private double rectangleScore(int ax, int ay, int tx, int ty)
			throws Exception {
			int temp;
			double sum = 0;
			int size = this.game.size();
			int[][] colors = this.game.colors();

			if (ax > tx) {temp = tx; tx = ax; ax = temp;}
			if (ay > ty) {temp = ty; ty = ay; ay = temp;}

			if (ax < 0 || ay < 0 || tx >= size || ty >= size)
				return 0;

			for (int i = ax; i <= tx; i++) {
				if (colors[i][ay] == _CFILLED || colors[i][ty] == _CFILLED) 
					return 0;
			}

			for (int i = ay; i <= ty; i++) {
				if (colors[ax][i] == _CFILLED || colors[tx][i] == _CFILLED) 
					return 0;
			}

			for (int i = ax + 1; i < tx; i++) {
				for (int j = ay + 1; j < ty; j++) {
					if (colors[i][j] != _CFILLED) {
						sum++;
					}
				}
			}

			return sum;
		}

		private double vertexScore(Vertex v) throws Exception {
			int x = v.xpos();
			int y = v.ypos();
			int size = this.game.size();
			int[][] colors = this.game.colors();
			double score = 0;
			
			for (int i = x-4; i <= x+4; i++) {
				for (int j = y-4; j <= y+4; j++) {
					if (i < 0 || j < 0 || i >= size || j >= size) continue;
					if (colors[i][j] != _CFILLED) score++;
				}
			}
			
			return score;
		}

		private void updateSamples() throws Exception {
			if (this.game.rounds() == lastSamplesUpdate) return;
			
			Vertex probe, target;
			int size = this.game.size();
			double score;
			
			Collection old = samples.values();
			samples.clear();
			
			for (Iterator iter = old.iterator(); iter.hasNext();) {
				probe = (Vertex)iter.next();
				score = vertexScore(probe);
				if (score >= 9) {
					samples.put(new Double(score), probe);
				}				
			}
			
			for (int i = 0; i < 5; i++) {
				probe = new Vertex(rand.nextInt(size), rand.nextInt(size));
				score = vertexScore(probe);
				if (score >= 9) {
					samples.put(new Double(score), probe);
				}
			}
			
			lastSamplesUpdate = this.game.rounds();
		}

		public void updateMoves() throws Exception {
			updateSamples();
			
			TeamMember member;
			
			for (Iterator iter = this.members.iterator(); iter.hasNext();) {
				member = (TeamMember) iter.next();

				if (isBeingChased(member)) {
					member.stop();
				} else {
					member.resumeIfStopped();
			
					do {
						if (member.getState() == State.kChangingAnchor) {
							boolean done = member.moveToCurrentDestination();
							if (!done) {
								Robot robot = member.getRobot();
								Vertex v = new Vertex(robot.xpos(), robot.ypos());
								Vertex w = getBestTarget(v);
								if (rectangleScore(v,w) > 0) {
									member.setAnchor(v);
									member.setTarget(w);
									member.setState(State.kMovingToTarget);
								} else break;
							}
							else 
								member.setState(State.kIdle);
						}
						
						if (member.isIdle()) {
							member.setAnchor();
							member.setTarget(getBestTarget(member.getAnchor()));

							if (rectangleScore(member.getAnchor(), 
									member.getTarget()) < SCORE_THRESHOLD) {
								if (!member.isEdgeGuard() && !samples.isEmpty()) {
									// we might have an alternate location to go to
									Double key = (Double) samples.lastKey();
									Vertex v = (Vertex)samples.get(key);
									member.changeAnchor(v);	
									continue;	
								}
								break;	// give up on this turn, no rectangles around					
							} else {
								// rectangle is good, we're in business
								member.setState(State.kMovingToTarget);
							}
						}
						
						Vertex anchor = member.getAnchor();
						Vertex target = member.getTarget();
						
						if (!member.isIdle()) {
							if (rectangleScore(anchor, target) < SCORE_THRESHOLD) {
								member.setState(State.kIdle);
								continue;		
							} else {
								member.buildRectangle();
							} 
						}
						
					} while (member.isIdle());
					
					// prevent colliding with wall
					while (!canMove(member)) {
						member.turnRight();
					}
				}

			}
		}
	}

	// ---------------------------------------- IFCPlayer Methods

	public String name() throws Exception {
		return _CNAME;
	}

	public Color color() throws Exception {
		return _CCOLOR;
	}

	public boolean interactive() throws Exception {
		return false;
	}

	//	---------------------------------------- TeamMember class

	private static class TeamMember {
		private Robot robot;
		private int teamIndex;
		private int robotIndex;
		private int direction = 0;
		private boolean isStopped = false;
		private Robot closestEnemyBot = null;
		private int numMovesBeingChased = 0;
		private Vertex anchor;
		private Vertex target;
		private State state = State.kIdle;

		public TeamMember(int i) {
			robotIndex = i;
		}

		public boolean isEdgeGuard() {
			return robotIndex < 8;
		}

		public int getRobotIndex() {
			return robotIndex;
		}

		public void setTeamIndex(int i) {
			teamIndex = i;
		}

		/*
		 * Thanks to Mark B.
		 */
		public boolean moveTo(Vertex v) throws Exception {
			if (teamIndex % 2 == 0) {
				if (robot.xpos() < v.xpos())
					setDirection(EAST);
				else if (robot.xpos() > v.xpos())
					setDirection(WEST);
				else if (robot.ypos() < v.ypos())
					setDirection(SOUTH);
				else if (robot.ypos() > v.ypos())
					setDirection(NORTH);
				else
					return true;
			} else {
				if (robot.ypos() < v.ypos())
					setDirection(SOUTH);
				else if (robot.ypos() > v.ypos())
					setDirection(NORTH);
				else if (robot.xpos() < v.xpos())
					setDirection(EAST);
				else if (robot.xpos() > v.xpos())
					setDirection(WEST);
				else
					return true;
			}
			return false;
		}

		public void setAnchor() throws Exception {
			anchor = new Vertex(robot.xpos(), robot.ypos());
		}

		public void turnRight() { turn(1); }
		public void turnLeft() { turn(-1); }

		public void turn(int dir) {
			direction = (direction + dir) % 4;
			while (direction < 0) {
				direction += 4;
			}
		}

		public int getDirection() {
			return direction;
		}

		public Robot getRobot() {
			return robot;
		}

		public void setDirection(int i) {
			direction = i;
		}

		public void setRobot(Robot robot) {
			this.robot = robot;
		}

		public Robot getClosestEnemyBot() {
			return closestEnemyBot;
		}

		public void setClosestEnemyBot(Robot robot) {
			closestEnemyBot = robot;
		}

		public int getNumMovesBeingChased() {
			return numMovesBeingChased;
		}

		public void setNumMovesBeingChased(int i) {
			numMovesBeingChased = i;
		}

		public void incrementMovesBeingChased() {
			numMovesBeingChased++;
		}

		public void stop() {
			isStopped = true;
		}

		public void resumeIfStopped() {
			isStopped = false;
		}

		public char getMove() {
			if (!isStopped)
				return turnDirs[direction];
			else
				return turnDirs[STAY];
		}

		public Vertex getAnchor() throws Exception {
			if (anchor == null) setAnchor();
			return anchor;
		}

		public Vertex getTarget() {
			return target;
		}

		public void setAnchor(Vertex vertex) {
			anchor = vertex;
		}

		public void setTarget(Vertex vertex) {
			target = vertex;
		}

		public void buildRectangle() throws Exception {
			if (state != State.kMovingToAnchor
				&& state != State.kMovingToTarget) {

				state = State.kMovingToTarget;
				setAnchor();
			}

			boolean done = moveToCurrentDestination();

			if (done) {
				if (state == State.kMovingToTarget) {
					state = State.kMovingToAnchor;
					moveToCurrentDestination();
				} else if (state == State.kMovingToAnchor) {
					state = State.kIdle;
				}
			}
		}

		public boolean moveToCurrentDestination() throws Exception {
			if (state == State.kChangingAnchor) 
				return moveTo(getAnchor());
			else
				return moveTo(
					(state == State.kMovingToAnchor)
						? getAnchor()
						: getTarget());
		}
		
		public boolean isIdle() {
			return state == State.kIdle;
		}
		
		public void changeAnchor(Vertex v) {
			state = State.kChangingAnchor;
			anchor = v;
		}

		public boolean isStopped() {
			return isStopped;
		}

		public State getState() {
			return state;
		}

		public void setState(State state) {
			this.state = state;
		}

	}
	
	//	---------------------------------------- State class
	//	thanks to Mark A. and Josh Bloch
	
	private static class State {
		private String state;

		public static final State kMovingToTarget = new State("to target");
		public static final State kMovingToAnchor = new State("to anchor");
		public static final State kIdle = new State("idle");
		public static final State kChangingAnchor = new State("change anchor");
		
		private State(String s) {
			state = "[" + s + "]";
		}

		public String toString() {
			return state;
		}
	}

}
