#include "mungal4.h"
#pragma hdrstop

#include "l4mfdview.h"
#include "l4padrio.h"

namespace
{
	const char mfdViewClass[] = "RPMFDView";

	const int buttonGap = 4;

	//---------------------------------------------------------------
	// Button banks run UNDER the glass: a button reaches this far in
	// from the display edge, but only the indicator strip clears that
	// edge - the rest sits behind the picture, which is what the
	// player actually clicks. Applies to both the MFD strips (depth
	// measured vertically) and the map's side columns (horizontally).
	//---------------------------------------------------------------
	const int buttonDepth = 100;
	const int indicatorStrip = 10;

	//---------------------------------------------------------------
	// Button fill colors by lamp brightness (0 off / 1-2 dim / 3
	// bright), red family for MFD strips, amber for the side columns.
	//---------------------------------------------------------------
	COLORREF ButtonFill(int amber, int level)
	{
		if (amber)
		{
			if (level >= 3)	return RGB(255, 210, 48);
			if (level >= 1)	return RGB(150, 124, 26);
			return RGB(64, 52, 10);
		}
		if (level >= 3)	return RGB(255, 72, 44);
		if (level >= 1)	return RGB(150, 44, 28);
		return RGB(64, 18, 12);
	}

	//---------------------------------------------------------------
	// Lamp byte -> current brightness level, animating flash modes.
	// Low 2 bits: solid/slow/med/fast; bits 2-3: state 1 brightness;
	// bits 4-5: state 2 brightness (the flash alternate).
	//---------------------------------------------------------------
	int LampLevel(int lamp_state)
	{
		int mode = lamp_state & 0x03;
		int level1 = (lamp_state >> 2) & 0x03;
		int level2 = (lamp_state >> 4) & 0x03;

		if (mode == 0)
		{
			return level1;
		}

		static const int half_period[4] = { 0, 500, 250, 125 };
		return ((GetTickCount() / half_period[mode]) & 1) ? level2 : level1;
	}

	LRESULT CALLBACK
		MFDViewWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		MFDSplitView *view =
			(MFDSplitView *) GetWindowLongPtrA(hwnd, GWLP_USERDATA);

		switch (message)
		{
		case WM_PAINT:
			if (view != NULL)
			{
				view->Paint();
				return 0;
			}
			// no instance (e.g. the plain viewscreen child): default paint
			break;

		case WM_LBUTTONDOWN:
			if (view != NULL)
			{
				view->MouseDown((int)(short) LOWORD(lParam), (int)(short) HIWORD(lParam));
				return 0;
			}
			break;

		case WM_LBUTTONUP:
		case WM_CAPTURECHANGED:
			if (view != NULL)
			{
				view->MouseUp();
				return 0;
			}
			break;

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

		case WM_ERASEBKGND:
			if (view != NULL)
			{
				return 1;
			}
			break;		// viewscreen child: erase with the class black brush
		}
		return DefWindowProcA(hwnd, message, wParam, lParam);
	}
}

//########################################################################
//############################ MFDSplitView ##############################
//########################################################################

MFDSplitView::MFDSplitView(
	const char *title,
	int source_width,
	int source_height,
	int display_width,
	int display_height,
	int x,
	int y,
	ButtonStyle button_style,
	int anchorA,
	int anchorB,
	void *parent
)
{
	Check_Pointer(this);

	sourceWidth = source_width;
	sourceHeight = source_height;
	buttonCount = 0;
	pressedIndex = -1;

	pixels = new unsigned long[source_width * source_height];
	memset(pixels, 0, source_width * source_height * sizeof(unsigned long));

	BITMAPINFOHEADER *header = new BITMAPINFOHEADER;
	memset(header, 0, sizeof(BITMAPINFOHEADER));
	header->biSize = sizeof(BITMAPINFOHEADER);
	header->biWidth = source_width;
	header->biHeight = -source_height;		// top-down
	header->biPlanes = 1;
	header->biBitCount = 32;
	header->biCompression = BI_RGB;
	blitHeader = header;

	buttonStyle = button_style;
	buttonAnchorA = anchorA;
	buttonAnchorB = anchorB;

	int client_width = display_width;
	int client_height = display_height;
	LayoutButtons(button_style, anchorA, anchorB,
		display_width, display_height, &client_width, &client_height);
	clientWidth = client_width;
	clientHeight = client_height;

	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 = MFDViewWndProc;
		window_class.hInstance = instance;
		window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
		window_class.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
		window_class.lpszClassName = mfdViewClass;
		RegisterClassA(&window_class);
	}

	DWORD style;
	int window_w, window_h;

	if (parent != NULL)
	{
		// chrome-less pane inside the cockpit window; clips siblings so
		// overlapping panes and the viewscreen never paint over it
		style = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS;
		window_w = client_width;
		window_h = client_height;
	}
	else
	{
		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;
	}

	window = CreateWindowExA(
		0,
		mfdViewClass,
		title,
		style,
		x, y,
		window_w,
		window_h,
		(HWND) parent, NULL, instance, NULL
	);

	if (window != NULL)
	{
		SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this);
		ShowWindow((HWND) window, SW_SHOWNOACTIVATE);
	}
	else
	{
		DEBUG_STREAM << "MFDSplitView: window creation failed (" << title << ")\n" << std::flush;
	}
}

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

	if (window != NULL)
	{
		SetWindowLongPtrA((HWND) window, GWLP_USERDATA, 0);
		DestroyWindow((HWND) window);
		window = NULL;
	}
	delete (BITMAPINFOHEADER *) blitHeader;
	blitHeader = NULL;
	delete [] pixels;
	pixels = NULL;
}

Logical
	MFDSplitView::TestInstance() const
{
	return pixels != NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Compute the display rectangle and the button rectangles, growing the
// client area to make room for the strips.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
	MFDSplitView::LayoutButtons(
		ButtonStyle button_style,
		int anchorA,
		int anchorB,
		int display_width,
		int display_height,
		int *client_width,
		int *client_height
	)
{
	displayX = 0;
	displayY = 0;
	displayW = display_width;
	displayH = display_height;

	if (button_style == MFDStrips)
	{
		//-----------------------------------------------------------
		// 4 buttons above the glass, 4 below, flush to the pane
		// edges (no border against the viewscreen behind); RIO
		// addresses descend from the anchor, row-major (vRIO
		// CockpitLayout::Mfd).
		//
		// The banks reach BEHIND the display: each button is
		// mfdButtonHeight tall but only mfdIndicatorStrip of it
		// clears the glass edge, so the lamp reads as a thin strip
		// and the picture above/below it is the click target. Paint
		// draws the buttons before the MFD image for that reason.
		//-----------------------------------------------------------
		int strip_h = indicatorStrip;
		int button_h = buttonDepth;

		// on a scaled-down cockpit, keep the two banks from meeting
		int limit = (display_height + 2 * strip_h) / 2;
		if (button_h > limit)		button_h = limit;
		if (button_h < strip_h)		button_h = strip_h;

		int button_w = (display_width - 3 * buttonGap) / 4;

		displayY = strip_h;
		*client_height = display_height + 2 * strip_h;

		for (int i = 0; i < 8; ++i)
		{
			int row = i / 4;			// 0 = top bank, 1 = bottom bank
			int col = i % 4;

			ScreenButton *button = &buttons[buttonCount++];
			button->unit = anchorA - i;
			button->amber = 0;
			button->x = col * (button_w + buttonGap);
			button->y = row
				? (displayY + displayH + strip_h - button_h)
				: 0;
			button->w = (col == 3)
				? (display_width - 3 * (button_w + buttonGap))
				: button_w;
			button->h = button_h;
		}
	}
	else if (button_style == SideColumns)
	{
		//-----------------------------------------------------------
		// 6 buttons down each side of the glass (the pod wires only 6
		// of each column's 8 addresses to buttons; the rest are Tesla
		// relays). Left = anchorA ascending, right = anchorB.
		//
		// Same trick as the MFD banks, turned on its side: each
		// button reaches buttonDepth in behind the map and leaves an
		// indicatorStrip clearing the edge, so the lamp reads as a
		// slim column and the map itself is the click target.
		//-----------------------------------------------------------
		int strip_w = indicatorStrip;
		int button_w = buttonDepth;

		// keep the two columns from meeting behind a narrow map
		int limit = (display_width + 2 * strip_w) / 2;
		if (button_w > limit)		button_w = limit;
		if (button_w < strip_w)		button_w = strip_w;

		int button_h = display_height / 6;

		displayX = strip_w;
		*client_width = display_width + 2 * strip_w;

		for (int i = 0; i < 6; ++i)
		{
			int h = (i == 5) ? (display_height - 5 * button_h) : button_h;

			ScreenButton *left = &buttons[buttonCount++];
			left->unit = anchorA + i;
			left->amber = 1;
			left->x = 0;
			left->y = i * button_h;
			left->w = button_w;
			left->h = h;

			ScreenButton *right = &buttons[buttonCount++];
			right->unit = anchorB + i;
			right->amber = 1;
			right->x = displayX + displayW + strip_w - button_w;
			right->y = left->y;
			right->w = button_w;
			right->h = h;
		}
	}
}

void
	MFDSplitView::Paint()
{
	PAINTSTRUCT ps;
	HDC hdc = BeginPaint((HWND) window, &ps);

	RECT client;
	GetClientRect((HWND) window, &client);

	//---------------------------------------------------------------
	// Double-buffered: compose the whole pane off screen and blit it
	// in one operation, otherwise the clear-then-draw sequence
	// flickers at the repaint cadence.
	//---------------------------------------------------------------
	HDC mem = CreateCompatibleDC(hdc);
	HBITMAP surface = CreateCompatibleBitmap(hdc, client.right, client.bottom);
	HBITMAP old_surface = (HBITMAP) SelectObject(mem, surface);

	FillRect(mem, &client, (HBRUSH) GetStockObject(BLACK_BRUSH));

	//---------------------------------------------------------------
	// Buttons first, glass second: the MFD banks extend under the
	// display so the picture is the click target, and the image
	// painted over them leaves only their indicator strips showing.
	//---------------------------------------------------------------
	for (int i = 0; i < buttonCount; ++i)
	{
		const ScreenButton *button = &buttons[i];

		int level = PadRIO::IsActive()
			? LampLevel(PadRIO::GetLampState(button->unit))
			: 0;

		RECT rect;
		rect.left = button->x;
		rect.top = button->y;
		rect.right = button->x + button->w;
		rect.bottom = button->y + button->h;

		HBRUSH fill = CreateSolidBrush(ButtonFill(button->amber, level));
		FillRect(mem, &rect, fill);
		DeleteObject(fill);

		HBRUSH frame = CreateSolidBrush(
			(i == pressedIndex) ? RGB(255, 255, 255) : RGB(48, 48, 48));
		FrameRect(mem, &rect, frame);
		DeleteObject(frame);
	}

	// HALFTONE area-averages the downscale (the compact glass shows the
	// full 640x480 canvas at ~half size); COLORONCOLOR dropped every other
	// row/column, which shredded the 1-bit vector strokes and small text.
	// HALFTONE requires the brush origin be set (MSDN).
	SetStretchBltMode(mem, HALFTONE);
	SetBrushOrgEx(mem, 0, 0, NULL);
	StretchDIBits(
		mem,
		displayX, displayY, displayW, displayH,
		0, 0, sourceWidth, sourceHeight,
		pixels, (const BITMAPINFO *) blitHeader,
		DIB_RGB_COLORS, SRCCOPY
	);

	BitBlt(hdc, 0, 0, client.right, client.bottom, mem, 0, 0, SRCCOPY);

	SelectObject(mem, old_surface);
	DeleteObject(surface);
	DeleteDC(mem);

	EndPaint((HWND) window, &ps);
}

void
	MFDSplitView::MouseDown(int x, int y)
{
	for (int i = 0; i < buttonCount; ++i)
	{
		const ScreenButton *button = &buttons[i];
		if (x >= button->x && x < button->x + button->w &&
			y >= button->y && y < button->y + button->h)
		{
			pressedIndex = i;
			SetCapture((HWND) window);
			PadRIO::SetScreenButton(button->unit, True);
			Repaint();
			return;
		}
	}
}

void
	MFDSplitView::MouseUp()
{
	if (pressedIndex >= 0)
	{
		PadRIO::SetScreenButton(buttons[pressedIndex].unit, False);
		pressedIndex = -1;
		if (GetCapture() == (HWND) window)
		{
			ReleaseCapture();
		}
		Repaint();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Paint NOW rather than queueing an invalidation: the game loop pumps
// one message per frame, and queued WM_PAINTs (lowest priority) starve
// behind it - panes would freeze on their first frame.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
	MFDSplitView::SetPosition(int x, int y)
{
	Check_Pointer(this);
	if (window != NULL)
	{
		SetWindowPos((HWND) window, NULL, x, y, 0, 0,
			SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Re-scale to a new glass size: the pixel buffer keeps its native
// source resolution, so only the destination rectangle, the button
// geometry and the window size change.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
	MFDSplitView::Resize(int display_width, int display_height)
{
	Check_Pointer(this);

	buttonCount = 0;
	pressedIndex = -1;

	int client_width = display_width;
	int client_height = display_height;
	LayoutButtons(buttonStyle, buttonAnchorA, buttonAnchorB,
		display_width, display_height, &client_width, &client_height);
	clientWidth = client_width;
	clientHeight = client_height;

	if (window != NULL)
	{
		SetWindowPos((HWND) window, NULL, 0, 0,
			client_width, client_height,
			SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
	}
}

void
	MFDSplitView::Repaint()
{
	Check_Pointer(this);
	if (window != NULL)
	{
		RedrawWindow((HWND) window, NULL, NULL,
			RDW_INVALIDATE | RDW_UPDATENOW);
	}
}
