/////////////////////////////////////////////////////////////////////
// CDXAStarDemo.cpp : Defines the entry point for the application.
/////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////
// Generated by the CDX Application Wizard
// By: Michael Rich [istan@alltel.net]
//
// With valuable help from:
//   Ioannis Karagiorgos, for the debugging help
//   Bil Simser, for the good ideas and constructive criticism
/////////////////////////////////////////////////////////////////////
//
// CDXAStarDemo
//
// Date: May, 30. 1999
// Author: Martin Seibert (m_s@magnet.at)
// Last updated: 05/30/99 03:59 by Martin Seibert
//
// This program is an enhanced and updated to CDX 2.2 version of a
// a demonstration program written by someone else from Germany.
//
// Various fixes were:
// 
// * Correction of a bug created as the CDXAStar class was converted to be compatible
//   with the currenct versio of CDX
// * Enhanced the diagonal movement in the pathfinding routine
//
// Below is the original header, since I don't want to "steal" otherones
// people brainwork!
//
////------------------------------------------------------------------------------
// PROJECT: The A* Pathfinder base Class
// FILE:   ASTAR.CPP
// OVERVIEW:
//    -Pretty good working Astar Pathfinder Algorithm Class.
//    -Based on SPATH.C Author: unknown (thanks)
//          	 http://www-cs-students.stanford.edu/~amitp/Articles/spath.zip
//    -Easy to implement, also with Your
//     favorite tiles based map but nevertheless a
//     little bit tricky to handle.
//    -Needs to be optimized or modified, perhaps a pointer to
//     a spritelist to reinitialisize the A* tilemap
//     and avoid sprite collosion.
//	   Let me hear about it.
//    Have fun!
// REVISION: 1.1
// Last Update: 31.08.97
//
//
// AUTHOR: pat@das-netz.de Germany 25.07.97
//------------------------------------------------------------------------------
//

#define WIN32_LEAN_AND_MEAN

// Cursor size

#define MOUSECURSOR_WIDTH 32	  // Mouse-Cursor-Sprite-Width
#define MOUSECURSOR_HEIGHT 32	  // Mouse-Cursor-Sprite-Height
#define MOUSECURSOR_SPRITES 1	  // Qty of sprites used to show the Cursor

// Map tile sizes

#define TILE_WIDTH 64
#define TILE_HEIGHT 64
#define TILE_QTY 6
#define FORBIDDEN_TILES 4		// Qty of forbidden Tiles

// Sprite sizes

#define UNIT_WIDTH 24			// Unit Width
#define UNIT_HEIGHT 24		// Unit Height
#define UNIT_FRAMES 40		// Qty of sprites used to show a unit
#define BAD_UNITS 2					  // The bad guys try to catch the good guys
#define GOOD_UNITS 1			// The good guys (the were two, but one died. ;-)

// Define your Map and Tile properties

#include <windows.h>
#define CDXINCLUDEALL     // this define includes all headers, otherwise include one by one
#include <cdx.h>
#include "cdxastar.h"

#include "resource.h"

/////////////////////////////////////////////////////////////////////
// Global Variables
/////////////////////////////////////////////////////////////////////
char szAppName[] = "CDXAStarDemo";
char szClassName[] = "CDXAStarDemoWndClass";
int  MouseX = 16, MouseY = 16;
int  ScreenWidth = 640, ScreenHeight = 480, ScreenBPP = 8;
int  lmb = 0;
BOOL g_bFullScreen = FALSE;	// running fullscreen?

HINSTANCE g_hInst;			// instance handle
HWND g_hWnd;				    // window handle


/////////////////////////////////////////////////////////////////////
// CDX Objects
/////////////////////////////////////////////////////////////////////
CDXScreen *Screen = 0;
CDXSprite *Cursor = 0;

CDXInput  *Input = 0;
CDXMap    *Map = 0;
CDXTile   *Tiles = 0;

CDXSprite *Virus[GOOD_UNITS];
CDXAStar *AstarVirus[GOOD_UNITS];

CDXSprite *Hunter[BAD_UNITS];
CDXAStar *AstarHunter[BAD_UNITS];

/////////////////////////////////////////////////////////////////////
// Forward declarations
/////////////////////////////////////////////////////////////////////
void AdjustWinStyle();
void cdx_GetSetMousePosition();

/////////////////////////////////////////////////////////////////////
// cdx_Init - handles initialization of the CDX objects
/////////////////////////////////////////////////////////////////////
BOOL cdx_Init()
{
  int y;
  char ErrorMsg[255];
  
	// Create the CDXSreen object
	Screen = new CDXScreen();
	if (Screen==NULL) return FALSE;

	// enable triple buffering
	Screen->SetTripleBuffering(TRUE);

	// start app fullscreen
	if( g_bFullScreen )
	{
		if (FAILED(Screen->CreateFullScreen(g_hWnd, 640, 480, 8))) return FALSE;
	}
	else
	{
		if (FAILED(Screen->CreateWindowed(g_hWnd, 640, 480))) return FALSE;
	}

	// TODO: Initialize your own CDX objects here

	Tiles = new CDXTile;
	if (!Tiles->Create(Screen, "maptiles.BMP", TILE_WIDTH, TILE_HEIGHT, TILE_QTY, CDXMEM_VIDTHENSYS))
	{
		MessageBox(NULL, "Error loading tiles!", "Error", MB_OK);
		PostQuitMessage(0);
		return FALSE;
	}

	// Create and load the map
	Map = new CDXMap(Tiles, Screen);
	if(!Map->Load("cdx.map"))
	{
		MessageBox(NULL, "Error loading map!", "Error", MB_OK);
		PostQuitMessage(0);
		return FALSE;
	}	

	Map->MoveTo(0,0);

	// Create and init the astar object with the CDXMmap object
	
	for( y = 0; y < GOOD_UNITS; y++ )
	{
		AstarVirus[y] = 0;
		AstarVirus[y] = new CDXAStar(Map, FORBIDDEN_TILES);
		if( AstarVirus[y] == 0 )
		{
			sprintf( ErrorMsg, "Astar[%d] = 0", y );
			MessageBox(NULL, ErrorMsg, "Error", MB_OK);
			PostQuitMessage(0);
			return FALSE;
		}
	}

	for( y = 0; y < BAD_UNITS; y++ )
	{
		AstarHunter[y] = new CDXAStar(Map, FORBIDDEN_TILES-1);
	}

	// Load cursor sprite
	Cursor = new CDXSprite;
	Cursor->Create(Screen, "MOUSE.BMP", MOUSECURSOR_WIDTH, MOUSECURSOR_HEIGHT, MOUSECURSOR_SPRITES, CDXMEM_VIDTHENSYS);
	if(Cursor==NULL)
	{
		MessageBox(NULL, "Cursor == NULL!", "Error", MB_OK);
		PostQuitMessage(0);
	}

	Cursor->SetFrame(0);
	Cursor->SetColorKey();

	// Load virus sprite
	
	for( y = 0; y < GOOD_UNITS; y++ )
	{
		if( y == 0 )
		{
			Virus[y] = new CDXSprite;
			Virus[y]->Create(Screen, "VIRUS.BMP", UNIT_WIDTH, UNIT_HEIGHT, UNIT_FRAMES, CDXMEM_VIDTHENSYS);
			Virus[y]->SetFrame(0);
			Virus[y]->SetColorKey();
		}
		else if( y == 1 )
		{
			Virus[y] = new CDXSprite;
			Virus[y]->Create(Screen, "smiley.BMP", UNIT_WIDTH, UNIT_HEIGHT, 1, CDXMEM_VIDTHENSYS);
			Virus[y]->SetFrame(0);
			Virus[y]->SetColorKey();
		}
	}

	for( y = 0; y < BAD_UNITS; y++ )
	{
		if( y == 0 )
		{
			Hunter[y] = new CDXSprite;
			Hunter[y]->Create(Screen, "bugz.bmp", 33, 20, 4, CDXMEM_VIDTHENSYS);
			Hunter[y]->SetDelay(2);
		}
		else if( y == 1 )
		{
			Hunter[y] = new CDXSprite;
			Hunter[y]->Create(Screen, "frog1.bmp", UNIT_WIDTH, UNIT_HEIGHT, 1, CDXMEM_VIDTHENSYS);
		}

		Hunter[y]->SetFrame(0);
		Hunter[y]->SetColorKey();
	}

	// create our input object
	Input = new CDXInput();
	if (Input==NULL) return FALSE;

	// Create input devices
	if(FAILED(Input->Create(g_hInst, g_hWnd))) return FALSE;

	Input->SetMouseLimits(0,0,640,480);
	Input->SetMousePos(0,0);

	return TRUE;
}

/////////////////////////////////////////////////////////////////////
// cdx_DeInit - handles cleanup of CDX objects
/////////////////////////////////////////////////////////////////////
void cdx_DeInit()
{
	int x;

	// TODO: Destroy your CDX objects here

	for( x = 0; x < GOOD_UNITS; x++ )
	{
		SAFEDELETE(Virus[x]);
		SAFEDELETE(AstarVirus[x]);
	}

	for( x = 0; x < BAD_UNITS; x++ )
	{
		SAFEDELETE(Hunter[x]);
		SAFEDELETE(AstarHunter[x]);
	}

	SAFEDELETE(Cursor);
	SAFEDELETE(Input);
	SAFEDELETE(Tiles);
	SAFEDELETE(Map);
	SAFEDELETE(Screen);
}


////////////////////////////////////////////////////////////////////////////////
//                         Check for sprite-tile collosion                    //
////////////////////////////////////////////////////////////////////////////////

BOOL TileHit(CDXSprite* Unit, int x, int y, int forbidden_tiles)
{
	Unit->SetPos(x - Map->GetPosX(), y - Map->GetPosY());

	for (int i = 0; i < forbidden_tiles; i++)	// for all forbidden Tiles
	{
		if ( Unit->TileHit(Map, i) )				// check Hit
			return TRUE;
	}

	return FALSE;
}

/////////////////////////////////////////////////////////////////////
// cdx_DoFrame - performs drawing of the current frame
/////////////////////////////////////////////////////////////////////
void cdx_DoFrame()
{
	static int	posX1[BAD_UNITS], posY1[BAD_UNITS], destX1[BAD_UNITS], destY1[BAD_UNITS];
	static int	posX[GOOD_UNITS], posY[GOOD_UNITS], destX[GOOD_UNITS], destY[GOOD_UNITS];
	static int curFrame = 0;
	static int count = 0;
	static int x = 0;
	static int y = 0;
	static int z = 0;
	static int actDelay = 0;

	if( count == 0 )
	{
		for( z = 0; z < GOOD_UNITS; z++ )
		{
			posX[z] = 16;
			destX[z] = 16;

			if( z == 0 )  // Top left corner
			{
				posY[z] = 16;
				destY[z] = 16;
			}
			else if( z == 1 ) // That was for the second Virus...
			{
				posY[z] = 96;
				destY[z] = 96;
			}
		}

		for( z = 0; z < BAD_UNITS; z++ )
		{
			destX1[z] = posX[0];
			destY1[z] = posY[0];

			if( z == 0 )  // Right top corner
			{
				posX1[z] = Map->GetPixelWidth()-32;
				posY1[z] = 32;
			}
			else if( z == 1 )  // Right bottom corner
			{
				posX1[z] = Map->GetPixelWidth()-32;
				posY1[z] = Map->GetPixelHeight()-32;
			}
		}

		count = 1;
	}

	// read input from devices
	Input->Update();

	// Get and set the mouse cursor position
	cdx_GetSetMousePosition();

	if( Input->GetKeyState( CDXKEY_MOUSELEFT ))
		lmb = 1;
	else
		lmb = 0;

	// perform the map with mouse

	if( MouseY == 0 ) Map->ScrollUp(4);
	if( MouseX == 0 ) Map->ScrollLeft(4);
	if( MouseY >= ScreenHeight-60 )  Map->ScrollDown(4);
	if( MouseX >= ScreenWidth-32 )  Map->ScrollRight(4);

  // ------------------------   A* Victim's stuff   ------------------------ //

  for( x = 0; x < GOOD_UNITS; x++ )
	{
    if ( lmb &&     // NewPath() must be called and be true before any other A* calls.
			AstarVirus[x]->NewPath(posX[x], posY[x], MouseX+Map->GetPosX(),     // Except Astar->ReachedGoal()
									 MouseY+Map->GetPosY()) )   // it returns true if path is false
		{
			AstarVirus[x]->PathNextNode();		     // point to next node = next tile on path
			destX[x] = AstarVirus[x]->NodeGetX();  // get the positions from current node
			destY[x] = AstarVirus[x]->NodeGetY();  // and set the new destination
		}

                                  // first we wait until reached next tile, performed below
		if ( !AstarVirus[x]->ReachedGoal() && (destX[x]==posX[x])&&(destY[x]==posY[x]))
		{
			AstarVirus[x]->PathNextNode();			  // point to next node
			destX[x] = AstarVirus[x]->NodeGetX();	// get the positions from current node
			destY[x] = AstarVirus[x]->NodeGetY();
		}
	
    //----------------------------------------------------------------- //
       
    // perform moves from tile to tile and adjust if a forbidden tile is hit

		if ( destX[x] > posX[x] ) posX[x]++;       // it is a right event?
		if ( TileHit(Virus[x],posX[x],posY[x],FORBIDDEN_TILES)) 
		{
			posX[x]--;
		}

   	if ( destY[x] > posY[x] ) posY[x]++;       // it is a down event?
 	  if ( TileHit(Virus[x],posX[x],posY[x],FORBIDDEN_TILES)) 
		{
			posY[x]--;
		}

   	if ( destX[x] < posX[x] ) posX[x]--;		// it is a left event?
 	  if ( TileHit(Virus[x],posX[x],posY[x],FORBIDDEN_TILES)) 
		{
			posX[x]++;
		}

   	if ( destY[x] < posY[x] ) posY[x]--;		// it is an up event?
 	  if ( TileHit(Virus[x],posX[x],posY[x],FORBIDDEN_TILES)) 
		{
			posY[x]++;
		}
  }

  // ------------------------   A* Hunter's stuff   ------------------------ //

	for( x = 0, y = 0; x < BAD_UNITS; x++ ) // Hunter[x] chases Virus[x]
	{
    // The destination position is the virus (y) position
    if ( AstarHunter[x]->NewPath(posX1[x], posY1[x], posX[y], posY[y]))
    {
			AstarHunter[x]->PathNextNode();	        // point to next node = next tile on path
			destX1[x] = AstarHunter[x]->NodeGetX();	// get the positions from current node
			destY1[x] = AstarHunter[x]->NodeGetY();	// and set the new destination
    }

                                  // first we wait until reached next tile, performed below
		if ( !AstarHunter[x]->ReachedGoal() && (destX1 == posX1) && (destY1 == posY1) )
		{
     	AstarHunter[x]->PathNextNode();         // point to next node
			destX1[x] = AstarHunter[x]->NodeGetX(); // get the positions from current node
   		destY1[x] = AstarHunter[x]->NodeGetY();
		}
	 
		if( AstarHunter[x]->ReachedGoal() && 
			(destX1[x] == posX1[x]) && 
			(destY1[x] == posY1[x]))  // Virus caught!
		{
			if( x == 0 )
			{
				posX1[x] = Map->GetPixelWidth()-32;
				posY1[x] = 32;
			}
			else
			{
				posX1[x] = Map->GetPixelWidth()-32;
				posY1[x] = Map->GetPixelHeight()-32;
			}
			
//			MessageBeep(0);
		}

    // ----------------------------------------------------------------- //
       
    // perform moves from tile to tile and adjust if a forbidden tile is hit
		if ( destX1[x] > posX1[x] )	// it is a right event?
		{
			posX1[x]++;
		}

		if ( TileHit(Hunter[x],posX1[x],posY1[x],FORBIDDEN_TILES-1) )
		{
			posX1[x]--;
		}
    	
		if ( destY1[x] > posY1[x] ) // it is a down event?
		{
			posY1[x]++;
		}

 	  if ( TileHit(Hunter[x],posX1[x],posY1[x],FORBIDDEN_TILES-1) ) 
		{
			posY1[x]--;
		}

   	if ( destX1[x] < posX1[x] )  // it is a left event?
		{
			posX1[x]--;
		}

 	  if ( TileHit(Hunter[x],posX1[x],posY1[x],FORBIDDEN_TILES-1) ) 
		{
			posX1[x]++;
		}

   	if ( destY1[x] < posY1[x] )  // it is an up event?
		{
			posY1[x]--;
		}

 	  if ( TileHit(Hunter[x],posX1[x],posY1[x],FORBIDDEN_TILES-1) ) 
		{
			posY1[x]++;
		}

		if( y < GOOD_UNITS-1 )  // Increment the GOOD_UNITS-counter
		{
			y++;
		}
	}

  // set the virus sprite frame
  if( curFrame >= UNIT_FRAMES ) 
    curFrame = 0;
  Virus[0]->SetFrame(curFrame++);

  // Is it time for the next frame? Keep in mind that only Hunter[0] is animated...
  if( Hunter[0]->GetDelay() == actDelay )
  {
    if( Hunter[0]->GetFrame() >= 3 ) 
      Hunter[0]->SetFrame(0);
    else
      Hunter[0]->SetFrame(Hunter[0]->GetFrame()+1);

    actDelay = 0;
  }
  else
  {
    actDelay++;
  }

  // Set the new sprite positions
	for( x = 0; x < GOOD_UNITS; x++ )
	{
		Virus[x]->SetPos(posX[x] - Map->GetPosX(), posY[x] - Map->GetPosY());
	}
	
	for( x = 0; x < BAD_UNITS; x++ )
	{
		Hunter[x]->SetPos(posX1[x] - Map->GetPosX(), posY1[x] - Map->GetPosY());
	}

	// clear the current frame
	Screen->GetBack()->Fill(0);
	// TODO: Add code to draw your objects during each frame

  // Draw the Map
  Map->Draw(Screen->GetBack());

  // Draw the sprites
	for( x = 0; x < GOOD_UNITS; x++ )
	{
		Virus[x]->Draw(Screen->GetBack(), 0, 0, CDXBLT_TRANS );
	}

	for( x = 0; x < BAD_UNITS; x++ )
	{
		Hunter[x]->Draw(Screen->GetBack() , 0 , 0 , CDXBLT_TRANS );
	}

  // Draw the "hand"-cursor
	Cursor->Draw(Screen->GetBack() , 0 , 0 , CDXBLT_TRANS );

	Screen->Flip();	 // Flip the back buffer to the front
}


/////////////////////////////////////////////////////////////////////
// Get and set the mouse cursor position
/////////////////////////////////////////////////////////////////////
void cdx_GetSetMousePosition()
{
  long PosX = 0, PosY = 0;
  
  // Get Mouse position
  Input->GetMousePos( &PosX, &PosY );

  // Keep the mouse cursor within the visible area!
  if( PosX < 0 )
  {
    PosX = 0;
  }

  if( PosY < 0 )
  {
    PosY = 0;
  }

  if( PosX + MOUSECURSOR_WIDTH > ScreenWidth )
  {
    PosX = ScreenWidth - MOUSECURSOR_WIDTH;
  }

  if( PosY + MOUSECURSOR_HEIGHT > ScreenHeight )
  {
    PosY = ScreenHeight - MOUSECURSOR_HEIGHT;
  }

  // Set Sprite to current mouse position
  Cursor->SetPos( (int)PosX, (int)PosY );

  // Keep the virtual mouse position also within the visible area
  Input->SetMousePos( PosX, PosY );

  MouseX = (int) PosX;
  MouseY = (int) PosY;
}



/////////////////////////////////////////////////////////////////////
// WinProc - handles application messages
/////////////////////////////////////////////////////////////////////
static long PASCAL WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch(message)
	{
		case WM_KEYDOWN:
			switch(wParam)
			{
			case VK_ESCAPE:
				// if Escape is pressed, close the application
				SendMessage(hWnd, WM_CLOSE, 0, 0);
				break;
			}
			return  0;

		// hide the mouse cursor
		case WM_SETCURSOR:
			SetCursor(NULL);
        
			return 1;
		case WM_CLOSE:
			cdx_DeInit();
		case WM_DESTROY:
			PostQuitMessage(0);
		return 0;

		default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
}

/////////////////////////////////////////////////////////////////////
// AdjustWinStyle - adjusts the window style according to the mode
/////////////////////////////////////////////////////////////////////
static void AdjustWinStyle()
{
	if (g_bFullScreen)
	{
		DWORD       dwStyle;

		// Change window attributes

		dwStyle = WS_POPUP | WS_VISIBLE;

		SetWindowLong(g_hWnd, GWL_STYLE, dwStyle);

		SetWindowPos(g_hWnd, HWND_TOPMOST, 0, 0, GetSystemMetrics(SM_CXFULLSCREEN),
			GetSystemMetrics(SM_CYFULLSCREEN), SWP_NOACTIVATE |
			SWP_NOZORDER);
	}
	else
	{
		RECT        rect = {0, 0, ScreenWidth , ScreenHeight};
		DWORD       dwStyle;

		// Change window attributes

		dwStyle = GetWindowStyle(g_hWnd);
		dwStyle &= ~WS_POPUP;
		dwStyle |= WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU;

		SetWindowLong(g_hWnd, GWL_STYLE, dwStyle);

		// Resize the window so that the client area is 640x480

		AdjustWindowRectEx(&rect, GetWindowStyle(g_hWnd), GetMenu(g_hWnd) != NULL,
			GetWindowExStyle(g_hWnd));

		// Just in case the window was moved off the visible area of the
		// screen.

		SetWindowPos(g_hWnd, NULL, 0, 0, rect.right-rect.left,
			rect.bottom-rect.top, SWP_NOMOVE | SWP_NOZORDER |
			SWP_NOACTIVATE);

		SetWindowPos(g_hWnd, HWND_NOTOPMOST, 0, 0, 0, 0,
			SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
	}
}

/////////////////////////////////////////////////////////////////////
// InitApp - Create the window and the CDX objects
/////////////////////////////////////////////////////////////////////
static BOOL InitApp(int nCmdShow)
{
	WNDCLASS WndClass;

	WndClass.style = CS_HREDRAW | CS_VREDRAW;
	WndClass.lpfnWndProc = WinProc;
	WndClass.cbClsExtra = 0;
	WndClass.cbWndExtra = 0;
	WndClass.hInstance = g_hInst;
	WndClass.hIcon = LoadIcon(g_hInst, "APPICON");
	WndClass.hCursor = LoadCursor(0, IDC_ARROW);
	WndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	WndClass.lpszMenuName = 0;
	WndClass.lpszClassName = szClassName;
	RegisterClass(&WndClass);


  	g_hWnd = CreateWindowEx(
		0,
		szClassName,
		szAppName,
 		WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
		0,
		0,
		320,
		240,
		NULL,
		NULL,
		g_hInst,
		NULL);
/*
    g_hWnd = CreateWindowEx(
		  WS_EX_TOPMOST,
		  szClassName,
		  szAppName,
 		  WS_POPUP,
		  0,
		  0,
		  GetSystemMetrics(SM_CXFULLSCREEN),
		  GetSystemMetrics(SM_CYFULLSCREEN),
		  NULL,
		  NULL,
		  g_hInst,
		  NULL);
*/
	if(!g_hWnd) return FALSE;


	ShowWindow(g_hWnd, nCmdShow);
	UpdateWindow(g_hWnd);

	AdjustWinStyle();

	return TRUE;
}

/////////////////////////////////////////////////////////////////////
// WinMain - inital function called by windows
/////////////////////////////////////////////////////////////////////
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow)
{
	MSG msg;

	// save the app instance
	g_hInst = hInstance;


	if(!InitApp(nCmdShow)) return FALSE;
	if(!cdx_Init())
	{
		PostQuitMessage(0);
		return FALSE;
	}

	while(1)
	{
		if(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
		{
			if(!GetMessage(&msg, NULL, 0, 0 )) return msg.wParam;
			TranslateMessage(&msg); 
			DispatchMessage(&msg);
		}
		else
		{
			cdx_DoFrame();
		}
	}
}
