package system.character;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.Timer;

import br.ufsc.inf.leobr.cliente.Jogada;
import br.ufsc.inf.leobr.cliente.exception.NaoJogandoException;
import graphics.scene.BattleScene;
import system.battle.Coordinate;
import system.battle.Tile;
import system.item.skill.Skill;
import system.loaders.DatabaseLoader;
import system.managers.Battle;
import system.managers.JogadaNetGames;

public class Enemy extends BaseCharacter implements Jogada {

	private static final long serialVersionUID = -5732577619496822995L;

	public Enemy(String name, int movement, int jump) {
		this.name = name;
		this.movement = movement;
		this.jump = jump;
	}
	
	/**
	 * Inicializa uma unidade inimiga aleatória.
	 */
	public Enemy() {
		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) + 75;
		totalMp = totalMpMax = random.nextInt(10) + 10;
		int attack = random.nextInt(20) + 20;
		int defense = random.nextInt(10) + 10;
		int speed = random.nextInt(20) + 20;
		
		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 = 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) {
					skills[i] = skillsAux[i];
					skills[i].setUser(this);
				}
				
				if (i > 12) {
					skills[i - 6] = skillsAux[i];
					skills[i - 6].setUser(this);
				}
			}
		}
		
		// Se não tiver esse loop bobão o inimigo não funciona e eu não sei porque
	}
	
	/**
	 * A bosta da "integência artificial" começa aqui
	 * @param battleScene - A porra da batalha do caralho
	 * @throws InterruptedException Joga essa porra de exceção na sua mãe programa bosta do caralho
	 */
	@Override
	public void turn(	BattleScene battleScene, int chosenSkillReceived, int chosenFoeReceived, int validChosenReceived,
						boolean selFoeIsInRange) throws InterruptedException {
		// Se não tá morto, faz alguma coisa
		if (!dead) {
			try {
				battleScene.getSceneManager().getProxy().enviaJogada(new JogadaNetGames(
						battleScene.getBattle().getRoundOrder(), battleScene.getBattle().getRoundOrder(), false, -1, -1, -1, coordinates.getX(), coordinates.getY(), 0,
						null, null,
						-1, -1, -1, -1, 
						false, null, null,
						false, null,
						false, null,
						false, battleScene.getBattle().getActiveUnitIndex(), false));
			} catch (NaoJogandoException e1) {
				e1.printStackTrace();
			}
			
			
			int current = battleScene.getBattle().getActiveUnitIndex() + 1;
			int total = battleScene.getBattle().getRoundOrder().length;
			
			// A porra da batalha
			Battle battle = battleScene.getBattle();

			// Escreve o nome do inimigo na caixa de atacantes
			battleScene.attacker.updateText(name);
			battleScene.attackerParameters.updateText(	"HP: " + getTotalHp() + " / " + getTotalHpMax() +
														"\nMP: " + getTotalMp() + " / " + getTotalMpMax());
			battleScene.turnOrder.updateText(current + " / " + total);
			
			// A posição ocupada por este inimigo na matriz de batalha
			Tile thisTile = battle.getMap().getTiles()[coordinates.getX()][coordinates.getY()];
			
			// Centraliza o cursor da batalha neste inimigo, para indicar o início de seu turno
			battleScene.mapCursor.setLocation(thisTile.getSprite().getLocation());
			
			// A porra do mapa
			Tile[][] battleMap = battle.getMap().getTiles();
			
			// Geradeiro de númeiro aleateiro
			Random random = new Random();
			
			chosenSkill = random.nextInt(skills.length);					// TODO AQUI
			chosenFoeIndex = random.nextInt(battle.getAllies().length);		// TODO AQUI
			
			// Netgames ----------------------------------------------
			if (chosenSkillReceived > -1) {
				chosenSkill = chosenSkillReceived;
			}
			
			if (chosenFoeReceived > -1) {
				chosenFoeIndex = chosenFoeReceived;
			}
			// Netgames ----------------------------------------------
			
			int effectiveRange = movement + skills[chosenSkill].getMaxRange();
			
			BaseCharacter chosenFoe = battle.getAllies()[chosenFoeIndex];
			
			ArrayList<Tile> effRange = getEffRange(battle.getMap().getTiles(), coordinates, effectiveRange);
			ArrayList<Tile> moveRange = ShowMovementRange(battleMap);
			
			// Se o alvo estiver travado, seleciona a primeira skill da lista, Ataque de SnS
			if (moveRange.size() == 0) {
				chosenSkill = 0;
			}
			
//			this.selectedFoeIsInRange = selFoeIsInRange; // selectedFoeIsInRange = false;// TODO Qualquer coisa descomentar aqui
			boolean selectedFoeIsInRange = false;
			
//			battleScene.showAttackRange(effRange);
			
			// Checa se o alvo está no alcance eficaz deste inimigo
			for (Tile tile : effRange) {
				if (tile.getOccupant() != null) {
					if (tile.getOccupant().name == chosenFoe.name) {
						selectedFoeIsInRange = true;// TODO Qualquer coisa descomentar aqui
						break;
					}					
				}
			}			
			
			// Checa se o alvo está no alcance eficaz do inimigo após este selecionar uma skill.
			if (selectedFoeIsInRange) {// TODO tirar o this em caso de merda
				
				System.out.println("AQUI 3 -------------------------");
				
				ArrayList<Tile> skillRange = skills[chosenSkill].showSkillRange(battleMap);
				ArrayList<Tile> radiusRange = skills[chosenSkill].showRadius(battleMap, chosenFoe.getCoordinate());
				ArrayList<Tile> affected = new ArrayList<Tile>();
				
				// Adiciona todos os alvos afetados pelo raio da skill a uma lista para ser utilizado posteriormente
				for (Tile tile : radiusRange) {
					if (tile.getOccupant() != null) {
						affected.add(tile);
					}
				}
				
				// A posição alvo inicialmente é a posição atual do inimigo
				int targetX = getCoordinate().getX();
				int targetY = getCoordinate().getY();
				
				// Lista de posições válidas para a movimentação
				ArrayList<Tile> validPositions = new ArrayList<Tile>();
				
				// Adiciona a posição atual para a lista de posições válidas a se mover
				moveRange.add(thisTile);
				
				// Para cada tile válido para movimentação checa se o alcance da skill selecionada possui a posição do alvo selecionado
				for (Tile tile : moveRange) {
					ArrayList<Tile> tileSkillRange = skills[chosenSkill].showSkillRange(battleMap, tile.getCoordinate());
					
					for (Tile tile2 : tileSkillRange) {
						if (tile2.getCoordinate().getX() == chosenFoe.getCoordinate().getX() &&
							tile2.getCoordinate().getY() == chosenFoe.getCoordinate().getY()) {
							validPositions.add(tile);
							break;
						}
					}
				}
				
				validChosen = random.nextInt(validPositions.size());					// TODO AQUI
				
				// Netgames ----------------------------------------------
				if (validChosenReceived > -1) {
					validChosen = validChosenReceived;					
				}
				// Netgames ----------------------------------------------
				
				targetX = validPositions.get(validChosen).getCoordinate().getX();
				targetY = validPositions.get(validChosen).getCoordinate().getY();
				
				System.out.println("TARGET: "+targetX + " " + targetY + " | CURRENT: "+getCoordinate().getX() +" "+getCoordinate().getY());
				
				// Se a moveRange por maior que 1, isto é, se o inimigo não estiver travado a movimentação segue normalmente
				Coordinate position = new Coordinate(targetX, targetY);
				
				// Conversão pra vetor, só pra não encher muito meu saco na execução
				BaseCharacter[] targets = new BaseCharacter[affected.size()];
				
				for (int i = 0; i < affected.size(); i++) {
					targets[i] = affected.get(i).getOccupant();
				}
				
				Skill theFuckingSkill = skills[chosenSkill];
				
				// Executa a porra toda aqui
				commandActExecuteHits(battleScene, targets, skillRange, radiusRange, theFuckingSkill, position);
				
				System.out.println("Sou o " + name + " e escolhi a skill " + chosenSkillReceived + ", o alvo " + chosenFoeReceived +
						" e a posição válida de índice " + validChosenReceived +". Alvo estava no alcance " + selectedFoeIsInRange);
				
				// TODO COLOCAR JOGADA NETGAMES AQUI
			}
			// Caso contrário se move de maneira aleatória
			else {
				// Se move aleatoriamente
				moveAction(battleScene);				// TODO AQUI
				
				// Passa o turno
				ActionListener mover = new ActionListener() {
					@Override
					public void actionPerformed(ActionEvent e) {
						try {
							battleScene.passTurnBattleScene();
						} catch (InterruptedException e1) {
							e1.printStackTrace();
						}
					}
				};
				
				Timer timerMover = new Timer(3300, mover);
				timerMover.setRepeats(false);
				timerMover.start();
				
				// TODO COLOCAR JOGADA NETGAMES AQUI PRA ENVIAR
			}
		}		
		// Tá morto, só passa o turno
		else {
			battleScene.passTurnBattleScene();
			
			// TODO COLOCAR JOGADA NETGAMES AQUI PRA ENVIAR
			// TODO Pra funcionar um dos jogadores tem que computar as jogadas e enviar ara ambos
		}
		
		
	}
	
	/**
	 * Faz o inimigo levar 3 segundos para se mover no mapa.<br>
	 * Primeiro ele mostra seu alcance de movimentação, depois a posição desejada e então se move até ela.
	 * @param battle - A batalha em que o inimigo pode se movimentar
	 * @throws InterruptedException
	 */
	private void moveAction(BattleScene battle) throws InterruptedException {
		final ArrayList<Tile> moveRange = ShowMovementRange(battle.getBattle().getMap().getTiles());
		final Tile enemyPosition = battle.getBattle().getMap().getTiles()[coordinates.getX()][coordinates.getY()];
		int randomPosition = random.nextInt(moveRange.size());

		ActionListener mostrar = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				battle.showMovementRange(moveRange);
				battle.drawMap(moveRange, enemyPosition);
				System.out.println("MOSTRA");
				
				try {
					battle.getSceneManager().getProxy().enviaJogada(new JogadaNetGames(
							battle.getBattle().getRoundOrder(), battle.getBattle().getRoundOrder(), false, -1, -1, -1, coordinates.getX(), coordinates.getY(), 1,
							battle.extractCoordinates(moveRange), coordinates,
							-1, -1, -1, -1, 
							false, null, null,
							false, null,
							false, null,
							false, battle.getBattle().getActiveUnitIndex(), false));
				} catch (NaoJogandoException e1) {
					e1.printStackTrace();
				}
			}
		};
		
		Timer parte1 = new Timer(1000, mostrar);
		parte1.setRepeats(false);
		parte1.start();
		
		ActionListener apontar = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				int x = randomPosition;

				battle.mapCursor.setLocation(moveRange.get(x).getSprite().getLocation());
				System.out.println("MOVE");
				
				try {
					battle.getSceneManager().getProxy().enviaJogada(new JogadaNetGames(
							battle.getBattle().getRoundOrder(), battle.getBattle().getRoundOrder(), false, -1, -1, -1, moveRange.get(x).getCoordinate().getX(), moveRange.get(x).getCoordinate().getY(), 1, 
							battle.extractCoordinates(moveRange), coordinates,
							-1, -1, -1, -1, 
							false, null, null,
							false, null,
							false, null,
							false, battle.getBattle().getActiveUnitIndex(), false));
				} catch (NaoJogandoException e1) {
					e1.printStackTrace();
				}
			}
		};
		
		Timer parte2 = new Timer(2000, apontar);
		parte2.setRepeats(false);
		parte2.start();
		
		Enemy enemy = this;
		
		ActionListener moverDeFato = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {				
				battle.hideRange(moveRange);
				
				int x = randomPosition;
				
				battle.getBattle().getMap().removeOccupant(coordinates);
				battle.getBattle().getMap().addOccupant(enemy, moveRange.get(x).getCoordinate());
				battle.drawMap(moveRange, enemyPosition);

				System.out.println("PASSA");
				
				try {
					battle.getSceneManager().getProxy().enviaJogada(new JogadaNetGames(
							battle.getBattle().getRoundOrder(), battle.getBattle().getRoundOrder(), false, -1, -1, -1, coordinates.getX(), coordinates.getY(), 0, 
							battle.extractCoordinates(moveRange), coordinates,
							-1, -1, -1, -1, 
							false, null, null,
							false, null,
							false, null,
							false, battle.getBattle().getActiveUnitIndex(), false));
				} catch (NaoJogandoException e1) {
					e1.printStackTrace();
				}
			}
		};
		
		Timer parte3 = new Timer(3000, moverDeFato);
		parte3.setRepeats(false);
		parte3.start();
	}
		
	/**
	 * Faz o inimigo levar 3 segundos para se mover no mapa.<br>
	 * Primeiro ele mostra seu alcance de movimentação, depois a posição desejada e então se move até ela.
	 * @param battle - A batalha em que o inimigo pode se movimentar
	 * @throws InterruptedException
	 */
	public void moveAction(BattleScene battle, Coordinate position) throws InterruptedException {
		final ArrayList<Tile> moveRange = ShowMovementRange(battle.getBattle().getMap().getTiles());
		final Tile enemyPosition = battle.getBattle().getMap().getTiles()[coordinates.getX()][coordinates.getY()];

		ActionListener mostrar = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				battle.showMovementRange(moveRange);
				battle.drawMap(moveRange, enemyPosition);
				System.out.println("MOSTRA");
			}
		};
		
		Timer parte1 = new Timer(1000, mostrar);
		parte1.setRepeats(false);
		parte1.start();
		
		ActionListener apontar = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				int x = 0;
				
				for (int i = 0; i < moveRange.size(); i++) {
					if (moveRange.get(i).getCoordinate().getX() == position.getX() &&
						moveRange.get(i).getCoordinate().getY() == position.getY()) {
						x = i;
						break;
					}
				}

				battle.mapCursor.setLocation(moveRange.get(x).getSprite().getLocation());
				System.out.println("MOVE");
			}
		};
		
		Timer parte2 = new Timer(2000, apontar);
		parte2.setRepeats(false);
		parte2.start();
		
		Enemy enemy = this;
		
		ActionListener moverDeFato = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {				
				battle.hideRange(moveRange);
				
				int x = 0;
				
				for (int i = 0; i < moveRange.size(); i++) {
					if (moveRange.get(i).getCoordinate().getX() == position.getX() &&
						moveRange.get(i).getCoordinate().getY() == position.getY()) {
						x = i;
						break;
					}
				}
				
				battle.getBattle().getMap().removeOccupant(coordinates);
				battle.getBattle().getMap().addOccupant(enemy, moveRange.get(x).getCoordinate());
				battle.drawMap(moveRange, enemyPosition);

				System.out.println("PASSA");
			}
		};
		
		Timer parte3 = new Timer(3000, moverDeFato);
		parte3.setRepeats(false);
		parte3.start();
	}
	
	public void commandActExecuteHits(BattleScene battleScene, BaseCharacter[] targets, ArrayList<Tile> skillRange, ArrayList<Tile> radiusRange, Skill skill, Coordinate position) throws InterruptedException {
		final ArrayList<Tile> moveRange = ShowMovementRange(battleScene.getBattle().getMap().getTiles());
		final Tile enemyPosition = battleScene.getBattle().getMap().getTiles()[coordinates.getX()][coordinates.getY()];
		
//		final int targetTotalHp = targets[0].getTotalHp();
//		final int targetTotalHpMax = targets[0].getTotalHpMax();
		
		System.out.println("ENEMY Command Act Execute Hits");
		// Mostra a skill range
		// Mostra a radius range
		// Mostra os alvos
		// Executa os alvos
		// Esconde tudo
		// Passa a vez

		// Se a moveRange for maior que 1, isto é, se o inimigo não estiver travado a movimentação segue normalmente
		if (moveRange.size() > 1) {
			ActionListener mostrar = new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					battleScene.showMovementRange(moveRange);
					battleScene.drawMap(moveRange, enemyPosition);
					System.out.println("MOSTRA");
					
					try {
						battleScene.getSceneManager().getProxy().enviaJogada(new JogadaNetGames(
								battleScene.getBattle().getRoundOrder(), battleScene.getBattle().getRoundOrder(), false, -1, -1, -1, coordinates.getX(), coordinates.getY(), 1,
								battleScene.extractCoordinates(moveRange), coordinates,
								-1, -1, -1, -1, 
								false, null, null,
								false, null,
								false, null,
								false, battleScene.getBattle().getActiveUnitIndex(), false));
					} catch (NaoJogandoException e1) {
						e1.printStackTrace();
					}// TODO AQUI
				}
			};
			
			Timer parte1 = new Timer(1000, mostrar);
			parte1.setRepeats(false);
			parte1.start();
			
			ActionListener apontar = new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					int x = 0;
					
					for (int i = 0; i < moveRange.size(); i++) {
						if (moveRange.get(i).getCoordinate().getX() == position.getX() &&
								moveRange.get(i).getCoordinate().getY() == position.getY()) {
							x = i;
							break;
						}
					}
					
					battleScene.mapCursor.setLocation(moveRange.get(x).getSprite().getLocation());
					System.out.println("MOVE");
					
					try {
						battleScene.getSceneManager().getProxy().enviaJogada(new JogadaNetGames(
								battleScene.getBattle().getRoundOrder(), battleScene.getBattle().getRoundOrder(), false, -1, -1, -1, position.getX(), position.getY(), 1,
								battleScene.extractCoordinates(moveRange), position,
								-1, -1, -1, -1, 
								false, null, null,
								false, null,
								false, null,
								false, battleScene.getBattle().getActiveUnitIndex(), false));
					} catch (NaoJogandoException e1) {
						e1.printStackTrace();
					}// TODO AQUI
				}
			};
			
			Timer parte2 = new Timer(2000, apontar);
			parte2.setRepeats(false);
			parte2.start();
			
			Enemy enemy = this;
			
			ActionListener moverDeFato = new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {				
					battleScene.hideRange(moveRange);
					
					int x = 0;
					
					for (int i = 0; i < moveRange.size(); i++) {
						if (moveRange.get(i).getCoordinate().getX() == position.getX() &&
							moveRange.get(i).getCoordinate().getY() == position.getY()) {
							x = i;
							break;
						}
					}
					
					battleScene.getBattle().getMap().removeOccupant(coordinates);
					battleScene.getBattle().getMap().addOccupant(enemy, moveRange.get(x).getCoordinate());
					battleScene.drawMap(moveRange, enemyPosition);
					
					System.out.println("PASSA");
					
					try {
						battleScene.getSceneManager().getProxy().enviaJogada(new JogadaNetGames(
								battleScene.getBattle().getRoundOrder(), battleScene.getBattle().getRoundOrder(), false, -1, -1, -1, coordinates.getX(), coordinates.getY(), 0,
								battleScene.extractCoordinates(moveRange), coordinates,
								-1, -1, -1, -1, 
								false, null, null,
								false, null,
								false, null,
								false, battleScene.getBattle().getActiveUnitIndex(), false));
					} catch (NaoJogandoException e1) {
						e1.printStackTrace();
					}// TODO AQUI
				}
			};
			
			Timer parte3 = new Timer(3000, moverDeFato);
			parte3.setRepeats(false);
			parte3.start();
			/////////////////////////////////////////////////////////
			Timer skillRangeTimer = new Timer(4000, new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					ArrayList<Tile> teste = skill.showSkillRange(battleScene.getBattle().getMap().getTiles(), position);
					battleScene.showAttackRange(teste);
					
					try {
						battleScene.getSceneManager().getProxy().enviaJogada(new JogadaNetGames(
								battleScene.getBattle().getRoundOrder(), battleScene.getBattle().getRoundOrder(), false, -1, -1, -1, position.getX(), position.getY(), 2,
								battleScene.extractCoordinates(teste), position,
								-1, -1, -1, -1, 
								false, null, null,
								false, null,
								false, null,
								false, battleScene.getBattle().getActiveUnitIndex(), false));
					} catch (NaoJogandoException e1) {
						e1.printStackTrace();
					}// TODO AQUI
				}
			});
			skillRangeTimer.setRepeats(false);
			skillRangeTimer.start();
			/////////////////////////////////////////////////////////
			Timer radiusRangeTimer = new Timer(5000, new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					battleScene.showRadiusRange(radiusRange);
					
					try {
						battleScene.getSceneManager().getProxy().enviaJogada(new JogadaNetGames(
								battleScene.getBattle().getRoundOrder(), battleScene.getBattle().getRoundOrder(), false, -1, -1, -1, targets[0].getCoordinate().getX(), targets[0].getCoordinate().getY(), 3,
								battleScene.extractCoordinates(radiusRange), position,
								-1, -1, -1, -1, 
								false, null, null,
								false, null,
								false, null,
								false, battleScene.getBattle().getActiveUnitIndex(), false));
					} catch (NaoJogandoException e1) {
						e1.printStackTrace();
					}// TODO AQUI
				}
			});
			radiusRangeTimer.setRepeats(false);
			radiusRangeTimer.start();
			/////////////////////////////////////////////////////////
			Timer showStuffTimer = new Timer(6000, new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					battleScene.defenderParameters.updateText(	"HP: " + targets[0].getTotalHp() + " / " + targets[0].getTotalHpMax() +
							"\nMP: " + targets[0].getTotalMp() + " / " + targets[0].getTotalMpMax());
					battleScene.defender.updateText(targets[0].getName());
					battleScene.defender.show();
					
					try {
						battleScene.getSceneManager().getProxy().enviaJogada(new JogadaNetGames(
								battleScene.getBattle().getRoundOrder(), battleScene.getBattle().getRoundOrder(), false, -1, -1, -1, targets[0].getCoordinate().getX(), targets[0].getCoordinate().getY(), 3,
								battleScene.extractCoordinates(radiusRange), position,
								-1, -1, -1, -1, 
								true, targets[0].getName(), "HP: " + targets[0].getTotalHp() + " / " + targets[0].getTotalHpMax() +
															"\nMP: " + targets[0].getTotalMp() + " / " + targets[0].getTotalMpMax(),
								false, null,
								false, null,
								false, battleScene.getBattle().getActiveUnitIndex(), false));
					} catch (NaoJogandoException e1) {
						e1.printStackTrace();
					}// TODO AQUI
				}
			});
			showStuffTimer.setRepeats(false);
			showStuffTimer.start();
			/////////////////////////////////////////////////////////
			// [alvo][dano e hits]
			int[][] skillExecutionResults = skill.executeSkill(targets);

			Timer executeTimer = new Timer(7000, new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					ArrayList<Tile> teste = skill.showSkillRange(battleScene.getBattle().getMap().getTiles(), position);
					
					battleScene.hideRange(teste);
					battleScene.hideRange(radiusRange);
					
					// Atualiza a caixa de texto do primeiro alvo apenas
					battleScene.defenderParameters.updateText(	"HP: " + targets[0].getTotalHp() + " / " + targets[0].getTotalHpMax() +
							"\nMP: " + targets[0].getTotalMp() + " / " + targets[0].getTotalMpMax());
					battleScene.defender.updateText(targets[0].getName());
					
					int lower = (skill.getStaticValue() * (100 - skill.getVariability())) / 100;
					int higher = (skill.getStaticValue() * (100 + skill.getVariability())) / 100;
					int numberOfHits = skill.getNumberOfHits();
					
					String accuracy = "Precisão: " + skill.getSkillAccuracy() + "%";
					String damage = "Dano: " + lower + "~" + higher + "    (x" + numberOfHits + ")";
					
					battleScene.accuracyAndDamage.updateText(accuracy + "\n" + damage);
					battleScene.accuracyAndDamage.show();
					
					try {
						battleScene.getSceneManager().getProxy().enviaJogada(new JogadaNetGames(
								battleScene.getBattle().getRoundOrder(), battleScene.getBattle().getRoundOrder(), false, -1, -1, -1, position.getX(), position.getY(), 0,
								battleScene.extractCoordinates(radiusRange), position,
								-1, -1, -1, -1, 
								true, targets[0].getName(), "HP: " + targets[0].getTotalHp() + " / " + targets[0].getTotalHpMax() +
															"\nMP: " + targets[0].getTotalMp() + " / " + targets[0].getTotalMpMax(),
								false, null,
								false, null,
								false, battleScene.getBattle().getActiveUnitIndex(), false));
					} catch (NaoJogandoException e1) {
						e1.printStackTrace();
					}// TODO AQUI
					
					try {
						battleScene.getSceneManager().getProxy().enviaJogada(new JogadaNetGames(
								battleScene.getBattle().getRoundOrder(), battleScene.getBattle().getRoundOrder(), false, -1, -1, -1, position.getX(), position.getY(), 0,
								battleScene.extractCoordinates(teste), position,
								-1, -1, -1, -1, 
								true, targets[0].getName(), "HP: " + targets[0].getTotalHp() + " / " + targets[0].getTotalHpMax() +
															"\nMP: " + targets[0].getTotalMp() + " / " + targets[0].getTotalMpMax(),
								true, accuracy + "\n" + damage,
								false, null,
								false, battleScene.getBattle().getActiveUnitIndex(), false));
					} catch (NaoJogandoException e1) {
						e1.printStackTrace();
					}// TODO AQUI
				}
			});
			executeTimer.setRepeats(false);
			executeTimer.start();
			/////////////////////////////////////////////////////////
			String notification = "";
			
			for (int i = 0; i < skillExecutionResults.length; i++) {
				notification += targets[i].getName() + " recebeu " + skillExecutionResults[i][0] + " de dano e " + skillExecutionResults[i][1] + " golpes.\n";
							
				if (targets[i].getTotalHp() == 0) {
					targets[i].die();
					radiusRange.get(i).setOccupant(null);
					
					battleScene.drawMap();

					System.out.println("MORREU " + targets[i].getName());
				}
			}
			// info tem apenas 3 linhas pra escrever 66 caracteres
			battleScene.info.updateText(notification);
			battleScene.info.hide();
			
			Timer hideTimer = new Timer(10000, new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					battleScene.defenderParameters.hide();
					battleScene.defender.hide();
					battleScene.accuracyAndDamage.hide();
					
					battleScene.info.show();
					
					///////////////////////////////////////////////////////////////
					
					String notf = "";
					
					for (int i = 0; i < skillExecutionResults.length; i++) {
						notf += targets[i].getName() + " recebeu " + skillExecutionResults[i][0] + " de dano e " + skillExecutionResults[i][1] + " golpes.\n";
									
						if (targets[i].getTotalHp() == 0) {
							targets[i].die();
							radiusRange.get(i).setOccupant(null);
							
							battleScene.drawMap();

							System.out.println("MORREU " + targets[i].getName());
						}
					}
					
					try {
						battleScene.getSceneManager().getProxy().enviaJogada(new JogadaNetGames(
								battleScene.getBattle().getRoundOrder(), battleScene.getBattle().getRoundOrder(), false, -1, -1, -1, position.getX(), position.getY(), 0,
								battleScene.extractCoordinates(radiusRange), position,
								-1, -1, -1, -1, 
								false, null, null,
								false, null,
								true, notf,
								false, battleScene.getBattle().getActiveUnitIndex(), false));
					} catch (NaoJogandoException e1) {
						e1.printStackTrace();
					}// TODO AQUI
					///////////////////////////////////////////////////////////////
				}
			});
			hideTimer.setRepeats(false);
			hideTimer.start();
			/////////////////////////////////////////////////////////
			// Parte de passar o turno
			ActionListener passarTurno = new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					try {
						battleScene.passTurnBattleScene();
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
				}
			};
			
			Timer timerPassar = new Timer(10500, passarTurno);
			timerPassar.setRepeats(false);
			timerPassar.start();
			/////////////////////////////////////////////////////////
			// Pra esconder a info após 5 segundos
			Timer infoTimer = new Timer(15000, new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					battleScene.info.hide();
				}
			});
			infoTimer.setRepeats(false);
			infoTimer.start();
			
			// Se os aliados ganharem
			if (battleScene.getBattle().victory()) {
				String victoryString =  "\n\n\n\n\n\n\n\n\n\n\n\n"
						+ "                                                                                                                     "
						+ "Vitória\n"
						+ "                                                                                   "
						+ "Feche a janela para iniciar uma nova partida";
				
				System.out.println("-----------------------VITORY-----------------------");
				battleScene.battleResults.updateText(victoryString);
				battleScene.battleResults.show();
				
				Runnable methodStandTheFuckBy = () -> battleScene.commandStandby();
				battleScene.battleCommands.getOptions()[0].setExecuteMethod(methodStandTheFuckBy);
				battleScene.battleCommands.getOptions()[1].setExecuteMethod(methodStandTheFuckBy);
				battleScene.battleCommands.getOptions()[2].setExecuteMethod(methodStandTheFuckBy);
			}
			
			// Se os inimigos ganharem
			if (battleScene.getBattle().defeat()) {
				String defeatString =  "\n\n\n\n\n\n\n\n\n\n\n\n"
						+ "                                                                                                                     "
						+ "Derrota\n"
						+ "                                                                                   "
						+ "Feche a janela para iniciar uma nova partida";
				
				System.out.println("-----------------------DEFEAT-----------------------");
				battleScene.battleResults.updateText(defeatString);
				battleScene.battleResults.show();
				
				Runnable methodStandTheFuckBy = () -> battleScene.commandStandby();
				battleScene.battleCommands.getOptions()[0].setExecuteMethod(methodStandTheFuckBy);
				battleScene.battleCommands.getOptions()[1].setExecuteMethod(methodStandTheFuckBy);
				battleScene.battleCommands.getOptions()[2].setExecuteMethod(methodStandTheFuckBy);
			}
		}
		// Aqui é o caso em que a movimentação do inimigo está bloqueada, então ele só age
		else {
			// delay menor --------------------------------------------------------------------------------------
			System.out.println("Delay menor");
			
			Timer skillRangeTimer = new Timer(1000, new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					ArrayList<Tile> teste = skill.showSkillRange(battleScene.getBattle().getMap().getTiles(), position);
					battleScene.showAttackRange(teste);
				}
			});
			skillRangeTimer.setRepeats(false);
			skillRangeTimer.start();
			
			/////////////////////////////////////////////////////////
			Timer radiusRangeTimer = new Timer(2000, new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					battleScene.showRadiusRange(radiusRange);
				}
			});
			radiusRangeTimer.setRepeats(false);
			radiusRangeTimer.start();
			/////////////////////////////////////////////////////////
			Timer showStuffTimer = new Timer(3000, new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					battleScene.defenderParameters.updateText(	"HP: " + targets[0].getTotalHp() + " / " + targets[0].getTotalHpMax() +
							"\nMP: " + targets[0].getTotalMp() + " / " + targets[0].getTotalMpMax());
					battleScene.defender.updateText(targets[0].getName());
					battleScene.defender.show();
				}
			});
			showStuffTimer.setRepeats(false);
			showStuffTimer.start();
			/////////////////////////////////////////////////////////
			// [alvo][dano e hits]
			int[][] skillExecutionResults = skill.executeSkill(targets);

			Timer executeTimer = new Timer(4000, new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					ArrayList<Tile> teste = skill.showSkillRange(battleScene.getBattle().getMap().getTiles(), position);
					
					battleScene.hideRange(teste);
					battleScene.hideRange(radiusRange);
					
					// Atualiza a caixa de texto do primeiro alvo apenas
					battleScene.defenderParameters.updateText(	"HP: " + targets[0].getTotalHp() + " / " + targets[0].getTotalHpMax() +
							"\nMP: " + targets[0].getTotalMp() + " / " + targets[0].getTotalMpMax());
					battleScene.defender.updateText(targets[0].getName());
					
					int lower = (skill.getStaticValue() * (100 - skill.getVariability())) / 100;
					int higher = (skill.getStaticValue() * (100 + skill.getVariability())) / 100;
					int numberOfHits = skill.getNumberOfHits();
					
					String accuracy = "Precisão: " + skill.getSkillAccuracy() + "%";
					String damage = "Dano: " + lower + "~" + higher + "    (x" + numberOfHits + ")";
					
					battleScene.accuracyAndDamage.updateText(accuracy + "\n" + damage);
					battleScene.accuracyAndDamage.show();
				}
			});
			executeTimer.setRepeats(false);
			executeTimer.start();
			/////////////////////////////////////////////////////////
			String notification = "";
			
			for (int i = 0; i < skillExecutionResults.length; i++) {
				notification += targets[i].getName() + " recebeu " + skillExecutionResults[i][0] + " de dano e " + skillExecutionResults[i][1] + " golpes.\n";
							
				if (targets[i].getTotalHp() == 0) {
					targets[i].die();
					radiusRange.get(i).setOccupant(null);
					
					battleScene.drawMap();

					System.out.println("MORREU " + targets[i].getName());
				}
			}
			// info tem apenas 3 linhas pra escrever 66 caracteres
			battleScene.info.updateText(notification);
			battleScene.info.hide();
			
			Timer hideTimer = new Timer(7000, new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					battleScene.defenderParameters.hide();
					battleScene.defender.hide();
					battleScene.accuracyAndDamage.hide();
					
					battleScene.info.show();
				}
			});
			hideTimer.setRepeats(false);
			hideTimer.start();
			/////////////////////////////////////////////////////////
			// Parte de passar o turno
			ActionListener passarTurno = new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					try {
						battleScene.passTurnBattleScene();
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
				}
			};
			
			Timer timerPassar = new Timer(7500, passarTurno);
			timerPassar.setRepeats(false);
			timerPassar.start();
			/////////////////////////////////////////////////////////
			// Pra esconder a info após 5 segundos
			Timer infoTimer = new Timer(12000, new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					battleScene.info.hide();
				}
			});
			infoTimer.setRepeats(false);
			infoTimer.start();
			
			// Se os aliados ganharem
			if (battleScene.getBattle().victory()) {
				String victoryString =  "\n\n\n\n\n\n\n\n\n\n\n\n"
						+ "                                                                                                                     "
						+ "Vitória\n"
						+ "                                                                                   "
						+ "Feche a janela para iniciar uma nova partida";
				
				System.out.println("-----------------------VITORY-----------------------");
				battleScene.battleResults.updateText(victoryString);
				battleScene.battleResults.show();
				
				Runnable methodStandTheFuckBy = () -> battleScene.commandStandby();
				battleScene.battleCommands.getOptions()[0].setExecuteMethod(methodStandTheFuckBy);
				battleScene.battleCommands.getOptions()[1].setExecuteMethod(methodStandTheFuckBy);
				battleScene.battleCommands.getOptions()[2].setExecuteMethod(methodStandTheFuckBy);
			}
			
			// Se os inimigos ganharem
			if (battleScene.getBattle().defeat()) {
				String defeatString =  "\n\n\n\n\n\n\n\n\n\n\n\n"
						+ "                                                                                                                     "
						+ "Derrota\n"
						+ "                                                                                   "
						+ "Feche a janela para iniciar uma nova partida";
				
				System.out.println("-----------------------DEFEAT-----------------------");
				battleScene.battleResults.updateText(defeatString);
				battleScene.battleResults.show();
				
				Runnable methodStandTheFuckBy = () -> battleScene.commandStandby();
				battleScene.battleCommands.getOptions()[0].setExecuteMethod(methodStandTheFuckBy);
				battleScene.battleCommands.getOptions()[1].setExecuteMethod(methodStandTheFuckBy);
				battleScene.battleCommands.getOptions()[2].setExecuteMethod(methodStandTheFuckBy);
			}
		}
	}
	
	/**
	 * 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() == Enemy.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() != Ally.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() != Ally.class)) &&
						heightDifference2 <= jump) {
						listOfAdjacentTiles.add(map[coordinate.getX()][auxCoordinate.getY()]);
					}
				}
			}	
		}
		
		return listOfAdjacentTiles;
	}

	private ArrayList<Tile> getEffRange(Tile[][] map, Coordinate startingPosition, int limit) {
		// Lista de Tiles visitados
		ArrayList<Tile> visited = new ArrayList<Tile>();
		// Lista dos próximos tiles a serem visitados
		ArrayList<Tile> visitedNext = new ArrayList<Tile>(CheckNeighbourTilesEffRange(map, startingPosition, limit));
		// Lista com todas as posições inválidas
		ArrayList<Tile> effRange = new ArrayList<Tile>();
		
		// Preenche a lista attackRange com os elementos válidos
		for (int i = 0; i < limit; i++) {
			ArrayList<Tile> border = new ArrayList<Tile>();
			
			for (Tile tile : visitedNext) {
				ArrayList<Tile> neighbours = new ArrayList<Tile>(CheckNeighbourTilesEffRange(map, tile.getCoordinate(), limit));
				 
				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 <= limit) {
						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 visitedMinNext de forma que não haja repetições de um mesmo tile
			for (Tile tile : visited) {
				ArrayList<Tile> neighbours = new ArrayList<Tile>(CheckNeighbourTilesEffRange(map, tile.getCoordinate(), limit));
				
				// 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);
					}
				}
			}
		}
		
		// Define o alcance de ataque como os tiles da lista visited
		effRange = visited;
		
		return effRange;
	}
	
	private ArrayList<Tile> CheckNeighbourTilesEffRange(Tile[][] map, Coordinate coordinate, int range) {
		// 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:
					 * heightDifference <= maxRange
					 */
					if (heightDifference1 <= range) {
						listOfAdjacentTiles.add(map[auxCoordinate.getX()][coordinate.getY()]);
					}
					/*
					 * A posição é válida se:
					 * heightDifference <= maxRange
					 */
					if (heightDifference2 <= range) {
						listOfAdjacentTiles.add(map[coordinate.getX()][auxCoordinate.getY()]);
					}
				}
			}	
		}
		
		return listOfAdjacentTiles;
	}
}
