#include "../const.h"
#include "menu.h"
#include <iostream>

// Constructor
Menu::Menu(SDL_Renderer *renderer, Asset *asset, Input *input, std::string file)
{
	// Copia punteros
	this->renderer = renderer;
	this->asset = asset;
	this->input = input;

	// Inicializa punteros
	soundMove = nullptr;
	soundAccept = nullptr;
	soundCancel = nullptr;

	// Inicializa variables
	name = "";
	selector.index = 0;
	itemSelected = MENU_NO_OPTION;
	x = 0;
	y = 0;
	w = 0;
	rectBG.rect = {0, 0, 0, 0};
	rectBG.color = {0, 0, 0};
	rectBG.a = 0;
	backgroundType = MENU_BACKGROUND_SOLID;
	isCenteredOnX = false;
	isCenteredOnY = false;
	areElementsCenteredOnX = false;
	centerX = 0;
	centerY = 0;
	widestItem = 0;
	colorGreyed = {128, 128, 128};
	defaultActionWhenCancel = 0;
	font_png = "";
	font_txt = "";

	// Selector
	selector.originY = 0;
	selector.targetY = 0;
	selector.despY = 0;
	selector.originH = 0;
	selector.targetH = 0;
	selector.incH = 0;
	selector.y = 0.0f;
	selector.h = 0.0f;
	selector.numJumps = 8;
	selector.moving = false;
	selector.resizing = false;
	selector.rect = {0, 0, 0, 0};
	selector.color = {0, 0, 0};
	selector.itemColor = {0, 0, 0};
	selector.a = 255;

	// Inicializa las variables desde un fichero
	if (file != "")
	{
		load(file);
	}

	// Deja el cursor en el primer elemento
	reset();
}

Menu::~Menu()
{
	renderer = nullptr;
	asset = nullptr;
	input = nullptr;

	if (soundMove)
	{
		JA_DeleteSound(soundMove);
	}

	if (soundAccept)
	{
		JA_DeleteSound(soundAccept);
	}

	if (soundCancel)
	{
		JA_DeleteSound(soundCancel);
	}

	if (text != nullptr)
	{
		delete text;
	}
}

// Carga la configuración del menu desde un archivo de texto
bool Menu::load(std::string file_path)
{
	// Indicador de éxito en la carga
	bool success = true;

	// Indica si se ha creado ya el objeto de texto
	bool textAllocated = false;

	const std::string filename = file_path.substr(file_path.find_last_of("\\/") + 1);
	std::string line;
	std::ifstream file(file_path);

	// El fichero se puede abrir
	if (file.good())
	{
		// Procesa el fichero linea a linea
		//std::cout << "Reading file " << filename.c_str() << std::endl;
		while (std::getline(file, line))
		{
			if (line == "[item]")
			{
				item_t item;
				item.label = "";
				item.hPaddingDown = 1;
				item.selectable = true;
				item.greyed = false;
				item.linkedDown = false;
				item.visible = true;

				do
				{
					// Lee la siguiente linea
					std::getline(file, line);

					// Encuentra la posición del caracter '='
					int pos = line.find("=");

					// Procesa las dos subcadenas
					if (!setItem(&item, line.substr(0, pos), line.substr(pos + 1, line.length())))
					{
						//std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
						success = false;
					}

				} while (line != "[/item]");

				addItem(item.label, item.hPaddingDown, item.selectable, item.greyed, item.linkedDown, item.visible);
			}

			// En caso contrario se parsea el fichero para buscar las variables y los valores
			else
			{
				// Encuentra la posición del caracter '='
				int pos = line.find("=");
				// Procesa las dos subcadenas
				if (!setVars(line.substr(0, pos), line.substr(pos + 1, line.length())))
				{
					//std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
					success = false;
				}

				// Crea el objeto text tan pronto como se pueda. Necesario para añadir items
				if (font_png != "" && font_txt != "" && !textAllocated)
				{
					text = new Text(asset->get(font_png), asset->get(font_txt), renderer);
					textAllocated = true;
				}
			}
		}

		// Cierra el fichero
		//std::cout << "Closing file " << filename.c_str() << std::endl;
		file.close();
	}
	// El fichero no se puede abrir
	else
	{
		//std::cout << "Warning: Unable to open " << filename.c_str() << " file" << std::endl;
		success = false;
	}

	return success;
}

// Asigna variables a partir de dos cadenas
bool Menu::setItem(item_t *item, std::string var, std::string value)
{
	// Indicador de éxito en la asignación
	bool success = true;

	if (var == "text")
	{
		item->label = value;
	}

	else if (var == "hPaddingDown")
	{
		item->hPaddingDown = std::stoi(value);
	}

	else if (var == "selectable")
	{
		item->selectable = value == "true" ? true : false;
	}

	else if (var == "greyed")
	{
		item->greyed = value == "true" ? true : false;
	}

	else if (var == "linkedDown")
	{
		item->linkedDown = value == "true" ? true : false;
	}

	else if (var == "visible")
	{
		item->visible = value == "true" ? true : false;
	}

	else if ((var == "") || (var == "[/item]"))
	{
	}

	else
	{
		success = false;
	}

	return success;
}

// Asigna variables a partir de dos cadenas
bool Menu::setVars(std::string var, std::string value)
{
	// Indicador de éxito en la asignación
	bool success = true;

	if (var == "font_png")
	{
		font_png = value;
	}

	else if (var == "font_txt")
	{
		font_txt = value;
	}

	else if (var == "sound_cancel")
	{
		soundCancel = JA_LoadSound(asset->get(value).c_str());
	}

	else if (var == "sound_accept")
	{
		soundAccept = JA_LoadSound(asset->get(value).c_str());
	}

	else if (var == "sound_move")
	{
		soundMove = JA_LoadSound(asset->get(value).c_str());
	}

	else if (var == "name")
	{
		name = value;
	}

	else if (var == "x")
	{
		x = std::stoi(value);
	}

	else if (var == "centerX")
	{
		centerX = std::stoi(value);
	}

	else if (var == "centerY")
	{
		centerY = std::stoi(value);
	}

	else if (var == "y")
	{
		y = std::stoi(value);
	}

	else if (var == "backgroundType")
	{
		backgroundType = std::stoi(value);
	}

	else if (var == "backgroundColor")
	{
		// Se introducen los valores separados por comas en un vector
		std::stringstream ss(value);
		std::string tmp;
		getline(ss, tmp, ',');
		rectBG.color.r = std::stoi(tmp);
		getline(ss, tmp, ',');
		rectBG.color.g = std::stoi(tmp);
		getline(ss, tmp, ',');
		rectBG.color.b = std::stoi(tmp);
		getline(ss, tmp, ',');
		rectBG.a = std::stoi(tmp);
	}

	else if (var == "selector_color")
	{
		// Se introducen los valores separados por comas en un vector
		std::stringstream ss(value);
		std::string tmp;
		getline(ss, tmp, ',');
		selector.color.r = std::stoi(tmp);
		getline(ss, tmp, ',');
		selector.color.g = std::stoi(tmp);
		getline(ss, tmp, ',');
		selector.color.b = std::stoi(tmp);
		getline(ss, tmp, ',');
		selector.a = std::stoi(tmp);
	}

	else if (var == "selector_text_color")
	{
		// Se introducen los valores separados por comas en un vector
		std::stringstream ss(value);
		std::string tmp;
		getline(ss, tmp, ',');
		selector.itemColor.r = std::stoi(tmp);
		getline(ss, tmp, ',');
		selector.itemColor.g = std::stoi(tmp);
		getline(ss, tmp, ',');
		selector.itemColor.b = std::stoi(tmp);
	}

	else if (var == "areElementsCenteredOnX")
	{
		areElementsCenteredOnX = value == "true" ? true : false;
	}

	else if (var == "isCenteredOnX")
	{
		isCenteredOnX = value == "true" ? true : false;
	}

	else if (var == "isCenteredOnY")
	{
		isCenteredOnY = value == "true" ? true : false;
	}

	else if (var == "defaultActionWhenCancel")
	{
		defaultActionWhenCancel = std::stoi(value);
	}

	else if (var == "")
	{
	}

	else
	{
		success = false;
	}

	return success;
}

// Carga los ficheros de audio
void Menu::loadAudioFile(std::string file, int sound)
{
	switch (sound)
	{
	case SOUND_ACCEPT:
		soundAccept = JA_LoadSound(file.c_str());
		break;

	case SOUND_CANCEL:
		soundCancel = JA_LoadSound(file.c_str());
		break;

	case SOUND_MOVE:
		soundMove = JA_LoadSound(file.c_str());
		break;

	default:
		break;
	}
}

// Obtiene el nombre del menu
std::string Menu::getName()
{
	return name;
}

// Obtiene el valor de la variable
int Menu::getItemSelected()
{
	// Al llamar a esta funcion, se obtiene el valor y se borra
	const int temp = itemSelected;
	itemSelected = MENU_NO_OPTION;
	return temp;
}

// Actualiza la posicion y el estado del selector
void Menu::updateSelector()
{
	if (selector.moving)
	{
		// Calcula el desplazamiento en Y
		selector.y += selector.despY;
		if (selector.despY > 0) // Va hacia abajo
		{
			if (selector.y > selector.targetY) // Ha llegado al destino
			{
				selector.originY = selector.y = selector.targetY;
				selector.moving = false;
			}
		}

		else if (selector.despY < 0) // Va hacia arriba
		{
			if (selector.y < selector.targetY) // Ha llegado al destino
			{
				selector.originY = selector.y = selector.targetY;
				selector.moving = false;
			}
		}
		selector.rect.y = int(selector.y);
	}
	else
	{
		selector.rect.y = int(selector.y);
	}

	if (selector.resizing)
	{
		// Calcula el incremento en H
		selector.h += selector.incH;
		if (selector.incH > 0) // Crece
		{
			if (selector.h > selector.targetH) // Ha llegado al destino
			{
				// selector.originH = selector.targetH = selector.rect.h = getSelectorHeight(selector.index);
				selector.originH = selector.h = selector.targetH;
				selector.resizing = false;
			}
		}

		else if (selector.incH < 0) // Decrece
		{
			if (selector.h < selector.targetH) // Ha llegado al destino
			{
				// selector.originH = selector.targetH = selector.rect.h = getSelectorHeight(selector.index);
				selector.originH = selector.h = selector.targetH;
				selector.resizing = false;
			}
		}
		selector.rect.h = int(selector.h);
	}
	else
	{
		selector.rect.h = getSelectorHeight(selector.index);
	}
}

// Coloca el selector en una posición específica
void Menu::setSelectorPos(int index)
{
	if (index < (int)item.size())
	{
		selector.index = index;
		selector.rect.y = selector.y = selector.originY = selector.targetY = item[selector.index].rect.y;
		selector.rect.w = rectBG.rect.w;
		selector.rect.x = rectBG.rect.x;
		selector.originH = selector.targetH = selector.rect.h = getSelectorHeight(selector.index);
		selector.moving = false;
		selector.resizing = false;
	}
}

// Obtiene la anchura del elemento más ancho del menu
int Menu::getWidestItem()
{
	int result = 0;

	// Obtenemos la anchura del item mas ancho
	for (auto &i : item)
	{
		result = std::max(result, i.rect.w);
	}

	return result;
}

// Deja el menu apuntando al primer elemento
void Menu::reset()
{
	itemSelected = MENU_NO_OPTION;
	selector.index = 0;
	selector.originY = selector.targetY = selector.y = item[0].rect.y;
	selector.originH = selector.targetH = item[0].rect.h;
	selector.moving = false;
	selector.resizing = false;

	// Si el primer elemento no es seleccionable, incrementa el selector
	if (!item[selector.index].selectable)
	{
		increaseSelectorIndex();
		setSelectorPos(selector.index);
	}
}

// Actualiza el menu para recolocarlo correctamente y establecer el tamaño
void Menu::reorganize()
{
	setRectSize();

	if (isCenteredOnX)
	{
		centerMenuOnX(centerX);
	}

	if (isCenteredOnY)
	{
		centerMenuOnY(centerY);
	}

	if (areElementsCenteredOnX)
	{
		centerMenuElementsOnX();
	}
}

// Deja el menu apuntando al siguiente elemento
bool Menu::increaseSelectorIndex()
{
	// Obten las coordenadas del elemento actual
	selector.y = selector.originY = item[selector.index].rect.y;
	selector.h = selector.originH = getSelectorHeight(selector.index);

	// Calcula cual es el siguiente elemento
	++selector.index %= item.size();
	while (!item[selector.index].selectable)
	{
		++selector.index %= item.size();
	}

	// Establece las coordenadas y altura de destino
	selector.targetY = item[selector.index].rect.y;
	selector.despY = (selector.targetY - selector.originY) / selector.numJumps;

	selector.targetH = getSelectorHeight(selector.index);
	selector.incH = (selector.targetH - selector.originH) / selector.numJumps;

	selector.moving = true;
	if (selector.incH != 0)
	{
		selector.resizing = true;
	}

	return true;
}

// Deja el menu apuntando al elemento anterior
bool Menu::decreaseSelectorIndex()
{
	// Obten las coordenadas del elemento actual
	selector.y = selector.originY = item[selector.index].rect.y;
	selector.h = selector.originH = getSelectorHeight(selector.index);

	// Calcula cual es el siguiente elemento
	if (selector.index == 0)
	{
		selector.index = item.size() - 1;
	}
	else
	{
		selector.index--;
	}

	while (!item[selector.index].selectable)
	{
		if (selector.index == 0)
		{
			selector.index = item.size() - 1;
		}
		else
		{
			selector.index--;
		}
	}

	// Establece las coordenadas y altura de destino
	selector.targetY = item[selector.index].rect.y;
	selector.despY = (selector.targetY - selector.originY) / selector.numJumps;

	selector.targetH = getSelectorHeight(selector.index);
	selector.incH = (selector.targetH - selector.originH) / selector.numJumps;

	selector.moving = true;
	if (selector.incH != 0)
	{
		selector.resizing = true;
	}

	return true;
}

// Actualiza la logica del menu
void Menu::update()
{
	checkInput();
	updateSelector();
}

// Pinta el menu en pantalla
void Menu::render()
{
	// Rendereritza el fondo del menu
	if (backgroundType == MENU_BACKGROUND_SOLID)
	{
		SDL_SetRenderDrawColor(renderer, rectBG.color.r, rectBG.color.g, rectBG.color.b, rectBG.a);
		SDL_RenderFillRect(renderer, &rectBG.rect);
	}

	// Renderiza el rectangulo del selector
	const SDL_Rect temp = {selector.rect.x, selector.rect.y - 1, selector.rect.w, selector.rect.h + 1};
	SDL_SetRenderDrawColor(renderer, selector.color.r, selector.color.g, selector.color.b, selector.a);
	SDL_RenderFillRect(renderer, &temp);

	// Renderiza el borde del fondo
	if (backgroundType == MENU_BACKGROUND_SOLID)
	{
		SDL_SetRenderDrawColor(renderer, rectBG.color.r, rectBG.color.g, rectBG.color.b, 255);
		SDL_RenderDrawRect(renderer, &rectBG.rect);
	}

	// Renderiza el texto
	for (int i = 0; i < (int)item.size(); ++i)
	{
		if (item[i].visible)
		{
			if (i == selector.index)
			{
				const color_t color = {selector.itemColor.r, selector.itemColor.g, selector.itemColor.b};
				text->writeColored(item[i].rect.x, item[i].rect.y, item[i].label, color);
			}

			else if (item[i].selectable)
			{
				text->write(item[i].rect.x, item[i].rect.y, item[i].label);
			}

			else if (item[i].greyed)
			{
				text->writeColored(item[i].rect.x, item[i].rect.y, item[i].label, colorGreyed);
			}

			else
			{ // No seleccionable
				if ((item[i].linkedUp) && (i == selector.index + 1))
				{
					const color_t color = {selector.itemColor.r, selector.itemColor.g, selector.itemColor.b};
					text->writeColored(item[i].rect.x, item[i].rect.y, item[i].label, color);
				}
				else // No enlazado con el de arriba
				{
					text->write(item[i].rect.x, item[i].rect.y, item[i].label);
				}
			}
		}
	}
}

// Establece el rectangulo de fondo del menu y el selector
void Menu::setRectSize(int w, int h)
{
	// Establece el ancho
	if (w == 0)
	{ // Si no se pasa un valor, se busca si hay uno prefijado
		if (this->w == 0)
		{ // Si no hay prefijado, coge el item mas ancho
			rectBG.rect.w = findWidth() + text->getCharacterSize();
		}
		else
		{ // Si hay prefijado, coge ese
			rectBG.rect.w = this->w;
		}
	}
	else
	{ // Si se pasa un valor, se usa y se prefija
		rectBG.rect.w = w;
		this->w = w;
	}

	// Establece el alto
	if (h == 0)
	{ // Si no se pasa un valor, se busca de manera automatica
		rectBG.rect.h = findHeight() + text->getCharacterSize();
	}
	else
	{ // Si se pasa un valor, se aplica
		rectBG.rect.h = h;
	}

	// La posición X es la del menú menos medio caracter
	if (this->w != 0)
	{ // Si el ancho esta prefijado, la x coinccide
		rectBG.rect.x = x;
	}
	else
	{ // Si el ancho es automatico, se le da un poco de margen
		rectBG.rect.x = x - (text->getCharacterSize() / 2);
	}

	// La posición Y es la del menu menos la altura de medio caracter
	rectBG.rect.y = y - (text->getCharacterSize() / 2);

	// Establecemos los valores del rectangulo del selector a partir de los valores del rectangulo de fondo
	setSelectorPos(selector.index);
}

// Establece el color del rectangulo de fondo
void Menu::setBackgroundColor(color_t color, int alpha)
{
	rectBG.color = color;
	rectBG.a = alpha;
}

// Establece el color del rectangulo del selector
void Menu::setSelectorColor(color_t color, int alpha)
{
	selector.color = color;
	selector.a = alpha;
}

// Establece el color del texto del selector
void Menu::setSelectorTextColor(color_t color)
{
	selector.itemColor = color;
}

// Centra el menu respecto un punto en el eje X
void Menu::centerMenuOnX(int value)
{
	isCenteredOnX = true;
	if (value != 0)
	{
		centerX = value;
	}
	else if (centerX == 0)
	{
		return;
	}

	// Establece la nueva posición centrada en funcion del elemento más ancho o del ancho fijo del menu
	if (w != 0)
	{ // Si se ha definido un ancho fijo
		x = (centerX) - (w / 2);
	}
	else
	{ // Si se actua en función del elemento más ancho
		x = (centerX) - (findWidth() / 2);
	}

	// Actualiza el rectangulo de fondo y del selector
	rectBG.rect.x = x;
	selector.rect.x = x;

	// Reposiciona los elementos del menu
	for (auto &i : item)
	{
		i.rect.x = x;
	}

	// Recalcula el rectangulo de fondo
	setRectSize();

	// Vuelve a centrar los elementos si fuera el caso
	if (areElementsCenteredOnX)
	{
		centerMenuElementsOnX();
	}
}

// Centra el menu respecto un punto en el eje Y
void Menu::centerMenuOnY(int value)
{
	isCenteredOnY = true;
	centerY = value;

	// Establece la nueva posición centrada en funcion del elemento más ancho
	y = (value) - (findHeight() / 2);

	// Reposiciona los elementos del menu
	replaceElementsOnY();

	// Recalcula el rectangulo de fondo
	setRectSize();
}

// Centra los elementos del menu en el eje X
void Menu::centerMenuElementsOnX()
{
	areElementsCenteredOnX = true;

	for (auto &i : item)
	{
		i.rect.x = (centerX - (i.rect.w / 2));
	}
}

// Añade un item al menu
void Menu::addItem(std::string text, int hPaddingDown, bool selectable, bool greyed, bool linkedDown, bool visible)
{
	item_t temp;

	if (item.empty())
	{ // Si es el primer item coge la posición en el eje Y del propio menu
		temp.rect.y = y;
	}
	else
	{ // En caso contrario, coge la posición en el eje Y a partir del último elemento
		temp.rect.y = item.back().rect.y + item.back().rect.h + item.back().hPaddingDown;
	}

	temp.rect.x = x;
	temp.hPaddingDown = hPaddingDown;
	temp.selectable = selectable;
	temp.greyed = greyed;
	temp.linkedDown = linkedDown;
	temp.visible = visible;

	item.push_back(temp);

	setItemCaption(item.size() - 1, text);

	if (item.size() > 1)
	{
		if (item[item.size() - 2].linkedDown)
		{
			item.back().linkedUp = true;
		}
	}

	centerX = x + (findWidth() / 2);
	reorganize();
}

// Elimina un item del menu
void Menu::removeItem(int index)
{
	/* POR HACER */
}

// Cambia el texto de un item
void Menu::setItemCaption(int index, std::string text)
{
	item[index].label = text;
	item[index].rect.w = this->text->lenght(item[index].label);
	item[index].rect.h = this->text->getCharacterSize();
	reorganize();
}

// Establece el indice del itemm que se usará por defecto al cancelar el menu
void Menu::setDefaultActionWhenCancel(int item)
{
	defaultActionWhenCancel = item;
}

// Gestiona la entrada de teclado y mando durante el menu
void Menu::checkInput()
{
	if (input->checkInput(input_up, REPEAT_FALSE))
	{
		if (decreaseSelectorIndex())
		{
			if (soundMove)
			{
				JA_PlaySound(soundMove);
			}
		}
	}

	if (input->checkInput(input_down, REPEAT_FALSE))
	{
		if (increaseSelectorIndex())
		{
			if (soundMove)
			{
				JA_PlaySound(soundMove);
			}
		}
	}

	if (input->checkInput(input_accept, REPEAT_FALSE))
	{
		itemSelected = selector.index;
		if (soundAccept)
		{
			JA_PlaySound(soundAccept);
		}
	}

	if (input->checkInput(input_cancel, REPEAT_FALSE))
	{
		itemSelected = defaultActionWhenCancel;
		if (soundCancel)
		{
			JA_PlaySound(soundCancel);
		}
	}
}

// Calcula el ancho del menu
int Menu::findWidth()
{
	return getWidestItem();
}

// Calcula el alto del menu
int Menu::findHeight()
{
	int height = 0;

	// Obtenemos la altura de la suma de alturas de los items
	for (auto &i : item)
	{
		height += i.rect.h + i.hPaddingDown;
	}

	return height - item.back().hPaddingDown;
}

// Recoloca los elementos del menu en el eje Y
void Menu::replaceElementsOnY()
{
	item[0].rect.y = y;

	for (int i = 1; i < (int)item.size(); i++)
	{
		item[i].rect.y = item[i - 1].rect.y + item[i - 1].rect.h + item[i - 1].hPaddingDown;
	}
}

// Establece el estado seleccionable de un item
void Menu::setSelectable(int index, bool value)
{
	item[index].selectable = value;
}

// Establece el estado agrisado de un item
void Menu::setGreyed(int index, bool value)
{
	item[index].greyed = value;
}

// Establece el estado de enlace de un item
void Menu::setLinkedDown(int index, bool value)
{
	item[index].linkedDown = value;
}

// Establece el estado de visibilidad de un item
void Menu::setVisible(int index, bool value)
{
	item[index].visible = value;
}

// Calcula la altura del selector
int Menu::getSelectorHeight(int value)
{
	if (item[value].linkedDown)
	{
		return item[value].rect.h + item[value].hPaddingDown + item[value + 1].rect.h;
	}
	else
	{
		return item[value].rect.h;
	}
}

// Establece el nombre del menu
void Menu::setName(std::string name)
{
	this->name = name;
}

// Establece la posición del menu
void Menu::setPos(int x, int y)
{
	this->x = x;
	this->y = y;
}

// Establece el tipo de fondo del menu
void Menu::setBackgroundType(int value)
{
	backgroundType = value;
}

// Establece la fuente de texto que se utilizará
void Menu::setText(std::string font_png, std::string font_txt)
{
	if (!text)
	{
		text = new Text(asset->get(font_png), asset->get(font_txt), renderer);
	}
}