package system.character;

import java.util.ArrayList;
import java.util.Random;

import graphics.scene.BattleScene;
import system.battle.Coordinate;
import system.battle.Tile;
import system.item.Equipment;
import system.item.skill.Skill;
import system.loaders.DatabaseLoader;

public class Ally extends BaseCharacter {
	/**
	 * Serve pra não dar merda na serialização de objetos.
	 */
	private static final long serialVersionUID = 2021883210062648395L;

	private static final int ARMOR_LIMIT = 4;
	private static final int CONSUMABLE_LIMIT = 5;
	
	private Equipment weapon;
	private Equipment[] armor = new Equipment[ARMOR_LIMIT];
	private Equipment[] consumable = new Equipment[CONSUMABLE_LIMIT];
	
	/**
	 * Inicializa uma unidade aliada aleatória.
	 */
	public Ally() {
		Random random = new Random();
		
		traits.setElementMelee(CharacterTraits.NEUTRAL);
		traits.setElementRanged(CharacterTraits.NEUTRAL);
		traits.setElementNonElemental(CharacterTraits.NEUTRAL);
		traits.setElementFire(CharacterTraits.NEUTRAL);
		traits.setElementIce(CharacterTraits.NEUTRAL);
		traits.setElementThunder(CharacterTraits.NEUTRAL);
		traits.setElementWind(CharacterTraits.NEUTRAL);
		traits.setElementDragon(CharacterTraits.NEUTRAL);
		
		movement = random.nextInt(2) + 3;
		jump = 2;
		
		totalHp = totalHpMax = random.nextInt(50) + 275;
		totalMp = totalMpMax = random.nextInt(10) + 110;
		int attack = random.nextInt(20) + 50;
		int defense = random.nextInt(10) + 20;
		int speed = random.nextInt(20) + 50;
		
		int[] mainP = new int[]{totalHp, totalHpMax, totalMp, totalMpMax, attack, defense, speed};
		double[] secP = new double[]{95, 5, 10, 0, 0};
		
		traits.setMainParameters(mainP);
		traits.setSecondaryParameters(secP);
		traits.clearParametersModifier();
		traits.clearSecondaryParametersModifier();
		traits.clearElementalDefenses();
		
		Skill[] skillsAux = (Skill[]) DatabaseLoader.loadDatabase("skills");
		skills = skillsAux;
		Skill[] skillsAuxNoHealing = new Skill[skillsAux.length - 6];
		
		// Loop bobão pra adicionar apenas as skills ofensivas aos inimigos
		for (int i = 0; i < skillsAux.length; i++) {
			if (!(i >= 8 && i <= 13)) {
				if (i < 8) {
					skillsAuxNoHealing[i] = skillsAux[i];
					skillsAuxNoHealing[i].setUser(this);
				}
				
				if (i > 12) {
					skillsAuxNoHealing[i - 6] = skillsAux[i];
					skillsAuxNoHealing[i - 6].setUser(this);
				}
			}
		}
		
		Skill chosenSkill1 = skillsAuxNoHealing[0];
		Skill chosenSkill2 = skillsAuxNoHealing[random.nextInt(skillsAuxNoHealing.length)];
		Skill chosenSkill3 = skillsAuxNoHealing[random.nextInt(skillsAuxNoHealing.length)];
		Skill chosenSkill4 = skillsAuxNoHealing[random.nextInt(skillsAuxNoHealing.length)];
		
		skills = new Skill[] {chosenSkill1, chosenSkill2, chosenSkill3, chosenSkill4};
		for (Skill skill : skills) {
			skill.setUser(this);
		}
	}
	
	/**
	 * Equipa uma arma neste aliado
	 * @param weapon
	 */
	public void equipWeapon(Equipment weapon) {
		if (this.weapon == null) {
			// Equipa uma arma
			this.weapon = weapon;
			weapon.equip();
		} else {
			// Retira a arma anterior e equipa a nova
			this.weapon.unequip();
			this.weapon = weapon;
			weapon.equip();
		}
	}
	
	public void equipArmor(Equipment armor, int slot) {
		if (this.armor[slot] == null) {
			this.armor[slot] = armor;
			armor.equip();
		} else {
			this.armor[slot].unequip();
			this.armor[slot] = armor;
			armor.equip();
		}
	}
	
	public void equipConsumable(Equipment item, int slot) {
		if (this.consumable[slot] == null) {
			this.consumable[slot] = item;
			item.equip();
		} else {
			this.consumable[slot].unequip();
			this.consumable[slot] = item;
			item.equip();
		}
	}
	
	/**
	 * Use este método toda santa vez que as skills de um Ally forem alteradas.
	 */
	public void updateSkillUser() {
		for (int i = 0; i < skills.length; i++) {
			skills[i].setUser(this);
		}
	}

	@Override
	public void turn(BattleScene battleScene, int asd, int asdd, int asddd, boolean selectedFoeIsInRange) throws InterruptedException {
		int current = battleScene.getBattle().getActiveUnitIndex() + 1;
		int total = battleScene.getBattle().getRoundOrder().length;

		battleScene.attacker.updateText(name);
		battleScene.attackerParameters.updateText(	"HP: " + getTotalHp() + " / " + getTotalHpMax() +
													"\nMP: " + getTotalMp() + " / " + getTotalMpMax());
		battleScene.allyTurnStart(posicao);
		battleScene.turnOrder.updateText(current + " / " + total);
		
		// A posição ocupada por este aliado na matriz de batalha
		Tile thisTile = battleScene.getBattle().getMap().getTiles()[coordinates.getX()][coordinates.getY()];
		
		// Centraliza o cursor da batalha neste aliado, para indicar o início de seu turno
		battleScene.mapCursor.setLocation(thisTile.getSprite().getLocation());
		
		battleScene.setMinhaVez(true);
	}

	/**
	 * 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 de Tiles visitados
		ArrayList<Tile> visited = new ArrayList<Tile>();
		// Lista dos próximos tiles a serem visitados
		ArrayList<Tile> visitedNext = new ArrayList<Tile>(CheckNeighbourTiles(map, coordinates));
		// Lista com todas as posições válidas para movimentação (sem ocupantes)
		ArrayList<Tile> movementRange = new ArrayList<Tile>();
		
		// Preenche a lista movementRange com os elementos válidos
		for (int i = 0; i < movement; i++) {
			ArrayList<Tile> border = new ArrayList<Tile>();
			
			for (Tile tile : visitedNext) {
				ArrayList<Tile> neighbours = new ArrayList<Tile>(CheckNeighbourTiles(map, tile.getCoordinate()));
				 
				for (Tile neighbour : neighbours) {
					int heightDifference = tile.getHeight() - neighbour.getHeight();
					
					if (heightDifference < 0) {
						heightDifference *= -1;
					}
					
					// Se a borda não ter o dito vizinho, adiciona o vizinho a ela.
					if (border.contains(neighbour) == false && heightDifference <= jump) {
						border.add(neighbour);
					}
				}
				
				// Se a lista de posições visitadas não possuem o tile, ele é adicionado a ela.
				if (visited.contains(tile) == false) {
					visited.add(tile);
				}
			}
			
			// Adiciona à lista de posições visitadas os tiles em visitedNext de forma que não haja repetições de um mesmo tile
			for (Tile tile : visited) {
				ArrayList<Tile> neighbours = new ArrayList<Tile>(CheckNeighbourTiles(map, tile.getCoordinate()));
				
				// foreach que adiciona as posições vizinhas contanto que não haja iguais
				for (Tile neighbour : neighbours) {
					if (visitedNext.contains(neighbour) == false) {
						visitedNext.add(neighbour);
					}
				}
			}
		}
		
		// Lista de tiles inválidos a serem removidos de visited
		ArrayList<Tile> invalidTiles = new ArrayList<Tile>();
		
		for (Tile tile : visited) {
			if (tile.getCoordinate() == coordinates || (tile.getOccupant() != null && tile.getOccupant().getClass() == Ally.class)) {
				invalidTiles.add(tile);
			}
		}
		
		// Remove os tiles inválidos de visited
		for (Tile tile : invalidTiles) {
			visited.remove(tile);
		}
		
		// Define o alcance de movimentação como os tiles da lista visited
		movementRange = visited;
		
		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<>();
		
		for (int i = -1; i <= 1; i++) {
			for (int j = -1; j <= 1; j++) {
				if (i != 0 && j != 0) {
					Coordinate auxCoordinate = new Coordinate(coordinate.getX() + i, coordinate.getY() + j);
					
					if ((auxCoordinate.getX() < 0 || auxCoordinate.getX() >= map.length) ||
						(auxCoordinate.getY() < 0 || auxCoordinate.getY() >= map[coordinate.getY()].length)) {
						// break leaves a loop, continue jumps to the next iteration.
						// Fonte: https://stackoverflow.com/questions/462373/difference-between-break-and-continue-statement
						continue;
					}

					int heightDifference1 = map[coordinate.getX()][coordinate.getY()].getHeight() - map[auxCoordinate.getX()][coordinate.getY()].getHeight();
					int heightDifference2 = map[coordinate.getX()][coordinate.getY()].getHeight() - map[coordinate.getX()][auxCoordinate.getY()].getHeight();
					
					// Se as diferenças na altura resultarem em um valor negativo, torne-os positivos
					if (heightDifference1 < 0) {
						heightDifference1 *= -1;
					}
					
					if (heightDifference2 < 0) {
						heightDifference2 *= -1;
					}
					
					/*
					 * A posição é válida se:
					 * (Ela estiver vazia OU (Ela não estiver vazia E não estiver ocupada por uma unidade inimiga)) E heightDifference <= jump
					 */
					if ((map[auxCoordinate.getX()][coordinate.getY()].getOccupant() == null ||
						(map[auxCoordinate.getX()][coordinate.getY()].getOccupant() != null && 
						 map[auxCoordinate.getX()][coordinate.getY()].getOccupant().getClass() != Enemy.class)) &&
						heightDifference1 <= jump) {
						listOfAdjacentTiles.add(map[auxCoordinate.getX()][coordinate.getY()]);
					}
					/*
					 * A posição é válida se:
					 * (Ela estiver vazia OU (Ela não estiver vazia E não estiver ocupada por uma unidade inimiga)) E heightDifference <= jump
					 */
					if ((map[coordinate.getX()][auxCoordinate.getY()].getOccupant() == null ||
						(map[coordinate.getX()][auxCoordinate.getY()].getOccupant() != null && 
						 map[coordinate.getX()][auxCoordinate.getY()].getOccupant().getClass() != Enemy.class)) &&
						heightDifference2 <= jump) {
						listOfAdjacentTiles.add(map[coordinate.getX()][auxCoordinate.getY()]);
					}
				}
			}	
		}
		
		return listOfAdjacentTiles;
	}
	
	// TODO Netgames daqui pra baixo -------------------------------------------------------------------
	
	private boolean minhaVez;

	public boolean isMinhaVez() {
		return minhaVez;
	}

	public void setMinhaVez(boolean vez) {
		this.minhaVez = vez;
	}
	
	
}
