package biz.plexers.sparrow.mp;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import biz.plexers.sparrow.core.InBattleShipAttribute;
import biz.plexers.sparrow.core.Player;
import biz.plexers.sparrow.core.Ship;
import biz.plexers.sparrow.core.UpgradableShipAttribute;
import biz.plexers.sparrow.core.UserManager;
import biz.plexers.sparrow.db.Arggg;
import biz.plexers.sparrow.db.DbHelper;
import biz.plexers.sparrow.db.DbManager;
import biz.plexers.sparrow.mp.Action.Choices;
import biz.plexers.sparrow.mp.exceptions.InsufficientCrewException;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

@JsonSerialize(using = Battle.Serializer.class)
public class Battle extends Arggg {
	private Player player1, player2;
	private History history;

	private Battle() {
		history = new History();
		player1 = UserManager.getUser();
		UserManager.getShip().engageBattle();
	}

	public static Battle getInstance() throws TimeoutException {
		Battle res = new Battle();
		DbManager.save(res);
		try {
			return DbManager.waitForChanges(res, Battle.class, 30,
					TimeUnit.SECONDS);
		} catch (TimeoutException e) {
			DbManager.delete(res);
			throw new TimeoutException("No player joined the game!");
		}
	}

	public Battle submitTurnAndWaitForOpponent(Turn turn)
			throws TimeoutException, InsufficientCrewException {
		history.pushTurn(turn);
		Battle newBattle = submitAndWait();
		applyResult(turn);
		return newBattle;

	}

	public Battle submitAndWait() throws TimeoutException {
		DbManager.save(this);
		Battle newBattle;
		try {
			newBattle = DbManager.waitForChanges(this, Battle.class, 30,
					TimeUnit.SECONDS);
		} catch (TimeoutException e) {
			throw new TimeoutException("Your opponent did not respond in time!");
		}

		return newBattle;
	}

	public void applyResult(Turn turn) {
		Ship myShip;
		Ship enemyShip;
		if (this.history.amIplayer1()){
			myShip = this.player1.getPirate().getShip();
			enemyShip = this.player2.getPirate().getShip();
		}
		else{
			myShip = this.player2.getPirate().getShip();
			enemyShip = this.player1.getPirate().getShip();
		}
		
		int attackCrew = turn.actions.get(Choices.AttackUsingCannons).getAssignedCrew();
		applyAttackAction(myShip, enemyShip, attackCrew);
		int repairCrew = turn.actions.get(Choices.RepairShip).getAssignedCrew();
		applyRepairAction(myShip, enemyShip, repairCrew);
		int LoadCrew = turn.actions.get(Choices.LoadCannons).getAssignedCrew();
		applyLoadCannonsAction(myShip, enemyShip, LoadCrew);
		
				
	}
	
	private void applyLoadCannonsAction(Ship myShip, Ship enemyShip,int loadCrew) {
		// TODO Auto-generated method stub
		
	}

	private void applyRepairAction(Ship myShip, Ship enemyShip, int repairCrew) {
		// TODO Auto-generated method stub
		
	}

	private void applyAttackAction(Ship myShip, Ship enemyShip, int attackCrew){
		double firedCannons = Math.min(attackCrew, myShip.getInBattleShipAttributeValue(InBattleShipAttribute.Choices.LoadedCannons));
		myShip.changeInBattleShipAttributeBy(InBattleShipAttribute.Choices.LoadedCannons, -firedCannons);
		double enemyArmor= enemyShip.getUpgradableShipAttributeValue(UpgradableShipAttribute.Choices.Armor);
		int damageTaken= (int) Math.floor(firedCannons*(100/(100+enemyArmor)*(1.1-Math.random()*0.2)));
		enemyShip.changeInBattleShipAttributeBy(InBattleShipAttribute.Choices.DamageTaken, damageTaken);
		
		double crewWoundChance = damageTaken / enemyShip.getUpgradableShipAttributeValue(UpgradableShipAttribute.Choices.Health);
		int enemyCrew = (int) Math.floor(enemyShip.getUpgradableShipAttributeValue(UpgradableShipAttribute.Choices.Crew));
		int enemyWoundedCrew =  (int) Math.floor(enemyShip.getInBattleShipAttributeValue(InBattleShipAttribute.Choices.WoundedCrew));
		int newlyWounded= 0;
		for(int i=enemyWoundedCrew; i<= enemyCrew; i++){
			if (Math.random()<= crewWoundChance) newlyWounded++;
		}
		enemyShip.changeInBattleShipAttributeBy(InBattleShipAttribute.Choices.WoundedCrew, newlyWounded);		
	}

	public void addPlayer() {
		UserManager.getShip().engageBattle();
		Player p = UserManager.getUser();
		if (player1 == null) {
			player1 = p;
		} else if (player2 == null) {
			player2 = p;
		}
	}

	private Battle(Map<String, Object> props) {
		super(props);
		Object objHistory = props.get("history");
		history = DbHelper.mapAsObject(objHistory, History.class);
		Object objPlayer1 = props.get("player1");
		player1 = DbHelper.mapAsObject(objPlayer1, Player.class);
		Object objPlayer2 = props.get("player2");
		player2 = DbHelper.mapAsObject(objPlayer2, Player.class);
	}

	@JsonCreator
	public static Battle factory(Map<String, Object> props) {
		return new Battle(props);
	}

	public static class Serializer extends JsonSerializer<Battle> {

		@Override
		public void serialize(Battle value, JsonGenerator jgen,
				SerializerProvider provider) throws IOException,
				JsonProcessingException {
			jgen.writeStartObject();

			value.superSerialize(jgen);
			jgen.writeObjectField("history", value.history);
			jgen.writeObjectField("player1", value.player1);
			jgen.writeObjectField("player2", value.player2);

			jgen.writeEndObject();

		}

	}
}

@@ -3029,46 +3029,321 @@
 %0D%0A%09%09
-// TODO Auto-generated method stub%0D%0A%09%09
+int newlyLoaded = (int) Math.min(loadCrew, myShip.getUpgradableShipAttributeValue(UpgradableShipAttribute.Choices.Cannons) %0D%0A%09%09%09%09- myShip.getInBattleShipAttributeValue(InBattleShipAttribute.Choices.LoadedCannons));%0D%0A%09%09myShip.changeInBattleShipAttributeBy(InBattleShipAttribute.Choices.LoadedCannons, newlyLoaded);
 %0D%0A%09%7D

Implement applyLoadCannonsAction