#define WINCPP

#include "defs.h"
#include "noctis-d.h"
#include <winuser.h>
#include "win.h"
#include "noctis-ng.h"

#ifdef DDRAWLINKEDIN
#include <initguid.h>
#include "ddraw.h"
#endif

/* 
   ------------------------------------------
	(Start)	Window initialization
   ------------------------------------------
*/

extern void mainA();

int isFullscreen;
HANDLE hInstance;
HANDLE threadHandle;
MSG Message;
WNDCLASS WndClass;
LRESULT CALLBACK WndProc (HWND hWnd, unsigned int iMessage, unsigned int wParam, LONG lParam);
HWND hWnd;
POINT coords;
void PushAMousePressOnStack(Dword add, Dword remove);

int cursorX;
int cursorY;
int cursorChangeX;
int cursorChangeY;
int buttons;
int wndPosX=0;
int wndPosY=0;

unsigned char * Screen8=0;
unsigned char * Screen24=0;
unsigned char * BackScreen24=0;

POINT previousCursorPos;
Bool hasFocus;
Bool exitNow=False;
Bool exited=False;
//If the user drags the window, but doesn't move the mouse into the window proper, then we shouldn't reposition the mouse when the window loses focus.
Bool mouseMoved=False;	//Since we don't get mouseMoved if the cursor is over the title bar, but we can get focus, we use this to note that.

Bool shiftHeld=False;
Bool ctrlHeld=False;
Bool altHeld=False;
Bool mouseStackLocked=False;
Bool keyStackLocked=False;

#define CM_FILE_EXIT 101

//#define USEDIRECTX
#ifdef USEDIRECTX
	#include "windx.cpp"
#else
	#include "winwin.cpp"
#endif

void error(char *msg) {
	MessageBox(NULL, "Error!", msg, MB_OK);
}

DWORD WINAPI RunMain(LPVOID lpParameter) {
	mainA();
	CloseHandle(threadHandle);
	return 0;
}

void WinLoop() {
	while (!exited) {
		WinCycle();
		Sleep(1);
	}
}

void WinCycle() {
	//printf("WinCycle\n");
	int retVal=0;
	while (PeekMessage(&Message, hWnd, 0, 0, PM_NOREMOVE)==TRUE) {
		//printf("In while loop.\n");
		BOOL Running=GetMessage(&Message, hWnd, 0, 0);
		DispatchMessage(&Message);
		if (exited) {
			return;
		}
	}
	return;
}

LRESULT CALLBACK WndProc( HWND hWnd, unsigned int iMessage, unsigned int wParam, LONG lParam) {
	//printf("WndProc\n");
	if (exited) return DefWindowProc(hWnd,(UINT)iMessage,(WPARAM)wParam,(LPARAM)lParam);
	//printf("Handling iMessage=%u wParam=%u lParam=%u HWND=%i\n", iMessage, wParam, lParam, hWnd);
	switch (iMessage) {
		//132 = WM_NCHITTEST
		//32 = WM_SETCURSOR
		//512 = WM_MOUSEFIRST
		case (WM_ACTIVATEAPP): {
			if (wParam) {
				hasFocus=True;
				GetCursorPos(&previousCursorPos);
				mouseMoved=False;
			} else {
				if (hasFocus) {
					hasFocus=False;
					if (mouseMoved) {
						SetCursorPos(previousCursorPos.x, previousCursorPos.y);
					}
				}
			}
			break;
		} case (WM_MOVE): {
			wndPosX=LOWORD(lParam);
			wndPosY=HIWORD(lParam);
			coords.x=cursorX;
			coords.y=cursorY;
			ClientToScreen(hWnd, &coords);
			break;
		//} case (WM_NCHITTEST): {
		//	goto default;
		//} case (WM_SETCURSOR): {
		//	goto default;
		} case (WM_COMMAND): {
			switch (LOWORD(wParam)) {
				case (CM_FILE_EXIT): {
					//The user wishes to exit
					exitNow=True;
					break;
				}
			}
			break;
		} case (WM_MBUTTONDOWN): {  //Show the cursor again if the user hits middle mouse button.
			//buttons=buttons&~1;
			ShowCursor(TRUE);
			break;
		} case (WM_LBUTTONUP): {
			//buttons=buttons&~1;
			PushAMousePressOnStack(0, 1);
			break;
		} case (WM_RBUTTONUP): {
			//buttons=buttons&~2;
			//printf("WM_RBUTTONUP\n");
			PushAMousePressOnStack(0, 2);
			break;
		} case (WM_LBUTTONDBLCLK):
		  case (WM_LBUTTONDOWN): {
			PushAMousePressOnStack(1, 0);
			//buttons=buttons|1;
			break;
		} case (WM_RBUTTONDBLCLK):
			//printf("WM_RBUTTONDBLCLK\n");
		  case (WM_RBUTTONDOWN): {
			//printf("WM_RBUTTONDOWN\n");
			PushAMousePressOnStack(2, 0);
			//buttons=buttons|2;
			break;
		} case (WM_MOUSEMOVE): {
			if (hasFocus) {
				int cX=GET_X_LPARAM(lParam);
				int cY=GET_Y_LPARAM(lParam);
				//printf("New pos=(%i,%i) Old pos=(%i,%i)\n", cX, cY, cursorX, cursorY);
				if (cX==cursorX && cY==cursorY) {
					break;
				}
				cursorChangeX+=cX-cursorX;
				cursorChangeY+=cY-cursorY;
				SetCursorPos(coords.x, coords.y);
				mouseMoved=True;
			}
			break;
		} case (WM_SYSKEYDOWN): {
			TranslateMessage(&Message);	//this always returns true, so checking the return value is pointless.
			KeyPressed(wParam, (lParam>>24)&0x1);
			break;
		} case (WM_SYSKEYUP): {
			KeyReleased(wParam, (lParam>>24)&0x1);
			break;
		} case (WM_KEYDOWN): {
			printf("KEYDOWN %i", wParam);
			//printf("KEYDOWN\n");
			//if (!TranslateMessage(&Message)) {
				//printf("Not Translated\n");
			TranslateMessage(&Message);	//this always returns true, so checking the return value is pointless.
			KeyPressed(wParam, (lParam>>24)&0x1);
			//}
			break;
		} case (WM_KEYUP): {
			KeyReleased(wParam, (lParam>>24)&0x1);
			break;
		} case (WM_CHAR): {
			printf("CHAR %i", wParam);
			//printf("CHAR\n");
			CharPressed(wParam, (lParam>>24)&0x1);
			break;
		} case (WM_DESTROY): {
			NgCleanup();
			ExitToWindows();
			break;
		} default: {
			//printf("Unhandled message %u\n", (unsigned int) iMessage);
			return DefWindowProc(hWnd,(UINT)iMessage,(WPARAM)wParam,(LPARAM)lParam);
			break;
		}
	}
	return 0;
}
		
//Splits a string into words (one at a time), includes ""ed stuff as one word.
//Failed is an out param, and search is modified by this function - note, not only is the pointer 'search'
//modified, but the string it points to is modified as well (to place \0s to actually split the string).
//
//That is, if you call this like so: char *nextWord = GetNextWordGroup(&word, ' ', '\"', &failed);
char* GetNextWordGroup(char **search, char stopat, char group, int* failed) {
	if (*search==NULL) {
		return NULL;
	}
	int inGroup = 0;
	register char *str=*search;
	while (*str!=stopat || inGroup) {
		if (*str==0) {
			if (inGroup) {
				*failed=1;
			}
			return NULL;
		} else if (*str==group) {
			inGroup=1-inGroup;
		}
		str++;
	}
	*(str++)=0;	//snip
	return str;
}

int WinMain (HINSTANCE instance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow) {
	isFullscreen = 0;
	//printf("lpszCmdParam=%s\n",lpszCmdParam);
	int fullscreen = 0;
	if (strlen(lpszCmdParam)>0) {
		char *word = lpszCmdParam;
		char *nextWord = word;
		int failed = 0;
		while (nextWord != NULL) {
			nextWord = GetNextWordGroup(&word, ' ', '\"', &failed);
			if (strcmpi("--fullscreen", word)==0) {
				fullscreen = 1;
			} else  {
				MessageBox(NULL, "Valid arguments: --fullscreen", "Note", MB_OK);
			}
		}
	}
	
	hInstance=instance;
	WndClass.style=0;
	WndClass.lpfnWndProc=WndProc;
	WndClass.cbClsExtra=0;
	WndClass.cbWndExtra=0;
	WndClass.hInstance=hInstance;
	//WndClass.hIcon=LoadIcon(hInstance,NULL);
	WndClass.hIcon=ExtractIcon(hInstance, "../Normal GO!.EXE", 0);
	WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
	WndClass.hbrBackground=GetStockObject(GRAY_BRUSH);
	WndClass.lpszMenuName=NULL;
	WndClass.lpszClassName="WNoctisIVCE";
	if (!RegisterClass(&WndClass)) {
		//error
		MessageBox(NULL, "Unable to register class 'WNoctisIVCE' with Windows.", "Error", MB_OK|MB_ICONERROR);
		return 0;
	}
	WinInit(fullscreen);
	threadHandle = CreateThread(NULL, 0, RunMain, NULL, 0, NULL);
	hasFocus = True;
	WinLoop();

	return 0;
}
void WinInit(int fullscreen) {
	WinInitB(fullscreen);	//in winwin.cpp or windx.cpp

	ShowCursor(FALSE);

	coords.x=cursorX;
	coords.y=cursorY;
	ClientToScreen(hWnd, &coords);
	SetCursorPos(coords.x, coords.y);
	cursorChangeX=0; cursorChangeY=0;
	buttons=0;
	Screen8 = new Uchar[sc_bytes];
	//void (__cdecl *) winThread (void*) = WinRun;


}

void WinClear() {
	memcpy(Screen8, 0, WIDTH*HEIGHT);

}



Uchar * WinGetScreen() {
	return Screen8;
}

void ExitToWindows() {
	if (isFullscreen) {
		RestoreDisplayMode();
	}
	/*if (paletteHandle!=NULL) {
		DeleteObject(paletteHandle);
	} else {
		MessageBox(NULL, "paletteHandle is null!", "Eek.", MB_OK);
	}*/
	if (bitmapHandle!=NULL) {
		DeleteBitmap(bitmapHandle);
	} else {
		MessageBox(NULL, "bitmapHandle is null!", "Eek.", MB_OK);
	}
	if (backBitmapHandle!=NULL) {
		DeleteBitmap(backBitmapHandle);
	} else {
		MessageBox(NULL, "bitmapHandle is null!", "Eek.", MB_OK);
	}
	if (WndClass.hIcon!=NULL) DestroyIcon(WndClass.hIcon);
	/*if (BitmapInfo!=NULL) {
		delete BitmapInfo;
	} else {
		MessageBox(NULL, "BitmapInfo is null!", "Eek.", MB_OK);
	}
	if (LogicalPalette!=NULL) {
		delete LogicalPalette;	//BUG: It's crashing here.
	} else {
		MessageBox(NULL, "LogicalPalette is null!", "Eek.", MB_OK);
	}*/
	
	if (Screen8!=NULL) delete [] Screen8;
	exited=true;
}

/* 
   ------------------------------------------
	(End)	Window initialization
   ------------------------------------------
*/

/* 
   ------------------------------------------
	(Start)	Circular FIFO key stack
   ------------------------------------------
*/

#define MAXKEYSINSTACK 10
#define MAXHELDKEYAMOUNT 100

int keyStackStart=0;
int keyStackEnd=0;
int lastKeyStackPos=0;
int keyStackSize=0;

Uword keyStack[MAXKEYSINSTACK];
Uword keyAmt[MAXKEYSINSTACK];
//Bool keyIsChar[MAXKEYSINSTACK];
//Bool keyIsExtended[MAXKEYSINSTACK];

int keyStackPushInstances=0;
void KLock() {
	while (keyStackLocked > 0) {
		Sleep(1);
	}
	keyStackLocked += 1;
}

void KUnlock() {
	keyStackLocked -= 1;
}

void MLock() {
	while (mouseStackLocked > 0) {
		Sleep(1);
	}
	mouseStackLocked += 1;
}

void MUnlock() {
	mouseStackLocked -= 1;
}
/*
	Returns 0 if no keys have been pressed, or the # of the least recent key that was pressed.
*/
Uword WinGetKey() {
	Uword key;
	//Bool isChar;
	//Bool isExtended;
	KLock();
	PopKey(key);
	KUnlock();
	return key;
}

Bool WinKeyAvailable() {
	KLock();
	if (keyStackSize>0) {
		KUnlock();
		return True;
	} else {
		KUnlock();
		return False;
	}
}

void PopKey(Uword &key) {
	if (keyStackSize>0) {
		if (keyAmt[keyStackStart]>1) {
			keyAmt[keyStackStart]--;
			key=keyStack[keyStackStart];
		} else {
			key=keyStack[keyStackStart];
			keyStackStart++;
			keyStackSize--;
			if (keyStackStart>=MAXKEYSINSTACK) {
				keyStackStart-=MAXKEYSINSTACK;
			}
		}
	} else {
		key=0;
	}
	printf("Popped key %i\n", (int)key);
}

void KeyReleased(Uword wParam, Bool extended) {
	switch(wParam) {
		case 16:	//shift
			shiftHeld = False;
			break;
		case 17:	//ctrl
			ctrlHeld = False;
			break;
		case 18:	//alt
			altHeld = False;
			break;
	}
}

//These two functions (KeyPressed and CharPressed) should be called by WndProc.
void KeyPressed(Uword wParam, Bool extended) {
	TranslateAndPushKey(wParam, extended, false);
}
void CharPressed(Uword wParam, Bool extended) {
	TranslateAndPushKey(wParam, extended, true);
}

void TranslateKey(Uword wParam, Bool extended, Bool isChar, int &key, Bool &precedingZero) {
	if (isChar) {
		printf("Char key: wParam=%i\n", (int)wParam);
		key = wParam;
		precedingZero=0;
	} else {
		precedingZero=1;
		switch(wParam) {
			case 16:	//shift
				shiftHeld = True;
				break;
			case 17:	//ctrl
				ctrlHeld = True;
				break;
			case 18:	//alt
				altHeld = True;
				break;
			case 0x23:	//end
				key=0x4F;
				break;
			case 0x22:	//page down
				if (ctrlHeld) {
					key=0x76;
				} else {
					key=0x51;
				}
				break;
			case 0x21:	//page up
				if (ctrlHeld) {
					key=0x84;
				} else {
					key=0x49;
				}
				break;
			case 0x24:	//home
				key=0x47;
				break;
			case 0x28:	//down arrow
				if (ctrlHeld) {
					key=0x91;
				} else {
					key=0x50;
				}
				break;
			case 0x26:	//up arrow
				if (ctrlHeld) {
					key=0x8d;
				} else {
					key=0x48;
				}
				break;
			case 0x25:	//left arrow
				if (ctrlHeld) {
					key=0x73;
				} else {
					key=0x4B;
				}
				break;
			case 0x27:	//right arrow
				if (ctrlHeld) {
					key=0x74;
				} else {
					key=0x4D;
				}
				break;
			case 0x2e:	//delete
				key=83;
				break;
			case 106:	//ctrl *
				if (ctrlHeld) {
					key=150;
				} else {
					printf("Untranslated key: wParam=%i extended=%i isChar=%i\n", (int)wParam, (int)extended, (int)isChar);
					precedingZero=0;
					key=0;
				}
				break;
			case 107:	//ctrl +
				if (ctrlHeld) {
					key=144;
				} else {
					printf("Untranslated key: wParam=%i extended=%i isChar=%i\n", (int)wParam, (int)extended, (int)isChar);
					precedingZero=0;
					key=0;
				}
				break;
			case 109:	//ctrl -
				if (ctrlHeld) {
					key=142;
				} else {
					printf("Untranslated key: wParam=%i extended=%i isChar=%i\n", (int)wParam, (int)extended, (int)isChar);
					precedingZero=0;
					key=0;
				}
				break;
			case 112:	//F1
				key=0x3B;
				break;
			case 113:	//F2
				key=0x3C;
				break;
			case 114:	//F3
				key=0x3D;
				break;
			case 115:	//F4
				key=0x3E;
				break;
			case 116:	//F5
				key=0x3F;
				break;
			case 117:	//F6
				key=0x40;
				break;
			case 118:	//F7
				key=0x41;
				break;
			case 119:	//F8
				key=0x42;
				break;
			case 120:	//F9s
				key=0x43;
				break;
			case 121:	//F10
				key=0x44;
				break;
			case 122:	//F11
				key=0x85;
				break;
			case 123:	//F12
				key=0x86;
				break;
			default:
				printf("Untranslated key: wParam=%i extended=%i isChar=%i\n", (int)wParam, (int)extended, (int)isChar);
				precedingZero=0;
				key=0;
		}
	}
	/*
	//convert keys to the key-codes NIV expects
	case 0x76:	//118	//ctrl-pgdown
	case 0x91: screenfile = OPENFILE (goesoutputfile, OPEN_RB);	//145	//Must be keypad *
	case 0x84:	//132	//ctrl-pgup
	case 0x8D: goesfile_pos = 0;		//141		//not listed in conio.h
	case 0x74: landing_pt_lon += 3;		//ctrl-right
	case 0x73: landing_pt_lon -= 3;		//ctrl-left
	case 0x91: landing_pt_lat += 3;		//145: unknown	//ctrl-down?
	case 0x8D: landing_pt_lat -= 3;		//141: unknown	//ctrl-up?
	if (w == 134){ hopfind=-hopfind; user_beta = user_beta + 5; snapping=1;
	
	if (w == 83){								// Delete - RAWSNAP  ( also "b")
	if (w==144){ if (brightencnt < 113) { brightencnt = brightencnt + 1; nobrighten=2; }
	if (w==142){ if (brightencnt > 13) { brightencnt = brightencnt - 1;	nobrighten=2; }
	if (w==150){ brightencnt = 63; nobrighten=2; }			
	*/
}

void TranslateAndPushKey(Uword wParam, Bool extended, Bool isChar) {
	Bool precedingZero = False;
	int key = 0;
	TranslateKey(wParam, extended, isChar, key, precedingZero);
	KLock();
	if (precedingZero) PushAKeyOnStack(0);
	if (key!=0) PushAKeyOnStack(key);
	KUnlock();
}

void PushAKeyOnStack(int key) {
	keyStackPushInstances+=1;
	if (keyStackSize>0) {
		//check to see if it can be combined with the last keypress
		if (keyStack[lastKeyStackPos]==key) {
			if (keyAmt[lastKeyStackPos]<MAXHELDKEYAMOUNT) {
				keyAmt[lastKeyStackPos]++;
				//fprintf(stderr, "[KSPI %i] Stacking keypress %i, keyStackEnd is %i.\n", keyStackPushInstances, key, keyStackEnd);
			
			}	//drop the keypress if that key is the last pressed, and has been held down for more than MAXHELDKEYAMOUNT clicks
			//else {
				//fprintf(stderr, "[KSPI %i] Dropping keypress %i.\n", keyStackPushInstances, key);
			//}
			keyStackPushInstances-=1;
			return;
		}
	}
	if (keyStackSize<MAXKEYSINSTACK) {
		//add to stack
		//fprintf(stderr, "[KSPI %i] Storing keypress %i at %i.\n", keyStackPushInstances, key, keyStackEnd);
		keyStack[keyStackEnd]=key;
		lastKeyStackPos = keyStackEnd;
		//fprintf(stderr, "[KSPI %i] For storing %i, incrementing %i.\n", keyStackPushInstances, key, keyStackEnd);
		keyStackEnd++;
		//fprintf(stderr, "[KSPI %i] For storing %i, incremented %i.\n", keyStackPushInstances, key, keyStackEnd);
		if (keyStackEnd>=MAXKEYSINSTACK) {
			keyStackEnd-=MAXKEYSINSTACK;	//loop back to 0
			//fprintf(stderr, "[KSPI %i] Looping next one back to 0.\n", keyStackPushInstances);
		}
		keyStackSize++;
	} else {
		//discard the key - fail silently
		//fprintf(stderr, "[KSPI %i] Discarding keypress %i, keyStackEnd is %i.\n", keyStackPushInstances, key, keyStackEnd);
	}
	keyStackPushInstances-=1;
}

/* 
   ------------------------------------------
	(End)	Circular FIFO key stack
   ------------------------------------------
*/

#define MAXMOUSEPRESSESINSTACK 10

int mousePressStackStart=0;
int mousePressStackEnd=0;
int mousePressStackSize=0;
int lastMouseStackPos=0;

Uword mousePressStack[MAXMOUSEPRESSESINSTACK];

Bool WinMousePressAvailable() {
	if (mousePressStackSize>0) {
		return True;
	} else {
		return False;
	}
}

void PopMousePress(int &nbuttons) {
	if (mousePressStackSize>0) {
		nbuttons = mousePressStack[mousePressStackStart];
		mousePressStackStart++;
		mousePressStackSize--;
		printf("Popped %i from %i - Stack size is now %i\n", nbuttons, mousePressStackStart-1, mousePressStackSize);
		if (mousePressStackStart>=MAXMOUSEPRESSESINSTACK) {
			mousePressStackStart-=MAXMOUSEPRESSESINSTACK;
		}
		
	} else {
		printf("Nothing to pop!\n");
	}
}

void PushAMousePressOnStack(Dword add, Dword remove) {
	MLock();
	int newButtons = buttons;
	if (mousePressStackSize>0) {
		newButtons = mousePressStack[lastMouseStackPos];	//set newButtons to the previous stored buttons state
		printf("Previous buttons state (in %i): %i\n", lastMouseStackPos, newButtons);
	}
	if (add!=0) {
		newButtons=newButtons|add;
	}
	if (remove!=0) {
		newButtons=newButtons&~remove;
	}
	printf("Storing new buttons state %i in %i\n", newButtons, lastMouseStackPos);
	
	if (mousePressStackSize<MAXMOUSEPRESSESINSTACK) {
		//add to stack
		mousePressStack[mousePressStackEnd]=newButtons;
		lastMouseStackPos = mousePressStackEnd;
		mousePressStackEnd++;
		if (mousePressStackEnd>=MAXMOUSEPRESSESINSTACK) mousePressStackEnd-=MAXMOUSEPRESSESINSTACK;	//loop back to 0
		mousePressStackSize++;
	} else {
		//discard the mouse press - fail silently
	}
	printf("Stack size now %i.\n", mousePressStackSize);
	MUnlock();
}

void WinGetMouseStats(Word *xChange, Word *yChange, Word *btns) {
	MLock();
	if (WinMousePressAvailable()) {
		printf("Mouse press available. Buttons was %i\n", buttons);
		PopMousePress(buttons);
		printf("Popped it, buttons is now %i.\n", buttons);
	}
	MUnlock();	
	*xChange=cursorChangeX;
	*yChange=cursorChangeY;
	*btns=buttons;
	cursorChangeX=0;
	cursorChangeY=0;
}
