// Author: Juan Carlos De Abreu (jcabreur@gmail.com)

#include "console.h"
#include "window_management.h"
#include "../Scene Parser/SceneFile.h"
#include "../Util/Clipboard.h"
#include "../Util/string.h"
#include "../Util/Vector.h"

#include "../Includes/OpenGL.h"

#include "../Scene Parser/SceneDriver.h"

BEGIN_TEG_NAMESPACE();
BEGIN_NAMESPACE(gui);

Console::Console() : Frame(0, 0, 590, 270) {
	lines_.push_back(_T(""));
	type_line_.push_back(INPUT_LINE);

	pos_y_ = 0; pos_x_ = 0;
	copyStartX = 0; copyEndX = 0;
}

Console::Console(int x, int y, int width, int height)
    : Frame(x, y, width, height) {

	lines_.push_back(_T(""));
	type_line_.push_back(INPUT_LINE);

	pos_y_ = 0; pos_x_ = 0;
	copyStartX = 0; copyEndX = 0;
}

Console::~Console() {}

// Method to display the console.
void Console::Render() const {
  glDepthFunc(GL_LEQUAL);

 
  glPushMatrix();
    // 
   // glTranslatef(x, y, 0);
   //glTranslatef(x, SCREEN_HEIGHT - y, 0);

	Frame::Render();


	glColor4f(1, 1, 1, 1);
	
	const float nPixelMarco = 7;
	const float nPixelTitle = 20;

 glColor3f(0,0,0);
    // Prueba para pintar lo que representaria la consola ...
	glTranslatef(x_ + 0.0, height_ - y_ + 0.0f, 0);

	glBegin(GL_QUADS);
      glVertex3f(nPixelMarco + 1.f, -nPixelTitle - nPixelMarco + 0.0f, 0.0f);
      glVertex3f(nPixelMarco + 1.f,  nPixelMarco - height_ + 1.f, 0.0f);
      glVertex3f(width_ - nPixelMarco - 1.f, nPixelMarco - height_ + 1.f, 0.0f);
      glVertex3f(width_ - nPixelMarco - 1.f, -nPixelTitle - nPixelMarco, 0.0f);
    glEnd();

	glEnable(GL_BLEND);

	glTranslatef(12, -35, 0);
//glColor3f(0, 1, 0);

	static const int maxLines = 20;
	static const int maxCharsPerLine = 80;
    const int nLines  = static_cast<int> (lines_.size());

	// Struct to store the color for the each character..
	int color1 = 0;	// Color para texto normal...
	int color2 = 1; // Color para texto seleccionado...
	std::vector<std::vector<int>> colorsTmp;

	// Pre-procesar las lienas para ver como van a quedar
	std::vector<std::string> linesTmp;
	std::vector<bool> typeLineTmp;
	for (int i = 0; i < nLines; ++i) {
		std::string line;
		if (type_line_[i] == INPUT_LINE) line = _T(">>> ");
		line += lines_[i];
	
		if (i == nLines - 1 && cont_ > 30)
			line = line.substr(0, pos_x_ + 4) + _T("_") + line.substr(pos_x_ + 4);

		// To set the color of each character ...
		std::vector<int> colorsi;
		const int cStart = min(copyStartX, copyEndX);
		const int cEnd = max(copyStartX, copyEndX);
		if(i == nLines - 1) {
			for (int j = 0; j < (int)line.size(); ++j)
				colorsi.push_back((j >= cStart + 4 && j < cEnd + 4) ? color2 : color1);
		}
		else {
			for (int j = 0; j < (int)line.size(); ++j)
				colorsi.push_back(color1);
		}
		std::vector<std::vector<int>> colorsInTheLine;


		std::vector<std::string> linesInTheLine;
		int pos = 0;
		while ((pos = line.find('\n', 0)) != -1) {
			linesInTheLine.push_back(line.substr(0, pos));
			//linesInTheLine.push_back(_T(""));
			line = line.substr(pos + 1);

			// The same for the colors.
			colorsInTheLine.push_back(subvector(colorsi, 0, pos));
			//colorsInTheLine.push_back(std::vector<int> ());
			colorsi = subvector(colorsi, pos + 1);
		}
		if (!line.empty()) {
			linesInTheLine.push_back(line);

			// The same for the colors.
			colorsInTheLine.push_back(colorsi);
		}

		for (int k = 0; k < (int)linesInTheLine.size(); ++k) {
			while (linesInTheLine[k].size() > maxCharsPerLine) {
				linesTmp.push_back(linesInTheLine[k].substr(0, maxCharsPerLine));
				typeLineTmp.push_back(type_line_[i]);
				linesInTheLine[k] = linesInTheLine[k].substr(maxCharsPerLine);

				// make the samething for the colors ..
				colorsTmp.push_back(subvector(colorsInTheLine[k], 0, maxCharsPerLine));
				colorsInTheLine[k] = subvector(colorsInTheLine[k], maxCharsPerLine);
			}
			if (linesInTheLine[k].size() > 0) {
				linesTmp.push_back(linesInTheLine[k]);
				typeLineTmp.push_back(type_line_[i]);
				
				// the same for the colors 
				colorsTmp.push_back(colorsInTheLine[k]);
			}
		}
	}

	const int nLinesTmp  = static_cast<int> (linesTmp.size());

	const int initial = nLinesTmp > maxLines ? nLinesTmp - maxLines : 0;
	for (int i = initial; i < nLinesTmp; ++i) {
		if (typeLineTmp[i] == INPUT_LINE) glColor3f(0, 1, 0);
		else glColor3f(0, 0, 1);
		
		if (typeLineTmp[i] == INPUT_LINE)
			printText(0, (i - initial + 1)*11.f, 0, linesTmp[i], colorsTmp[i]);
		else
			printText(0, (i - initial + 1)*11.f, 0, linesTmp[i]);
	}

	glPopMatrix();

	glDisable(GL_BLEND);

  glDepthFunc(GL_LESS);
}

// Method to add a new char.
void Console::addANewChar(_TCHAR c) {
	const int nLines = static_cast<int> (lines_.size());
	//lines[nLines - 1] += toString(c); posX++;
	lines_[nLines - 1] = lines_[nLines - 1].substr(0, pos_x_) + toString(c) + lines_[nLines - 1].substr(pos_x_);
	pos_x_++;
}

// Method to save the last line in a file.
void Console::saveLastLine() {
	// Save the line in a tmp file.
	//FILE* f = fopen("tmp/commandLine.txt", "w");
	FILE* f = fopen("stdin", "w");
	if (f == NULL) {
		PRINT_LOG(TYPE_WARNING, "Cannot save the command line in the tmp file.");
		return;
	}
	//const int nLines = static_cast<int> (lines.size());
	//if (nLines > 0) fprintf(f, "%s", lines[nLines - 3].c_str());
	int lastInputLine = static_cast<int> (lines_.size()) - 2;
	while (lastInputLine >= 0 && type_line_[lastInputLine] == OUTPUT_LINE) --lastInputLine;
	if (lastInputLine >= 0) fprintf(f, "%s\n", lines_[lastInputLine].c_str());
	fclose(f);
}

// Method to execute teh command line.
void Console::execute(void) {
	const int nLines = static_cast<int> (lines_.size());

	// Obtain the file that is proccesing.
	const int prevProcessFile = SceneFileManagement::GetInstance().proccessingFile;
	const int nFiles = static_cast<int> (SceneFileManagement::GetInstance().pathFiles.size());

	SceneFileManagement::GetInstance().add(_T(""));	
	SceneDriver driver;
	driver.parse("stdin");

	if (driver.bError) {
		FILE* f = fopen("stdin", "w");
		if (f != NULL) {
			fprintf(f, ""); fclose(f);
			execute();
		}
	}

	// Set the file index that is proceessing.
	SceneFileManagement::GetInstance().proccessingFile = nFiles;

	// Process the global isnt in the file.
	//SceneFileManagement::GetInstance().sceneFiles[nFiles].apply();
	std::list<SceneFile>::iterator it = SceneFileManagement::GetInstance().sceneFiles.begin();
	for (int i = 0; i < nFiles; ++i) ++it;
	it->apply();
	SceneFileManagement::GetInstance().pathFiles[nFiles] = _T("~tmp") + toString(nLines);

	// Restore the file that was proccessing.
	SceneFileManagement::GetInstance().proccessingFile = prevProcessFile;
}


// Method to add a new line.
void Console::addNewLine() {
	lines_.push_back(_T(""));
	type_line_.push_back(INPUT_LINE);
	pos_x_ = 0;
	pos_y_ = static_cast<int> (lines_.size()) - 1;
	PRINT_LOG(TYPE_EVENT, "The user entry \'%s\' by console", lines_[pos_y_ - 1].c_str());
	// Save the last command line and executed.
	saveLastLine();
	execute();
}

// Method to clear a char.
void Console::clearAChar() {
	const int nLines = static_cast<int> (lines_.size());
	const int nChars = static_cast<int> (lines_[nLines - 1].size());
	//if (nChars > 0) { lines[nLines - 1].resize(nChars - 1); --posX; }

	if (copyStartX != copyEndX)  {
		lines_[nLines - 1] = lines_[nLines - 1].substr(0, min(copyStartX, copyEndX)) + lines_[nLines - 1].substr(max(copyStartX, copyEndX));
		if (copyStartX < copyEndX) pos_x_ = copyStartX;
		else pos_x_ = copyEndX;
		copyStartX = copyEndX = pos_x_;
	}
	else if (pos_x_ > 0) {
		lines_[nLines - 1] = lines_[nLines - 1].substr(0, pos_x_ - 1) + lines_[nLines - 1].substr(pos_x_);
		--pos_x_;
	}
}

// 
void Console::keyUp(void) {
	const int nLines = static_cast<int> (lines_.size());
	lines_[nLines - 1] = lines_[pos_y_];
	//if (pos > 0) --pos;
	
	pos_x_ = lines_[pos_y_].size();

	int tmp = pos_y_;
	while (--tmp > 0 && type_line_[tmp] == OUTPUT_LINE);
	if (tmp >= 0 && type_line_[tmp] == INPUT_LINE) { pos_y_ = tmp;  }
}

// 
void Console::keyDown(void) {
	const int nLines = static_cast<int> (lines_.size());
	lines_[nLines - 1] = lines_[pos_y_];
	//if (pos < nLines - 1) ++pos;

	pos_x_ = lines_[pos_y_].size();

	int tmp = pos_y_;
	while (++tmp < nLines - 1 && type_line_[tmp] == OUTPUT_LINE);
	if (tmp < nLines - 1 && type_line_[tmp] == INPUT_LINE) { pos_y_ = tmp; }
}

void Console::keyLeft(void) {
	if (pos_x_ > 0) pos_x_--;
	copyStartX = copyEndX = pos_x_;
}

void Console::keyRight(void) {
	const int nLines = static_cast<int> (lines_.size());
	const int nChars = static_cast<int> (lines_[nLines - 1].size());
	if (pos_x_ < nChars) pos_x_++;
	copyStartX = copyEndX = pos_x_;
}


void Console::addMessage(const std::tstring& msg) {
	const int nLines = static_cast<int> (lines_.size());

	// Copy the actual input as last line.
	lines_.push_back(lines_[nLines - 1]);
	type_line_.push_back(INPUT_LINE);

	// Update the output
	lines_[nLines - 1] = msg;
	type_line_[nLines - 1] = OUTPUT_LINE;

	pos_y_ = nLines;
}

void Console::keyEnd() {
	const int nLines = static_cast<int> (lines_.size());
	pos_x_ = static_cast<int> (lines_[nLines - 1].size());
	copyStartX = copyEndX = pos_x_;
}

void Console::keyHome() {
	pos_x_ = 0;
	copyStartX = copyEndX = pos_x_;
}

// 
void Console::paste() {
	if (copyStartX != copyEndX) clearAChar();
	std::tstring text, tmp; CLIPBOARD_PASTE(tmp);
	// Remove any \n character from the input.
	for (int i = 0; i < (int)tmp.size(); ++i) if (tmp[i] != '\r') text += tmp[i];

	const int nLines = static_cast<int> (lines_.size());
	const int nText = static_cast<int> (text.size());
	lines_[nLines - 1] = lines_[nLines - 1].substr(0, pos_x_) + text + lines_[nLines - 1].substr(pos_x_);
	pos_x_ += nText;
}

void Console::keyShiftLeft() {
	if (copyEndX > 0) copyEndX--;
	pos_x_ = copyEndX;
}

void Console::keyShiftRight() {
	const int nLines = static_cast<int> (lines_.size());
	const int nChars = static_cast<int> (lines_[nLines - 1].size());
	if (copyEndX < nChars) copyEndX++;
	pos_x_ = copyEndX;
}

// 
void Console::copy() {
	if (copyStartX == copyEndX) return;
	const int nLines = static_cast<int> (lines_.size());
	const std::tstring text = lines_[nLines - 1].substr(min(copyStartX, copyEndX), max(copyStartX, copyEndX));
	CLIPBOARD_COPY(text);
}

void Console::cut() {
	if (copyStartX == copyEndX) return;
	const int nLines = static_cast<int> (lines_.size());
	const std::tstring text = lines_[nLines - 1].substr(min(copyStartX, copyEndX), max(copyStartX, copyEndX));
	CLIPBOARD_COPY(text);
	lines_[nLines - 1] = lines_[nLines - 1].substr(0, min(copyStartX, copyEndX)) + lines_[nLines - 1].substr(max(copyStartX, copyEndX));
	if (copyEndX > copyStartX) pos_x_ -= copyEndX - copyStartX;

	copyStartX = copyEndX = pos_x_;
}

void Console::keyCtrlLeft(void) {
	const int nLines = static_cast<int> (lines_.size());
	const int startPos = pos_x_ - 1;
	for (int i = startPos; i >= 0; --i) {
		if (!(lines_[nLines - 1][i] >= 'A' && lines_[nLines - 1][i] <= 'Z') &&
			!(lines_[nLines - 1][i] >= 'a' && lines_[nLines - 1][i] <= 'z') &&
			lines_[nLines - 1][i] != '_') {
			pos_x_ = i; break;
		}
	}
	if (pos_x_ - 1 == startPos) pos_x_ = 0;
	copyStartX = copyEndX = pos_x_;
}

void Console::keyCtrlRight(void) {
	const int nLines = static_cast<int> (lines_.size());
	const int nChars = static_cast<int> (lines_[nLines - 1].size());
	const int startPos = pos_x_ + 1;
	for (int i = startPos; i < nChars; ++i) {
		if (!(lines_[nLines - 1][i] >= 'A' && lines_[nLines - 1][i] <= 'Z') &&
			!(lines_[nLines - 1][i] >= 'a' && lines_[nLines - 1][i] <= 'z') &&
			lines_[nLines - 1][i] != '_') {
			pos_x_ = i; break;
		}
	}
	if (pos_x_ + 1 == startPos) pos_x_ = nChars;
	copyStartX = copyEndX = pos_x_;
}

// Method to process the keys.
void Console::process(int key) {
	if (key == VK_SHIFT) {
		copyStartX = copyEndX = pos_x_;
		KEY[key] = TRUE;
	}
	else if (KEY[VK_CONTROL] && key == 'V') paste();
	else if (KEY[VK_CONTROL] && key == 'C') copy();
	else if (KEY[VK_CONTROL] && key == 'X') cut();
	else if (key >= 'A' && key <= 'Z') {
		if (KEY[VK_CAPITAL] || KEY[VK_SHIFT]) addANewChar(key);
		else addANewChar(key - 'A' + 'a');
	}
	else if (key >= '0' && key <= '9') {
		static const _TCHAR specialKey[] = _T("=!\"·$%&/()");
		if (KEY[VK_SHIFT]) addANewChar(specialKey[key - '0']);
		else addANewChar(key);
	}
	else if (key >= VK_NUMPAD0 && key <= VK_NUMPAD9) {
		addANewChar(key - VK_NUMPAD0 + '0');
	}
	else if (key >= VK_MULTIPLY && key <= VK_DIVIDE) {
		static const _TCHAR specialKey[] = _T("*+--./");
		addANewChar(specialKey[key - VK_MULTIPLY]);
	}
	else if (key >= VK_OEM_1 && key <= VK_OEM_3) {
		static const _TCHAR specialKey[]    = _T(";+,-,/`");
		static const _TCHAR altSpecialKey[] = _T(":+,-.?~");

		if (KEY[VK_CONTROL]) addANewChar(specialKey[key - VK_OEM_1]);
		else addANewChar(altSpecialKey[key - VK_OEM_1]);
	}
	else if (key >= VK_OEM_4 && key <= VK_OEM_7) {
		static const _TCHAR specialKey[]    = _T("[\\]'");
		static const _TCHAR altSpecialKey[] = _T("{|}\"");

		if (KEY[VK_CONTROL]) addANewChar(specialKey[key - VK_OEM_4]);
		else addANewChar(altSpecialKey[key - VK_OEM_4]);
	}
	else if (key == VK_OEM_102) {
		if (KEY[VK_SHIFT]) addANewChar('>');
		else addANewChar('<');
	}
	else if (key == VK_SPACE) addANewChar(' ');
	else if (key == VK_UP) keyUp();
	else if (key == VK_DOWN) keyDown();
	else if (key == VK_LEFT) {
		if (KEY[VK_SHIFT]) keyShiftLeft();
		else if (KEY[VK_CONTROL]) keyCtrlLeft();
		else keyLeft();
	}
	else if (key == VK_RIGHT) {
		if (KEY[VK_SHIFT]) keyShiftRight();
		else if (KEY[VK_CONTROL]) keyCtrlRight();
		else keyRight();
	}
	else if (key == VK_BACK) clearAChar();
	else if (key == VK_RETURN) {
		if (KEY[VK_SHIFT]) addANewChar('\n');
		else addNewLine();
	}
	else if (key == VK_END) keyEnd();
	else if (key == VK_HOME) keyHome();
	else if (key == VK_CAPITAL) KEY[key] = !KEY[key];
	else KEY[key] = TRUE; // If so, mark it as TRUE
}

// TODO: Colocar este metodo en  la clase  de texto...
// Que permita imprimir el texto de diferentes maneras por ejemplo seguido en la lineas de abajo...
void Console::printText(float x__, float y__, float z__, std::string str) const {
	// Select the texture to be use
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, FONT_TEXTURE);

	// 
	glPushMatrix();
		// 
		glTranslated(x__, - y__, z__);

		glListBase(FONT_LIST);
		glCallLists(str.size(), GL_BYTE, str.c_str());
	glPopMatrix();

	glBindTexture(GL_TEXTURE_2D, 0);
	glDisable(GL_TEXTURE_2D);

}

// TODO: Colocar este metodo en  la clase  de texto...
// Que permita imprimir el texto de diferentes maneras por ejemplo seguido en la lineas de abajo...
void Console::printText(float x__, float y__, float z__, std::string str, std::vector<int> colors) const {
	// 
	glPushMatrix();
		// 
		glTranslated(x__, - y__, z__);

		glListBase(FONT_LIST);
		for (int i = 0; i < (int)str.size(); ++i) {
			if (colors[i] == 0) glColor3f(0, 1, 0);
			else {
				glColor3f(0, 1, 0);
				glBegin(GL_QUADS);
					glVertex3f(0,0,0);
					glVertex3f(7,0,0);
					glVertex3f(7,14,0);
					glVertex3f(0,14,0);
				glEnd();

				glColor3f(0, 0, 0);
			}

			// Select the texture to be use
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, FONT_TEXTURE);

			glCallLists(1, GL_BYTE, toString(str[i]).c_str());

			glBindTexture(GL_TEXTURE_2D, 0);
			glDisable(GL_TEXTURE_2D);
		}
	glPopMatrix();
}

END_NAMESPACE(gui);
END_TEG_NAMESPACE();
