#define DOUBLESIZE

#include <WindowsX.h>
#define NUM_PALETTE_ENTRIES 256

int GoFullscreen(int width, int height, int tries);

HBITMAP	bitmapHandle, backBitmapHandle;
HPALETTE paletteHandle;

#ifdef DDRAWLINKEDIN

LPDIRECTDRAW7 directDraw = NULL;
#endif

#ifdef DOUBLESIZE
const int BYTEWIDTH = WIDTH*6;
#endif

struct NICEBITMAPINFO {
	BITMAPINFOHEADER	bmiHeader;
	RGBQUAD				bmiColor [ NUM_PALETTE_ENTRIES ];
} BitmapInfo;
struct NICELOGICALPALETTE {
	WORD	palVersion;
	WORD	palNumEntries;
	PALETTEENTRY	palPalEntry [NUM_PALETTE_ENTRIES ];
} LogicalPalette;

void WinInitB(int fullscreen) {
	#ifndef DDRAWLINKEDIN
	if (fullscreen) {
		MessageBox(NULL, "You can't go fullscreen with this build of NICE - DirectDraw wasn't linked in.", "Error", MB_OK|MB_ICONERROR);
		fullscreen=0;
	}
	#endif
	//hWnd=CreateWindowEx(WS_EX_TOPMOST, "WNoctisIVCE", "Noctis IV CE for Windows", WS_POPUP, 0, 0, //WIDTH, HEIGHT,
	
	for (int tries=0; tries<1; tries++) {
		int dwExStyle = 0;
		int dwStyle = 0;
		int width = 0;
		int height = 0;
		#ifdef DOUBLESIZE
		width = WIDTH*2;
		height = HEIGHT*2;
		if (fullscreen) {
			if (tries==0) {
				height = 400;
			} else {
				height = 480;
			}
		}
		#else
		width = WIDTH;
		height = HEIGHT;
		if (fullscreen) {
			if (tries==0) {
				height = 200;
			} else {
				height = 240;
			}
		}
		#endif
		if (fullscreen) {
			dwExStyle = 0;	//WS_EX_TOPMOST
			dwStyle = WS_POPUP;
		} else {
			dwExStyle = WS_EX_WINDOWEDGE|WS_EX_APPWINDOW;	//WS_EX_TOPMOST
			dwStyle = WS_BORDER|WS_CAPTION|WS_CLIPSIBLINGS|WS_SYSMENU|WS_VISIBLE;
			#ifdef DOUBLESIZE
			height+=27*2;
			#else
			height+=27;
			#endif
		}
		
		hWnd=CreateWindowEx(dwExStyle, "WNoctisIVCE", "Noctis IV CE for Windows", dwStyle, 0, 0, width, height, NULL, NULL, hInstance, NULL);
		
		if (hWnd==NULL) {
			Dword err = GetLastError();
			char msg[512];
			sprintf(msg, "Unable to create window. Reason: ");
			FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, err, 0, msg+strlen(msg), 511-strlen(msg), NULL);
			MessageBox(NULL, msg, "Error", MB_OK|MB_ICONERROR);
			exit(0xff);
		}
		
		if (fullscreen) {
			int fsretval = GoFullscreen(width, height, tries);
			if (fsretval==1) {
				break;	//success
			} else if (fsretval==2) {
				return; //unavoidable failure; stay windowed
			} else if (fsretval==0) {	//continue
			}
		}
	}
	//ShowCursor(False);
	RECT wrect;
	GetWindowRect(hWnd, &wrect);
	wndPosX = wrect.left;
	wndPosY = wrect.top;
	#ifdef DOUBLESIZE
	cursorX=WIDTH; cursorY=HEIGHT;
	#else
	cursorX=WIDTH/2; cursorY=HEIGHT/2;
	#endif
	HDC screenDC=GetDC(hWnd);
	//BitmapInfo = new NICEBITMAPINFO;
	//LogicalPalette = new NICELOGICALPALETTE;
	
	BitmapInfo.bmiHeader.biSize		= sizeof(BITMAPINFOHEADER);
	#ifdef DOUBLESIZE
	BitmapInfo.bmiHeader.biWidth 		= WIDTH*2;
	BitmapInfo.bmiHeader.biHeight		= -abs(HEIGHT*2);
	#else
	BitmapInfo.bmiHeader.biWidth 		= WIDTH;
	BitmapInfo.bmiHeader.biHeight		= -abs(HEIGHT);
	#endif
	//Top-down bitmap
	BitmapInfo.bmiHeader.biPlanes		= 1;
	BitmapInfo.bmiHeader.biBitCount	= 24;	//8;
	BitmapInfo.bmiHeader.biCompression	= BI_RGB;
	//BitmapInfo.bmiHeader.biSizeImage	= 0;
	BitmapInfo.bmiHeader.biSizeImage	= 0;	//sc_bytes;
	BitmapInfo.bmiHeader.biXPelsPerMeter	= 0;
	BitmapInfo.bmiHeader.biYPelsPerMeter	= 0;
	BitmapInfo.bmiHeader.biClrUsed			= NUM_PALETTE_ENTRIES;
	BitmapInfo.bmiHeader.biClrImportant	= NUM_PALETTE_ENTRIES;
	
	bitmapHandle = CreateDIBSection(screenDC, (BITMAPINFO*) &BitmapInfo, DIB_RGB_COLORS, (VOID **) & Screen24, NULL, 0);
	backBitmapHandle = CreateDIBSection(screenDC, (BITMAPINFO*) &BitmapInfo, DIB_RGB_COLORS, (VOID **) & BackScreen24, NULL, 0);
	
	LogicalPalette.palVersion=0x300;
	LogicalPalette.palNumEntries=NUM_PALETTE_ENTRIES;
	for (int a=0; a<NUM_PALETTE_ENTRIES; a++) {
		LogicalPalette.palPalEntry[a].peRed=a;
		LogicalPalette.palPalEntry[a].peGreen=a;
		LogicalPalette.palPalEntry[a].peBlue=a;
		LogicalPalette.palPalEntry[a].peFlags=PC_RESERVED;
	}
	/*paletteHandle = CreatePalette((tagLOGPALETTE*)&LogicalPalette);
	if (paletteHandle==NULL) {
		Dword err = GetLastError();
		char msg[512];
		sprintf(msg, "Unable to create palette. Reason: ");
		FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, err, 0, msg+strlen(msg), 511-strlen(msg), NULL);
		MessageBox(NULL, msg, "Error", MB_OK|MB_ICONERROR);
	}
	SelectPalette(screenDC,paletteHandle,FALSE);
	RealizePalette(screenDC);
	*/
	ReleaseDC(hWnd,screenDC);
}

int GoFullscreen(int width, int height, int tries) {
	#ifdef DDRAWLINKEDIN
	if (directDraw==NULL) {
		HRESULT result;
		//result = DirectDrawCreate(NULL, &directDraw, NULL);
		
		result = DirectDrawCreateEx((GUID*)DDCREATE_HARDWAREONLY, (void**) &directDraw, IID_IDirectDraw7, NULL);
		//result = DirectDrawCreate(const_cast<_GUID*>(&IID_IDirectDraw2), (IDirectDraw**) &directDraw, NULL);
		//result = -2147024809;
		if (result!=DD_OK) {
			printf("DirectDrawCreateEx return value: %i. It means:\n", result);
			switch (result) {
				case DDERR_DIRECTDRAWALREADYCREATED:
					printf("DDERR_DIRECTDRAWALREADYCREATED\n");
					break;
				case DDERR_GENERIC:
					printf("DDERR_GENERIC\n");
					break;
				case DDERR_INVALIDDIRECTDRAWGUID:
					printf("DDERR_INVALIDDIRECTDRAWGUID\n");
					break;
				case DDERR_INVALIDPARAMS:
					printf("DDERR_INVALIDPARAMS\n");
					break;
				case DDERR_NODIRECTDRAWHW:
					printf("DDERR_NODIRECTDRAWHW\n");
					break;
				case DDERR_OUTOFMEMORY:
					printf("DDERR_OUTOFMEMORY\n");
					break;
				default:
					printf("Unknown error.");
					break;
			}
			error("You must have DirectX 7 or newer to run the Windows version of NICE in fullscreen.");
			return 2;
		}
	}
	HRESULT result=directDraw->SetCooperativeLevel(hWnd, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN|DDSCL_ALLOWREBOOT);
	if (result==DD_OK) {
		//video modes
		result=directDraw->SetDisplayMode(width, height, 24, 0, 0);
		if (result!=DD_OK) {
			result=directDraw->SetDisplayMode(width, height, 32, 0, 0);
			if (result!=DD_OK) {
				printf("Tried width=%i height=%i, failed\n", width, height);
				if (tries==1) {
					error("Unable to find an appropriate fullscreen video mode with 24 or 32 bits per pixel. Reverting to windowed mode.");
				}
				isFullscreen = 0;
				return 0;
			} else {
				printf("Tried width=%i height=%i, succeeded\n", width, height);
				isFullscreen = 1;
			}
		}
	} else {
		error("Failed setting cooperative level.\n");
		isFullscreen = 0;
		return 2;
	}
	#endif
	return 1;
}

void WinShow() {
	ShowWindow(hWnd, SW_SHOWNORMAL);
}
void WinStorePalette(int firstColor, int numColors, Uchar *tmppal) {
	int b=firstColor*3;
	for (int a=firstColor; a<numColors+firstColor; a++, b+=3) {
		//printf("Setting palette entry %i, with R=%x G=%x B=%x.\n", a, tmppal[b], tmppal[b+1], tmppal[b+2]);
		Uchar red = tmppal[b]<<2;
		Uchar green = tmppal[b+1]<<2;
		Uchar blue = tmppal[b+2]<<2;
		
		//SL's color-preserving brightness-inversion algorithm:
		//... Which is really so simple that I'm astounded I haven't seen anything like this before by anyone else.
		// -SL
		//P.S. Now I just need a way to apply this to the whole screen at once instead of using the color-inverting color-profile I set up in the NVidia graphics control panel thingy. Unfortunately there doesn't appear to be any way to feed the other colors into each other or do any math, much less use min/max.
		//I suppose perhaps it'd be possible with a pixel shader which affected the whole screen, or an always-on-top transparent app which read the colors 'underneath' it and used this algorithm to determine what to draw for itself. It'd also somehow have to not catch mouse clicks or keypresses. Those are both beyond my understanding of the necessary technologies at the moment - And the pixel shader one is far far FAR beyond my understanding of the necessary technologies.
		
		/*
		//Initial version which used color ranges from 0.0 to 1.0:
		double dred = ((double)red)/255.0;
		double dgreen = ((double)green)/255.0;
		double dblue = ((double)blue)/255.0;
		double dmost = max(dred, max(dgreen, dblue));
		double dless = min(dred, min(dgreen, dblue));
		double ndred, ndblue, ndgreen;
		double dchange = 1.0-dmost-dless;
		ndred = dchange + dred;
		ndgreen = dchange + dgreen;
		ndblue = dchange + dblue;
		Uchar ured = (Uchar) (ndred*255);
		Uchar ugreen = (Uchar) (ndgreen*255);
		Uchar ublue = (Uchar) (ndblue*255);
		//You would have to set red=ured etc to use those - they're like this for easily comparing the results of both versions of the algorithm.
		*/
		
		//Revised version which uses the normal 0 to 255 color range:
		/*
		Dword most = max(red, max(green, blue));
		Dword less = min(red, min(green, blue));
		Dword change = 255-most-less;
		red = (Uchar) (change+red);
		green = (Uchar) (change+green);
		blue = (Uchar) (change+blue);
		*/
		
		/*
		//Testing code to show the difference between the results of the two versions:
		//By the way, the largest possible value for diff would be 441.672955930063709849498817084. That's just sqrt(255*255*3) of course.
		//This gives a screen which *appears* pitch-black, but there are tiny differences. If you multiply the results a hundredfold (green=(Uchar) (100*255*diff/442)), then the tiny differences become visible. Try infrared view.
		double diff = sqrt((ured-red)*(ured-red) + (ugreen-green)*(ugreen-green) + (ublue-blue)*(ublue-blue));
		green=(Uchar) (255*diff/442); red=0; blue=0;
		*/
		
		LogicalPalette.palPalEntry[a].peRed=red;
		LogicalPalette.palPalEntry[a].peGreen=green;
		LogicalPalette.palPalEntry[a].peBlue=blue;
		LogicalPalette.palPalEntry[a].peFlags=PC_RESERVED;
	}
	/*int success = AnimatePalette(paletteHandle, firstColor, numColors, LogicalPalette.palPalEntry);
	if (!success) {
		Dword err = GetLastError();
		char msg[512];
		sprintf(msg, "Unable to change palette. Reason: ");
		FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, err, 0, msg+strlen(msg), 511-strlen(msg), NULL);
		MessageBox(NULL, msg, "Error", MB_OK|MB_ICONERROR);
	}
	*/
	//SetPaletteEntries(paletteHandle, 0, colors, LogicalPalette.palPalEntry);
	//UnrealizeObject(paletteHandle);
	//RealizePalette(paletteHandle, NUM_PALETTE_ENTRIES);
	//lpDirectDrawSurface.SetPalette();
	//HDC ScreenDC=GetDC(hWnd);
	
		//Release the screen device context:
	//ReleaseDC(hWnd,ScreenDC);

}

void FlipBuffers() {
	unsigned char *tmp=BackScreen24;
	HBITMAP htmp=backBitmapHandle;
	BackScreen24=Screen24;
	backBitmapHandle=bitmapHandle;
	Screen24=tmp;
	bitmapHandle=htmp;
}

void WinBlit() {
	for (int pos8=0, pos24=0, x=0; pos8<WIDTH*HEIGHT; pos8++, 
		#ifdef DOUBLESIZE
		pos24+=6, x++
		#else
		pos24+=3
		#endif
	) {
		int palIdx = Screen8[pos8];
		Screen24[pos24]=LogicalPalette.palPalEntry[palIdx].peBlue;
		Screen24[pos24+1]=LogicalPalette.palPalEntry[palIdx].peGreen;
		Screen24[pos24+2]=LogicalPalette.palPalEntry[palIdx].peRed;
		#ifdef DOUBLESIZE
		Screen24[pos24+BYTEWIDTH]=LogicalPalette.palPalEntry[palIdx].peBlue;
		Screen24[pos24+BYTEWIDTH+1]=LogicalPalette.palPalEntry[palIdx].peGreen;
		Screen24[pos24+BYTEWIDTH+2]=LogicalPalette.palPalEntry[palIdx].peRed;
		Screen24[pos24+BYTEWIDTH+3]=LogicalPalette.palPalEntry[palIdx].peBlue;
		Screen24[pos24+BYTEWIDTH+4]=LogicalPalette.palPalEntry[palIdx].peGreen;
		Screen24[pos24+BYTEWIDTH+5]=LogicalPalette.palPalEntry[palIdx].peRed;
		Screen24[pos24+3]=LogicalPalette.palPalEntry[palIdx].peBlue;
		Screen24[pos24+4]=LogicalPalette.palPalEntry[palIdx].peGreen;
		Screen24[pos24+5]=LogicalPalette.palPalEntry[palIdx].peRed;
		if (x>=WIDTH) {
			x-=WIDTH;
			pos24+=BYTEWIDTH;
		}
		#endif
	}
	//FlipBuffers();	//Not really needed right now.
	
	HDC screenDC;
	RECT dest;
	
	screenDC=GetDC(hWnd);
	GetClientRect(hWnd,&dest);
	
	HDC context=CreateCompatibleDC(0);
	HBITMAP defaultBitmap=SelectBitmap(context,bitmapHandle);
	
	BitBlt(	screenDC,
			0,	//x-cordinate of upper-left corner of destination rectangle
			0,	//y-coordinate of upper-left corner of destination rectangle
			dest.right-dest.left,	//width of dest rect
			dest.bottom-dest.top,	//height of dest rect
			context,	//handle of source device context
			0,	//x coordinate of upper left corner of source rect
			0,	//y same thing
			SRCCOPY	//raster operation code
		);
	SelectBitmap(context,defaultBitmap);
	DeleteDC(context);
	ReleaseDC(hWnd,screenDC);
	GdiFlush();
}

void RestoreDisplayMode() {
	#ifdef DDRAWLINKEDIN
	directDraw->RestoreDisplayMode();
	#endif
}