//
// Project UnReality: The First Nintendo 64 Emulator
// Copyright (C) 1997-1998, Cold Fusion Entertainment
// All rights reserved
//
// This source code is unpublished propietary information of Cold Fusion Entertainment.  No portion of this software
// may be disclosed to any third party or copied or duplicated in any form, in whole or in part, without the prior
// written consent of Cold Fusion Entertainment.
//

//
// $Log: CFB.CPP $
// Revision 1.4  1998/04/02 21:17:58  BREAKPOINT
// Implemented new memory mapping routines and changed ROM loading code.
// Revision 1.3  1998/03/08 19:11:36  BREAKPOINT
// Version 0.3b.3 checkin... I need to really check this stuff in more often..
// Revision 1.2  1998/01/31 19:48:07  BREAKPOINT
// Fixed code to support new non-byteswapped frame buffer.
// Added code to print the FPS rate.
// Revision 1.1  1998/01/10 01:24:46  BREAKPOINT
// Initial revision
//

static char *rcsid = "$Id: CFB.CPP 1.4 1998/04/02 21:17:58 BREAKPOINT Exp $";

//---------------------------------------------------------------------------------------------------------------------

#include <time.h>
#include <types.h>
#include "unreal.h"
#include "emu.h"
#include "mmio.h"
#include "debugger.h"
#include "gfx.h"
#include "cfb.h"
#include "font8x8.h"
#include "heap.h"

// Include inline functions...
#include "inline.cpp"

#include <windows.h>
#include <windowsx.h>

#define FONT_XSIZE			8

uchar *CFB_Ptr = NULL;
bool CFB_Exit = FALSE, CFB_Blank = FALSE;
uint CFB_Xsize, CFB_Ysize, CFB_BitDepth = 8, CFB_RealXsize;
ushort CFB_RGBDesc;
HANDLE hCFBSyncEvent, hCFBSyncThread;
clock_t CFB_LastFrame;

DWORD CFB_SyncThreadFunc(LPDWORD lpdwParam);

void printstr16(uchar *dptr, int xpos, int ypos, char *str)
{
	ushort *scrptr, col = 0xFFFF;
	uchar ch, *fptr, fbyte;
	char *ptr;
	int x, y;

	ptr = str;
	while (*ptr)
	{
		ch   = *ptr++;
		fptr = &VGA8X8_DATA[ch << 3];

		for (y = 0; y < 8; y++)
		{
			fbyte  = *fptr;
			scrptr = (ushort *)(dptr + ((ypos + y) * (DD_FullXsize * (CFB_BitDepth >> 3))) + xpos);

			for (x = 0; x < 8; x++)
			{
				if (fbyte & 0x01) *scrptr = col;
				fbyte >>= 1;
				scrptr++;
			}

			fptr++;
		}

		xpos += FONT_XSIZE * (CFB_BitDepth >> 3);
	}
}

void printstr32(uchar *dptr, int xpos, int ypos, char *str)
{
	ulong *scrptr, col = 0xFFFFFFFF;
	uchar ch, *fptr, fbyte;
	char *ptr;
	int x, y;

	ptr = str;
	while (*ptr)
	{
		ch   = *ptr++;
		fptr = &VGA8X8_DATA[ch << 3];

		for (y = 0; y < 8; y++)
		{
			fbyte  = *fptr;
			scrptr = (ulong *)(dptr + ((ypos + y) * (DD_FullXsize * CFB_BitDepth >> 3)) + xpos);

			for (x = 0; x < 8; x++)
			{
				if (fbyte & 0x01) *scrptr = col;
				fbyte >>= 1;
				scrptr++;
			}

			fptr++;
		}

		xpos += FONT_XSIZE * (CFB_BitDepth >> 3);
	}
}

bool CFB_StartSyncThread(void)
{
	DWORD ThreadID;

	CFB_Exit = FALSE;

	// Initialize event.
	hCFBSyncEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	if (hCFBSyncEvent == NULL) return FALSE;

	// Kick off the thread.
	hCFBSyncThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CFB_SyncThreadFunc, 0, 0, &ThreadID);
	if (hCFBSyncThread == NULL) return FALSE;

	return TRUE;
}

void CFB_StopSyncThread(void)
{
	CFB_Exit = TRUE;

	// Wait until thread stops...
	WaitForSingleObject(hCFBSyncThread, INFINITE);

	CloseHandle(hCFBSyncEvent);
	CloseHandle(hCFBSyncThread);

	hCFBSyncEvent = NULL;
	hCFBSyncThread = NULL;
}

bool CFB_Init(void)
{
	hCFBSyncEvent = NULL;
	hCFBSyncThread = NULL;

    CFB_Xsize = 640;
    CFB_Ysize = 480;
    CFB_BitDepth = 8;
	CFB_RealXsize = 640;
    CFB_LastFrame = 0;

    win_Xsize = 640;
    win_Ysize = 480;

    // We'll init our CFB in a window to begin with just so the user can see our pretty logo...
    if (InitDirectDraw(MainhWnd, CFB_Xsize, CFB_Ysize, CFB_BitDepth, FALSE, TRUE) == FALSE)
		return FALSE;

	// If we want to sync, create the sync thread and event.
	if (cfg.FPSLock)
	{
		if (CFB_StartSyncThread() == FALSE)
			return FALSE;
	}

	// All ok!
	return TRUE;
}

void CFB_Deinit(void)
{
	if (cfg.FPSLock) CFB_StopSyncThread();

	// Cleanup.
	DeinitDirectDraw();
}

void CFB_Update(void)
{
    DWORD dwStyle;
	RECT rc;

    // If we want to shut down the emulation thread, don't bother updating the CFB.
    if (want_to_exit)
    {
        return;
    }

	// Check if we're in windowed mode or in the debugger.
	if (cfg.Windowed || inDebugger)
    {
		// If we want windowed mode and we're in fullscreen, switch to window and setup menu.
		if (!UsingWindow())
		{
			SwitchToWindow();
			SetMenu(MainhWnd, MainhMenu);
		}

		// Allow resizing if applicable.
		if (cfg.Resizeable)
        {
            dwStyle  = GetWindowStyle(MainhWnd);
            dwStyle |= WS_THICKFRAME;
            SetWindowLong(MainhWnd, GWL_STYLE, dwStyle);
        }
        else
        {
            dwStyle  = GetWindowStyle(MainhWnd);
            dwStyle &= ~WS_THICKFRAME;
            SetWindowLong(MainhWnd, GWL_STYLE, dwStyle);
        }

		// Update window size.
		if (CFB_Xsize == 320)
		{
			// Keep frame buffer 320x240!
			ChangeResolution(320, 240, CFB_BitDepth);

			if (cfg.Upscale320)
			{
				win_Xsize = 640;
				win_Ysize = 480;
			}
			else
			{
				win_Xsize = 320;
				win_Ysize = 240;
			}
		}
		else
		{
			// Make frame buffer 640x480.
			ChangeResolution(640, 480, CFB_BitDepth);

			win_Xsize = 640;
			win_Ysize = 480;
		}
    }
	else
	{
		// Update fullscreen size.
		if (CFB_Xsize == 320)
		{
			if (cfg.Upscale320)
			{
				// Make frame buffer 640x480, we'll handle the copying.
				ChangeResolution(640, 480, CFB_BitDepth);

				win_Xsize = 640;
				win_Ysize = 480;
			}
			else
			{
				// Make frame buffer 320x240.
				ChangeResolution(320, 240, CFB_BitDepth);

				win_Xsize = 320;
				win_Ysize = 240;
			}
		}
		else
		{
			// Make frame buffer 640x480.
			ChangeResolution(640, 480, CFB_BitDepth);

			win_Xsize = 640;
			win_Ysize = 480;
		}

		// If in window and we want fullscreen, turn off the menubar and switch to fullscreen.
		if (UsingWindow())
		{
			SetMenu(MainhWnd, NULL);
			SwitchToFullscreen();
		}

		CFB_RGBDesc = GetSurfaceRGB();
	}

	// Now resize window to new size.
	SetRect(&rc, 0, 0, win_Xsize, win_Ysize);
	AdjustWindowRectEx(&rc, GetWindowStyle(MainhWnd), GetMenu(MainhWnd) != NULL, GetWindowExStyle(MainhWnd));
	SetWindowPos(MainhWnd, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
	SetWindowPos(MainhWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
}

void CFB_Reset(void)
{
	CFB_Update();
}

void CFB_BitDepth_Change(uint depth)
{
    // See if we can skip initialization...
    if (depth == CFB_BitDepth) return;

    CFB_BitDepth = depth;
	CFB_Update();
}

void CFB_Resolution_Change(uint width)
{
    // See if we can skip initialization...
    if (width == CFB_Xsize) return;

    switch (width)
    {
        case 320:
            CFB_Xsize = 320;
            CFB_Ysize = 240;
            break;

        case 640:
            CFB_Xsize = 640;
            CFB_Ysize = 480;
            break;
    }

	// Update the CFB with the new size.
	CFB_Update();
}

void CFB_Origin_Change(ulong addr)
{
	CFB_Ptr = (uchar *)FindMemoryPtr(addr);
}

void _cdecl CFB_FlipMode(void)
{
	EventHeapDeleteMin();

	if (cfg.Windowed)
		cfg.Windowed = FALSE;
	else
		cfg.Windowed = TRUE;

	CFB_Update();
}

void CFB_Flush(void)
{
	uchar *sptr, *dptr, *fptr;
	ulong len;

	// Do nothing if the CFB hasn't been initialized.
	if (CFB_BitDepth == 8) return;

	// If we are FPS locking, block until we're allowed to draw.
	if ((cfg.FPSLock) && (hCFBSyncEvent != NULL)) WaitForSingleObject(hCFBSyncEvent, INFINITE);

	// Handle case if we're in blank mode.
	if (CFB_Blank)
	{
		Clear();
		Flip();
		return;
	}

	// Blit the frame!
	sptr = CFB_Ptr;
	dptr = Lock();
	if (dptr == NULL) return;

    // Save a copy for the FPS counter.
    fptr = dptr;
	len = CFB_Xsize * CFB_Ysize;

	if (UsingWindow())
	{
		if (CFB_BitDepth == 16)
			Convert5551to555_Dswap(sptr, dptr, len);
		else
			Convert8888to8888_Dswap(sptr, dptr, len);
	}
	else
	{
		// Blit frame fullscreen
		if ((CFB_Xsize == 320) && (cfg.Upscale320))
		{
			if (CFB_BitDepth == 16)
			{
				if (CFB_RGBDesc == 0x565)
					Convert5551to565_Dswap_DoubleUp(sptr, dptr, len);
				else
					Convert5551to555_Dswap_DoubleUp(sptr, dptr, len);
			}
			else
			{
				Convert8888to8888_Dswap_DoubleUp(sptr, dptr, len);
			}
		}
		else
		{
			if (CFB_BitDepth == 16)
			{
				if (CFB_RGBDesc == 0x565)
					Convert5551to565_Dswap(sptr, dptr, len);
				else
					Convert5551to555_Dswap(sptr, dptr, len);
			}
			else
			{
				Convert8888to8888_Dswap(sptr, dptr, len);
			}
		}
	}

	// Show the FPS counter if they want it...
	if (cfg.ShowFPS)
	{
        char buf[32];
        float fps;
        clock_t curr;

		curr = clock();
		if (CFB_LastFrame != 0)
		{
			if (curr == CFB_LastFrame)
				fps = (float)99.99;
			else
			{
				fps = (float)CLOCKS_PER_SEC / (float)(curr - CFB_LastFrame);
				if (fps > (float)99.99) fps = (float)99.99;
			}

			sprintf(buf, "%.2f FPS", fps);

			if (CFB_BitDepth == 16)
				printstr16(fptr, 0, 0, buf);
			else
				printstr32(fptr, 0, 0, buf);
		}

		CFB_LastFrame = curr;
	}

	Unlock();
	Flip();
}

//---------------------------------------------------------------------------------------------------------------------

DWORD CFB_SyncThreadFunc(LPDWORD lpdwParam)
{
	ulong sleep_time;
	float FPS = 30.0;

	// Calculate how long we need to sleep in milliseconds to lock to 30fps, minus a few to compensate for lag.
	sleep_time = ((ulong)(1000.0 / (float)FPS)) - 5;

	while (!CFB_Exit)
	{
		SetEvent(hCFBSyncEvent);
		Sleep(sleep_time);
	}

    // Toggle the event one more time just in case it's about to wait and we're told to shutdown.
    SetEvent(hCFBSyncEvent);
	return 0;
}
