
#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include <gl/glut.h>
#include <stdio.h>
#include <stdlib.h>
#include "DMGHLif.h"

/*////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Sigh...
*/

namespace
{

extern int g_nFontHeight;

};

/*////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Private use classes declaration?
*/

class FontDisplayList
{
	DWORD listBase;
public:
	// 
	FontDisplayList()
	{
		listBase = 0;
	}
	// 
	~FontDisplayList()
	{
		Close();
	}
	// 
	void Close()
	{
		if (listBase) {
			glDeleteLists(listBase, 256);
			listBase = 0;
		}
	}
	// 
	bool Make()
	{
		Close();

		HWND hWnd = GetActiveWindow();
		HDC hDC = GetDC(hWnd);
		if (hDC) {
			LOGFONT lf;
			GetObject(GetStockObject(DEFAULT_GUI_FONT), sizeof(LOGFONT), &lf);
			//lf.lfHeight = lf.lfHeight * 2;

			HFONT hFont = CreateFontIndirect(&lf);
			HGDIOBJ hObj = SelectObject(hDC, hFont);

			TEXTMETRIC tm;
			ZeroMemory(&tm, sizeof(tm));
			GetTextMetrics(hDC, &tm);
			g_nFontHeight = __max(1, tm.tmInternalLeading + abs(tm.tmHeight) + tm.tmExternalLeading);

			if (wglUseFontBitmaps(hDC, 0, 255, 1000)) {
				listBase = 1000;

				if (hObj) SelectObject(hDC, hObj);
				if (hFont) DeleteObject(hFont);

				ReleaseDC(hWnd, hDC);
				return true;
			}

			if (hObj) SelectObject(hDC, hObj);
			if (hFont) DeleteObject(hFont);

			ReleaseDC(hWnd, hDC);
		}
		listBase = 0;
		return false;
	}
	// 
	DWORD GetBase() const { return listBase; }
	// 
	bool IsOk() const { return listBase != 0; }
	// 
	bool PutText(LPCSTR psz)
	{
		if (!IsOk()) return false;

		glListBase(listBase);
		glCallLists(strlen(psz), GL_UNSIGNED_BYTE, psz);

		return true;
	}
};

class GraphicsManagement
{
	// 
	char graphicsFile[256];
	// 
	HDMGHLF handle;

public:
	// 
	GraphicsManagement()
	{

	}
	// 
	~GraphicsManagement()
	{
		Close();
	}
	// 
	bool IsOpened()
	{
		if (handle != NULL)
			return true;
		return false;
	}
	// 
	bool Open(LPCSTR psz, int iType)
	{
		Close();

		struct FilterType {
			int iType;
			int iIsLE;
		} tbl[] = {
			0, 0,
			DMDFT_DMCSB1, 0,
			DMDFT_DMCSB2, 1,
			DMDFT_DM2_EVOLVED, 1, // DM2PC
			DMDFT_DM2_CLASSIC, 0, // DM2Amiga
			DMDFT_DM2_CLASSIC, 1, // DM2PC98
		};
		iType = __max(iType, 1);
		iType = __min(iType, 5);

		handle = dmghl_open_graphics_dat(psz, tbl[iType].iType, tbl[iType].iIsLE);
		if (handle != NULL) {
			strcpy(graphicsFile, psz);
			return true;
		}
		return false;
	}
	// 
	void Close()
	{
		if (handle != NULL) {
			dmghl_close_graphics_dat(handle);
			handle = NULL;
		}
	}
	// 
	bool GetImage(int iIndex, PDMGHLIMGF pimgf)
	{
		if (!IsOpened()) return false;

		int r = dmghl_get_image(handle, iIndex, pimgf);
		if (r != 0)
			return true;
		return false;
	}
	// 
	bool ReleaseImage(PDMGHLIMGF pimgf)
	{
		int r = dmghl_release_image(pimgf);
		if (r != 0)
			return true;
		return false;
	}
};

/*////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Global variable declaration space
*/

namespace
{

// displayed list of font for convenience
FontDisplayList g_fontDisplayList;

// latest viewport dimension size
static int g_cx = 0, g_cy = 0;

// height of display listed font in device units
static int g_nFontHeight = 1;

// graphics.dat manager class
static GraphicsManagement g_graphicsManager;

// caption
static LPCSTR g_pszTitle = "Test --- DMGHL(DM Graphics Helper Library)";

// 
static int g_currentImageIdx = 0;

// 
static const int g_maxImageIdx = 9999;

// 
static int g_moveDelta = 0;

}

/*////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// fnXXX functions?
*/

void fnDisplay()
{
	glLoadIdentity();

	glClear(GL_COLOR_BUFFER_BIT);

	char statusText[256] = {"read err"};
	bool fReadOk = false;
	if (g_graphicsManager.IsOpened()) {
		DMGHLIMGF imgf;
		for (; ; ) {
			bool fHitEnd = false;
			g_currentImageIdx += g_moveDelta;
			if (g_currentImageIdx < 0) {
				g_currentImageIdx = 0;
				fHitEnd = true;
			}
			if (g_currentImageIdx > g_maxImageIdx) {
				g_currentImageIdx = g_maxImageIdx;
				fHitEnd = true;
			}

			if (g_graphicsManager.GetImage(g_currentImageIdx, &imgf)) {
				fReadOk = true;

				glEnable(GL_TEXTURE_2D);

				glTexImage2D(
					GL_TEXTURE_2D,
					0,
					imgf.internalformat,
					imgf.width,
					imgf.height,
					0,
					imgf.format,
					imgf.type,
					imgf.pixels
					);

				float fWidth, fHeight;
				if (imgf.width > imgf.height) {
					fWidth = 1.0;
					fHeight = (float)imgf.height / imgf.width;
				} else {
					fWidth = (float)imgf.width / imgf.height;
					fHeight = 1.0;
				}
				fWidth *= 0.8;
				fHeight *= 0.8;

				// 0  3
				// |  |
				// 1--2       // vertex order

				// A +y
				// |
				// |
				// 0 --> +x   // vertex coordination

				// 0 --> +x
				// |
				// |
				// V +y       // texture coordination

				glColor3f(1, 1, 1);
				glBegin(GL_QUADS);
					glTexCoord2f(0, 0); glVertex2f(-fWidth, +fHeight);
					glTexCoord2f(0, 1); glVertex2f(-fWidth, -fHeight);
					glTexCoord2f(1, 1); glVertex2f(+fWidth, -fHeight);
					glTexCoord2f(1, 0); glVertex2f(+fWidth, +fHeight);
				glEnd();

				// need to flush. because the texture memory for delay rendering cannot be stayed any more.
				glFlush();

				glDisable(GL_TEXTURE_2D);

				sprintf(statusText, "textureSize(%u,%u) imageSize(%u,%u)", imgf.width, imgf.height, imgf.widthImage, imgf.heightImage);

				g_graphicsManager.ReleaseImage(&imgf);
				break;
			}
			if (g_moveDelta == 0)
				break;
			if (fHitEnd)
				break;
		}
		g_moveDelta = 0;
	}

	float fTextHeight = (float)g_nFontHeight / g_cy * 2.0;
	float fy = -1;

	if (!g_graphicsManager.IsOpened()) {
		fy += 1*fTextHeight;
		glRasterPos2f(-0.95, fy); g_fontDisplayList.PutText("Click mouse to choose graphics.dat");
	} else {
		fy += 5*fTextHeight;
		char statusLine[256];
		sprintf(statusLine, "binary data index #%u (%s)", g_currentImageIdx, statusText);
		glRasterPos2f(-0.95, fy -fTextHeight*0); g_fontDisplayList.PutText(statusLine);
		glRasterPos2f(-0.95, fy -fTextHeight*1); g_fontDisplayList.PutText("Left click or Left cursor --- previous image");
		glRasterPos2f(-0.95, fy -fTextHeight*2); g_fontDisplayList.PutText("Right click or Right cursor --- next image");
		glRasterPos2f(-0.95, fy -fTextHeight*3); g_fontDisplayList.PutText("Shift+Left click or Shift+Left cursor --- previous available image");
		glRasterPos2f(-0.95, fy -fTextHeight*4); g_fontDisplayList.PutText("Shift+Right click or Shift+Right cursor --- next available image");
	}

	glutSwapBuffers();
}

void fnReshape(int width, int height)
{
	float fcx, fcy;
	if (width > height) {
		fcx = (float)width / height;
		fcy = 1.0;
	} else {
		fcx = 1.0;
		fcy = (float)height / width;
	}
	g_cx = width;
	g_cy = height;

	glViewport(0, 0, width, height);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-fcx, +fcx, -fcy, +fcy, 0, 1);

	glMatrixMode(GL_MODELVIEW);
}

void fnMouse(int button, int state, int x, int y)
{
	bool fLbtn = (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN);
	bool fRbtn = (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN);

	if (!g_graphicsManager.IsOpened()) {
		if (fLbtn || fRbtn) {
			char fileNameSelected[256] = {""};
			OPENFILENAME ofn;
			ZeroMemory(&ofn, sizeof(ofn));
			ofn.lStructSize = sizeof(ofn);
			ofn.hwndOwner = GetActiveWindow();
			ofn.hInstance = GetModuleHandle(NULL);
			ofn.lpstrFilter = 
				"GRAPHICS.DAT of Atari ST ver? (*.dat)\0" "*.dat\0"
				"GRAPHICS.DAT of PC ver? (*.dat)\0" "*.dat\0"
				"GRAPHICS.DAT of DM2 PC ver (*.dat)\0" "*.dat\0"
				"GRAPHICS.DAT of DM2 Amiga (*.dat)\0" "*.dat\0"
				"GRAPHICS.DAT of DM2 PC98 (*.dat)\0" "*.dat\0"
				"All (*.*)\0" "*.*\0"
				"\0";
			ofn.lpstrFile = fileNameSelected;
			ofn.nMaxFile = 256;
			ofn.Flags = OFN_ENABLESIZING|OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_LONGNAMES|OFN_PATHMUSTEXIST;
			if (GetOpenFileName(&ofn)) {
				glutPostRedisplay();
				if (g_graphicsManager.Open(fileNameSelected, ofn.nFilterIndex)) {

				} else {
					MessageBox(GetActiveWindow(), "open error", g_pszTitle, MB_ICONEXCLAMATION);
				}
			}
		}
	} else {
		bool fShift = (GetKeyState(VK_SHIFT) & 0x8000) == 0x8000;
		if (fLbtn && g_currentImageIdx > 0)
			if (fShift)
					g_moveDelta--;
			else	g_currentImageIdx--;
		if (fRbtn && g_currentImageIdx < g_maxImageIdx)
			if (fShift)
					g_moveDelta++;
			else	g_currentImageIdx++;

		glutPostRedisplay();
	}
}

void fnSpecial(int key, int x, int y)
{
	switch (key) {
	case GLUT_KEY_LEFT:
	case GLUT_KEY_RIGHT:
		bool fShift = (GetKeyState(VK_SHIFT) & 0x8000) == 0x8000;
		switch (key) {
		case GLUT_KEY_LEFT:
			if (fShift)
					g_moveDelta--;
			else	g_currentImageIdx--;
			glutPostRedisplay();
			break;
		case GLUT_KEY_RIGHT:
			if (fShift)
					g_moveDelta++;
			else	g_currentImageIdx++;
			glutPostRedisplay();
			break;
		}
	}
}

void fnInit()
{
	glTexParameterf(
		GL_TEXTURE_2D,
		GL_TEXTURE_MIN_FILTER,
		GL_NEAREST
		);
	glTexParameterf(
		GL_TEXTURE_2D,
		GL_TEXTURE_MAG_FILTER,
		GL_NEAREST
		);

	g_fontDisplayList.Make();
}

void fnUninit()
{
	g_fontDisplayList.Close();
	g_graphicsManager.Close();
}

/*////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// main
*/

int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE);
	glutInitWindowPosition(0, 0);
	glutInitWindowSize(400, 400);
	glutCreateWindow(g_pszTitle);
	glutReshapeFunc(fnReshape);
	glutMouseFunc(fnMouse);
	glutDisplayFunc(fnDisplay);
	glutSpecialFunc(fnSpecial);

	fnInit();

	atexit(fnUninit);

	glutMainLoop();

//	fnUninit();

	return 0;
}
