#include "const.h"
#include "menu.h"

// Constructor
Menu::Menu(SDL_Renderer *renderer, Text *text, Input *input, std::string *fileList)
{
	mRenderer = renderer;
	mText = text;
	mInput = input;
	mFileList = fileList;
}

Menu::~Menu()
{
	mRenderer = nullptr;
	mText = nullptr;
	mInput = nullptr;
	mFileList = nullptr;
}

// Inicializador
void Menu::init(std::string name, int x, int y, int backgroundType)
{
	loadMedia();

	// Inicia variables
	mName = name;
	mSelector.index = 0;
	mTotalItems = 0;
	mItemSelected = MENU_NO_OPTION;
	mPosX = x;
	mPosY = y;
	mRectBG.rect.x = 0;
	mRectBG.rect.y = 0;
	mRectBG.rect.w = 0;
	mRectBG.rect.h = 0;
	mRectBG.r = 0;
	mRectBG.g = 0;
	mRectBG.b = 0;
	mBackgroundType = backgroundType;
	mIsCenteredOnX = false;
	mIsCenteredOnY = false;
	mAreElementsCenteredOnX = false;
	mCenterX = 0;
	mCenterY = 0;
	mWidestItem = 0;
	mColorGreyed = {128, 128, 128};

	// Selector
	mSelector.originY = 0;
	mSelector.targetY = 0;
	mSelector.despY = 0;
	mSelector.originH = 0;
	mSelector.targetH = 0;
	mSelector.incH = 0;
	mSelector.y = 0.0f;
	mSelector.h = 0.0f;
	mSelector.numJumps = 8;
	mSelector.moving = false;
	mSelector.resizing = false;
	mSelector.rect = {0, 0, 0, 0};
	mSelector.r = 0;
	mSelector.g = 0;
	mSelector.b = 0;
	mSelector.a = 255;

	// Elementos del menu
	for (int i = 0; i < MENU_MAX_ITEMS; i++)
	{
		mItem[i].label = "";
		mItem[i].rect = {0, 0, 0, 0};
		mItem[i].hPaddingDown = 0;
		mItem[i].selectable = false;
		mItem[i].greyed = false;
		mItem[i].linkedDown = false;
		mItem[i].linkedUp = false;
	}
}

// Carga los recursos necesarios para la sección 'Title'
bool Menu::loadMedia()
{
	// Indicador de éxito en la carga
	bool success = true;

	// Sonidos
	mSoundMove = JA_LoadSound(mFileList[17].c_str());
	mSoundAccept = JA_LoadSound(mFileList[18].c_str());
	mSoundCancel = JA_LoadSound(mFileList[16].c_str());

	return success;
}

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

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

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

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

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

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

	// Obtenemos la anchura del item mas ancho
	for (int i = 0; i < mTotalItems; i++)
		if (mItem[i].rect.w > result)
			result = mItem[i].rect.w;

	return result;
}

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

// Actualiza el menu para recolocarlo correctamente y establecer el tamaño
void Menu::reorganize()
{
	setRectSize();
	if (mIsCenteredOnX)
		centerMenuOnX(mCenterX);
	if (mIsCenteredOnY)
		centerMenuOnY(mCenterY);
	if (mAreElementsCenteredOnX)
		centerMenuElementsOnX();
}

// Deja el menu apuntando al siguiente elemento
bool Menu::increaseSelectorIndex()
{
	bool success = false;

	// Obten las coordenadas del elemento actual
	mSelector.y = mSelector.originY = mItem[mSelector.index].rect.y;
	mSelector.h = mSelector.originH = getSelectorHeight(mSelector.index);

	// Calcula cual es el siguiente elemento
	++mSelector.index %= mTotalItems;
	while (!mItem[mSelector.index].selectable)
		//mSelector.index++;
		++mSelector.index %= mTotalItems;
	success = true;

	// Establece las coordenadas y altura de destino
	if (success)
	{
		mSelector.targetY = mItem[mSelector.index].rect.y;
		mSelector.despY = (mSelector.targetY - mSelector.originY) / mSelector.numJumps;

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

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

	return success;
}

// Deja el menu apuntando al elemento anterior
bool Menu::decreaseSelectorIndex()
{
	bool success = false;

	// Obten las coordenadas del elemento actual
	mSelector.y = mSelector.originY = mItem[mSelector.index].rect.y;
	mSelector.h = mSelector.originH = getSelectorHeight(mSelector.index);

	// Calcula cual es el siguiente elemento
	if (mSelector.index == 0)
		mSelector.index = mTotalItems;
	else
		mSelector.index--;
	while (!mItem[mSelector.index].selectable)
	{
		if (mSelector.index == 0)
			mSelector.index = mTotalItems;
		else
			mSelector.index--;
	}
	success = true;

	// Establece las coordenadas y altura de destino
	if (success)
	{
		mSelector.targetY = mItem[mSelector.index].rect.y;
		mSelector.despY = (mSelector.targetY - mSelector.originY) / mSelector.numJumps;

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

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

	return success;
}

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

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

	// Renderiza el rectangulo del selector
	SDL_Rect temp = mSelector.rect;
	temp.y--;
	temp.h++;
	SDL_SetRenderDrawColor(mRenderer, mSelector.r, mSelector.g, mSelector.b, mSelector.a);
	SDL_RenderFillRect(mRenderer, &temp);

	// Renderiza el borde del fondo
	if (mBackgroundType == MENU_BACKGROUND_SOLID)
	{
		SDL_SetRenderDrawColor(mRenderer, mRectBG.r, mRectBG.g, mRectBG.b, 255);
		SDL_RenderDrawRect(mRenderer, &mRectBG.rect);
	}

	// Renderitza el texto
	for (int i = 0; i < mTotalItems; i++)
	{
		if (i == mSelector.index)
		{
			const color_t color = {mSelector.itemR, mSelector.itemG, mSelector.itemB};
			mText->writeColored(mItem[i].rect.x, mItem[i].rect.y, mItem[i].label, color);
		}
		else if (mItem[i].selectable)
		{
			mText->write(mItem[i].rect.x, mItem[i].rect.y, mItem[i].label);
		}
		else if (mItem[i].greyed)
		{
			mText->writeColored(mItem[i].rect.x, mItem[i].rect.y, mItem[i].label, mColorGreyed);
		}
		else // No seleccionable
		{
			if ((mItem[i].linkedUp) && (i == mSelector.index + 1))
			{
				const color_t color = {mSelector.itemR, mSelector.itemG, mSelector.itemB};
				mText->writeColored(mItem[i].rect.x, mItem[i].rect.y, mItem[i].label, color);
			}
			else // No enlazado con el de arriba
			{
				mText->write(mItem[i].rect.x, mItem[i].rect.y, mItem[i].label);
			}
		}
	}
}

// Establece el rectangulo de fondo del menu y el selector
void Menu::setRectSize()
{
	mRectBG.rect.w = findWidth() + mText->getCharacterWidth();
	mRectBG.rect.h = findHeight() + mText->getCharacterWidth();

	// La posición X es la del menú menos medio caracter
	mRectBG.rect.x = mPosX - (mText->getCharacterWidth() / 2);

	// La posición Y es la del menu menos la altura de medio caracter
	mRectBG.rect.y = mPosY - (mText->getCharacterWidth() / 2);

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

// Establece el valor de la variable
void Menu::setTotalItems(int num)
{
	mTotalItems = num;
	if (mTotalItems > MENU_MAX_ITEMS)
		mTotalItems = MENU_MAX_ITEMS;
}

// Establece el color del rectangulo de fondo
void Menu::setBackgroundColor(int r, int g, int b, int alpha)
{
	mRectBG.r = r;
	mRectBG.g = g;
	mRectBG.b = b;
	mRectBG.a = alpha;
}

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

// Establece el color del texto del selector
void Menu::setSelectorTextColor(int r, int g, int b)
{
	mSelector.itemR = r;
	mSelector.itemG = g;
	mSelector.itemB = b;
}

// Centra el menu respecto un punto en el eje X
void Menu::centerMenuOnX(int value)
{
	mIsCenteredOnX = true;
	mCenterX = value;

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

	// Reposiciona los elementos del menu
	for (int i = 0; i < MENU_MAX_ITEMS; i++)
		mItem[i].rect.x = mPosX;

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

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

	// Establece la nueva posición centrada en funcion del elemento más ancho
	mPosY = (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()
{
	mAreElementsCenteredOnX = true;

	for (int i = 0; i < mTotalItems; i++)
		mItem[i].rect.x = (mCenterX - (mItem[i].rect.w / 2));
}

// Añade un item al menu
void Menu::addItem(std::string text, Uint8 hPaddingDown, bool selectable, bool greyed, bool linkedDown)
{
	// Si es el primer item coge la posición en el eje Y del propio menu
	if (mTotalItems == 0)
		mItem[mTotalItems].rect.y = mPosY;
	else
		// En caso contrario, coge la posición en el eje Y a partir del elemento anterior
		mItem[mTotalItems].rect.y = mItem[mTotalItems - 1].rect.y + mItem[mTotalItems - 1].rect.h + mItem[mTotalItems - 1].hPaddingDown;

	setItemCaption(mTotalItems, text);
	mItem[mTotalItems].rect.x = mPosX;
	mItem[mTotalItems].hPaddingDown = hPaddingDown;
	mItem[mTotalItems].selectable = selectable;
	mItem[mTotalItems].greyed = greyed;
	mItem[mTotalItems].linkedDown = linkedDown;
	if (mTotalItems > 0)
		if (mItem[mTotalItems - 1].linkedDown)
			mItem[mTotalItems].linkedUp = true;

	setTotalItems(mTotalItems + 1);
	mCenterX = mPosX + (findWidth() / 2);
	reorganize();
}

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

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

// Gestiona la entrada de teclado y mando durante el menu
void Menu::checkInput()
{
	if (mInput->checkInput(INPUT_UP, REPEAT_FALSE))
		if (decreaseSelectorIndex())
			JA_PlaySound(mSoundMove);

	if (mInput->checkInput(INPUT_DOWN, REPEAT_FALSE))
		if (increaseSelectorIndex())
			JA_PlaySound(mSoundMove);

	if (mInput->checkInput(INPUT_ACCEPT, REPEAT_FALSE))
	{
		mItemSelected = mSelector.index;
		JA_PlaySound(mSoundAccept);
	}

	if (mInput->checkInput(INPUT_CANCEL, REPEAT_FALSE))
	{
		mItemSelected = mDefaultActionWhenCancel;
		JA_PlaySound(mSoundCancel);
	}
}

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

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

	// Obtenemos la altura de la suma de alturas de los items
	for (int i = 0; i < mTotalItems; i++)
		height += mItem[i].rect.h + mItem[i].hPaddingDown;

	return height - mItem[mTotalItems - 1].hPaddingDown;
}

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

	for (int i = 1; i < mTotalItems; i++)
		mItem[i].rect.y = mItem[i - 1].rect.y + mItem[i - 1].rect.h + mItem[i - 1].hPaddingDown;
}

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

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

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

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