
// Something for later
// 
// #include <windows.h>
// #include <stdio.h>
// 
// void enable_ansi_escape_sequences(void) {
// 	HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
// 	DWORD mode = 0;
// 
// 	if (!GetConsoleMode(hOut, &mode)) {
// 		return; // handle error if needed
// 	}
// 
// 	mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
// 
// 	SetConsoleMode(hOut, mode);
// }
// 
// int main() {
// 	enable_ansi_escape_sequences();
// 
// 	printf("\x1b[32mGreen text\x1b[0m\n");
// 	printf("\x1b[31mRed text\x1b[0m\n");
// 
// 	return 0;
// }

#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
#include <conio.h>
#include "ConsoleHandler.h"

// For special cursor positioning
static bool consoleInit = false;
static HANDLE hStdout, hStdin;
static CONSOLE_SCREEN_BUFFER_INFO csbi;
static WORD wOldColorAttrs;			// Save the current console colors

static short consoleWidth = 0;
static short consoleHeight = 0;
static short scrollBot = 0;	// set at init: consoleHeight - 1
static short scrollTop = 0;	// set at init: consoleHeight - consoleScrollHeight - 1

void Console_SetColor(WORD color) {
	SetConsoleTextAttribute(hStdout, color);
}
void Console_RestoreColor() {
	SetConsoleTextAttribute(hStdout, wOldColorAttrs);
}


void Console_Init(short Left, short Top, short Width, short Height, short bottomScrollHeight) {
	hStdin = GetStdHandle(STD_INPUT_HANDLE);
	hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
	if (!GetConsoleScreenBufferInfo(hStdout, &csbi)) {
		fprintf(stderr, "Error: Unable to get console buffer info. Code: %lu\n", GetLastError());
		exit(1);
	}
	wOldColorAttrs = csbi.wAttributes;
	scrollBot = Height - 1;
	scrollTop = Height - bottomScrollHeight - 1;



	#if 1
	COORD max = GetLargestConsoleWindowSize(hStdout);
	SHORT w = (Width > max.X) ? max.X : Width;
	SHORT h = (Height > max.Y) ? max.Y : Height;

	SMALL_RECT win = { 0, 0, (SHORT)(w - 1), (SHORT)(h - 1) };
	// Enlarge buffer first (for growth), then set window.
	SetConsoleScreenBufferSize(hStdout, { w, h });
	SetConsoleWindowInfo(hStdout, TRUE, &win);
	#else
	// Desired size (width x height)
	COORD bufferSize = { 0, 0 };
	bufferSize.X = (csbi.dwSize.X < Width) ? Width : csbi.dwSize.X; // columns
	bufferSize.Y = (csbi.dwSize.Y < Height) ? Height : csbi.dwSize.Y;  // rows

	// Step 2: Set new buffer size
	if (!SetConsoleScreenBufferSize(hStdout, bufferSize)) {
		fprintf(stderr, "Error: Unable to set console buffer size. Code: %lu\n", GetLastError());
		
		// figure out why ...
		CONSOLE_SCREEN_BUFFER_INFOEX info;
		info.cbSize = sizeof(info);
		if (!GetConsoleScreenBufferInfoEx(hStdout, &info)) {
			// handle error
		}
		fprintf(stderr, "Width[%u] > info.dwMaximumWindowSize.X[%u] || Height[%u] > info.dwMaximumWindowSize.Y[%u]\n",
			Width, info.dwMaximumWindowSize.X, Height, info.dwMaximumWindowSize.Y);
		fprintf(stderr, "Width[%u] < GetSystemMetrics(SM_CXMIN)[%u] || Height[%u] < GetSystemMetrics(SM_CYMIN)[%u]\n",
			Width, GetSystemMetrics(SM_CXMIN), Height, GetSystemMetrics(SM_CYMIN));		
		exit(1);
	}

	// Step 3: Set final window size to match buffer
	// The final window size we want to set
	SMALL_RECT newWindow = { 0 };
	newWindow.Left = 0;
	newWindow.Top = 0;
	newWindow.Right = bufferSize.X - 1;
	newWindow.Bottom = bufferSize.Y - 1;

	if (!SetConsoleWindowInfo(hStdout, TRUE, &newWindow)) {
		fprintf(stderr, "Error: Unable to set console window size. Code: %lu\n", GetLastError());
		CONSOLE_SCREEN_BUFFER_INFOEX info;
		info.cbSize = sizeof(info);
		if (!GetConsoleScreenBufferInfoEx(hStdout, &info)) {
			// handle error
		}
		fprintf(stderr, "Width[%u] > info.dwMaximumWindowSize.X[%u] || Height[%u] > info.dwMaximumWindowSize.Y[%u]\n",
			Width, info.dwMaximumWindowSize.X, Height, info.dwMaximumWindowSize.Y);
		fprintf(stderr, "Width[%u] < GetSystemMetrics(SM_CXMIN)[%u] || Height[%u] < GetSystemMetrics(SM_CYMIN)[%u]\n",
			Width, GetSystemMetrics(SM_CXMIN), Height, GetSystemMetrics(SM_CYMIN));
		exit(1);
	}
	// end

	Console_SetWindowPosition(Left, Top);
	#endif
}

void Console_Close() {
	SetConsoleTextAttribute(hStdout, wOldColorAttrs);
}

void Console_Cls() {
	COORD coordScreen = { 0, 0 };    // home for the cursor 
	DWORD cCharsWritten;
	DWORD dwConSize;

	// Get the number of character cells in the current buffer. 
	if (!GetConsoleScreenBufferInfo(hStdout, &csbi)) {
		return;
	}
	dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
	// Fill the entire screen with blanks.
	if (!FillConsoleOutputCharacter(hStdout,        // Handle to console screen buffer 
		(TCHAR)' ',     // Character to write to the buffer
		dwConSize,       // Number of cells to write 
		coordScreen,     // Coordinates of first cell 
		&cCharsWritten)) { // Receive number of characters written
		return;
	}
	// Get the current text attribute.
	if (!GetConsoleScreenBufferInfo(hStdout, &csbi)) {
		return;
	}
	// Set the buffer's attributes accordingly.
	if (!FillConsoleOutputAttribute(hStdout,         // Handle to console screen buffer 
		csbi.wAttributes, // Character attributes to use
		dwConSize,        // Number of cells to set attribute 
		coordScreen,      // Coordinates of first cell 
		&cCharsWritten)) { // Receive number of characters written
		return;
	}
	// Put the cursor at its home coordinates.
	SetConsoleCursorPosition(hStdout, coordScreen);
}

void Console_SetWindowPosition(int x, int y) {
	HWND hwnd = GetConsoleWindow();
	if (!hwnd) return;
	// Move the window to (x,y) in screen coordinates without changing size or z-order
	SetWindowPos(hwnd, HWND_TOP, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);
}

void Console_Write(const char *text) {
	DWORD written;
	WriteConsole(hStdout, text, (DWORD)strlen(text), &written, nullptr);
}

void Console_WriteAt(short x, short y, const char *text) {
	DWORD written;
	if (hStdout) {
		Console_SetCursor(x, y);
		WriteConsole(hStdout, text, (DWORD)strlen(text), &written, nullptr);
	} else {
		printf("%s\n", text);
	}
}

void Console_SetCursor(short x, short y) {
	GetConsoleScreenBufferInfo(hStdout, &csbi);
	csbi.dwCursorPosition.X = (short)((x < 0) ? csbi.srWindow.Right + x : x);
	csbi.dwCursorPosition.Y = (short)((y < 0) ? csbi.srWindow.Bottom + 1 + y : y);
	SetConsoleCursorPosition(hStdout, csbi.dwCursorPosition);
}

void Console_ScrollBottomRegion() {
	short topLine = scrollTop + 1;
	short bottomLine = scrollBot;
	GetConsoleScreenBufferInfo(hStdout, &csbi);
	if (topLine < 0) topLine = csbi.srWindow.Bottom + topLine;
	if (bottomLine < 0) bottomLine = csbi.srWindow.Bottom + bottomLine;
	SMALL_RECT scrollRect = { 0, topLine, csbi.dwMaximumWindowSize.X - 1, bottomLine };
	COORD dest = { 0, topLine - 1 };
	CHAR_INFO fill = { ' ', { FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE } };
	ScrollConsoleScreenBuffer(hStdout, &scrollRect, &scrollRect, dest, &fill);
	_putch('\r');	// Cursor to left margin
}

bool Console_AdvanceToNextLineIfNotRoomFor(short x, int scroll) {
	bool advanced = false;
	GetConsoleScreenBufferInfo(hStdout, &csbi);
	if (csbi.dwCursorPosition.X + x > csbi.dwMaximumWindowSize.X) {
		advanced = true;
		if (scroll == 1) {
			Console_ScrollBottomRegion();
		} else {
			csbi.dwCursorPosition.Y++;
		}
		Console_SetCursor(0, csbi.dwCursorPosition.Y);
	}
	return advanced;
}

// Console_SetCursorVisibility
//
//	Set the state of the cursor and provide the prior state so it
//	can be restored
//
// @param[in] bool the desired state, true is visible
// @returns the prior state, true is visible
//
bool Console_SetCursorVisibility(bool visible) {
	CONSOLE_CURSOR_INFO cursorInfo;
	bool priorState = true;

	if (!GetConsoleCursorInfo(hStdout, &cursorInfo)) {
		// printf("***** Error: Unable to get cursor info.\n");
		return priorState;
	}
	priorState = cursorInfo.bVisible;
	cursorInfo.bVisible = visible; // true = show, false = hide
	if (!SetConsoleCursorInfo(hStdout, &cursorInfo)) {
		// std::cerr << "Error: Unable to set cursor info.\n";
	}
	return priorState;
}