#include "animator.h"
#include <string.h>

VOID NEAR PASCAL ParseCmdLine (LPSTR);

//////////////////////////////////////////////////////////////////////////
//  WinMain - Obviously sets up the Window class, loads string
//            and menu resources, creates the frame window,
//            sets the timer function pointer, toolhelp notify,
//            and allows the non-preemted loop to start.
//////////////////////////////////////////////////////////////////////////

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst,
	LPSTR lpszCmdLine, int nCmdShow)
{
	HACCEL      hAccel;
	WNDCLASS    wndclass;
	MSG         msg;

	if ((short)GetVersion() < 0x0310)
	{
		MESSAGE (IDS_WinVer);
		return 0;
	}

	_hInst = hInst;

	// Load all application strings... (defined in .RC file)
	LoadStr (IDS_AppName, _szAppName);
	LoadStr (IDS_AppTitle, _szTitleBar);
	LoadStr (IDS_ChildClassName, _szChildClass);
	LoadStr (IDS_StatBarClassName, _szStatBarClass);
	LoadStr (IDS_Extension, _szExtension);
	LoadStr (IDS_Untitled, _szUntitled);
	LoadStr (IDS_TimerIntervalKey, _szTimeInt);
	LoadStr (IDS_InfoSection, _szInfo);
	LoadStr (IDS_NumIconsKey, _szNumIcons);
	LoadStr (IDS_LinkFileKey, _szLinkFile);
	LoadStr (IDS_IconsSection, _szIconSection); 
	LoadStr (IDS_IconFrameKey, _szIcon);
	LoadStr (IDS_AutoAnimateKey, _szAutoAnimateKey);

	if (hPrevInst)
	{
		HWND hwnd = FindWindow (_szAppName,NULL);
		BringWindowToTop (hwnd);
		return 0;
	}

	_hmenuMain        = LoadMenu(_hInst, MAINMENU);
	_hmenuChild       = LoadMenu(_hInst, CHILDMENU);
	_hmenuMainWindow  = 
	_hmenuChildWindow = GetSubMenu(_hmenuChild, WNDMENUPOS);

	_hpnBlack = CreatePen (PS_SOLID, 1, GetSysColor(COLOR_BTNTEXT));
	_hpnGray  = CreatePen (PS_SOLID, 1, GetSysColor(COLOR_BTNSHADOW));
	_hpnWhite = CreatePen (PS_SOLID, 1, GetSysColor(COLOR_BTNHIGHLIGHT));

	if (!hPrevInst) 
	{
		wndclass.style          = CS_HREDRAW | CS_VREDRAW;
		wndclass.lpfnWndProc    = FrameProc;
		wndclass.cbClsExtra     = 0;
		wndclass.cbWndExtra     = 0;
		wndclass.hInstance      = _hInst;
		wndclass.hIcon          = LoadIcon(_hInst, MAINICON);
		wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
		wndclass.hbrBackground  = COLOR_APPWORKSPACE + 1;
		wndclass.lpszMenuName   = NULL;
		wndclass.lpszClassName  = _szAppName;

		if (!RegisterClass(&wndclass)) return 0;

		wndclass.lpfnWndProc    = ChildProc;
		wndclass.cbWndExtra     = WW_ENDOFWORDS;
		wndclass.hIcon          = LoadIcon(_hInst, CHILDICON);
		wndclass.hbrBackground  = COLOR_WINDOW + 1;
		wndclass.lpszClassName  = _szChildClass;
		
		if (!RegisterClass(&wndclass)) return 0;

		wndclass.lpfnWndProc    = StatusBarProc;
		wndclass.cbWndExtra     = 0;
		wndclass.hIcon          = NULL;
		wndclass.hbrBackground  = COLOR_BTNFACE + 1;
		wndclass.lpszClassName  = _szStatBarClass;

		if (!RegisterClass (&wndclass)) return 0;
	}

	_lpfnTimer = MakeProcInstance ((FARPROC)TimerCallback, _hInst);
	if (_lpfnTimer == NULL) goto MErr0;

	_lpfnNotify = MakeProcInstance ((FARPROC)NotifyProc, _hInst);
	if (_lpfnNotify == NULL) goto MErr1;

	_lpfnSettings = MakeProcInstance ((FARPROC)SettingsDlg, _hInst);
	if (_lpfnSettings == NULL) goto MErr2;

	_lpfnAbout = MakeProcInstance ((FARPROC)About, _hInst);
	if (_lpfnAbout == NULL) goto MErr3;

	_lpfnBroadcast = MakeProcInstance ((FARPROC)BroadcastProc, _hInst);
	if (_lpfnBroadcast == NULL) goto MErr4;

	_hwndFrame = CreateWindow(_szAppName, _szTitleBar,
		WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
		0, 0, FRAME_X, FRAME_Y,
		NULL, _hmenuMain, _hInst, NULL);

	if (!IsWindow(_hwndFrame)) goto MErr5;

	if (!RestorePosition (_hwndFrame, nCmdShow)) 
	{
		CenterWindow (_hwndFrame);
	}
	
	if (lpszCmdLine)
	{
		ParseCmdLine (lpszCmdLine);
		RefreshAnimations ();
	}

	ShowWindow(_hwndFrame, SW_SHOW);
	UpdateWindow(_hwndFrame);
	
	hAccel = LoadAccelerators(_hInst, ACCELTABLE);
	if (hAccel == NULL) goto MErr5;

	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!TranslateMDISysAccel(_hwndClient, &msg) &&
			!TranslateAccelerator(_hwndFrame, hAccel, &msg)) 
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

MErr5:
	FreeProcInstance (_lpfnBroadcast);
MErr4:
	FreeProcInstance (_lpfnAbout);
MErr3:
	FreeProcInstance (_lpfnSettings);
MErr2:
	FreeProcInstance (_lpfnNotify);
MErr1:
	FreeProcInstance (_lpfnTimer);    
MErr0:
	DeletePen (_hpnBlack);
	DeletePen (_hpnWhite);
	DeletePen (_hpnGray);

	return msg.wParam;
}



//////////////////////////////////////////////////////////////////////////
// CenterWindow - takes any window (more especially dialog boxes) and
//                centers it on the display.
//////////////////////////////////////////////////////////////////////////

VOID WINAPI CenterWindow (HWND hDlg)
{
  RECT rc;

  GetWindowRect(hDlg,&rc);
  SetWindowPos(hDlg,NULL,
   (GetSystemMetrics(SM_CXSCREEN) - (rc.right - rc.left)) / 2,
   (GetSystemMetrics(SM_CYSCREEN) - (rc.bottom - rc.top)) / 3,
	0, 0, SWP_NOSIZE | SWP_NOACTIVATE);
}



//////////////////////////////////////////////////////////////////////////
// RestorePosition() - Gets the formated information for the 
// WINDOWPLACEMENT structure from the WIN.INI file, parses it by
// calling IntFromString(), and then calls SetWindowPlacement().
//////////////////////////////////////////////////////////////////////////

BOOL WINAPI RestorePosition (HWND hwnd, short nCmdShow)
{
	char szFmt[80];
	LPSTR lpszCur = (LPSTR)szFmt;
	WINDOWPLACEMENT wpl;

	GetProfileString ((LPSTR)_szTitleBar, (LPSTR)"Position\0", (LPSTR)"\0",
		(LPSTR)szFmt, sizeof(szFmt));

	if (szFmt[0] == '\0' || IntFromString((LPSTR FAR *)&lpszCur) != 9)
	{
		return FALSE;
	}
	else
	{
		// Format in the WIN.INI file for the position restoration is:
		// [Animator]
		// Position = 9 showcmd minX minY maxX maxY normX normY normDX normDY
		//
		wpl.length = sizeof(wpl); 
		wpl.showCmd = (UINT)IntFromString((LPSTR FAR *)&lpszCur);
		wpl.showCmd = (UINT)nCmdShow;
		wpl.ptMinPosition.x = IntFromString((LPSTR FAR *)&lpszCur);
		wpl.ptMinPosition.y = IntFromString((LPSTR FAR *)&lpszCur);
		wpl.ptMaxPosition.x = IntFromString((LPSTR FAR *)&lpszCur);
		wpl.ptMaxPosition.y = IntFromString((LPSTR FAR *)&lpszCur);
		wpl.rcNormalPosition.left = IntFromString((LPSTR FAR *)&lpszCur);
		wpl.rcNormalPosition.top = IntFromString((LPSTR FAR *)&lpszCur);
		wpl.rcNormalPosition.right = IntFromString((LPSTR FAR *)&lpszCur);
		wpl.rcNormalPosition.bottom = IntFromString((LPSTR FAR *)&lpszCur);

		return SetWindowPlacement(hwnd, &wpl);
	}
}



//////////////////////////////////////////////////////////////////////////
// RecordPosition() - Gets the window placement and show state, by
// using the GetWindowPlacement function.  the length member MUST
// be filled or it will fail.  We then write the information to 
// the WIN.INI with the info separated by spaces.
//////////////////////////////////////////////////////////////////////////

VOID WINAPI RecordPosition(HWND hwnd)
{
	WINDOWPLACEMENT wpl;

	char szFmt[80];

	wpl.length = sizeof(wpl);    // necessary...
	GetWindowPlacement(hwnd, &wpl);

	wsprintf((LPSTR)szFmt, "%d %d %d %d %d %d %d %d %d %d",
		9,
		wpl.showCmd,
		wpl.ptMinPosition.x,
		wpl.ptMinPosition.y,
		wpl.ptMaxPosition.x,
		wpl.ptMaxPosition.y,
		wpl.rcNormalPosition.left,
		wpl.rcNormalPosition.top,
		wpl.rcNormalPosition.right,
		wpl.rcNormalPosition.bottom
	);

	WriteProfileString((LPSTR)_szTitleBar, (LPSTR)"Position", (LPSTR)szFmt);
}



//////////////////////////////////////////////////////////////////////////
// IntFromString() - Our version of the C function itoa, with a little
// more flexibility given our situation: a string of ints separated by
// spaces.
//////////////////////////////////////////////////////////////////////////

short WINAPI IntFromString(LPSTR FAR * lplpsz)
{
	LPSTR lpsz = *lplpsz;
	short i = 0;
	char ch;
	BOOL fNeg;

	while (*lpsz == ' ')
		lpsz++;

	fNeg = FALSE;
	while (ch = *lpsz++)
	{
		if (ch == '-')
		{
			fNeg = !fNeg;
			continue;
		}

		if (ch < '0' || ch > '9')
			break;
		i = (i * 10) + (ch - '0');
	}
	*lplpsz = lpsz;

	return (fNeg ? -i : i);
}



//////////////////////////////////////////////////////////////////////////
// DeletePreviousHandles - Frees the memory occupied by the icons
// in the list, if any in the list.
//////////////////////////////////////////////////////////////////////////

VOID WINAPI DeletePreviousHandles (HWND hwnd)
{
	short sChild = WINDOWNUM(hwnd);
	short sLimit = NUMICONS (sChild);
	short i;
	
	if (sLimit)
	{
		for (i=0; i<sLimit ;i++)
		{
			if (HICONS(sChild)[i] != NULL)
			{
				GlobalFree (HICONS(sChild)[i]);
				HICONS(sChild)[i] = NULL;
			}
		}
		SET_NUMICONS (sChild, 0);
	}
}




//////////////////////////////////////////////////////////////////////////
// SetupIconHandles - takes the active window, gets the icon filenames
// from the listbox, loads those icons into memory in an array kept
// in the animStruct.
//////////////////////////////////////////////////////////////////////////

VOID WINAPI SetupIconHandles (HWND hwnd)
{
	short    sChild, sLimit, i;
	HWND     hwndList;
	HICON *  phIcons;
	char     szlistbox[MAX_FILE_SIZE];

	if (!IsWindow(hwnd)) return;

	sChild = WINDOWNUM(hwnd);
	hwndList = HWNDLIST(hwnd);

	SET_NUMICONS (sChild, (short)ListBox_GetCount(hwndList));
	
	sLimit    = NUMICONS(sChild);
	phIcons  = HICONS(sChild);

	for (i=0 ; i<sLimit ; i++)
	{   
		ListBox_GetText (hwndList,i,(LPSTR)szlistbox);

		// If the user manually entered the filename in the
		// .ANM file, and did not include a path, this condition
		// takes care of it:
		if (strrchr (szlistbox, '\\') == NULL)
		{
			char szDir[MAX_FILE_SIZE];

			getcwd (szDir, MAX_FILE_SIZE);

			if (szDir[strlen(szDir)-1] != '\\')
			{
				strcat (szDir, "\\");
			}
			
			strcat (szDir, szlistbox);

			phIcons[i] = ExtractIcon(_hInst, (LPSTR)szDir, 0);
		}
		else
		{
			phIcons[i] = ExtractIcon(_hInst, (LPSTR)szlistbox, 0);
		}
	}

	phIcons[sLimit] = NULL;
}



//////////////////////////////////////////////////////////////////////////
// DeleteCurSel - takes the selected files in the listbox, and
// deletes them.
//////////////////////////////////////////////////////////////////////////

BOOL WINAPI DeleteCurSel (HWND hwnd)
{
	short  sSel;
	short  sChild;
	short  sNumIcons;
	HWND   hwndList;
	short  i;
	
	if (!IsWindow(hwnd)) return FALSE;

	sChild = WINDOWNUM (hwnd);
	hwndList = HWNDLIST (hwnd);
	sNumIcons = NUMICONS (sChild);
	
	sSel = (short)ListBox_GetCurSel (hwndList);
			
	if (sSel == LB_ERR) 
	{
		return FALSE;
	}

	for (i=sSel ; i<sNumIcons ; i++)
	{
		if (i == sSel)
		{
			GlobalFree (HICONS(sChild)[i]);
		}
		HICONS(sChild)[i] = HICONS(sChild)[i+1];
	}
	
	SET_NUMICONS (sChild, --sNumIcons);

	ListBox_DeleteString (hwndList, sSel);
	ListBox_SetCurSel (hwndList, sSel);

	return TRUE;
}



//////////////////////////////////////////////////////////////////////////
// GetWindowListbox - gets the active window, then looks to its first
// extra window word for the handle to its listbox, and returns it.
//////////////////////////////////////////////////////////////////////////

HWND WINAPI GetWindowListbox(VOID)
{
	return (HWND)(IsWindow(_hwndClient) ? 
								  HWNDLIST(MDI_GetActive(_hwndClient))
								: NULL);
}



//////////////////////////////////////////////////////////////////////////
// GetWindowNumber - looks to the second extra window word in the 
// window data structure, and finds the number of the window, and
// returns it.  The number is kept for data structure access purposes.
//////////////////////////////////////////////////////////////////////////

short WINAPI GetWindowNumber(VOID)
{
	return (short)(IsWindow(_hwndClient) ? 
							  WINDOWNUM(MDI_GetActive(_hwndClient))
							: -1);
}



//////////////////////////////////////////////////////////////////////////
// CheckForDoubles - Just ensures that nobody tries to set up two 
// animations on one running application.
//////////////////////////////////////////////////////////////////////////

BOOL WINAPI CheckForDoubles (HWND hwnd)
{
	int  i;
	BOOL result = FALSE;

	for (i=0 ; i<MAXANIMATIONS ; i++)
	{
		if (HWNDANIM(i)!=NULL)
		{
			if (HWNDTARGET(i) == hwnd)
			{
				result = TRUE;
			}
		}
	}

	return result;
}



//////////////////////////////////////////////////////////////////////////
// MESSAGE - just loads the appropriate string via the string ID value,
// and displays it in the form of an error message box.
//////////////////////////////////////////////////////////////////////////

VOID WINAPI MESSAGE (WORD wStringID)
{
	char szString[96];
	LoadStr (wStringID,szString);
	MessageBox (NULL, (LPSTR)szString, (LPSTR)_szAppName,
				MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL);
}



//////////////////////////////////////////////////////////////////////////
// GetTextWidth() - Gets the height of the string in the DC which
// already has the font selected into it.
//////////////////////////////////////////////////////////////////////////

UINT WINAPI GetTextWidth (HDC hdc, LPSTR lp)
{
	return (UINT)LOWORD(GetTextExtent (hdc, lp, lstrlen(lp)));
}



//////////////////////////////////////////////////////////////////////////
// GetTextHeight() - Gets the height of the string in the DC which
// already has the font selected into it.
//////////////////////////////////////////////////////////////////////////

UINT WINAPI GetTextHeight (HDC hdc, LPSTR lp)
{
	return (UINT)HIWORD(GetTextExtent (hdc, lp, lstrlen(lp)));
}



//////////////////////////////////////////////////////////////////////////
// GetANSITextHeight() - returns the height including leading to the
// caller.
//////////////////////////////////////////////////////////////////////////

UINT WINAPI GetANSITextHeight(VOID)
{
	TEXTMETRIC tm;
	HDC hdc = GetDC (NULL);
	HFONT hold = SelectFont (hdc, GetStockFont(ANSI_VAR_FONT));

	if (!hold) return 0;

	GetTextMetrics (hdc, &tm);
	SelectFont (hdc, hold);
	ReleaseDC (NULL, hdc);
	return (UINT)(tm.tmHeight + tm.tmExternalLeading);
}



//////////////////////////////////////////////////////////////////////////
// NotifyProc() - Everytime TOOLHELP figures out that a task is 
// exiting or starting, it notifies us via this callback function.
// We immediately post the message to get the condition into the queue 
// of a window. 
//////////////////////////////////////////////////////////////////////////

BOOL _export CALLBACK NotifyProc (WORD wID, DWORD dwData)
{
	switch (wID)
	{
		case NFY_EXITTASK:
		{
			PostMessage (_hwndFrame, WM_COMMAND, IDN_EXITTASK, 0L);
			break;
		}

		case NFY_STARTTASK:
		{
			PostMessage (_hwndFrame, WM_COMMAND, IDN_NEWTASK, 0L);
			break;
		}

		default:
			return FALSE;
	}
	return TRUE;
}




///////////////////////////////////////////////////////////////////
// ParseCmdLine() takes the command line for animator and parses 
// it to find which files to initially open, and opens them.
///////////////////////////////////////////////////////////////////

VOID NEAR PASCAL ParseCmdLine (LPSTR lpszCmdLine)
{
	LPSTR   lpszCur = lpszCmdLine;
	char    szFileName[MAX_FILE_SIZE];
	short   i = 0;    
	
	AnsiUpper (lpszCmdLine);

	while (*lpszCur)
	{
		while (*lpszCur && *lpszCur != ' ')
		{
			szFileName[i++] = *lpszCur++;
		}

		szFileName[i] = '\0';

		if (!GetPathIfNoPath ((LPSTR)szFileName))
		{
			MESSAGE (IDS_InvalidScript);
		}
		else
		{
			OpenIconsInFile (szFileName);
		}

		if (*lpszCur) 
		{
			lpszCur++;  // kill leading blank character.
		}

		i = 0;
	}
}
