package system.character;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Random;

import graphics.scene.BattleScene;
import system.battle.Coordinate;
import system.battle.Map;
import system.battle.Tile;
import system.item.skill.Skill;

/**
 * O BaseCharacter dispõe de parâmetros e métodos básicos para um combatente de uma batalha.
 * */
public class BaseCharacter implements Serializable {
	/**
	 * Serve pra não dar merda na serialização de objetos.
	 */
	private static final long serialVersionUID = 9043742250621357100L;
	
	/**
	 * Identificador do BaseCharacter
	 */
	protected int id;
	/**
	 * Valor de movimentação horizontal deste BaseCharacter
	 */
	protected int movement;
	/**
	 * Valor de movimentação vertical deste BaseCharacter
	 */
	protected int jump;
	/**
	 * Direção que este BaseCharacter está virado.<br>
	 * Referência:<br>
	 * X 8 X<br>
	 * 4 X 6<br>
	 * X 2 X
	 */
	protected int direction;
	/**
	 * Valor de iniciativa deste BaseCharacter.<br>
	 * Valores menores = age antes.<br>
	 * Valores maiores = age depois
	 */
	protected int initiative;
	
	/**
	 * Hp de batalha
	 */
	protected int totalHp;
	/**
	 * HpMax de batalha
	 */
	protected int totalHpMax;
	/**
	 * Mp de batalha
	 */
	protected int totalMp;
	/**
	 * MpMax de batalha
	 */
	protected int totalMpMax;
	
	// Netgames-------------------------------------
	protected int posicao;
	protected int chosenSkill;
	protected int chosenFoeIndex;
	protected int validChosen;
	protected boolean selectedFoeIsInRange;
	// Netgames-------------------------------------
		
	/**
	 * Gerador de números aleatórios deste personagem.
	 */
	Random random = new Random();
	
	/**
	 * Nome do BaseCharaceter
	 */
	protected String name;

	/**
	 * Traços do personagem como HP, MP ataque e etc. estão todos armazenados aqui.
	 * */
	protected CharacterTraits traits = new CharacterTraits();
	
	/**
	 * Coordenadas em um mapa de batalha
	 */
	protected Coordinate coordinates;
	
	boolean dead = false;
	
	protected Skill[] skills;
	protected Skill action;
	
	/**
	 * Inicializa um BaseCharacter vazio.
	 * */
	public BaseCharacter() {	}
	
	/**
	 * Inicializa um BaseCharacter com parâmetros principais e secundários
	 * @param mainParameters - parâmetros principais como Hp, Mp, etc.
	 * @param secondaryParameters - parâmetros secundários como precisão e evasão.
	 */
	public BaseCharacter(int[] mainParameters, double[] secondaryParameters) {
		traits.setMainParameters(mainParameters);
		traits.setSecondaryParameters(secondaryParameters);
		traits.clearParametersModifier();
		traits.clearSecondaryParametersModifier();
		traits.clearElementalDefenses();
	}
	
	/**
	 * Retorna o modificador de ataque a 100%
	 */
	protected void clearAttackModifier() {
		traits.setAttackModifier(1.0);
	}

	/**
	 * Retorna o modificador de defesa a 100%
	 */
	protected void clearDefenseModifier() {
		traits.setDefenseModifier(1.0);
	}

	/**
	 * Retorna o modificador de precisão a 100%
	 */
	protected void clearAccuracyModifer() {
		traits.setAccuracyModifier(1.0);
	}
	
	/**
	 * Retorna o modificador de evasão a 100%
	 */
	protected void clearEvasionModifer() {
		traits.setEvasionModifier(1.0);
	}
	
	/**
	 *  Inicializa Hp e Mp no máximo
	 */
	public void updateTotalHpMp() {
		totalHp = traits.totalMainParameter(0);
		totalHpMax = traits.totalMainParameter(1);
		totalMp = traits.totalMainParameter(2);
		totalMpMax = traits.totalMainParameter(3);
	}
	
	/**
	 * Incrementa o Hp em amount.
	 * @param amount o valor do incremento
	 */
	public void incrementHp(int amount) {
		int aux = totalHp + amount;
		
		if (aux < 0) {
			aux = 0;
		}
		
		if (aux > totalHpMax) {
			aux = totalHpMax;
		}
		
		totalHp = aux;
	}
	
	/**
	 * Decrementa o Hp em amount.
	 * @param amount - o valor do decremento
	 */
	public void decrementHp(int amount) {
		int aux = totalHp - amount;

		if (aux < 0) {
			aux = 0;
		}
		
		if (aux > totalHpMax) {
			aux = totalHpMax;
		}
		
		totalHp = aux;
	}
	
	/**
	 * Incrementa o Mp em amount.
	 * @param amount - o valor do incremento
	 */
	public void incrementMp(int amount) {
		int aux = totalMp + amount;
		
		if (aux < 0) {
			aux = 0;
		}
		
		if (aux > totalMpMax) {
			aux = totalMpMax;
		}
		
		totalMp = aux;
	}
	
	/**
	 * Decrementa o Mp em amount.
	 * @param amount - o valor do decremento
	 */
	public void decrementMp(int amount) {
		int aux = totalMp - amount;

		if (aux < 0) {
			aux = 0;
		}
		
		if (aux > totalMpMax) {
			aux = totalMpMax;
		}
		
		totalMp = aux;
	}
	
	/**
	 * Recupera/perde um percentual do Hp Máximo
	 */
	public void regenHp() {
		int regenValue = traits.percentageOfMaxHp(traits.totalSecondaryParameter(3));

		if (regenValue != 0) {
			// E se ele fosse extrapolar o limite superior, o valor não extrapola
			if (regenValue > totalHpMax) {
				regenValue = totalHpMax;
			}
			
			incrementHp(regenValue);			
			checkDeathState(); // Se a regeneração for negativa o BaseCharacter pode morrer.
			
		}
	}
	
	/**
	 * Recupera/perde um percentual do Mp Máximo
	 */
	public void regenMp() {
		int regenValue = traits.percentageOfMaxMp(traits.totalSecondaryParameter(4));
		
		if (regenValue != 0) {
			// E se ele fosse extrapolar o limite superior, o valor não extrapola
			if (regenValue > totalMpMax) {
				regenValue = totalMpMax;
			}
			
			incrementMp(regenValue);		
		}
	}
	
	/**
	 * Adiciona a condição negativa id
	 * @param id - identificador da condição
	 */
	public void addAffliction(int id) {
		traits.addAffliction(id); // Primeiro se adiciona um status negativo
		traits.applyAfflictions(); // Depois se soma os efeitos
	}
	
	/**
	 * Remove a condição negativa id
	 * @param id - identificador da condição
	 */
	public void removeAffliction(int id) {
		traits.unapplyAfflictions(); // Primeiro desfaz as alterações nos status
		traits.removeAffliction(id); // Depois se remove
	}
	
	/**
	 * Calcula a iniciativa do personagem com uma fórmula toda cagada que depende do speed.<br>
	 * Se o BaseCharacter NÃO agir NEM mover adicione 50.<br>
	 * Se o BaseCharacter agir OU mover adicione 80.<br>
	 * Se o BaseCharacter agir E mover adicione 100.
	 * @param modifier - modificador da iniciativa
	 * @return a iniciativa deste personagem para esta rodada
	 */
	public void calculateInitiative(int modifier) {
		initiative = modifier - traits.totalMainParameter(6);
	}
	
	/**
	 * Move este BaseCharacter para a posição position. Confia cara, o {@link BaseCharacter} é removido de sua
	 * posição inicial e alocado a sua nova.
	 * @param map o mapa da batalha
	 * @param position a posição escolhida
	 */
	public void moveTo(Map map, Coordinate position) {
		// Primeiro remove este aliado da posição atual dele
		map.removeOccupant(coordinates);
		// Depois o adiciona na posição final
		map.addOccupant(this, position);
		// Seta o estado do tile como 0 pro drawMap() desenhar a porra do chão
		map.getTiles()[position.getX()][position.getY()].state = 0;
	}
	
	/**
	 * Retorna a ArrayList de Skills deste Base character
	 * @return todas as skills que o personagem possua em uma ArrayList
	 */
	public ArrayList<Skill> getSkillList() {
		ArrayList<Skill> skillList = new ArrayList<Skill>();
		
		for (int i = 0; i < skills.length; i++) {
			skillList.add(skills[i]);
		}
		
		return skillList;
	}
	
	/**
	 * Seta a ação como mover ou esperar. Valores válidos: move, wait.
	 * @param action
	 */
	public void setActionMoveOrWait(String action) {
		if (action.equalsIgnoreCase("Move")) {
			Skill move = new Skill(0, action, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, this, null, null, null, null, false, null);
			this.action = move;
		}
		if (action.equalsIgnoreCase("Wait")) {
			Skill wait = new Skill(0, action, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, this, null, null, null, null, false, null);
			this.action = wait;
		}
	}
	
	/**
	 * Termina o turno e permite passar a vez.
	 */
	public void endTurn(BattleScene battle) {
		action = null;
		regenHp();
		regenMp();
		checkDeathState();
	}
	
	/**
	 * Checa a condição de incapacitação do personagem (HP <= 0).<br>
	 * Caso seja verdadeira a condição, limpa a lista de condições e adiciona a definida como a de incapacitação (0),
	 * caso contrário remove a condição de incapacitação (0).
	 * */
	protected void checkDeathState() {
		// Se o HP estiver zerado, aplicar a condição de morte, caso contrário deixe vivo
		if (totalHp <= 0 || traits.hasAffliction(0)) {
			totalHp = 0;
			die();
		}
	}
		
	/**
	 * Cura todo o Hp e Mp e cura todas as condições negativas no personagem.
	 * */
	public void healAll() {
		traits.clearAllAfflictions();
		totalHp = totalHpMax;
		totalMp = totalMpMax;
	}
	
	/**
	 * Remove a condição de incapacitação ao personagem, caso ele a tenha
	 * */
 	public void revive() {
 		if (traits.hasAffliction(0)) {
 			traits.removeAffliction(0);
 		}
	}
	
	/**
	 * Adiciona a condição de incapacitação ao personagem, caso ele não a tenha. A lista [statusAilments]
	 * é limpa de todos as outras condições também.
	 * */
	public void die() {
		traits.clearAllAfflictions();
		traits.addAffliction(0);
		totalHp = 0;
		clearAttackModifier();
		clearDefenseModifier();
		clearAccuracyModifer();
		dead = true;
	}
	
//	public void turn(BattleScene battleScene) throws InterruptedException {
//		
//	}
	
	public void turn(BattleScene battleScene, int chosenSkillReceived, int chosenFoeReceived, int validChosenReceived, boolean selectedFoeIsInRange) throws InterruptedException {
		
	}
	
	/**
	 * Percorre o mapa para encontrar posições válidas conforme os seguintes critérios:<br>
	 * - Só expande até o valor da varável movement<br>
	 * - Se a diferença dos height de tiles vizinhos for maior que jump, não será possível expandir para tais tiles<br>
	 * - Unidades do mesmo tipo (aliadas) não bloqueiam movimentação, mas não são posições válidas para movimentação<br>
	 * - Unidades inimigas bloqueiam a movimentação e não são posições válidas para movimentação
	 * @param map - o mapa da batalha atual
	 * @return a lista de posições válidas para movimentação
	 */
	public ArrayList<Tile> ShowMovementRange(Tile[][] map) {
		// Lista com todas as posições válidas para movimentação (sem ocupantes)
		ArrayList<Tile> movementRange = new ArrayList<Tile>();
		
		return movementRange;
	}
	
//	/**
//	 * Checa os tiles vizinhos e retorna uma ArrayList com as posições válidas.
//	 * @param map - o mapa que este BaseCharacter se encontra
//	 * @param coordinate - coordenada do mapa que será analizada
//	 * @return uma ArrayList com os elementos vizinhos a coordinate
//	 */
//	private ArrayList<Tile> CheckNeighbourTiles(Tile[][] map, Coordinate coordinate) {
//		// Lista de Tiles que será retornada no final das operações
//		ArrayList<Tile> listOfAdjacentTiles = new ArrayList<>();
//		
//		return listOfAdjacentTiles;
//	}
	
	// --------------------------------------------------------------------------------------------
	// Getters e setters
	// --------------------------------------------------------------------------------------------

	public boolean isActive() {
		return traits.isActive();
	}

	public void setActive(boolean active) {
		traits.setActive(active);
	}

	public boolean isRestricted() {
		return traits.isRestricted();
	}

	public void setRestricted(boolean restricted) {
		traits.setRestricted(restricted);
	}

	public boolean isAutoBattle() {
		return traits.isAutoBattle();
	}

	public void setAutoBattle(boolean autoBattle) {
		traits.setAutoBattle(autoBattle);
	}
	
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}

	public Coordinate getCoordinate() {
		return coordinates;
	}

	public void setCoordinates(Coordinate coordinates) {
		this.coordinates = coordinates;
	}

	public int getMovement() {
		return movement;
	}

	public void setMovement(int movement) {
		this.movement = movement;
	}

	public int getJump() {
		return jump;
	}

	public void setJump(int jump) {
		this.jump = jump;
	}

	public CharacterTraits getTraits() {
		return traits;
	}
	
	public int getInitiative() {
		return initiative;
	}

	public int getDirection() {
		return direction;
	}

	public void setDirection(int direction) {
		this.direction = direction;
	}

	public int getId() {
		return id;
	}

	public int getTotalHp() {
		return totalHp;
	}

	public int getTotalHpMax() {
		return totalHpMax;
	}

	public int getTotalMp() {
		return totalMp;
	}

	public int getTotalMpMax() {
		return totalMpMax;
	}
	
	public Skill getAction() {
		return action;
	}

	public void setAction(Skill action) {
		this.action = action;
	}

	public boolean isDead() {
		return dead;
	}
	
	// Gambiarra ----------------------------------------
	
	public void setBaseCharacter(BaseCharacter baseCharacter) {
		traits = baseCharacter.getTraits();
		
		dead = baseCharacter.isDead();
		id = baseCharacter.getId();
		initiative = baseCharacter.getInitiative();
		skills = baseCharacter.skills;
		totalHp = baseCharacter.getTotalHp();
		totalHpMax = baseCharacter.getTotalHpMax();
		totalMp = baseCharacter.getTotalMp();
		totalMpMax = baseCharacter.getTotalMpMax();
		
		setCoordinates(baseCharacter.getCoordinate());
		setJump(baseCharacter.getJump());
		setMovement(baseCharacter.getMovement());
		setName(baseCharacter.getName());
	}

	public int getChosenSkill() {
		return chosenSkill;
	}

	public int getChosenFoeIndex() {
		return chosenFoeIndex;
	}

	public int getValidChosen() {
		return validChosen;
	}
	
	public int getPosicao() {
		return posicao;
	}

	public void setPosicao(int posicao) {
		this.posicao = posicao;
	}

	public Skill[] getSkills() {
		return skills;
	}
}
