/*  Sarien - A Sierra AGI resource interpreter engine
 *  Copyright (C) 1999-2001 Stuart George and Claudio Matsuoka
 *  
 *  $Id: wince.cpp,v 1.3.2.1 2001/11/10 12:13:54 cmatsuoka Exp $
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; see docs/COPYING for further details.
 */

/*
 * Win32 port by Felipe Rosinha <rosinha@helllabs.org>
 */

/*
 * Massively modified by Vasyl Tsvirkunov <vasyl@pacbell.net> for
 * Pocket PC/WinCE port
 */

#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#include <commctrl.h>
#include <aygshell.h>
#include "gx.h"

#include "sarien.h"
#include "graphics.h"
#include "keyboard.h"
#include "console.h"
#include "text.h"
/* Prevent name conflict */
#undef snprintf
#include "win32.h"

#include "resource.h"

#define USE_F_KEYS
#define SMOOTH

static LPTSTR szAppName = TEXT("Pocket Sarien");
static GXDisplayProperties gxdp;
static GXKeyList gxkl;
static bool active = true;

static HWND hwndMB = NULL;

typedef unsigned char UBYTE;
static UBYTE* screen;
#ifdef SMOOTH
static UBYTE* palRed;
static UBYTE* palGreen;
static UBYTE* palBlue;
#else
static unsigned short* pal;
#endif

static bool bmono;
static bool b565;
static bool b555;

static UBYTE invert = 0;
static int colorscale = 0;

#define COLORCONV565(r,g,b) (((r&0xf8)<<(11-3))|((g&0xfc)<<(5-2))|((b&0xf8)>>3))
#define COLORCONV555(r,g,b) (((r&0xf8)<<(10-3))|((g&0xf8)<<(5-2))|((b&0xf8)>>3))
#define COLORCONVMONO(r,g,b) ((((3*r>>3)+(g>>1)+(b>>3))>>colorscale)^invert)

static int	wince_init_vidmode	(void);
static int	wince_deinit_vidmode(void);
static void	wince_put_block		(int, int, int, int);
static void	wince_put_pixels	(int, int, int, BYTE *);
static int	wince_keypress		(void);
static int	wince_get_key		(void);
static void	wince_new_timer		(void);

LRESULT CALLBACK WindowProc(HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam);

#ifdef USE_F_KEYS
void setup_extra_windows(HWND hwndTop);
void adjust_extra_windows();
HWND hwndFKeys;
#endif

static struct gfx_driver gfx_wince =
{
	wince_init_vidmode,
	wince_deinit_vidmode,
	wince_put_block,
	wince_put_pixels,
	wince_new_timer,
	wince_keypress,
	wince_get_key
};


extern "C" struct gfx_driver *gfx;

int init_machine(int argc, char **argv)
{
	gfx = &gfx_wince;
	return err_OK;
}

int deinit_machine()
{
	return err_OK;
}

void set_palette(int ent, UBYTE r, UBYTE g, UBYTE b)
{
	if (ent >= 256)
		return;
#ifdef SMOOTH
	palRed[ent] = r;
	palGreen[ent] = g;
	palBlue[ent] = b;
#else
	if(b565)
		pal[ent] = COLORCONV565(r,g,b);
	else if(b555)
		pal[ent] = COLORCONV555(r,g,b);
	else if(bmono)
		pal[ent] = COLORCONVMONO(r,g,b);
#endif
}


static int wince_init_vidmode()
{
	WNDCLASS wc;
	
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = WindowProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = GetModuleHandle(NULL);
	wc.hIcon = (HICON)LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_SARIEN));
	wc.hCursor = NULL;
	wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	wc.lpszMenuName = NULL;
	wc.lpszClassName = szAppName;
	RegisterClass(&wc);
	
	hwndMain = CreateWindow(szAppName,
		szAppName,
		WS_VISIBLE,
		0,
		0,
		GetSystemMetrics(SM_CXSCREEN),
		GetSystemMetrics(SM_CYSCREEN),
		NULL,
		NULL,
		GetModuleHandle(NULL),
		NULL);
	
	if(!hwndMain)
		return err_Unk;

	SHMENUBARINFO smbi;
	smbi.cbSize = sizeof(smbi); 
	smbi.hwndParent = hwndMain; 
	smbi.dwFlags = 0; 
	smbi.nToolBarId = IDM_MENU; 
	smbi.hInstRes = GetModuleHandle(NULL); 
	smbi.nBmpId = 0; 
	smbi.cBmpImages = 0; 
	smbi.hwndMB = NULL;
	BOOL res = SHCreateMenuBar(&smbi);
	hwndMB = smbi.hwndMB;
	
	screen = new UBYTE[GFX_WIDTH*GFX_HEIGHT];
#ifdef SMOOTH
	palRed = new UBYTE[256];
	palGreen = new UBYTE[256];
	palBlue = new UBYTE[256];
	if(!palRed || !palGreen || !palBlue)
		return err_Unk;
#else
	pal = new unsigned short[256];
	if(!pal)
		return err_Unk;
#endif

	if(!screen)
		return err_Unk;

	memset(screen, 0, GFX_WIDTH*GFX_HEIGHT);

	GXOpenDisplay(hwndMain, GX_FULLSCREEN);
	GXOpenInput();

	SetWindowPos(hwndMain, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
	SetForegroundWindow(hwndMain);
	SHFullScreen(hwndMain, SHFS_SHOWSIPBUTTON);
	SHFullScreen(hwndMain, SHFS_HIDETASKBAR);

	gxdp = GXGetDisplayProperties();
	gxkl = GXGetDefaultKeys(GX_NORMALKEYS);


	bmono = (gxdp.ffFormat & kfDirect) && (gxdp.cBPP <= 8);
	b565 = (gxdp.ffFormat & kfDirect565) != 0;
	b555 = (gxdp.ffFormat & kfDirect555) != 0;
	if(bmono)
	{
		if(gxdp.ffFormat & kfDirectInverted)
			invert = (1<<gxdp.cBPP)-1;
		colorscale = gxdp.cBPP < 8 ? 8-gxdp.cBPP : 0;
	}

	int i;
	for(i=0; i<16; i++)
		set_palette(i, palette[i*3+0]<<2, palette[i*3+1]<<2, palette[i*3+2]<<2);
	for(i=17; i<256; i++)
		set_palette(i, 0, 255, 0);

#ifdef USE_F_KEYS
	setup_extra_windows(hwndMain);
#endif

	return err_OK;
}

static void INLINE process_events()
{
	MSG msg;
	while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

static int wince_deinit_vidmode(void)
{
	GXCloseInput();
	GXCloseDisplay();

	delete[] screen;
#ifdef SMOOTH
	delete[] palRed;
	delete[] palGreen;
	delete[] palBlue;
#else
	delete[] pal;
#endif


	PostMessage(hwndMain, WM_QUIT, 0, 0);
	return err_OK;
}


#define TICK_SECONDS		18
#define TICK_IN_MSEC		(1000 / (TICK_SECONDS))

static void wince_new_timer()
{
	DWORD	now;
	static DWORD last = 0;

	now = GetTickCount();

	while (now - last < TICK_IN_MSEC)
	{
		Sleep (TICK_IN_MSEC - (now - last));
		now = GetTickCount ();
	}
	last = now;

	process_events ();
}

#define REPEATED_KEYMASK	(1<<30)
#define RELEASED_KEYMASK	(1<<31)
#define KEY_QUEUE_SIZE		16

static struct{
	int start;
	int end;
	int queue[KEY_QUEUE_SIZE];
} g_key_queue = { 0, 0 };


#define key_enqueue(k) do {				\
	g_key_queue.queue[g_key_queue.end++] = (k);	\
	g_key_queue.end %= KEY_QUEUE_SIZE;		\
} while (0)

#define key_dequeue(k) do {				\
	(k) = g_key_queue.queue[g_key_queue.start++];	\
	g_key_queue.start %= KEY_QUEUE_SIZE;		\
} while (0)


static int wince_keypress(void)
{
	process_events();
	return (g_key_queue.start != g_key_queue.end);
}

static int wince_get_key(void)
{
	int k;
	while (!wince_keypress())
		wince_new_timer();
	key_dequeue(k);
	return k;
}


static void wince_put_pixels(int x, int y, int w, BYTE *p)
{
	memcpy(screen+x+y*GFX_WIDTH, p, w);
}

static void wince_put_block(int x1, int y1, int x2, int y2)
{
	if(!active)
		return;

/* Somehow this function gets called with unchecked bounds causing visual artefacts */
	if(x2 > 319)
		x2 = 319;
	if(y2 > 199)
		y2 = 199;

	static UBYTE *src;
	static UBYTE *dst;

	static UBYTE *scraddr;
	static UBYTE *scr_ptr;
	
	static UBYTE *scr_ptr_limit;
	static UBYTE *src_limit;
	
	static long pixelstep;
	static long linestep;

// Special code is used to deal with packed pixels in monochrome mode
	static UBYTE bitmask;
	static int   bitshift;

#ifdef SMOOTH
	x1 &= ~3;
#endif

	scr_ptr = screen + x1 + y1*GFX_WIDTH;
	scr_ptr_limit = screen + x2 + y2*GFX_WIDTH;

	linestep = gxdp.cbyPitch;
	pixelstep = gxdp.cbxPitch;

	if(bmono)
	{
		if(pixelstep == 0)
			return; // unsupported screen geometry
	// this will work on mono iPAQ and @migo, don't know about any others
		bitshift = 0;
		bitmask = (1<<gxdp.cBPP)-1;
		linestep = (pixelstep > 0) ? -1 : 1;
	}


	scraddr = (UBYTE*)GXBeginDraw();
	if(scraddr)
	{
		if(bmono)
		{
		// Some partial pixel voodoo. I don't know if this works in all cases.
			scraddr += (x1*3/4)*pixelstep;
			int full = (y1*gxdp.cBPP)>>3;
			int partial = (y1*gxdp.cBPP)&7;
			scraddr += full*linestep;
			bitshift += gxdp.cBPP*partial;
			bitmask <<= gxdp.cBPP*partial;
			if(linestep < 0)
				scraddr += (pixelstep-1);
		}
		else
			scraddr += (x1*3/4)*pixelstep + y1*linestep;
		src_limit = scr_ptr + x2-x1+1;

		while(scr_ptr < scr_ptr_limit)
		{
			src = scr_ptr;
			dst = scraddr;
			while(src < src_limit)
			{
#ifdef SMOOTH
			/* Let's see how fast that CPU is */
				UBYTE r, g, b;
				r = (3*palRed[*(src+0)] + palRed[*(src+1)])>>2;
				g = (3*palGreen[*(src+0)] + palGreen[*(src+1)])>>2;
				b = (3*palBlue[*(src+0)] + palBlue[*(src+1)])>>2;

				if(b565)
					*(unsigned short*)dst = COLORCONV565(r,g,b);
				else if(b555)
					*(unsigned short*)dst = COLORCONV555(r,g,b);
				else if(bmono)
					*dst = (*dst & ~bitmask) | (COLORCONVMONO(r,g,b)<<bitshift);

				dst += pixelstep;

				r = (palRed[*(src+1)] + palRed[*(src+2)])>>1;
				g = (palGreen[*(src+1)] + palGreen[*(src+2)])>>1;
				b = (palBlue[*(src+1)] + palBlue[*(src+2)])>>1;

				if(b565)
					*(unsigned short*)dst = COLORCONV565(r,g,b);
				else if(b555)
					*(unsigned short*)dst = COLORCONV555(r,g,b);
				else if(bmono)
					*dst = (*dst & ~bitmask) | (COLORCONVMONO(r,g,b)<<bitshift);

				dst += pixelstep;

				r = (palRed[*(src+2)] + 3*palRed[*(src+3)])>>2;
				g = (palGreen[*(src+2)] + 3*palGreen[*(src+3)])>>2;
				b = (palBlue[*(src+2)] + 3*palBlue[*(src+3)])>>2;

				if(b565)
					*(unsigned short*)dst = COLORCONV565(r,g,b);
				else if(b555)
					*(unsigned short*)dst = COLORCONV555(r,g,b);
				else if(bmono)
					*dst = (*dst & ~bitmask) | (COLORCONVMONO(r,g,b)<<bitshift);

				dst += pixelstep;

				src += 4;
#else
				if((unsigned long)src & 3)
				{
					if(bmono)
						*dst = ((*dst)&~bitmask)|(pal[*src]<<bitshift);
					else
						*(unsigned short*)dst = pal[*src];
					dst += pixelstep;
				}
				src ++;
#endif
			}
			if(bmono)
			{
				bitshift += gxdp.cBPP;
				if(bitshift >= 8)
				{
					bitshift = 0;
					bitmask = (1<<gxdp.cBPP)-1;
					scraddr += linestep;
				}
				else
					bitmask <<= gxdp.cBPP;
			}
			else
				scraddr += linestep;
			scr_ptr += GFX_WIDTH;
			src_limit += GFX_WIDTH;
		}

		GXEndDraw();
	}
}


LRESULT CALLBACK WindowProc(HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
	HDC          hDC;
	PAINTSTRUCT  ps;
	int          key = 0;
	static		 SHACTIVATEINFO sai;
	int x, y;
	RECT		 rc;

	switch (nMsg)
	{
	case WM_CREATE:
		memset(&sai, 0, sizeof(sai));
		SHSipPreference(hwnd, SIP_INPUTDIALOG);
		return 0;
	case WM_DESTROY:
		wince_deinit_vidmode ();
		exit (-1);
		return 0;
	case WM_ERASEBKGND:
		{
			GetClientRect(hwnd, &rc);
			rc.top = 200;
			hDC = GetDC(hwnd);
			if(rc.top < rc.bottom)
				FillRect(hDC, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));
			ReleaseDC(hwnd, hDC);
		}
		return 1;
	case WM_PAINT:
		hDC = BeginPaint (hwndMain, &ps);
		EndPaint (hwndMain, &ps);
		SHSipPreference(hwndMain, SIP_UP); /* Hack! */
#ifdef USE_F_KEYS
		adjust_extra_windows();
#endif
		/* It does not happen often but I don't want to see tooltip traces */
		wince_put_block(0, 0, 319, 199);
		return 0;
	case WM_ACTIVATE:
		if(!active)
		{
			active = true;
			GXResume();
		}
		SHHandleWMActivate(hwnd, wParam, lParam, &sai, SHA_INPUTDIALOG);
#ifdef USE_F_KEYS
		adjust_extra_windows();
#endif
		return 0;
	case WM_HIBERNATE:
		if(active)
		{
			active = false;
			GXSuspend();
		}
		return 0;
	case WM_SETTINGCHANGE:
		SHHandleWMSettingChange(hwnd, wParam, lParam, &sai);
#ifdef USE_F_KEYS
		adjust_extra_windows();
#endif
		return 0;
	case WM_COMMAND:
		switch(wParam)
		{
		case IDC_ABOUT:
			message_box("Pocket Sarien R4 (" __DATE__ ")\n\n"
						"Ported by Vasyl Tsvirkunov\n"
						"http://pocketatari.\n"
						"               retrogames.com\n\n"
						"Visit Sarien homepage at\n"
						"http://sarien.sourceforge.net");
			break;
		case IDC_EXIT:
			DestroyWindow(hwndMain);
			break;
		}
		return 0;

	/* Multimedia functions
	 * (Damn! The CALLBACK_FUNCTION parameter doesn't work!)
	 */
	/* VT: I think it works but I don't care to test at this moment */
	case MM_WOM_DONE:
		flush_sound ((PWAVEHDR) lParam);
		return 0;

	case WM_LBUTTONDOWN:
		if(HIWORD(lParam)<200)
		{
			key = BUTTON_LEFT;
			mouse.button = TRUE;
			mouse.x = LOWORD(lParam)*4/3;
			mouse.y = HIWORD(lParam);
		}
		break;

	case WM_RBUTTONDOWN:
		if(HIWORD(lParam)<200)
		{
			key = BUTTON_RIGHT;
			mouse.button = TRUE;
			mouse.x = LOWORD(lParam)*4/3;
			mouse.y = HIWORD(lParam);
		}
		break;

	case WM_LBUTTONUP:
	case WM_RBUTTONUP:
		mouse.button = FALSE;
		return 0;

	case WM_MOUSEMOVE:
		if(HIWORD(lParam)<200)
		{
			mouse.x = LOWORD(lParam)*4/3;
			mouse.y = HIWORD(lParam);
		}
		return 0;

	case WM_KEYDOWN:
		switch(wParam)
		{
		case VK_UP:
			if(!(lParam & REPEATED_KEYMASK))
				key = KEY_UP;
			break;
		case VK_LEFT:
			if(!(lParam & REPEATED_KEYMASK))
				key = KEY_LEFT;
			break;
		case VK_DOWN:
			if(!(lParam & REPEATED_KEYMASK))
				key = KEY_DOWN;
			break;
		case VK_RIGHT:
			if(!(lParam & REPEATED_KEYMASK))
				key = KEY_RIGHT;
			break;
		case VK_HOME:
			if(!(lParam & REPEATED_KEYMASK))
			key = KEY_UP_LEFT;
				break;
		case VK_PRIOR:
			if(!(lParam & REPEATED_KEYMASK))
				key = KEY_UP_RIGHT;
			break;
		case VK_NEXT:
			if(!(lParam & REPEATED_KEYMASK))
				key = KEY_DOWN_RIGHT;
			break;
		case VK_END:
			if(!(lParam & REPEATED_KEYMASK))
				key = KEY_DOWN_LEFT;
			break;
		case VK_F1:
			key = 0x3b00;
			break;
		case VK_F2:
			key = 0x3c00;
			break;
		case VK_F3:
			key = 0x3d00;
			break;
		case VK_F4:
			key = 0x3e00;
			break;
		case VK_F5:
			key = 0x3f00;
			break;
		case VK_F6:
			key = 0x4000;
			break;
		case VK_F7:
			key = 0x4100;
			break;
		case VK_F8:
			key = 0x4200;
			break;
		case VK_F9:
			key = 0x4300;
			break;
		case VK_F10:
			key = 0x4400;
			break;
		case VK_SNAPSHOT:
			key = KEY_PRIORITY;
			break;
		default:
			if(key == gxkl.vkA)
			{
				key = KEY_ENTER;
				break;
			}
			else if(key == gxkl.vkB)
			{
				key = KEY_ESCAPE;
				break;
			}
#ifdef USE_CONSOLE
			else if(key == gxkl.vkC)
			{
				key = CONSOLE_ACTIVATE_KEY;
				break;
			}
#endif
		};
		break;

	case WM_CHAR:
		if(!(lParam & RELEASED_KEYMASK)) /* pressed? */
		{
			key = (int)wParam;
			if(key == '\\')
				key = KEY_ESCAPE; /* menu */
		}
		else
			return 0;
		break;
	};

	/* Keyboard message handled */
	if (key) {
		key_enqueue (key);
		return 0;
	}

	return DefWindowProc (hwnd, nMsg, wParam, lParam);
}

#ifdef USE_F_KEYS
/* Window for F-key input. Hack but some people asked for it. */
LRESULT CALLBACK FWindowProc(HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
	switch (nMsg)
	{
	case WM_CREATE:
		return 0;
	case WM_PAINT:
		{
			HDC          hDC;
			PAINTSTRUCT  ps;
			RECT		 rc;
			hDC = BeginPaint(hwnd, &ps);
			GetClientRect(hwnd, &rc);
			HGDIOBJ oldPen = SelectObject(hDC, (HGDIOBJ)GetStockObject(BLACK_PEN));
			HGDIOBJ oldBr = SelectObject(hDC, (HGDIOBJ)GetStockObject(WHITE_BRUSH));
			HGDIOBJ oldFont = SelectObject(hDC, (HGDIOBJ)GetStockObject(SYSTEM_FONT));
			int rcWidth = rc.right-rc.left;
			RECT rcItem;
			rcItem.top = rc.top;
			rcItem.bottom = rc.bottom;
			POINT pts[2];
			pts[0].y = rc.top;
			pts[1].y = rc.bottom;
			TCHAR text[4];
			for(int i=0; i<10; i++)
			{
				wsprintf(text, TEXT("F%d"), i+1);
				rcItem.left = rc.left+rcWidth*i/10;
				rcItem.right = rc.left+rcWidth*(i+1)/10;
				pts[0].x = pts[1].x = rcItem.right;
				Polyline(hDC, pts, 2);
				DrawText(hDC, text, -1, &rcItem, DT_CENTER|DT_VCENTER);
			}
			SelectObject(hDC, oldPen);
			SelectObject(hDC, oldBr);
			SelectObject(hDC, oldFont);
			EndPaint(hwnd, &ps);
		}
		return 0;
	case WM_LBUTTONDOWN:
		{
			int x = LOWORD(lParam);
			RECT rc; GetWindowRect(hwnd, &rc);
			int fnum = x*10/(rc.right-rc.left);
			PostMessage(hwndMain, WM_KEYDOWN, VK_F1+fnum, 0);
		}
		return 0;
	}

	return DefWindowProc(hwnd, nMsg, wParam, lParam);
}

void setup_extra_windows(HWND hwndTop)
{
	LPTSTR fkeyname = TEXT("fkeys");

	WNDCLASS wc;
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = FWindowProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = GetModuleHandle(NULL);
	wc.hIcon = NULL;
	wc.hCursor = NULL;
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName = NULL;
	wc.lpszClassName = fkeyname;
	RegisterClass(&wc);
	
	hwndFKeys = CreateWindow(fkeyname,
		fkeyname,
		WS_VISIBLE|WS_CHILD,
		0,
		200,
		GetSystemMetrics(SM_CXSCREEN),
		20,
		hwndTop,
		(HMENU)100,
		GetModuleHandle(NULL),
		NULL);
}

void adjust_extra_windows()
{
	SIPINFO si;
	si.cbSize = sizeof(SIPINFO);
	SHSipInfo(SPI_GETSIPINFO, 0, &si, 0);
	if(si.fdwFlags & SIPF_ON)
	{
		int bottom = si.rcVisibleDesktop.bottom;
		SetWindowPos(hwndFKeys, 0, 0, 200, GetSystemMetrics(SM_CXSCREEN), bottom-200,
			SWP_NOACTIVATE|SWP_NOOWNERZORDER|SWP_NOZORDER);
	}
}
#endif
