package jp.ac.nii.icpc2010.manager;

import java.awt.Color;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

public class OptionsManager implements IOptionSource {
	private boolean _loggingOn = false;
	
	private boolean tournamentMode;// tournament mode: do not show display and terminate game after it ends
	
	public boolean isTournamentMode() {
		return tournamentMode;
	}


	private int numOfPlayers;
	int maxSteps;//number of max step for one round
	private boolean lastmanStanding;//if true game ends when one lastman is fixed, otherwise game ends when lastman falls

	private String openingName;
	private String levelName;
	private Vector<String> playerClasses;
	private String scoreClass;

	private String recordFile;
	private String playbackFile;
	
	private boolean trailLimit;
	private boolean backbufferEnabled;
	private boolean drawOnlyChanges;

	private int trailLimitAmount;

	private String graphicSet;
	private String[] backgroundImages;
	private String wallImage;
	private String[] tronImages; 
	private Color[] tronColors;
	private String coinImage;
	private Color boardBgColor;
	private Color counterColor;
	private Color scoreColor;
	private Color commentColor;
	private boolean tronHeadImage;
	private String[] tronHeadImages;
	private String[] tronTrailCornerImages;
	
	
	private boolean displayQuote;

	private int gameRounds;
	private int openingTime;
	private TimeoutAction timeoutAction;
	private long displayInterval;
	private int cellWidth;
	private int cellHeight;
	private double itemInitProb;
	private double itemRegenProb;
	
	private int commentLine;
	private long initTimeout;
	private long runTimeout;
	private long turnTimeslot;
	
	private String redTitle = null;
	private String blueTitle = null;
	
	public static void main(String... args) {
		OptionsManager om = new OptionsManager();

		System.out.println(om.getLevelName());

		for (String s : om.playerClasses)
			System.out.println(s);
	}
	
	
	public OptionsManager() {
		this(null);
	}
	
	public OptionsManager(String[] commandLineArgs) {
		loadOptions();
		if (commandLineArgs != null) {
			overrideWithCommandLine(commandLineArgs);
		}
	}

	Document doc;
	XPathFactory xfactory; 
	XPath xpath;
	
	private void loadOptions() {

		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(true); 
		DocumentBuilder builder;
		try {
			builder = factory.newDocumentBuilder();
			doc = builder.parse("config.xml");
			xfactory = XPathFactory.newInstance();
			xpath = xfactory.newXPath();

			tournamentMode = false;
			
			numOfPlayers = overWrite(2, "//Player/@num");			
			
			openingName = overWrite("opening", "//Opening/@name");			
			playerClasses = xpathQuery("//Player/@className");
			scoreClass = overWrite("SurvivalScore", "//Score/@className");

			/** GRAPHICS OPTIONS */
			graphicSet = overWrite("Flat", "//UseSet/@set");		
			
			backgroundImages = overWrite(new String[]{"flat/black.png"}, graphicProperty("Background/@imageName"), true);
			wallImage = overWrite("flat/grey.png", graphicProperty("Wall/@imageName"));
			tronImages = overWrite(new String[]{"flat/red.png", "flat/blue.png", "flat/green.png", "flat/magenta.png"}, graphicProperty("Tron/@imageName"), false);
			tronColors = parseColors(overWrite(new String[]{"RED", "BLUE", "GREEN", "MAGENTA"}, graphicProperty("Tron/@color"), false));
			coinImage = overWrite("flat/yellow.png", graphicProperty("Coin/@imageName"));
			boardBgColor = parseColor(overWrite("BLACK", graphicProperty("Board/@bgColor")).toLowerCase());
			counterColor = parseColor(overWrite("GREEN", graphicProperty("Board/@counterColor")).toLowerCase());
			scoreColor = parseColor(overWrite("WHITE", graphicProperty("Board/@scoreColor")).toLowerCase());
			commentColor = parseColor(overWrite("ORANGE", graphicProperty("Board/@commentColor")).toLowerCase());
			tronHeadImage = ("on".compareTo(overWrite("off", "//Display/@tronHeadImage")) == 0) ? true : false;
			tronTrailCornerImages = overWrite(new String[]{"flat/red.png", "flat/blue.png", "flat/green.png", "flat/magenta.png"}, graphicProperty("TronTrailCorner/@imageName"), false);
			if(tronHeadImage)
			{
				tronHeadImages = overWrite(new String[]{"flat/red.png", "flat/blue.png", "flat/green.png", "flat/magenta.png"}, graphicProperty("TronHead/@imageName"), false);
			}
			
			
			/** GLOBAL LEVEL OPTIONS */			
			runTimeout = overWrite(100l, "//Timeout/@run");
			initTimeout = overWrite(1000l, "//Timeout/@init");
			turnTimeslot = overWrite(250l, "//Turn/@timeslot");
			cellWidth = overWrite(16, "//Display/@width");
			cellHeight = overWrite(16, "//Display/@height");
			itemInitProb = overWrite(1.0, "//Item/@initProb");
			itemRegenProb = overWrite(0.01, "//Item/@regenProb");
			trailLimit = Boolean.parseBoolean(overWrite("true", "//Trail/@limit"));
			trailLimitAmount = overWrite(25, "//Trail/@amount");
			maxSteps = overWrite(0, "//Game/@maxSteps");
			lastmanStanding = Boolean.parseBoolean(overWrite("true", "//Game/@lastmanStanding"));
			gameRounds = overWrite(2, "//Game/@rounds");
			
			
			/** LEVEL OPTIONS 
			/* At this point the options may already have been read once, but we give the level a chance to
			 * override them.
			 */
			
			//potentially set by command line
			if (levelName == null) {
				levelName = overWrite("level01.txt", "//UseLevel/@level");
			}
			runTimeout = overWrite(runTimeout, levelProperty("Timeout/@run"));
			initTimeout = overWrite(initTimeout, levelProperty("Timeout/@init"));
			turnTimeslot = overWrite(turnTimeslot, levelProperty("Turn/@timeslot"));
			cellWidth = overWrite(cellWidth, levelProperty("Display/@width"));
			cellHeight = overWrite(cellHeight, levelProperty("Display/@height"));
			itemInitProb = overWrite(itemInitProb, levelProperty("Item/@initProb"));
			itemRegenProb = overWrite(itemRegenProb, levelProperty("Item/@regenProb"));
			trailLimit = Boolean.parseBoolean(overWrite(trailLimit + "", levelProperty("Trail/@limit")));
			trailLimitAmount = overWrite(trailLimitAmount, levelProperty("Trail/@amount"));
			maxSteps = overWrite(maxSteps, levelProperty("Game/@maxSteps"));
			lastmanStanding = Boolean.parseBoolean(overWrite(lastmanStanding + "", levelProperty("Game/@lastmanStanding")));
			gameRounds = overWrite(gameRounds, levelProperty("Game/@rounds"));

			
			/** OTHER OPTIONS
			 * 
			 */
			displayQuote = Boolean.parseBoolean(overWrite("false", "//Display/@displayQuote"));

			
			openingTime = overWrite(1000, "//Display/@openingTime");
			timeoutAction = TimeoutAction.valueOf(overWrite("Last", "//Timeout/@action"));			
			
			displayInterval = overWrite(100l, "//Display/@interval");
			
			commentLine = overWrite(20, "//Display/@commentLine");
			backbufferEnabled = Boolean.parseBoolean(overWrite("true", "//Display/@backbufferEnabled"));
			drawOnlyChanges = Boolean.parseBoolean(overWrite("false", "//Display/@drawOnlyChanges"));
			
		
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		// The following codes are commented out because small time slot is useful for player tests.
		//if (turnTimeslot < 2 * runTimeout + 50) {
		//	turnTimeslot = 2 * runTimeout + 50;
		//	System.out.println("TurnTimeslot is too small compared to runTimeout. Adjusting to: " + turnTimeslot);
		//}

	}
	
	private String graphicProperty(String name) {
		return "//GraphicSet[@name='" + graphicSet + "']/" + name;
	}
	
	private String levelProperty(String name) {
		return "//Level[@name='" + levelName + "']/" + name;
	}
	

	private Vector<String> xpathQuery(String query) throws Exception {

		Vector<String> result = new Vector<String>();
		XPathExpression expr = xpath.compile(query);
		Object xresult = expr.evaluate(doc, XPathConstants.NODESET);
		NodeList nodes = (NodeList) xresult;

		for (int i = 0; i < nodes.getLength(); i++) 
			result.add(nodes.item(i).getNodeValue()); 

		return result;
	}

	private String[] overWrite(String[] oldStrings, String expr, boolean shrink) throws Exception{
		Vector<String> newStrings = xpathQuery(expr);
		if(newStrings.size() == 0){
			return oldStrings;
		}else if(shrink || newStrings.size() > oldStrings.length){
			return newStrings.toArray(new String[newStrings.size()]);
		}else{
			for(int i = 0; i < newStrings.size(); i++){
				oldStrings[i] = newStrings.get(i);
			}
			return oldStrings;
		}
	}
	
	private String overWrite(String oldString, String expr) throws Exception{
		Vector<String> newStrings = xpathQuery(expr);
		if(newStrings.size() == 0){
			return oldString;
		}else{
			return newStrings.get(0);
		}
	}
	
	private int overWrite(int oldInt, String expr) throws Exception {
		return Integer.parseInt(overWrite(oldInt + "", expr));
	}
	
	private double overWrite(double oldDouble, String expr) throws Exception {
		return Double.parseDouble(overWrite(oldDouble + "", expr));
	}
	
	private long overWrite(long oldLong, String expr) throws Exception {
		return Long.parseLong(overWrite(oldLong + "", expr));
	}
	
	private Color parseColor(String str) throws Exception{
		if(str.charAt(0) == '#'){
			return Color.decode(str);
		}else{
			Object o = null;
			o = Class.forName("java.awt.Color").getDeclaredField(str).get(null);
			if (o instanceof Color){
				return (Color)o;
			}else{
				return Color.BLACK;
			}
		}
	}
	private Color[] parseColors(String[] strs) throws Exception{
		Color[] colors = new Color[strs.length];
		for(int i = 0; i < strs.length; i++){
			colors[i] = parseColor(strs[i]);
		}
		return colors;
	}
	/** 
	 * Read options from the command line, overriding those from the XML file as appropriate.
	 * @param args
	 */
	public void overrideWithCommandLine(String[] args) {
		boolean gotPlayerClasses = false;
		Vector<String> myPlayerClasses = new Vector<String>();

		for (int i = 0; i < args.length; ++i) {
			if (args[i].equals("-level")) {
				assert(args.length >= i+2);
				levelName = args[i+1];
				i++;
			} else if (args[i].equals("-playerClass")) {
				assert(args.length >= i+2);
				gotPlayerClasses = true;
				myPlayerClasses.add(args[i+1]);
				i++;
			} else if (args[i].equals("-record")) {
				assert(args.length >= i+2);
				recordFile = args[i+1];
				i++;
			} else if (args[i].equals("-playback")) {
				assert(args.length >= i+2);
				playbackFile = args[i+1];
				i++;
			} else if (args[i].equals("-cmdMode")) {
				tournamentMode = true;
			} else if (args[i].equals("-redTitle")) {
				assert(args.length >= i+2);
				redTitle = args[i+1];
				i++;
			} else if (args[i].equals("-blueTitle")) {
				assert(args.length >= i+2);
				blueTitle = args[i+1];
				i++;
			} else if (args[i].equals("-time")) {
				assert (args.length >= i+2);
				int val = Integer.parseInt(args[i+1]);
				turnTimeslot = val;
				displayInterval = val;
				i++;
			}
		}

		if (gotPlayerClasses) {
			playerClasses = myPlayerClasses;
		}
	}

	public String getOpeningName() {
		return openingName;
	}

	public String getLevelName() {
		return levelName;
	}

	/**
	 * File to record the game to, or null if none was set
	 * @return
	 */
	public String getRecordFile() {
		return recordFile;
	}
	
	/**
	 * File to playback the game from, or null if none was set
	 * @return
	 */
	public String getPlaybackFile() {
		return playbackFile;
	}
	
	public Vector<String> getPlayerClasses() {
		return playerClasses;
	}

	
	public boolean isLoggingOn() {
		return _loggingOn;
	}

	public String[] getBackgroundImageName() {
		return backgroundImages;
	}

	public String getWallImageName() {
		return wallImage;
	}

	public String getTronImageName(int playerId){
		return tronImages[playerId % tronImages.length];
	}
	
	public String getTronHeadImageName(int playerId)
	{
		if(tronHeadImage)
		{
			return tronHeadImages[playerId % tronHeadImages.length];
		}
		else
		{
			return "";
		}
	}
	
	public String getTronTrailCornerImageName(int playerId)
	{
		return tronTrailCornerImages[playerId % tronTrailCornerImages.length];
	}

	public String getCoinImageName() {
		return coinImage;
	}

	public Color getBoardBgColor() {
		return boardBgColor;
	}
	public Color getCounterColor() {
		return counterColor;
	}
	public Color getScoreColor() {
		return scoreColor;
	}
	public Color getCommentColor() {
		return commentColor;
	}
	
	public boolean isTronHeadImage()
	{
		return tronHeadImage;
	}
	
	public boolean isDisplayQuote()
	{
		return displayQuote;
	}
	
	public Color getTronColor(int playerId){
		return tronColors[playerId % tronColors.length];
	}

	public int getGameRounds(){
		return gameRounds;
	}
	public TimeoutAction getTimeoutAction(){
		return timeoutAction;
	}
	public long getInitTimeout(){
		return initTimeout;
	}
	public long getRunTimeout(){
		return runTimeout;
	}
	public long getTurnTimeslot(){
		return turnTimeslot;
	}
    public void setTurnTimeslot(long timeslot){
        this.turnTimeslot = timeslot;
    }
	public long getDisplayInterval(){
		return displayInterval;
	}

	public int getCellWidth() {
		return cellWidth;
	}
	public int getCellHeight() {
		return cellHeight;
	}

	public int getTrailLimitAmount() {
		return trailLimitAmount;
	}

	public boolean hasTrailLimit() {
		return trailLimit;
	}

	public int getOpeningTime() {

		return openingTime;
	}

	public String getScoreClass() {
		return scoreClass;
	}

	public int getNumOfPlayers() {
		return numOfPlayers;
	}
	
	public int getMaxTurns()
	{
		return maxSteps;
	}
	
	public boolean isLastmanStanding()
	{
		return lastmanStanding;
	}

	public double getItemInitProb() {
		return itemInitProb;
	}
	public double getItemRegenProb() {
		return itemRegenProb;
	}
	public int getCommentLines() {
		return commentLine;
	}

	public boolean getBackbufferEnabled() {
		return backbufferEnabled;
	}

	public boolean getDrawOnlyChanges() {
		return drawOnlyChanges;
	}
	
	public String getRedTitle() {
		return redTitle;
	}
	
	public String getBlueTitle() {
		return blueTitle;
	}
}
