#include <string>
#include <vector>
#include <queue>
#include <windows.h>

using std::string;



class AsyncConsole
{
public:
	// Tworzy konsolę
	// Tworzyć tylko pojedynczy obiekt tej klasy.
	AsyncConsole();
	// Zamyka konsolę
	~AsyncConsole();

	// Zmienia tytuł okna konsoli
	void SetTitle(const string &Title);
	// Zmienia bieżący kolor wyjścia
	// Podawać kombinację stałych FOREGROUND_ i BACKGROUND_ z WinAPI
	// udokumentowanych w opisie funkcji SetConsoleTextAttribute.
	void SetOutputColor(WORD Color);
	// Zmienia kolor używany do wejścia
	void SetInputColor(WORD Color);

	// Zapisuje podany tekst na wyjście konsoli
	void Write(const string &s);
	// Zapisuje linijkę tekstu na wyjście konsoli
	void Writeln(const string &s);

	// Zwraca true jeśli kolejka poleceń jest pusta
	bool InputQueueEmpty();
	// Zwraca true i pierwsze polecenie z kolejki, jeśli nie jest pusta
	// Usuwa to polecenie.
	// Jesli kolejka jest pusta, zwraca false.
	bool GetInput(string *s);
	// Czeka na wejście zatrzumując wątek, który to wywoła
	void WaitForInput();
	// Zwraca uchwyt do eventa, żeby można sobie urządzić czekanie na niego we
	// własnym zakresie
	HANDLE GetWaitEvent() { return m_Event; }

private:
	static const DWORD BUFFER_SIZE = 1024;

	HANDLE m_HandleIn;
	HANDLE m_HandleOut;
	CRITICAL_SECTION m_CriticalSection;
	// Niesygnalizowany = kolejka pusta
	// Sygnalizowany = kolejka niepusta
	HANDLE m_Event;
	HANDLE m_ThreadHandle;
	WORD m_InputColor;
	WORD m_OutputColor;
	std::vector<char> m_Buffer;
	std::queue<string> m_Queue;

	static DWORD WINAPI ReadThreadProc_s(void *This);
	DWORD ReadThreadProc();
};

AsyncConsole::AsyncConsole() :
	m_HandleIn(0),
	m_HandleOut(0),
	m_ThreadHandle(0),
	m_Event(0)
{
	m_Buffer.resize(BUFFER_SIZE+1);

	AllocConsole();

	m_HandleIn = GetStdHandle(STD_INPUT_HANDLE);
	m_HandleOut = GetStdHandle(STD_OUTPUT_HANDLE);

	SetInputColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
	SetOutputColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

	InitializeCriticalSection(&m_CriticalSection);
	m_Event = CreateEvent(0, TRUE, FALSE, 0);
	m_ThreadHandle = CreateThread(0, 0, &ReadThreadProc_s, this, 0, 0);
}

AsyncConsole::~AsyncConsole()
{
	TerminateThread(m_ThreadHandle, 0);
	CloseHandle(m_Event);
	DeleteCriticalSection(&m_CriticalSection);
	FreeConsole();
}

void AsyncConsole::SetTitle(const string &Title)
{
	SetConsoleTitle(Title.c_str());
}

void AsyncConsole::SetOutputColor(WORD Color)
{
	m_OutputColor = Color;
}

void AsyncConsole::SetInputColor(WORD Color)
{
	m_InputColor = Color;
	SetConsoleTextAttribute(m_HandleOut, Color);
}
void AsyncConsole::Write(const string &s)
{
	DWORD Foo;

	SetConsoleTextAttribute(m_HandleOut, m_OutputColor);
	WriteConsole(m_HandleOut, s.data(), (DWORD)s.length(), &Foo, 0);
	SetConsoleTextAttribute(m_HandleOut, m_InputColor);
}

void AsyncConsole::Writeln(const string &s)
{
	Write(s);
	Write("\r\n");
}

DWORD WINAPI AsyncConsole::ReadThreadProc_s(void *This)
{
	return ((AsyncConsole*)This)->ReadThreadProc();
}

DWORD AsyncConsole::ReadThreadProc()
{
	DWORD CharactersRead;
	string s;

	while (true)
	{
		ReadConsole(m_HandleIn, &m_Buffer[0], BUFFER_SIZE, &CharactersRead, 0);

		EnterCriticalSection(&m_CriticalSection);
		s.assign(&m_Buffer[0], CharactersRead-2); // -2, bo bez końca wiersza
		m_Queue.push(s);
		s.clear();
		SetEvent(m_Event);
		LeaveCriticalSection(&m_CriticalSection);
	}

	return 0;
}

bool AsyncConsole::InputQueueEmpty()
{
	EnterCriticalSection(&m_CriticalSection);
	bool R = m_Queue.empty();
	LeaveCriticalSection(&m_CriticalSection);
	return R;
}

bool AsyncConsole::GetInput(string *s)
{
	EnterCriticalSection(&m_CriticalSection);
	bool R = m_Queue.empty();
	if (!R)
	{
		*s = m_Queue.front();
		m_Queue.pop();
		if (m_Queue.empty())
			ResetEvent(m_Event);
	}
	LeaveCriticalSection(&m_CriticalSection);
	return !R;
}

void AsyncConsole::WaitForInput()
{
	WaitForSingleObject(m_Event, INFINITE);
}



int WINAPI WinMain(HINSTANCE Instance, HINSTANCE, char *CmdLine, int CmdShow)
{
	AsyncConsole con;
	con.SetTitle("My AsyncConsole Title");
	con.SetOutputColor(FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_BLUE);

	while (true)
	{
		con.Writeln("Sth...");
		Sleep(2000);

		string s;
		while (con.GetInput(&s))
		{
			if (s == "exit")
				return 0;
			else if (s == "wait")
			{
				con.Writeln("Waiting...");
				con.WaitForInput();
			}
			else
				con.Writeln("You have entered: \""+s+"\"");
		}
	}
}

/*
Działanie programu można w każdej chwili przerwać naciskając w konsoli
CTRL+C lub CTRL+BREAK.
*/