package Rectangles;

import ui.*;
import java.util.*;
import java.io.*;
import java.awt.Color;
import java.awt.Point;

/**
 * Group6Player1
 *
 * modified from Group1Player1
 */
public final class Group6Player1 implements IFCPlayer {
	private static final String kName = "Group6: Rooz, Val, Andy";
	private static final Color kColor = new Color(1.0f, 0.5f, 1.0f);
	private static final float kEarlyLateThreshold = 0.5f;
	private static final int kCandidateHoleLength = 6;
	
	private static final char[] kDirs = {
		IFCConstants._CEAST, IFCConstants._CSOUTH,
		IFCConstants._CWEST, IFCConstants._CNORTH
	};
	
    private Rectangles mRect;
    private int mPlayerIndex;
    private int mNumRobots;
    private int mBoardLength;
    private int mNumPlayers;
    private Random mRandom;
    private Bot[] mBots;
    private List mHoles;
    int moveCounter = 1;
    int dirCounter = 0;


	public Robot[] register(Rectangles iRectangles) throws Exception {
		mRandom = new Random();
		
		// differentiate state from that of PRNG instances used by other players
		mRandom.nextInt();
		
		mRect = iRectangles;
		mNumRobots = mRect.numRobots();
		mBoardLength = mRect.size();
		mNumPlayers = mRect.numPlayers();
		mPlayerIndex = mRect.indexOf(this);
				
		return initializeBots();
	}
	
	/**
	 * Instantiate our robots and put them in their initial positions.  To start,
	 * half the robots are placed on the top/bottom perimeter (moving N/S) and
	 * the other half on the left/right perimeter (moving E/W).  Therefore,
	 * throughout the game, half the robots will move horizontally and the other
	 * half vertically.
	 */
	private Robot[] initializeBots() throws Exception {

		mBots = new Bot[mNumRobots];
			
		float halfNumRobots = mNumRobots / 2f;
		int gap = (int) (mBoardLength / halfNumRobots);
		Point p = new Point();		

		int boxesCompleted = 0;

		//place each bot 
		for (int i = 0; i < mNumRobots; i++) {			
			boolean flag = mRandom.nextBoolean();			
		     
			//place the bots
			
			if (i % 4 == 0) {
			    // top left
			    if (boxesCompleted == 0)
				p = new Point(0,0);
			    if (boxesCompleted == 1)
				p = new Point((mBoardLength-1)/2,0);
			    if (boxesCompleted == 2)
				p = new Point(0,(mBoardLength-1)/2);
			    if (boxesCompleted == 3)
				p = new Point((mBoardLength-1)/2,(mBoardLength-1)/2);

			} 
			else if (i % 4 == 1) {
			    // top right
			    if (boxesCompleted == 0)
				p = new Point((mBoardLength - 1)/2, 0);
			    if (boxesCompleted == 1)
				p = new Point((mBoardLength - 1), 0);
			    if (boxesCompleted == 2)
				p = new Point((mBoardLength - 1)/2, (mBoardLength-1)/2);
			    if (boxesCompleted == 3)
				p = new Point((mBoardLength - 1), (mBoardLength-1)/2);

			}
			else if (i % 4 == 2) {
			    // bottom right
			    if (boxesCompleted == 0)
				p = new Point((mBoardLength - 1)/2, (mBoardLength - 1)/2);
			    if (boxesCompleted == 1)
				p = new Point(mBoardLength - 1, (mBoardLength - 1)/2 );
			    if (boxesCompleted == 2)
				p = new Point((mBoardLength - 1)/2, (mBoardLength-1 ));
			    if (boxesCompleted == 3)
				p = new Point((mBoardLength - 1), (mBoardLength-1));
			    
			}
			else if (i % 4 == 3) {

			    // bottom left
			    if (boxesCompleted == 0)
				p = new Point(0 , (mBoardLength - 1)/2);
			    if (boxesCompleted == 1)
				p = new Point((mBoardLength - 1)/2, (mBoardLength - 1)/2);
			    if (boxesCompleted == 2)
				p = new Point(0, mBoardLength-1);
			    if (boxesCompleted == 3)
				p = new Point((mBoardLength - 1)/2, (mBoardLength-1));

			    boxesCompleted++;
			    //System.out.println(boxesCompleted + " boxes were completed.");
			}
			
			int edge = (i % 2) + (flag ? 2 : 0);
			mBots[i] = new Bot(this, p.x, p.y, kDirs[getOppositeDir(edge)]);
			mBots[i].setState(BotState.kBoxMove);
		}
		
		return mBots;
		
	}
	
	private int getTotalScores() throws Exception {
		double scoreTotal = 0;
		
		MoveResult[] history = mRect.history();
		if (history.length == 0) {
			return 0;
		}
		
		double[] scores = history[history.length - 1].scores();
		
		for (int i = 0; i < scores.length; i++) {
			scoreTotal += scores[i];
		}
		
		return (int) scoreTotal;
	}
	

    //dont think i need
	private void rebuildHolesListIfNecessary() throws Exception {
		int numCandidateHoles = Math.min(mNumRobots * 10, 100);
		int numHoles = mNumRobots;
		
		if (mHoles == null) {
			mHoles = new ArrayList(numCandidateHoles);			
		}
		
		if (mHoles.isEmpty()) {
			findCandidateHoles(numCandidateHoles, numHoles);
		}
	}

	public char[] move() throws Exception {
		char[] allMoves = new char[mNumRobots];
		
		for (int i = 0; i < mNumRobots; i++) {
			allMoves[i] = moveRobot(i);
		}
		
		return allMoves;
	}
	
	/**
	 * Initially, the robots will all arrive at the opposite side of the board at
	 * the same time.  When this happens, a list of "candidate holes" is compiled
	 * (inspired by last year's "Men on Mission" strategy).  A large number of
	 * random 6x6 squares are considered and ranked on the amount of unfilled
	 * space they contain.  The highest-ranking holes are kept and the others
	 * discarded.  As each robot arrives at the opposite edge, it selects the
	 * hole that it is closest to and moves along the edge toward that hole.
	 * Once it lines up with the hole, it sets off across the board to intersect
	 * the hole.  This process repeats once the robot reaches the opposite side
	 * of the board.	
	 */

	private char moveRobot(int iIndex) throws Exception {
		Bot b = mBots[iIndex];

		//		if (iIndex == mNumRobots-1)
		//    moveCounter++;

		//		System.out.println("we are on move " + moveCounter);
		
		// defend against chasing parasites
		if (b.isBeingChased()) {			
			return IFCConstants._CSTAY;
		}		
		
		BotState state = b.getState();
	       		
		// if box move
		if (state == BotState.kBoxMove) {
		    //  System.out.println((mBoardLength/2) % moveCounter + " is boardl mod movecounter");
		    //if ((mBoardLength/2) % moveCounter == 0) {		     
		    //		dirCounter++;
		    // }		    		   
		    b.moveToTargetPoint(iIndex, mBoardLength, dirCounter);		   		

		}


		b.setState(state);


		int dir = b.getDir();


		return (dir >= 0) ? kDirs[dir] : IFCConstants._CSTAY;
		
	}
	
	private void flattenPointToEdge(int iEdge, Point iP) throws Exception {
		switch (kDirs[iEdge]) {
			case IFCConstants._CNORTH:
				iP.y = 0;
				break;
			case IFCConstants._CSOUTH:
				iP.y = mBoardLength - 1;
				break;
			case IFCConstants._CEAST:
				iP.x = mBoardLength - 1;
				break;
			case IFCConstants._CWEST:
				iP.x = 0;
				break;
			default:
				throw new Exception("Invalid direction index: " + iEdge);
		}		
	}
	
	private void findCandidateHoles(int iNumCandidates, int iNumHoles) throws Exception {		
		int largestHoleStart = mBoardLength - kCandidateHoleLength;		
		
		if (largestHoleStart < 0) {
			// board is tiny - no need to spend time sampling for good holes
			int x = (mBoardLength - 1) / 2;
			int y = x;
			for (int i = 0; i < iNumHoles; i++) {
				mHoles.add(new Point(x, y));			
			}
			return;
		}
		
		int x;
		int y;		
		for (int i = 0; i < iNumCandidates; i++) {
			x = mRandom.nextInt(largestHoleStart);
			y = mRandom.nextInt(largestHoleStart);
			int xOffset = mRandom.nextInt(kCandidateHoleLength);
			int yOffset = mRandom.nextInt(kCandidateHoleLength);
						
			Hole h = new Hole(
				new Point(x + xOffset, y + yOffset),
				measureFilledSpace(x, y)
			);
			
			mHoles.add(h);
		}
		
		// sort candidate holes
		Collections.sort(mHoles);
		for (int i = 0; i < iNumHoles; i++) {
			Hole h = (Hole) mHoles.get(i);
			mHoles.set(i, h.mCenter);			
		}
		
		// remove remaining candidates that we no longer care about
		for (int i = iNumCandidates - 1; i >= iNumHoles; i--) {
			mHoles.remove(i);
		}
	}
	
	private int measureFilledSpace(int iX, int iY) throws Exception {
		boolean[][] filled = mRect.filled();
		int[][] colors = mRect.colors();
		float score = 0f;
		
		for (int i = iX; i < iX + kCandidateHoleLength; i++) {
			for (int j = iY; j < iY + kCandidateHoleLength; j++) {
				if (!filled[i][j]) {
					score++;
					
					if (colors[i][j] != mPlayerIndex) {
						score += 0.3;
					}
				}
			}			
		}
		
		return Math.round(score);
	}
	
	private int getOppositeDir(int iDir) {
		return (iDir + 2) % 4;
	}

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

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

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

	private static int indexOfDir(char iDir) {
		switch (iDir) {		
			case IFCConstants._CNORTH:
				return 3;			
			case IFCConstants._CSOUTH:
				return 1;
			case IFCConstants._CEAST:
				return 0;
			case IFCConstants._CWEST:
				return 2;
			default:
				return -1;
		}
	}
	
	private class Bot extends Robot {
		private int mDir;
		private int mCrossBoardDir;
		private int mBoxDir;
		private BotState mState;
		private Point mTargetPoint;		
		private Robot mLastClosestEnemy;
		private int mRoundsClosestPlayerUnchanged;


		/**
		 * @return
		 */
		public int getDir() {
			return mDir;
		}

		/**
		 * @param iI
		 */
		public void setDir(int iI) {
			mDir = iI;
		}
		/**
		 * @return
		 */
		public BotState getState() {
			return mState;
		}

		/**
		 * @param iState
		 */
		public void setState(BotState iState) {
			mState = iState;
		}

		/**
		 * @return
		 */
		public Point getTargetPoint() {
			return mTargetPoint;
		}

		/**
		 * @param iPoint
		 */
		public void setTargetPoint(Point iPoint) {
			mTargetPoint = iPoint;
		}

		/**
		 * @return
		 */
		public int getCrossBoardDir() {
			return mCrossBoardDir;
		}

		/**
		 * @param iI
		 */
		public void setCrossBoardDir(int iI) {
			mCrossBoardDir = iI;
		}

		/**
		 * @param 
		 */
		public void setBoxDir(int iI) {
			mBoxDir = iI;
		}

		
		public Bot(Group6Player1 iOwner, int iX, int iY, char iDir) throws Exception {
			super(iX, iY);
			setDir(indexOfDir(iDir));			
			setPlayerIndex(iOwner.mPlayerIndex);
			mLastClosestEnemy = null;
			mRoundsClosestPlayerUnchanged = 0;		
		}
		
		public void moveToTargetPoint(int iIndex, int mBoardLength, int dirCounter) throws Exception {
		    
		    char dir = IFCConstants._CSOUTH;

		    //System.out.println(dirCounter);
		    if (iIndex % 4 == 0) {
			    dir = IFCConstants._CEAST;
		    } 
		    else if (iIndex % 4 == 1) {
			//top right
			    dir = IFCConstants._CSOUTH;

		    } 
		    else if (iIndex % 4 == 2) {
			//bottom right
			    dir = IFCConstants._CWEST;

		    } 
		    else {
			//bottom left

			    dir = IFCConstants._CNORTH;

		    } 

		    setDir(indexOfDir(dir));			

		}
		
		/**
		 * Robots cannot move diagonally, so Manhattan distance is technically more
		 * correct than Euclidean distance.
		 */
		public int manhattanDistanceTo(int iX, int iY) throws Exception {
			return Math.abs(iX - xpos()) + Math.abs(iY - ypos());
		}
		
		public boolean isBeingChased() throws Exception {			
			Robot closestEnemy = closestEnemyRobot();
			
			if (robotsEqual(closestEnemy, mLastClosestEnemy)) {
				mRoundsClosestPlayerUnchanged++;				
			} else {
				mRoundsClosestPlayerUnchanged = 0;				
			}
			
			mLastClosestEnemy = closestEnemy;
			
			if (mRoundsClosestPlayerUnchanged >= 15 &&
					manhattanDistanceTo(closestEnemy.xpos(), closestEnemy.ypos()) <= 4) {				
				return true;
			}
			
			return false;
		}
		
		private 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());
		}
		
		/**
		 * Routine for determining the closest enemy robot was taken from
		 * OldGroup5Robot.java and refactored to save time. 
		 */
		private Robot closestEnemyRobot() throws Exception {
			Robot[][] allRobots = mRect.allRobots();
			int closest = Integer.MAX_VALUE;
			Robot closestRobot = null;
			
			for (int i = 0; i < allRobots.length; i++) {
				if (i == mPlayerIndex) {
					continue;
				}
				
				for (int j = 0; j < allRobots[i].length; j++) {
					Robot enemyRobot = allRobots[i][j];
					int dist = manhattanDistanceTo(enemyRobot.xpos(), enemyRobot.ypos());
					if (dist < closest) {
						closest = dist;
						closestRobot = enemyRobot;
					}
				}
			}
			
			return closestRobot;
		}
		
		public boolean willCollide() throws Exception {
			switch (kDirs[mDir]) {
				case IFCConstants._CNORTH:
					return ypos() == 0;
				case IFCConstants._CSOUTH:
					return ypos() == (mBoardLength - 1);
				case IFCConstants._CEAST:
					return xpos() == (mBoardLength - 1);
				case IFCConstants._CWEST:
					return xpos() == 0;
				default:
					return false;
			}
		}
		
	}
	
	private static class BotState {
		public static final BotState kBoxMove =
			new BotState("moving in box");
		public static final BotState kMoveAlongEdge =
			new BotState("move along edge");
		
		private final String mDesc;
		
		private BotState(String iDesc) {
			mDesc = iDesc;
		}
		
		public String toString() {
			return mDesc;
		}	
	}
	
	private static class Hole implements Comparable {
		private Point mCenter;
		private int mUnfilledSpace;
		
		public Hole(Point iCenter, int iUnfilledSpace) {
			mCenter = iCenter;
			mUnfilledSpace = iUnfilledSpace;
		}
		
		public int compareTo(Object iO) {
			Hole other = (Hole) iO;
			return other.mUnfilledSpace - mUnfilledSpace;
		}
	}
}
