/*
 * Stderr - helper class to manipulate console on GUI app (under Windows)
 * Copyright (c) 2008 Marcin 'Malcom' Malich
 * Released under X11/MIT license
 * More info: http://docs.malcom.pl/cpp/shellgui.xhtml
 *
 */

#include <windows.h>
#include <stdlib.h>
#include <tchar.h>
#include "stderr.h"

class Stderr {

	public:
		Stderr() {
			m_stderr = INVALID_HANDLE_VALUE;
			m_history = NULL;
			m_historyLen = 0;
			m_data = NULL;
			m_dataLen = 0;
			m_dataLine = 0;

			m_ok = Init();
		}

		~Stderr() {
			::FreeConsole();
			::FreeLibrary(m_dll);
			delete [] m_history;
			delete [] m_data;
		}

		// is console available?
		bool IsAvailable() const;

		// write text to console
		bool Write(const TCHAR* text, size_t length);

	private:
		bool Init();

		bool m_ok;
		HANDLE m_stderr;

		char* m_history;	// command history on startup app
		int m_historyLen;	// length command history buffer

		char* m_data;		// data between empty line nad current cursor position
		int m_dataLen;		// length data buffer
		int m_dataLine;		// line offset

		HINSTANCE m_dll;

		typedef DWORD (WINAPI *GetConsoleCommandHistoryA_t) (LPSTR sCommands, DWORD nBufferLength, LPSTR sExeName);
		typedef DWORD (WINAPI *GetConsoleCommandHistoryLengthA_t) (LPSTR sExeName);

		GetConsoleCommandHistoryA_t m_pfnGCCH;
		GetConsoleCommandHistoryLengthA_t m_pfnGCCHL;

};

bool Stderr::Init() {

	m_dll = ::LoadLibrary(_T("kernel32.dll"));
	if (m_dll == NULL)
		return false;

	typedef BOOL (WINAPI *AttachConsole_t)(DWORD dwProcessId);
	AttachConsole_t pfnAC = (AttachConsole_t)GetProcAddress(m_dll, "AttachConsole");

	m_pfnGCCH = (GetConsoleCommandHistoryA_t)GetProcAddress(m_dll, "GetConsoleCommandHistoryA");
	m_pfnGCCHL = (GetConsoleCommandHistoryLengthA_t)GetProcAddress(m_dll, "GetConsoleCommandHistoryLengthA");

	if (!pfnAC || !pfnAC(ATTACH_PARENT_PROCESS) || !m_pfnGCCH || !m_pfnGCCHL)
		return false;

	m_stderr = ::GetStdHandle(STD_ERROR_HANDLE);

	if (m_stderr == INVALID_HANDLE_VALUE)
		return false;

	// get command history
	// GetConsoleCommandHistoryLength returned lenght of the internal buffer
	// but GetConsoleCommandHistoryA need and return 2x length, bug in xp/vista?
	m_historyLen = m_pfnGCCHL("cmd.exe") * 2;
	m_history = new char[m_historyLen];
	m_historyLen = m_pfnGCCH(m_history, m_historyLen, "cmd.exe") / 2;

	// command history always exist
    if (m_historyLen == 0)
        return false;

	DWORD ret;
	CONSOLE_SCREEN_BUFFER_INFO csbi;

	if (!GetConsoleScreenBufferInfo(m_stderr, &csbi))
		return false;

	COORD pos;
	pos.X = 0;
	pos.Y = csbi.dwCursorPosition.Y + 1;

	// we may institute that line is empty if first 4 characters are spaces
	char buf[4];
	do {
		pos.Y--;
		if (!ReadConsoleOutputCharacterA(m_stderr, buf, sizeof(buf), pos, &ret))
			return false;
	} while (strncmp("    ", buf, 4) != 0);

	// calculate line offset and length of data
	m_dataLine = csbi.dwCursorPosition.Y - pos.Y;
	m_dataLen = m_dataLine * csbi.dwMaximumWindowSize.X + csbi.dwCursorPosition.X;

	if (m_dataLen > 0) {
		m_data = new char[m_dataLen];
		if (!ReadConsoleOutputCharacterA(m_stderr, m_data, m_dataLen, pos, &ret))
			return false;
	}

	return true;
}


bool Stderr::IsAvailable() const {
	bool available = false;

	if (m_ok) {
		// get command history
		int length = m_pfnGCCHL("cmd.exe") * 2;
		char* history = new char[length];
		length = m_pfnGCCH(history, length, "cmd.exe") / 2;

		// and compare it
		if (m_historyLen == length && memcmp(m_history, history, length) == 0)
			available = true;

		delete [] history;
	}

	return available;
}

bool Stderr::Write(const TCHAR* text, size_t length) {
	DWORD ret;

	CONSOLE_SCREEN_BUFFER_INFO csbi;

	// get current position
	if (!GetConsoleScreenBufferInfo(m_stderr, &csbi))
		return false;

	// and calcuate new position (where is empty line)
	csbi.dwCursorPosition.X = 0;
	csbi.dwCursorPosition.Y -= m_dataLine;

	if (!SetConsoleCursorPosition(m_stderr, csbi.dwCursorPosition))
		return false;

	if (!FillConsoleOutputCharacter(m_stderr, ' ', m_dataLen, csbi.dwCursorPosition, &ret))
		return false;


	WriteConsole(m_stderr, text, static_cast<DWORD>(length), &ret, 0);

	// write \n only if last string char isn't \n
	if (_tcsncmp(text+length-1, _T("\n"), 1) != 0)
		WriteConsole(m_stderr, _T("\n"), 1, &ret, 0);

	WriteConsoleA(m_stderr, m_data, m_dataLen, &ret, 0);

	return true;
}

