#include "mungal4.h"
#pragma hdrstop

#include "l4plasmascreen.h"
#include "l4app.h"

namespace
{
	const char plasmaWindowClass[] = "RPPlasmaScreen";

	LRESULT CALLBACK
		PlasmaWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		PlasmaScreen *screen =
			(PlasmaScreen *) GetWindowLongPtrA(hwnd, GWLP_USERDATA);

		switch (message)
		{
		case WM_PAINT:
			if (screen != NULL)
			{
				// handled below through the friend-free public surface:
				// the window procedure only asks the screen to repaint.
				PAINTSTRUCT ps;
				HDC hdc = BeginPaint(hwnd, &ps);
				// PlasmaScreen stores what StretchDIBits needs in
				// window properties to keep the class decoupled.
				const BITMAPINFO *bmi =
					(const BITMAPINFO *) GetPropA(hwnd, "PlasmaBitmapInfo");
				const void *pixels = GetPropA(hwnd, "PlasmaPixels");
				RECT client;
				GetClientRect(hwnd, &client);
				if (bmi != NULL && pixels != NULL)
				{
					SetStretchBltMode(hdc, COLORONCOLOR);
					StretchDIBits(
						hdc,
						0, 0, client.right, client.bottom,
						0, 0, PlasmaScreen::plasmaWidth, PlasmaScreen::plasmaHeight,
						pixels, bmi, DIB_RGB_COLORS, SRCCOPY
					);
				}
				EndPaint(hwnd, &ps);
				return 0;
			}
			break;

		case WM_CLOSE:
			// The glass is part of the cockpit; just hide it.
			ShowWindow(hwnd, SW_HIDE);
			return 0;

		case WM_ERASEBKGND:
			return 1;
		}
		return DefWindowProcA(hwnd, message, wParam, lParam);
	}
}

//########################################################################
//############################ PlasmaScreen ##############################
//########################################################################

PlasmaScreen *PlasmaScreen::activeInstance = NULL;

void
	PlasmaScreen::Position(void *parent, int x, int y, int client_width, int client_height)
{
	if (activeInstance == NULL || activeInstance->window == NULL)
	{
		return;
	}

	HWND glass = (HWND) activeInstance->window;
	int window_w = client_width;
	int window_h = client_height;

	if (parent != NULL)
	{
		// fold the glass into the cockpit window as a chrome-less pane
		SetWindowLongPtrA(glass, GWL_STYLE, WS_CHILD | WS_VISIBLE);
		SetParent(glass, (HWND) parent);
	}
	else
	{
		DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
		RECT bounds;
		bounds.left = 0;
		bounds.top = 0;
		bounds.right = client_width;
		bounds.bottom = client_height;
		AdjustWindowRect(&bounds, style, FALSE);
		window_w = bounds.right - bounds.left;
		window_h = bounds.bottom - bounds.top;
	}

	SetWindowPos(
		glass, NULL,
		x, y,
		window_w,
		window_h,
		SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED | SWP_SHOWWINDOW
	);
}

void
	PlasmaScreen::Hide()
{
	if (activeInstance != NULL && activeInstance->window != NULL)
	{
		ShowWindow((HWND) activeInstance->window, SW_HIDE);
	}
}

PlasmaScreen::PlasmaScreen():
	Video8BitBuffered(plasmaWidth, plasmaHeight)
{
	Check_Pointer(this);

	window = NULL;
	bitmapInfo = NULL;

	scale = 4;
	const char *scale_string = getenv("L4PLASMASCALE");
	if (scale_string != NULL)
	{
		int requested = atoi(scale_string);
		if (requested >= 1 && requested <= 16)
		{
			scale = requested;
		}
	}

	//---------------------------------------------------------------
	// Build the 8bpp DIB header with the plasma-orange palette:
	// index 0 is the dark glass, everything else lights up.
	//---------------------------------------------------------------
	BITMAPINFOHEADER *header = (BITMAPINFOHEADER *) new char[
		sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)];
	memset(header, 0, sizeof(BITMAPINFOHEADER));
	header->biSize = sizeof(BITMAPINFOHEADER);
	header->biWidth = plasmaWidth;
	header->biHeight = -plasmaHeight;		// top-down, matching PixelMap8
	header->biPlanes = 1;
	header->biBitCount = 8;
	header->biCompression = BI_RGB;
	header->biClrUsed = 256;

	RGBQUAD *palette = (RGBQUAD *)((char *) header + sizeof(BITMAPINFOHEADER));
	for (int i = 0; i < 256; ++i)
	{
		if (i == 0)
		{
			palette[i].rgbRed = 24; palette[i].rgbGreen = 10; palette[i].rgbBlue = 4;
		}
		else
		{
			palette[i].rgbRed = 255; palette[i].rgbGreen = 144; palette[i].rgbBlue = 32;
		}
		palette[i].rgbReserved = 0;
	}
	bitmapInfo = header;

	CreateGlassWindow();

	Tell("PlasmaScreen: on-screen plasma display, scale x" << scale << "\n");
}

PlasmaScreen::~PlasmaScreen()
{
	Check_Pointer(this);

	if (activeInstance == this)
	{
		activeInstance = NULL;
	}
	if (window != NULL)
	{
		RemovePropA((HWND) window, "PlasmaBitmapInfo");
		RemovePropA((HWND) window, "PlasmaPixels");
		DestroyWindow((HWND) window);
		window = NULL;
	}
	delete [] (char *) bitmapInfo;
	bitmapInfo = NULL;
}

Logical
	PlasmaScreen::TestInstance() const
{
	return valid;
}

void
	PlasmaScreen::CreateGlassWindow()
{
	HINSTANCE instance = GetModuleHandleA(NULL);

	static Logical class_registered = False;
	if (!class_registered)
	{
		class_registered = True;

		WNDCLASSA window_class;
		memset(&window_class, 0, sizeof(window_class));
		window_class.style = CS_HREDRAW | CS_VREDRAW;
		window_class.lpfnWndProc = PlasmaWndProc;
		window_class.hInstance = instance;
		window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
		window_class.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
		window_class.lpszClassName = plasmaWindowClass;
		RegisterClassA(&window_class);
	}

	RECT bounds;
	bounds.left = 0;
	bounds.top = 0;
	bounds.right = plasmaWidth * scale;
	bounds.bottom = plasmaHeight * scale;
	AdjustWindowRect(&bounds, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, FALSE);

	int window_w = bounds.right - bounds.left;
	int window_h = bounds.bottom - bounds.top;

	//---------------------------------------------------------------
	// Default position: directly below the main view (the pod mounts
	// the glass adjacent to the displays). The main window sits at
	// (0,0) with the -res size; clamp to the work area in case the
	// main view fills the screen. L4PLASMAPOS=x,y overrides.
	//---------------------------------------------------------------
	int x = 0;
	int y = (int) L4Application::GetScreenHeight();

	const char *pos_string = getenv("L4PLASMAPOS");
	if (pos_string != NULL)
	{
		int px = 0, py = 0;
		if (sscanf(pos_string, "%d,%d", &px, &py) == 2)
		{
			x = px;
			y = py;
		}
	}
	else
	{
		RECT work;
		if (SystemParametersInfoA(SPI_GETWORKAREA, 0, &work, 0))
		{
			if (y + window_h > work.bottom)
			{
				y = work.bottom - window_h;
			}
			if (y < 0)
			{
				y = 0;
			}
		}
	}

	window = CreateWindowExA(
		0,
		plasmaWindowClass,
		"Plasma Display",
		WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
		x, y,
		window_w,
		window_h,
		NULL, NULL, instance, NULL
	);

	if (window != NULL)
	{
		SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this);
		SetPropA((HWND) window, "PlasmaBitmapInfo", bitmapInfo);
		SetPropA((HWND) window, "PlasmaPixels", pixelBuffer->Data.MapPointer);
		ShowWindow((HWND) window, SW_SHOWNOACTIVATE);
		activeInstance = this;
	}
	else
	{
		DEBUG_STREAM << "PlasmaScreen: window creation failed\n" << std::flush;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Called from the gauge renderer's background task, same slot the serial
// plasma used for streaming. Repaint when any line changed.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Logical
	PlasmaScreen::Update(Logical forceall)
{
	Check(this);

	Logical dirty = forceall;

	for (int y = 0; y < plasmaHeight; ++y)
	{
		if (changedLine[y])
		{
			changedLine[y] = 0;
			dirty = True;
		}
	}

	if (dirty && window != NULL)
	{
		// paint now - queued WM_PAINTs starve behind the game loop's
		// one-message-per-frame pump
		RedrawWindow((HWND) window, NULL, NULL,
			RDW_INVALIDATE | RDW_UPDATENOW);
	}

	return False;		// 'done' - nothing left to stream
}

void
	PlasmaScreen::WaitForUpdate()
{
	Check(this);
}
