package {
	public class Inteligente {
		// Propriedades
		public static var cache:Array = [];
		
		// Coloca os exércitos de direito e os ganhados por causa dos continentes
		public static function colocar():void {
			var conts:Array, i:String;
			
			// Verifica se o jogador tem algum território
			if (Relacao.get_relacoes(Reguladora.vez, Relacao.DONO).length == 0) {
				Reguladora.vez.ativo = false;
				Reguladora.finalizar_jogada();
				return;
			}
			
			// Limpa o cache
			cache = [];
			
			// Coloca os soldados ganhados por possuir um continente inteiro
			conts = Reguladora.calcular_continentes();
			for (i in conts) {
				colocar2(conts[i], Relacao.e(Relacao.get_relacoes(Grafica.continentes[i], Relacao.CONTINENTE), Relacao.get_relacoes(Reguladora.vez, Relacao.DONO)));
			}
			
			// Coloca os soldados no mundo todo
			cache.push(new Movimento(Movimento.FIM_ETAPA));
			colocar2(Reguladora.calcular_direito(), Relacao.get_relacoes(Reguladora.vez, Relacao.DONO));
			
			// Reverte a ordem de execução dos movimentos
			cache.reverse();
			
			// Agrupa movimentos semelhantes
			arrumar_movimentos();
			cache.push(new Movimento(Movimento.FIM_ETAPA));
			
			// Executa
			executar_movimentos();
		}
		
		// Subrotina da função colocar()
		// Coloca o número de soldados nos territórios indicados
		private static function colocar2(num:int, terras:Object):void {
			var probs:Array, i:int, alvo:Territorio, n:int, mov:Movimento, max:int = num;
			while (num>0) {
				probs = [];
				// Calcula a probabilidade de cada um
				for (i=0; i<terras.length; i++) {
					probs.push(prob_colocar(terras[i]));
				}
				
				// Escolhe um dos territórios e quantos soldados por
				alvo = Reguladora.escolher(probs, terras);
				if (Reguladora.troca == 1) {
					n = Reguladora.escolher([1/2, 1/3, 1/6], [3, 2, 1]);
				} else if (Reguladora.troca == 2) {
					n = Reguladora.escolher([1/5, 3/5, 1/5], [3, 2, 1]);
				} else {
					n = Reguladora.escolher([1/6, 1/3, 1/2], [3, 2, 1]);
				}
				n = n>num ? num : n;
				
				// Cria o movimento
				num -= n;
				mov = new Movimento(Movimento.COLOCAR);
				mov.alvo = alvo;
				mov.num = n;
				mov.max = max;
				mov.simular();
				cache.push(mov);
			}
		}
		
		// Calcula a probabilidade do jogador atual colocar soldados nesse território
		private static function prob_colocar(terra:Territorio):Number {
			var r:Number = 1, inimigos:Array, cont:Continente, a:int, b:int, i:String;
			var num_sold_ini:int = 1, furia_ini:Number = 0;
			r /= terra.soldados;
			inimigos = Relacao.menos(Relacao.get_relacoes(terra, Relacao.VIZINHO), Relacao.get_relacoes(Reguladora.vez, Relacao.DONO));
			r *= inimigos.length+1;
			for (i in inimigos) {
				num_sold_ini += inimigos[i].soldados;
				furia_ini += Relacao.get_relacao(inimigos[i], Relacao.DONO).get_raiva(Reguladora.vez);
			}
			furia_ini = inimigos.length==0 ? .01 : furia_ini/inimigos.length+0.01;
			r *= num_sold_ini;
			r *= furia_ini*furia_ini;
			cont = Relacao.get_relacao(terra, Relacao.CONTINENTE);
			a = cont.get_num(Reguladora.vez);
			b = cont.get_max();
			r *= a>b/2 ? 3 : 1;
			r /= a==b ? 3 : 1;
			r *= Math.pow(e_objetivo(terra), Reguladora.vez.objetividade);
			return r;
		}
		
		// Calcula os ataques
		public static function atacar():void {
			// Limpa o cache
			cache = [];
			
			// Monta os ataques
			while (atacar2()) {
			}
			
			ordenar_ataques();
			executar_movimentos();
		}
		
		// Subrotina da função atacar()
		// Retorna false quando o jogador não for atacar mais
		private static function atacar2():Boolean {
			var terras:Object = [], i:String, vizinhos:Object = [], j:String, prob:Number;
			var soma:Number = 0, probs:Array = [], movs:Array = [], mov:Movimento;
			
			// Cria todos os ataques possíveis
			terras = Relacao.get_relacoes(Reguladora.vez, Relacao.DONO);
			for (i in terras) {
				// Para cada território do jogador encontra os vizinhos
				vizinhos = Relacao.get_relacoes(terras[i], Relacao.VIZINHO);
				for (j in vizinhos) {
					if (Relacao.get_relacao(vizinhos[j], Relacao.DONO) !== Reguladora.vez && terras[i].soldados>1) {
						prob = prob_atacar(terras[i], vizinhos[j]);
						soma += prob;
						mov = new Movimento(Movimento.ATACAR);
						mov.origem = terras[i];
						mov.alvo = vizinhos[j];
						mov.num = Math.min(3, terras[i].soldados-1);
						probs.push(prob);
						movs.push(mov);
					}
				}
			}
			
			if (movs.length == 0) {
				// Não é possível atacar
				mov = new Movimento(Movimento.FIM_ETAPA);
			} else {
				// Calcula a probabilidade de parar de atacar
				prob = 0.04*(2*Reguladora.vez.agressividade+1)/(Reguladora.vez.ganhou_carta ? 1 : 2);
				probs.push(prob*soma/(1-prob));
				movs.push(new Movimento(Movimento.FIM_ETAPA));
				
				// Escolhe um dos movimentos
				mov = Reguladora.escolher(probs, movs);
			}
			
			cache.push(mov);
			if (mov.simular()) {
				// O jogador ganhou
				cache.push(new Movimento(Movimento.FIM_ETAPA));
				return false;
			}
			
			// Retorna
			if (mov.tipo == Movimento.FIM_ETAPA) {
				return false;
			} else {
				return true;
			}
		}
		
		// Move (ou não) os soldados após um ataque
		public static function mover_ataque(mov:Movimento):void {
			var max:int = Math.min(mov.num-1, mov.origem.soldados-1);
			while (max>0 && prob_colocar(mov.alvo)>prob_colocar(mov.origem)) {
				max--;
				mov.movidos++;
				mov.origem.soldados--;
				mov.alvo.soldados++;
			}
		}
		
		// Ordena os ataques (como?)
		public static function ordenar_ataques():void {
			/*var origens:Array = [], i:int, novo:Array = [], j:int;
			
			// Colhe todas as origens dos ataques
			for (i=0; i<cache.length; i++) {
				if (cache[i].tipo == Movimento.ATACAR && origens.indexOf(cache[i].origem) == -1) {
					origens.push(cache[i].origem);
				}
			}
			
			// Ordena
			for (i=0; i<origens.length; i++) {
				for (j=0; j<cache.length; j++) {
					if (cache[j].tipo == Movimento.ATACAR && cache[j].origem === origens[i]) {
						novo.push(cache[j]);
					}
				}
			}
			novo.push(new Movimento(Movimento.FIM_ETAPA));
			cache = novo;*/
		}
		
		// Calcula a probabilidade do jogador atual atacar
		private static function prob_atacar(origem:Territorio, alvo:Territorio):Number {
			var r:Number = 1, cont:Continente, jog:Jogador;
			r *= Math.pow(origem.soldados-1, 2);
			r /= Math.pow(alvo.soldados, 3);
			cont = Relacao.get_relacao(alvo, Relacao.CONTINENTE);
			jog = Relacao.get_relacao(alvo, Relacao.DONO);
			if (Relacao.get_relacao(origem, Relacao.CONTINENTE) === cont && cont.tem_maioria(Reguladora.vez)) {
				r *= 3;
			}
			r *= Reguladora.vez.get_raiva(jog);
			r *= Math.pow(e_objetivo(alvo), Reguladora.vez.objetividade);
			if (cont.get_num(jog) == cont.get_max()) {
				r *= 2;
			}
			return r;
		}
		
		// Verifica se esse território faz parte do objetivo do jogador atual
		// Retorna 3, 2 ou 1
		private static function e_objetivo(terra:Territorio):int {
			var id:int, id_cont:int;
			id = Relacao.get_relacao(Reguladora.vez, Relacao.OBJETIVO).id-1;
			id_cont = Relacao.get_relacao(terra, Relacao.CONTINENTE).id;
			
			// Usa regras separadas para cada tipo de objetivo
			if (id<7 && Grafica.jogadores[id-1].ativo && Reguladora.vez.id != id) {
				// Destrua o exército x (enquanto x ainda existe)
				if (Relacao.get_relacao(terra, Relacao.DONO).id === id) {
					return 3;
				} else if (Relacao.get_relacao(terra, Relacao.CONTINENTE).tem_maioria(Grafica.jogadores[id-1])) {
					return 2;
				} else {
					return 1;
				}
			} else if (id == 9) {
				if (id_cont == 2 || id_cont == 3) {
					return 3;
				} else {
					return 1;
				}
			} else if (id == 12) {
				if (id_cont == 0 || id_cont == 3) {
					return 3;
				} else {
					return 1;
				}
			} else if (id == 13) {
				if (id_cont == 0 || id_cont == 1) {
					return 3;
				} else {
					return 1;
				}
			} else if (id == 14) {
				if (id_cont == 1 || id_cont == 5) {
					return 3;
				} else {
					return 1;
				}
			} else if (id == 10) {
				if (id_cont == 2 || id_cont == 4) {
					return 3;
				} else {
					return 2;
				}
			} else if (id == 11) {
				if (id_cont == 4 || id_cont == 5) {
					return 3;
				} else {
					return 2;
				}
			} else {
				if (terra.soldados<3) {
					return 3;
				} else if (Reguladora.etapa == Reguladora.ATACAR) {
					return 2;
				} else {
					return 1;
				}
			}
		}
		
		// Ordena os movimentos, tornando-os mais naturais
		private static function arrumar_movimentos():void {
			var a:Array, b:Array, i:int, j:int, pares:Array, encontrado:Boolean, novo:Array;
			if (Reguladora.etapa == Reguladora.DIREITO) {
				// Divide o cache em duas partes: direito e continente
				for (i=0; i<cache.length; i++) {
					if (cache[i].tipo == Movimento.FIM_ETAPA) {
						a = cache.slice(0, i);
						b = cache.slice(i+1);
						break;
					}
				}
				
				// Agrupa as partes individualmente e depois as junta
				a = agrupar_colocar(a);
				a.push(new Movimento(Movimento.FIM_ETAPA));
				b = agrupar_colocar(b);
				b.reverse();
				cache = a.concat(b);
			} else if (Reguladora.etapa == Reguladora.ATACAR) {
			} else if (Reguladora.etapa == Reguladora.MOVER) {
				novo = [];
				
				// Percorre todo o cache
				for2: for (i=0; i<cache.length-1; i++) {
					// Verifica se o movimento já existe
					for (j=0; j<novo.length; j++) {
						if (novo[j].origem === cache[i].origem && novo[j].alvo === cache[i].alvo) {
							// Junta os dois movimentos
							novo[j].num += cache[i].num;
							continue for2;
						} else if (novo[j].origem === cache[i].alvo && novo[j].alvo === cache[i].origem) {
							if (novo[j].num>=cache[i].num) {
								// Reduz o efeito de um deles
								novo[j].num -= cache[i].num;
							} else {
								// Inverte
								novo[j].origem = cache[i].origem;
								novo[j].alvo = cache[i].alvo;
								novo[j].num = cache[i].num-novo[j].num;
							}
							continue for2;
						}
					}
					
					// Novo movimento
					novo.push(cache[i]);
				}
				
				// Elimina movimentos nulos (num == 0)
				cache = [];
				for (i=0; i<novo.length; i++) {
					if (novo[i].num != 0) {
						cache.push(novo[i]);
					}
				}
				cache.push(new Movimento(Movimento.FIM_ETAPA));
			}
		}
		
		// Subrotina da função arrumar_movimentos
		private static function agrupar_colocar(array:Array):Array {
			var r:Array = [], i:int, j:int, terras:Array = [], mov:Movimento;
			
			// Percorre cada elemento da array buscando por semelhantes (mesmo alvo)
			for (i=0; i<array.length; i++) {
				if (terras.indexOf(array[i].alvo) == -1) {
					mov = new Movimento(Movimento.COLOCAR);
					mov.alvo = array[i].alvo;
					mov.num = array[i].num;
					mov.max = array[i].max;
					for (j=i+1; j<array.length; j++) {
						if (array[i].alvo === array[j].alvo) {
							mov.num += array[j].num;
						}
					}
					r.push(mov);
					terras.push(mov.alvo);
				}
			}
			
			// Retorna o resultado
			return r;
		}
		
		// Executa os movimentos em ordem
		public static function executar_movimentos():void {
			var i:int, tempo:int = 0;
			for (i=0; i<cache.length; i++) {
				tempo += cache[i].executar(tempo);
			}
		}
		
		// Calcula as transferências de exércitos
		public static function mover():void {
			var i:int = 0, terras:Object, probs_colocar:Array = [], j:String;
			
			// Limpa o cache
			cache = [];
			
			// Calcula os valores para cada territorio
			terras = Relacao.get_relacoes(Reguladora.vez, Relacao.DONO);
			for (j in terras) {
				probs_colocar[terras[j].id] = prob_colocar(terras[j]);
			}
			
			// Monta os movimentos (máximo 30)
			while (i<30) {
				if (!mover2(terras, probs_colocar)) {
					break;
				}
				i++;
			}
			
			if (cache[cache.length-1].tipo != Movimento.FIM_ETAPA) {
				cache.push(new Movimento(Movimento.FIM_ETAPA));
			}
			
			arrumar_movimentos();
			executar_movimentos();
		}
		
		// Subrotina da função mover()
		// Retorna false quando o jogador não for mover mais seus exércitos
		private static function mover2(terras:Object, probs_colocar:Array):Boolean {
			var i:String, vizinhos:Object = [], j:String, prob:Number;
			var med:Number = 0, n:int = 0;
			var probs:Array = [], movs:Array = [], mov:Movimento;
			
			// Cria todos os movimentos possíveis
			for (i in terras) {
				// Para cada território do jogador encontra os vizinhos
				vizinhos = Relacao.get_relacoes(terras[i], Relacao.VIZINHO);
				for (j in vizinhos) {
					if (Relacao.get_relacao(vizinhos[j], Relacao.DONO) === Reguladora.vez && terras[i].livres>0) {
						prob = probs_colocar[vizinhos[j].id]/probs_colocar[terras[i].id];
						med += prob;
						n++;
						mov = new Movimento(Movimento.MOVER);
						mov.origem = terras[i];
						mov.alvo = vizinhos[j];
						mov.num = Math.ceil(terras[i].soldados/5);
						probs.push(prob);
						movs.push(mov);
					}
				}
			}
			
			if (n == 0) {
				// Não é possível mover
				mov = new Movimento(Movimento.FIM_ETAPA);
			} else {
				// Calcula a probabilidade de parar de mover
				med /= n;
				probs.push(med+.01);
				movs.push(new Movimento(Movimento.FIM_ETAPA));
				
				// Escolhe um dos movimentos
				mov = Reguladora.escolher(probs, movs);
			}
			
			cache.push(mov);
			mov.simular();
			
			// Retorna
			if (mov.tipo == Movimento.FIM_ETAPA) {
				return false;
			} else {
				// Altera os valores de probs_colocar
				probs_colocar[mov.origem.id] = prob_colocar(mov.origem);
				probs_colocar[mov.alvo.id] = prob_colocar(mov.alvo);
				return true;
			}
		}
		
		// Faz a troca
		public static function trocar():void {
			var cartas:Object, i:int, j:int, k:int, trocas:Array = [], temp:int;
			var nota:int;
			
			// Limpa o cache
			cache = [];
			
			// Monta todas as combinações possíveis com as cartas do jogador
			cartas = Relacao.get_relacoes(Reguladora.vez, Relacao.CARTA);
			for (i=0; i<cartas.length; i++) {
				for (j=i+1; j<cartas.length; j++) {
					for (k=j+1; k<cartas.length; k++) {
						temp = cartas[i].figura*cartas[j].figura*cartas[k].figura;
						if (temp != 2 && temp != 3 && temp != 4 && temp != 9 && temp != 12 && temp != 18) {
							nota = prob_trocar(cartas[i])*prob_trocar(cartas[j])*prob_trocar(cartas[k]);
							trocas.push({"nota" : nota, "cartas" : [cartas[i], cartas[j], cartas[k]]});
						}
					}
				}
			}
			
			if (trocas.length>0 && Math.random()<(cartas.length>4 ? 1 : (cartas.length==4 ? .5 : .25))) {				
				// Ordena as trocas pelas notas
				trocas.sortOn("nota", Array.NUMERIC | Array.DESCENDING);
				
				// Executa a melhor troca
				colocar2(Reguladora.trocar(trocas[0]["cartas"][0], trocas[0]["cartas"][1], trocas[0]["cartas"][2]), Relacao.get_relacoes(Reguladora.vez, Relacao.DONO));
				cache = agrupar_colocar(cache);
				cache.unshift(new Movimento(Movimento.FIM_ETAPA));
				cache.push(new Movimento(Movimento.FIM_ETAPA));
				Reguladora.timeout(executar_movimentos, 1000*Reguladora.velocidade, false);
			} else {
				// Não faz troca
				(new Movimento(Movimento.NAO_TROCAR)).executar(1000*Reguladora.velocidade);
			}
		}
		
		// Verifica se é vantajoso usar essa carta numa troca agora
		// Retorna 1, 2 ou 3
		private static function prob_trocar(carta:Carta):int {
			if (carta.figura == Carta.BONUS) {
				// Carta bônus é indiferente
				return 2;
			} else if (Relacao.tem_relacao(carta.territorio, Reguladora.vez, Relacao.DONO)) {
				// Se você é dono do território então vale a pena
				return 3;
			} else if (e_objetivo(carta.territorio)>1 && Relacao.e(Relacao.get_relacoes(Reguladora.vez, Relacao.DONO), Relacao.get_relacoes(carta.territorio, Relacao.VIZINHO)).length>0) {
				// Se o território é seu vizinho e seu objetivo então não vale a pena
				return 1;
			} else {
				// Indiferente
				return 2;
			}
		}
	}
}