/************************************************************************\
 * File Version Information
 * $Header: /Altair32v2/init.c 7     11/08/06 9:21p Racini $
 ************************************************************************/
/************************************************************************\
  MITS Altair Emulator
  WinMain and Emulator Initialization
 
  Copyright (c) 2000-2006 Richard A. Cini

  This module contains only the emulator initialization code. This is to
  separate the global init stuff from the emulator UI stuff in altair.c.
  Hopefully this will make code maintenance easier.


Change Log:
  2005/04/22  RAC -- Forked init code from altair.c
  2005/06/16  RAC -- Minor changes
  2006/04/12  RAC -- Fork complete; required changes made for front panel
  2006/05/01  RAC -- Added OS version check
  2006/05/12  RAC -- RELEASE MARKER -- v3.10.0200
  2006/08/21  RAC -- Changed version test function so that it will work
  						on Win95/98/ME (VerifyVersionInfo is only available
  						on NT platform).
  2006/11/15  RAC -- RELEASE MARKER -- v3.20.0400
\************************************************************************/
#include <windows.h>	// required for all Windows applications
#include <stdlib.h>		// C standard lib
#include <string.h>		// C-lib - strings
#include <stdio.h>		// C-lib - I/O
#include <process.h>	// threads
#include <commctrl.h>	// Windows common controls
#include "altair32.h"	// includes "resource.h" and exports from other major modules
#include "comcthlp.h"	// common control macros
#include "htmlhelp.h"	// HTML help engine
#include "windbg.h"		// integrated debugger header
#include "i8080.h"		// processor emulation
#include "fpdll.h"		// front panel


/**  Forward Prototypes  **************************************/
static int  InitApplication( HINSTANCE );
static BOOL InitInstance( HINSTANCE, int );
static void InitWinstate( void );
static BOOL Is_WinNT_or_Later( void );

/**  Globals  *************************************************/
winstate_t	 winstate;		// windows state
simstate_t	 simstate;		// simulation state
DWORD 		 dwCookie;		// HTML Help Engine handle
char szHelpDir[_MAX_PATH];


/***************************************************************\
*	WINMAIN
*
*	The place where all Windows programs start. Where do you
*	want to go today?
*
\***************************************************************/
int APIENTRY WinMain (HINSTANCE hInstance,
					  HINSTANCE hPrevInstance,
					  LPSTR     lpCmdLine,
					  int       nCmdShow)
{
	char *ProfBuf;
	HACCEL hAccel = NULL;
	HANDLE hEvent = NULL;	
	LPCTSTR errmsg = {"The Altair32 Emulator requires Windows NT, 2000, XP or Vista."};
	MSG msg;
	RECT rc;
	unsigned threadID;


	// Find out what system we're running on and exit if not right. A32 will work on
	// systems as early as Win95, but certain things like the console screen work
	// best with an NT-derivative. This test can be removed if needed.
// defined in altair32.h
#ifdef VER_NT_ONLY
	if (!Is_WinNT_or_Later()){
		// OS is not based on Windows NT, so exit
		MessageBox(NULL, errmsg, SZWINDOWTEXT, MB_OK | MB_ICONERROR | MB_TASKMODAL);
		return FALSE;	// exit to Windows
	}
#endif
	
	// Install custom exception handler so we can control any faults.
	InstallFaultHandler();

	/*
	 *	Are other instances of app running? In a Win32 system, hPrevInstance
	 *  will always be NULL. In a Win3.x system, hPrevInstance was a valid
	 *  instance handle (*pPDB). Legacy code from original Win16 code.
	 *  Since we still only want one instance of the emulator to run, we
	 *  check for a previous instance using FindWindow and defer to that
	 *  copy.
	 */
	if (!hPrevInstance)
		if (!InitApplication(hInstance)){
			UninstallFaultHandler();
			return FALSE;
		}

	// Create main window and initialize some members of winstate
	if (!InitInstance(hInstance, nCmdShow)){
		UninstallFaultHandler();
		return FALSE;
	}

	/*
	 * These inits are done here and not in InitEmu because they
	 * are one-time-only inits that register window classes and stuff.
	 */
	// Do front panel first so that if it's available, the toggle positions
	// can overide anything else.
	simstate.hFrontPanel = FP_OpenFrontPanel();
	if (simstate.hFrontPanel == NULL){
		Status_SetText(hwndStatusBar, STATBAR_READY, 0, "Front panel not detected.");
		simstate.fpstate = DISABLED;
	}
	else {
		Status_SetText(hwndStatusBar, STATBAR_READY, 0, "Altair32 USB Control Panel Detected!\n");
		simstate.fpstate = ACTIVE;
		// Aswitches should be set to the current SR within the update thread
	}

	// Main emulator inits. InitEmu is also called by power_switch so
	// you need to be careful about what code is called from it.
	InitEmu();				// CPU and certain hardware items

	// Items that must be initialized only once because of WndCls registration
	InitDebugger();		// Debugger init
	DAZZLER_Init();		// initialize Dazzler video board
	
	HtmlHelp(NULL, NULL, HH_INITIALIZE, (DWORD)&dwCookie);	// Init HTML help
	// LoadAccelerators requires a LPCTSTR pointing to accelerator table
	hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1));
	InitCommonControls();		// Init commctl library last

	// Move main window into position
	ProfBuf = SYSCFG_ReadConfigInfo("windows", "main");
	if (sscanf(ProfBuf, "org=%d,%d, size=%d,%d", &rc.left, &rc.top, &rc.right, &rc.bottom) == 4) {
		rc.right  += rc.left;
		rc.bottom += rc.top;
	}
	else {
		rc.left = rc.top = 0;
	}
#ifdef IMSAI
	rc.right =  rc.left + ALTAIRBMP_W;					// IMSAI width
	rc.bottom = rc.top  + ALTAIRBMP_H
				+ GetSystemMetrics( SM_CYCAPTION )
				+ GetSystemMetrics( SM_CYFIXEDFRAME )
				+ (GetSystemMetrics( SM_CYMENU ) * 2);// height (23)
#else
	rc.right =  rc.left + ALTAIRBMP_W
				+ GetSystemMetrics( SM_CXFRAME );		// Altair width
	rc.bottom = rc.top  + ALTAIRBMP_H
				+ GetSystemMetrics( SM_CYCAPTION )
				+ (GetSystemMetrics( SM_CYFIXEDFRAME ) * 2)
				+ (GetSystemMetrics( SM_CYMENU ) * 2);// height
#endif

	MoveWindow(winstate.hWnd, rc.left, rc.top,
			(rc.right - rc.left), (rc.bottom - rc.top), TRUE);

 	/*
 	 * START TIMESLICING SCHEDULER
 	 */
	hEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
	if (hEvent == NULL) {
		HOST_ThrowErrMsg("CreateEvent in init.c failed!");
		HtmlHelp(NULL, NULL, HH_UNINITIALIZE, (DWORD)&dwCookie);	// kill HTML help
		return FALSE;
	}

	simstate.hThread = (HANDLE)_beginthreadex( NULL, 0, &SCHED_DoTimeSlicing, hEvent, 0, &threadID );
	if (simstate.hThread == 0) {
		HOST_ThrowErrMsg("Unable to create scheduler thread in init.c!");
		HtmlHelp(NULL, NULL, HH_UNINITIALIZE, (DWORD)&dwCookie);	// kill HTML help
		CloseHandle(hEvent);
		return FALSE;
	}

	WaitForSingleObject(hEvent, INFINITE);	// wait until SCHED_DoTimeSlicing is running
	ShowWindow(winstate.hWnd, nCmdShow);	// show the window
	UpdateWindow(winstate.hWnd);			// send first WM_PAINT message

	/*
	 *	ENTER MAIN MESSAGE LOOP
	 */
	while (GetMessage(&msg, NULL, 0, 0)) {	
		// Trap messages to support keyboard navigation in child dialogs.
		// One line for each child dialog requiring navigation.

		if ((!IsWindow(hwndPTP)  || !IsDialogMessage(hwndPTP, &msg)) &&
			(!IsWindow(hwndDISK) || !IsDialogMessage(hwndDISK, &msg)) &&
			(!IsWindow(hwndMiscCfg) || !IsDialogMessage(hwndMiscCfg, &msg)) &&		
			(!IsWindow(hwndSpeedCfg) || !IsDialogMessage(hwndSpeedCfg, &msg)) &&
			(!IsWindow(hwndMemCfg) || !IsDialogMessage(hwndMemCfg, &msg)) &&
			(!IsWindow(hwndConCfg) || !IsDialogMessage(hwndConCfg, &msg))
			// && (!IsWindow(hwndPrinterCfg) || !IsDialogMessage(hwndPrinterCfg, &msg))
			// && (!IsWindow(hwndRomCfg) || !IsDialogMessage(hwndRomCfg, &msg))
			) {
			TranslateMessage(&msg);		// translate any accelerator keys
			DispatchMessage(&msg);		// send the message to the WndProc
		}
	}

	/*
	 * EXIT PROCESSING -- kill any threads and events as well as HTMLHelp
	 *		Do not call any GDI-related functions here since all of the DCs
	 *		have been destroyed by the time the code reaches this point.
	 */
	if(FP_CloseFrontPanel(simstate.hFrontPanel)){
		// success
		simstate.fpstate = DISABLED;
		simstate.hFrontPanel = (HANDLE)NULL;
	}
	else {
		// Error while turning off LEDs before closing panel driver,
		// so just swallow error.
	}
	//DAZZLER_ExitHandler();		// used to shut-down video threads
	CloseHandle(hEvent);			// kill timeslicing synch object and thread
	CloseHandle(simstate.hThread);	//
	HtmlHelp(NULL, NULL, HH_UNINITIALIZE, (DWORD)&dwCookie);
	UninstallFaultHandler();
	return msg.wParam;		// return exit code to Windows
}


/***************************************************************************\
*
*   FUNCTION: InitApplication(HINSTANCE)
*
*   PURPOSE: Initializes window data and registers window class
*
\***************************************************************************/
static int InitApplication(HINSTANCE hInstance)
{

	WNDCLASSEX  wc;
	HWND		hwnd;


	/*
	 *	A Win32 system will always set hPrevInstance to NULL, so we need
	 *  to check things a little closer as we only want a single instance
	 *  of this app to run at a time.
	 */
	hwnd = FindWindow(winstate.szAppName, winstate.szTitleName);
	if (hwnd){
		// We found another version of ourself...lets defer to it
		if (IsIconic(hwnd)){
        	ShowWindow(hwnd, SW_RESTORE);
		}
		SetForegroundWindow(hwnd);

        /* If this applies, we would also want to communicate any
         * action that our 'twin' should now perform based on how
         * the user tried to execute us.
       	 */
		return FALSE;
	}
	
	// Fill in window class structure and register the main window
	ZeroMemory(&wc, sizeof(WNDCLASSEX));
	wc.cbSize			= sizeof(WNDCLASSEX);
	wc.style			= CS_OWNDC;			// Class style(s)
	wc.lpfnWndProc		= MainWndProc;		// Main message loop function
	wc.cbClsExtra		= 0;				// No per-class extra data
	wc.cbWndExtra		= 0;				// No per-window extra data
	wc.hInstance		= hInstance;		// Application that owns the class
	wc.hIcon			= LoadIcon(hInstance, ICON_NAME );
	wc.hCursor			= LoadCursor(hInstance, "altaircursor" );
	wc.hbrBackground 	= GetStockObject(BLACK_BRUSH); 
	wc.lpszMenuName		= "AltairMenu";		// Name of menu resource in .RC file
	wc.lpszClassName	= "AltairWClass";	// Name used in call to CreateWindow
	wc.hIconSm			= (HICON)NULL;		// if null, search hIcon file for icon of
											//  the right size.
	return (RegisterClassEx(&wc));
}


/***************************************************************************\
*
*	FUNCTION:  InitInstance(HINSTANCE, int)
*
*	PURPOSE:   Saves instance handle and creates main window
*
*	COMMENTS:
*
*       This function is called at initialization time for every instance of 
*       this application.  This function performs initialization tasks that 
*       cannot be shared by multiple instances.  
*
*       In this case, we save the instance handle in a static variable and 
*       create and display the main program window.  
*        
\***************************************************************************/
static BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{

	char *helpfile = "Altair32Help.chm::/altair_8800_history.htm";	// TOC page

	
    winstate.hInst = hInstance;
	strcpy(winstate.szAppName, SZWINDOWTEXT);	// application name

    // Create a main window for this application instance.
#ifdef IMSAI
	winstate.hWnd = CreateWindow("AltairWClass", winstate.szAppName,
		WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME ^ WS_MAXIMIZEBOX,
		0,		// Horizontal position.
		0,		// Vertical position.
        ALTAIRBMP_W,		// IMSAI width
        ALTAIRBMP_H + GetSystemMetrics( SM_CYCAPTION )
					+ GetSystemMetrics( SM_CYFIXEDFRAME )
					+ (GetSystemMetrics( SM_CYMENU ) * 2),		// height 23
        NULL,                           // Overlapped windows have no parent.
        NULL,                           // Use the window class menu.
        hInstance,                      // This instance owns this window.
        NULL                            // Pointer not needed.
    );
#else
	winstate.hWnd = CreateWindow("AltairWClass", winstate.szAppName,
		WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME ^ WS_MAXIMIZEBOX,
		0,		// Horizontal position.
		0,		// Vertical position.
        ALTAIRBMP_W + GetSystemMetrics( SM_CXFRAME ),		// Altair width
        ALTAIRBMP_H + GetSystemMetrics( SM_CYCAPTION )
					+ (GetSystemMetrics( SM_CYFIXEDFRAME ) * 2)
					+ (GetSystemMetrics( SM_CYMENU ) * 2),		// height 23
        NULL,                           // Overlapped windows have no parent.
        NULL,                           // Use the window class menu.
        hInstance,                      // This instance owns this window.
        NULL                            // Pointer not needed.
    );
#endif

	if (!winstate.hWnd)	return FALSE;	// return FALSE if can't create window

	winstate.szFileName[0] = 0;				// path to file
	winstate.szTitleName[0] = 0;				// loaded app title
	GetCurrentDirectory(sizeof(winstate.szBaseDir), winstate.szBaseDir);
	strcpy(szHelpDir, winstate.szBaseDir);		// help directory
 	strcat(szHelpDir, "\\docs\\");
	strcat(szHelpDir, helpfile);
	strcpy(winstate.szFilesDir, winstate.szBaseDir);	// files directory
	strcat(winstate.szFilesDir, "\\files\\");
	winstate.hMenu  = GetMenu(winstate.hWnd);	// global handle to main menu
	winstate.hdcWin = GetDC(winstate.hWnd);	// need this to allow first FP paint
	winstate.exitcode = 0;
    return TRUE;
}


/***************************************************************************\
*
*	FUNCTION:  InitEmu
*
*	PURPOSE:   Initializes emulator by resetting CPU, setting simstate
*				variables and function pointers and starting the simulation
*				timer.
*
*	COMMENTS:
*
*       This function is called at initialization time by WinMain and at any
*		other time by the power switch handler.
*        
\***************************************************************************/
void InitEmu(void)
{

	
	CpuPOR();				// reset CPU registers and zeros memory	
	simstate.exec8080_func = clock8080;	// default function pointers
	simstate.di80_func = GetMem;
	simstate.op80_func = GetMem;
	simstate.rd80_func = GetMem;
	simstate.wr80_func = SetMem;
	simstate.in80_func = GetIO;
	simstate.out80_func = SetIO;
									// Initialize main emulation variables
	simstate.bPpower = 0;			// power off
	simstate.bPprotect = 0;			// mem protect off
	simstate.runstate = HALTED;		// scheduler exec halted
	simstate.bPrun = RUN_OFF;		// FP runstate
	
	DISK_InitController();	// initialize disk controller and disk ROM
	CASS_Init();			// initialize cassette device
    SCHED_TimerInit();		// reset simulation scheduler
	breakpoint_init();		// remove all breakpoints


	/* Load any other options and set default configuration if INI
	 * file not found. MUST BE CALLED BEFORE THE EMULATOR IS TURNED
	 * OVER TO THE USER SINCE IT PERFORMS THE FINAL INITIALIZATION OF
	 * THE BASE MEMORY CONFIGURATION, ROMS AND CERTAIN EMULATED
	 * HARDWARE DEVICES, AS WELL AS CHANGING WINDOW SIZES.
	 */
    SYSCFG_LoadOptions(); 			// Sets defaults and loads options
}


/***************************************************************************\
*
*	FUNCTION:  Is_WinNT_or_Later
*
*	PURPOSE:   Determine if we're running on a Windows NT (or derivative)
*				operating system.
*   PARAMS:	   None
*	RETURNS:   TRUE/FALSE
*	COMMENTS:
*
\***************************************************************************/
BOOL Is_WinNT_or_Later ( void ) 
{
	OSVERSIONINFO osvi;
	//DWORDLONG dwlConditionMask = 0;
	//int op = VER_GREATER_EQUAL;

	// Initialize the OSVERSIONINFO structure 
	ZeroMemory(&osvi, sizeof(OSVERSIONINFO));

#if 0
	// This verification method only works on 2000/XP/Vista
	OSVERSIONINFOEX osvi;
	ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
	osvi.dwMajorVersion = 4;	//4=NT 5=2k/XP/Svr2k3 6=Vista
	osvi.dwMinorVersion = 0;	//0=Vista/Longhorn/2k/XP 1=XPSP1 2=Svr2k3R2 XP64
	osvi.wServicePackMajor = 0;	//0=no svc_pack 1+=SP#
	osvi.wServicePackMinor = 0;	//0=no svc_pack 1+=SP#

	// Initialize the condition mask
	VER_SET_CONDITION( dwlConditionMask, VER_MAJORVERSION, op );
	VER_SET_CONDITION( dwlConditionMask, VER_MINORVERSION, op );
	VER_SET_CONDITION( dwlConditionMask, VER_SERVICEPACKMAJOR, op );
	VER_SET_CONDITION( dwlConditionMask, VER_SERVICEPACKMINOR, op );

	// Perform the test. Returns 0 if condition not met, non-zero if met.
	return VerifyVersionInfo(
		&osvi, 
		VER_MAJORVERSION | VER_MINORVERSION | 
		VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR,
		dwlConditionMask);
#endif

	// This is the "all-versions" compatible way to verify the version
	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	GetVersionEx(&osvi);
	
	// Compare the major platform ID. This can be further broken down to actual
	// OS version, minor_version, service_pack and function (workstation or server)
	// if really needed.
	switch (osvi.dwPlatformId){
		// Test for the Windows NT product family (NT/2000/XP/Vista).
		case VER_PLATFORM_WIN32_NT:
			return TRUE;
			break;

		// Test for the Windows Me/98/95.
		case VER_PLATFORM_WIN32_WINDOWS:
			return FALSE;
			break;
			
		// Win32s. This is "WOW" (Win32-on-Windows 3.1) and definitely
		// won't work.
		case VER_PLATFORM_WIN32s:
			return FALSE;
			break;
	}
	return FALSE;
}
/* end of file: init.c */
