/*
	Força: um (livre) clone do jogo Forca
	Copyright (C) 2020  luca0N!

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.

	Entre em contato comigo por e-mail via <luca0n@luca0n.com>.
*/

class Sala {
	/**
	 * Objeto Sala.
	 * @param string sala O nome da sala.
	 * @param Array A lista de clientes.
	 * @param string O apelido do líder.
	 * @param string O estado da sala.
	 */
	constructor(sala, clientes, líder, estado, opções){
		this.sala = sala;
		this.clientes = clientes;
		this.opções = opções;
		this.uas = Date.now(); // Última Ação Significante: marca o tempo em que um último evento significante tenha acontecido. Caso este evento tenha acontecido muito tempo atrás, esta sala será apagada.
		this.líder = líder === undefined ? clientes[0].apelido : líder;
		this.estado = estado === undefined ? "AGUARDANDO_JOGADORES" : estado;
		this.pontuaçãoMeta = 0;
		this.tin = 0; // tentativas incorretas

		this.jfr = []; // fila de jogadores para rodadas

		this.banidos = [];
	}


	/**
	 * Inicia uma nova partida nesta sala.
	 * @since 19 de outubro de 2020.
	 */
	novaPartida(){
		this.estado = 'EM_PARTIDA';
		// Popular a fila JFR.
		this.jf = [];

		let tmpJf = [];
		for (let x = 0; x < this.clientes.length; x++)
			if (this.clientes[x].apelido !== this.vezDe)
				tmpJf.push(this.clientes[x].apelido);

		for (let x = tmpJf.length; x > 0; x--){
			// Pegar um jogador aleatório da fila.
			let num = Sala.gerarNúmeroAleatório(tmpJf.length);
			this.jf.push(tmpJf[num]);
			tmpJf.splice(num, 1);
		}
	
		let tmpJfr = []; // JFR temporária, utilizado para popular a JFR verdadeira.

		for (let x = 0; x < this.clientes.length; x++)
			tmpJfr.push(this.clientes[x].apelido);

		for (let x = tmpJfr.length; x > 0; x--){
			// Pegar um jogador aleatório da fila.
			let num = Sala.gerarNúmeroAleatório(tmpJfr.length);
			this.jfr.push(tmpJfr[num]);
			tmpJfr.splice(num, 1);
		}

		this.novaRodada();
	}

	/**
	 * Inicia uma nova rodada nesta sala, escolhendo um jogador aleatório.
	 * @returns null
	 * @since 05 de novembro de 2020.
	 */
	novaRodada(){
		this.lei = []; // Letras escolhidas inexistentes
		this.lee = []; // Letras escolhidas existentes
		this.pei = []; // Palavras escolhidas inexistentes
		this.ldc = []; // Letras descobertas
		this.jpa = null; // Jogador que escolheu a palavra atual (Jogador Palavra Atual)
		this.palavra = null;
		this.tin = 0;
	
		// Vamos escolher um jogador aleatório.
		if (this.jfr.indexOf(this.vezDe) === this.jfr.length - 1)
			// Fim da fila JFR atingido. Voltar para o começo.
			this.vezDe = null;

		this.vezDe = this.vezDe === undefined || this.vezDe === null ? this.jfr[0] : this.jfr[this.jfr.indexOf(this.vezDe) + 1]; // Escolher o primeiro jogador na fila caso o limite tenha sido atingido, ou escolher o próximo jogador na fila caso contrário.
	}

	/**
	 * Retorna o próximo item da fila JF.
	 * @returns String O próximo item da fila JF.
	 * @since 02/01/2020
	 */
	próxItemJf(){
		let pv1 = this.jf[this.jf.indexOf(this.termoVezDe) + 1];
		if (pv1 !== undefined)
			return pv1;
		return this.jf[0];
	}

	/**
	 * Calcula e define a quantidade de letras na palavra atual.
	 * @since 23 de outubro de 2020.
	 */
	definirPalavraLetrasQtd(){
		let letrasQtd = 0;
		for (let x = 0; x < this.palavra.length; x++)
			if (this.palavra[x] !== ' ')
				letrasQtd++;
		this.palavraLetrasQtd = letrasQtd;
	}

	/**
	 * Deduz se é possível que um jogador descubra a palavra atual baseado na quantidade de letras descobertas da palavra.
	 * @returns boolean Verdadeiro caso mais de 75% da palavra tenha sido descoberto ou caso apenas falte uma letra para descobrir a palavra inteira.
	 * @since 23 de outubro de 2020.
	 */
	possívelDescoberta(){
		let ld = 0;
		for (let x = 0; x < this.palavra.length; x++)
			if (this.ldc[x] !== undefined)
				ld++;

		// Pelo menos 75% da palavra deve ter sido descoberta para retornar o evento de descoberta de palavra.
		// Caso a palavra seja pequena, então pelo menos uma letra restante deve estar faltando.

		if(ld / this.palavraLetrasQtd >= 0.75){
			return true;
		} else {
			// Menos de 75% da palavra foi descoberta. Caso apenas uma letra esteja faltando, então retornar verdadeiro de qualquer maneira.
			return this.palavraLetrasQtd - 1 === ld;
		}
	}

	/**
	 * Retorna uma lista contendo as posições dos espaços na palavra atual.
	 * @param String busca O alvo para procurar na palavra atual.
	 * @returns Array Lista contendo as posições dos espaços na palavra atual.
	 * @since 22 de outubro de 2020.
	 */
	procurar(busca){
		let espaços = [];
		for (var x = 0; x < this.palavra.length; x++){
			if (this.palavra[x] === busca)
				espaços.push(x);
		}
		return espaços;
	}

	/**
	 * Esta função processa o termo enviado por um jogador.
	 * @param string termo O termo enviado pelo jogador escolhido.
	 * @returns "LETRA_INEXISTENTE" caso a letra inserida não exista na palavra atual; "LETRAS_PREENCHIDAS" caso a letra inserida apareça na palavra atual.
	 * @since 19 de outubro de 2020.
	 */
	processarTermo(termo){
		termo = termo.toUpperCase();
		let resultadoTermo = { letrasPreenchidas: 0, resultado: null };
		if (termo.length === 1){
			// Este termo já foi enviado?
			if (this.lee.indexOf(termo) !== -1
				|| this.lei.indexOf(termo) !== -1)
				resultadoTermo.resultado = "LETRA_JÁ_ESCOLHIDA";
			// A palavra escolhida possui a letra enviada?
			else if (this.palavra.indexOf(termo) === -1){
				this.lei.push(termo);
				resultadoTermo.resultado = "LETRA_INEXISTENTE";
			} else {
				resultadoTermo.letrasPreenchidas = this.preencherCampos(termo);
				this.lee.push(termo);
				resultadoTermo.resultado = "LETRAS_PREENCHIDAS";
			}
			return resultadoTermo;
		} else {
			// Esta palavra já foi enviada?
			if (this.pei.indexOf(termo) !== -1){
				resultadoTermo.resultado = "PALAVRA_JÁ_ESCOLHIDA";
				return resultadoTermo;
			}
			this.pei.push(termo);
			let palavraCorreta = this.palavra === termo;
			resultadoTermo.resultado = palavraCorreta ? "PALAVRA_CORRETA" : "PALAVRA_INCORRETA";
			if (palavraCorreta){
				for(var x = 0; x < this.palavra.length; x++)
					if (this.ldc[x] === undefined)
						resultadoTermo.letrasPreenchidas++;
			}
		}
		return resultadoTermo;
	}

	/**
	 * Gera um jogador aleatório. Esta função é utilizada para escolher a vez de alguma pessoa aleatoriamente sem ecolher pessoas que já foram escolhidas.
	 * @returns string O apelido do jogador escolhido.
	 * @since 19 de outubro de 2019.
	 */
	gerarJogadorAleatório(){
		// Se todos os jogadores já tiverem sido escolhidos, então redefinir a fila de jogadores.
		if (this.jf.length === this.clientes.length - 1)
			this.jf = [];

		var clientesPossíveis = [];
		for (var x = 0; x < this.clientes.length; x++){
			var ca = this.clientes[x]; // Cliente atual
			if (ca.apelido !== this.jpa  // A pessoa que escolheu a palavra não pode ser escolhida.
				&& this.jf.indexOf(ca.apelido) === -1) { // Jogadores que já foram escolhidos não podem ser escolhidos novamente.
				clientesPossíveis.push(ca.apelido);
			}
		}
		var je = clientesPossíveis[Sala.gerarNúmeroAleatório(clientesPossíveis.length)]; // Jogador escolhido
		return je;
	}

	/**
	 * Gera um jogador aleatório para uma nova rodada.
	 * @returns String O apelido do jogador escolhido.
	 * @since 25 de dezembro de 2020.
	 */
	gerarJogadorAleatórioRodada(){
		// Se todos os jogadores já tiverem sido escolhidos, então redefinir a fila de jogadores.
		if (this.jfr.length === this.clientes.length - 1)
			this.jfr = [];

		let clientesPossíveis = [];
		for (let x = 0; x < this.clientes.length; x++){
			let ca = this.clientes[x]; // Cliente atual
			if (this.jfr.indexOf(ca.apelido) === -1) // Jogadores que já foram escolhidos não podem ser escolhidos novamente.
				clientesPossíveis.push(ca.apelido);
		}
		let je = clientesPossíveis[Sala.gerarNúmeroAleatório(clientesPossíveis.length)]; // Jogador escolhido
		return je;
	}


	/**
	 * Preenche os campos com a letra especificada.
	 * @param string letra A letra desejada.
	 * @since 19 de outubro de 2020.
	 */
	preencherCampos(letra){
		let qtd = 0; // quantidade de campos preenchidos
		for (let x = 0; x < this.palavra.length; x++){
			if (this.palavra[x] === letra){
					this.ldc[x] = letra;
					qtd++;
				}
		}
		return qtd;
	}
	/**
	 * Retorna o vencedor da partida atual caso exista.
	 * @returns Object O cliente do jogador que venceu a partida ou nulo caso não há um vencedor.
	 * @since 23 de dezembro de 2020.
	 */
	receberVencedor(){
		let pv = []; // Lista de possíveis vencedores. Adicionar jogadores que ultrapassaram a meta aqui. Caso mais de um cliente seja adicionado aqui, então conceder vitória ao jogador que tiver a maior quantidade de pontos ou retornar nulo caso houve um empate.
		for (let x = 0; x < this.clientes.length; x++)
			if (this.clientes[x].pontos >= this.pontuaçãoMeta)
				pv.push(this.clientes[x]);

		if (pv.length === 1)
			return pv[0];
		else if (pv.length === 0)
			return null;
		else {
			pv.sort(function (a, b){
				return b.pontos - a.pontos;
			});
			// Houve um empate?
			return pv[0].pontos === pv[1].pontos ? null // Houve um empate; retornar nulo.
				: pv[0]; // Não houve um empate. Retornar o cliente que ficou no topo.
		}
	}
	/**
	 * Checa se o endereço dado está banido nesta sala.
	 * @param String endereço O endereço remoto do cliente.
	 * @returns Verdadeiro caso o endereço esteja banido nesta sala, falso caso contrário.
	 * @since 24 de dezembro de 2020.
	 */
	banido(endereço){
		for (let x = 0; x < this.banidos.length; x++)
			if (this.banidos[x] === endereço)
				return true;
		return false;
	}

	/**
	 * Gera um número aleatório baseado no argumento providenciado.
	 * @param int máximo O número máximo desejado.
	 * @since 15 de outubro de 2020.
	 */
	static gerarNúmeroAleatório(máximo){
		var rnd = Math.random();
		var num = Math.floor(rnd * máximo);
		return num;
	}
}

module.exports = Sala;