package star;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.Random;

/**
 * Simulates combat.
 * @author SC
 * @date September 12, 2020
 *
 */
public class Simulate {
	
	/*--------------------------------------------------------------*/
	/*----------------        Initialization        ----------------*/
	/*--------------------------------------------------------------*/
	
	/**
	 * Code entrance from the command line.
	 * @param args Command line arguments
	 */
	public static void main(String[] args){
		
		//Create an instance of this class
		Simulate x=new Simulate(args);
		
		//Run the object
		x.process();
	}
	
	/**
	 * Constructor.
	 * @param args Command line arguments
	 */
	public Simulate(String[] args){
		parse(args);
		assert(sideA.size()>0) : "No ships on side A";
		assert(sideB.size()>0) : "No ships on side B";
	}
	
	/*--------------------------------------------------------------*/
	/*----------------    Initialization Helpers    ----------------*/
	/*--------------------------------------------------------------*/
	
	/** Parse arguments from the command line */
	private void parse(String[] args){
		for(String s : args){
			parseTerm(s);
		}
	}
	
	void parseTerm(String term){
		if(term==null || term.length()<1 || term.charAt(0)=='#'){return;}
		
		int mult=1;
		if(Character.isDigit(term.charAt(0))){
			int x=term.indexOf('x');
			if(x<0){x=term.indexOf('X');}
			if(x>0){
				mult=Integer.parseInt(term.substring(0, x));
				term=term.substring(x+1);
			}
		}
		
		String[] split=term.split("=");
		String a=split[0].toLowerCase();
		String b=split.length>1 ? split[1] : null;
		
		if(term.startsWith("ship:")){
			Ship sh=new Ship(term.substring("ship:".length()));
			if(sh.shipName==null){sh.shipName=sh.shipClass;}
			ArrayList<Ship> list=(sh.side=='A' ? sideA : sideB);
			list.add(sh);
			for(int i=1; i<mult; i++){
				Ship sh2=sh.copy();
				sh2.shipName+=(i+1);
				list.add(sh2);
			}
		}else if(term.startsWith("weapon:")){
			Weapon.makeWeapon(term.substring("weapon:".length()));
		}else if(a.equalsIgnoreCase("verbose")){
			Weapon.verbose=verbose=parseBoolean(b);
		}else if(a.equalsIgnoreCase("printShips")){
			printShips=parseBoolean(b);
		}else if(a.equalsIgnoreCase("useFullName") || a.equalsIgnoreCase("printFullName") || a.equalsIgnoreCase("FullName")){
			Weapon.useFullName=parseBoolean(b);
		}else if(a.equalsIgnoreCase("useShortName") || a.equalsIgnoreCase("printShortName") || a.equalsIgnoreCase("ShortName")){
			Weapon.useFullName=false;
		}else if(a.equalsIgnoreCase("printShots")){
			Weapon.printShots=parseBoolean(b);
		}else if(a.equalsIgnoreCase("preventArmorDestuction")){
			Weapon.preventArmorDestuction=parseBoolean(b);
		}else if(a.equalsIgnoreCase("preventTorpedoes") || a.equalsIgnoreCase("preventMissiles")){
			Weapon.preventTorpedoes=parseBoolean(b);
		}else{
			assert(new File(term).exists()) : "Unknown term '"+term+"'";
			readFile(term);
		}
	}
	
	void readFile(String fname){
		File f=new File(fname);
		assert(f.exists()) : "Can't read "+fname;

		try {
			BufferedReader br=new BufferedReader(new FileReader(f)); 
			for(String line=br.readLine(); line!=null; line=br.readLine()){ 
				parseTerm(line);
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	} 
	
	/*--------------------------------------------------------------*/
	/*----------------         Outer Methods        ----------------*/
	/*--------------------------------------------------------------*/
	
	void process(){
		long start=System.nanoTime();
		
		if(verbose || printShips){
//			printWeapons();
			printShips();
		}
		
		long maxRounds=1000000;
		long rounds=processInner(maxRounds);
		
		int dif=sideA.size()-sideB.size();
		
		outstream.println("\nFinished in "+String.format("%.2f seconds (simulated)", rounds/100.0));
		
		if(rounds>=maxRounds){
			outstream.println("Stalemate.");
		}else if(dif==0){
			outstream.println("Tie.");
		}else{
			outstream.println("Side "+(dif>0 ? "A" : "B")+" wins.");
		}

		outstream.println();
		printSide(sideA, sideA_dead, 'A');
		outstream.println();
		printSide(sideB, sideB_dead, 'B');
		
		long elapsed=System.nanoTime()-start;
		outstream.println(String.format("\nTime: %.3f ms", elapsed/1000000.0));
	}
	
	/*--------------------------------------------------------------*/
	/*----------------         Inner Methods        ----------------*/
	/*--------------------------------------------------------------*/
	
	void printWeapons(){

		outstream.println("Weapons: "+Weapon.map.keySet());
		for(Entry<String, Weapon> e : Weapon.map.entrySet()){
			outstream.println(e.getKey()+" -> "+e.getValue());
		}
	}
	
	void printShips(){
		if(sideA.size()>0){
			outstream.println("Side A live: ");
			for(Ship s : sideA){outstream.println(s);}
			outstream.println();
		}

		if(sideA_dead.size()>0){
			outstream.println("Side A dead: ");
			for(Ship s : sideA_dead){outstream.println(s);}
			outstream.println();
		}

		if(sideB.size()>0){
			outstream.println("Side B live: ");
			for(Ship s : sideB){outstream.println(s);}
			outstream.println();
		}

		if(sideB_dead.size()>0){
			outstream.println("Side B dead: ");
			for(Ship s : sideB_dead){outstream.println(s);}
			outstream.println();
		}
	}
	
	long processInner(long maxRounds){
		long round=0;
		while(round<maxRounds && !sideA.isEmpty() && !sideB.isEmpty()){
			processCenti(round);
			round++;
		}
		return round;
	}
	
	void processCenti(long centi){
		shotsThisTurn=0;
		processSide(sideA, sideB);
		processSide(sideB, sideA);
		cullSide(sideA, sideA_dead);
		cullSide(sideB, sideB_dead);
		if(verbose){
			if(centi%10==0){
				outstream.println("\n\n*****   T = "+String.format("%.2fs", centi*0.01)+"   *****\n");
				printShips();
			}
		}else if(Weapon.printShots && shotsThisTurn>0){
			outstream.println("\n\n*****   T = "+String.format("%.2fs", centi*0.01)+"   *****\n");
		}
		
	}
	
	void processSide(ArrayList<Ship> liveA, ArrayList<Ship> liveB){
		for(Ship a : liveA){
			int indexB=randy.nextInt(liveB.size());
			Ship b=liveB.get(indexB);
			a.process(b);
		}
	}
	
	void cullSide(ArrayList<Ship> live, ArrayList<Ship> dead){
		for(int i=0; i<live.size(); i++){
			Ship a=live.get(i);
			if(a.hull<=0){
				live.remove(i);
				i--;
				dead.add(a);
			}
		}
	}
	
	void printSide(ArrayList<Ship> live, ArrayList<Ship> dead, char side){
		outstream.println("Side "+Character.toString(side)+":");
		outstream.println("Class\tName\tHull\tArmor\tFlux\tShield\tVent\tWeapons");
		for(Ship s : live){
			outstream.println(summary(s));
		}
		for(Ship s : dead){
			outstream.println(summary(s));
		}
	}
	
	private String summary(Ship s){
		return s.shipClass+"\t"+s.shipName+"\t"+
				String.format("%.1f\t%.1f\t%.1f", s.hull, s.armor, s.flux())+
				(s.hasShield ? "\tT" : "\tF")+(s.canVent ? "\tT" : "\tF")+
				"\t"+listWeapons(s);
	}
	
	private String listWeapons(Ship s){
		StringBuilder sb=new StringBuilder();
		LinkedHashMap<String, Integer> map=new LinkedHashMap<String, Integer>();
		for(Weapon w : s.weapons){
			String name=(w.name2!=null && !Weapon.useFullName ? w.name2 : w.name);
			Integer count=map.get(name);
			map.put(name, count==null ? 1 : count+1);
		}
		for(Entry<String, Integer> e : map.entrySet()){
			String name=e.getKey();
			int count=e.getValue();
			if(sb.length()>0){sb.append(',');}
			if(count>1){sb.append(count).append('x');}
			assert(count>=1) : name+", "+count;
			sb.append(name);
		}
		return sb.toString();
	}
	
	/*--------------------------------------------------------------*/
	/*----------------           Helpers            ----------------*/
	/*--------------------------------------------------------------*/
	
	/**
	 * Parse this argument.  More liberal than Boolean.parseBoolean.
	 * Null, t, true, or 1 all yield true.
	 * Everything else, including the String "null", is false.
	 * @param s Argument to parse
	 * @return boolean form
	 */
	public static boolean parseBoolean(String s){
		if(s==null || s.length()<1){return true;}
		if(s.length()==1){
			char c=Character.toLowerCase(s.charAt(0));
			return c=='t' || c=='1';
		}
		if(s.equalsIgnoreCase("null") || s.equalsIgnoreCase("none")){return false;}
		return Boolean.parseBoolean(s);
	}
	
	/*--------------------------------------------------------------*/
	/*----------------            Fields            ----------------*/
	/*--------------------------------------------------------------*/

	public ArrayList<Ship> sideA=new ArrayList<Ship>();
	public ArrayList<Ship> sideB=new ArrayList<Ship>();

	public ArrayList<Ship> sideA_dead=new ArrayList<Ship>();
	public ArrayList<Ship> sideB_dead=new ArrayList<Ship>();
	
	Random randy=new Random();

	public static boolean printShips=false;
	public static int shotsThisTurn=0;
	
	/*--------------------------------------------------------------*/
	/*----------------        Common Fields         ----------------*/
	/*--------------------------------------------------------------*/
	
	private PrintStream outstream=System.out;
	public static boolean verbose=false;
	
}
