/* **********************************************************************************************************************
   Included Header Files
   **********************************************************************************************************************/

#include "VDEngine.h"



#define _CRTDBG_MAP_ALLOC
#ifdef _DEBUG
    #define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif  // _DEBUG



/* **********************************************************************************************************************
   Constructor
   **********************************************************************************************************************/

VDEngine::VDEngine() {
    vdEngineRunning = TRUE;
    mapEditor = NULL;
    npcEditor = NULL;
    graphics = NULL;
    input = NULL;
    world = NULL;
    user = NULL;
    worldOffset = NULL;
    npcs.clear();
    width = 0;
    height = 0;
    actualMousePosition.x = 0;
    actualMousePosition.y = 0;
    
};



/* **********************************************************************************************************************
   Destructor
   **********************************************************************************************************************/

VDEngine::~VDEngine() {
    if (worldOffset != NULL) {
        delete worldOffset;
        worldOffset = NULL;
    };
    if (user != NULL) {
        delete user;
        user = NULL;
    };
    if (world != NULL) {
        delete world;
        world = NULL;
    };
    if (input != NULL) {
        delete input;
        input = NULL;
    };
    if (graphics != NULL) {
        delete graphics;
        graphics = NULL;
    };
    if (npcEditor != NULL) {
        delete npcEditor;
        npcEditor = NULL;
    };
    if (mapEditor != NULL) {
        delete mapEditor;
        mapEditor = NULL;
    };
};



/* **********************************************************************************************************************
   Global Variables
   **********************************************************************************************************************/

// these are used to keep track of whether the windows are open or not
HWND vdEngineWindow = NULL;
HWND mapEditorWindow = NULL;
HWND npcEditorWindow = NULL;



/* **********************************************************************************************************************
   WindowProcedure
   ---------------
   Processes messages for the main window.
   **********************************************************************************************************************/

LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
    switch (Msg) {
        case WM_CLOSE:
            // TODO: maybe only use GetForegroundWindow(), and not need global pointers to windows
            // user clicked "X"
            DestroyWindow(hWnd);
            if (hWnd == mapEditorWindow) {
                mapEditorWindow = NULL;
            } else if (hWnd == npcEditorWindow) {
                npcEditorWindow = NULL;
            } else if (hWnd == vdEngineWindow) {
                vdEngineWindow = NULL;
            };
            break;
        case WM_DESTROY:
            // sent by DestroyWindow, only exit if it's the main window clicking "X"
            if (hWnd == vdEngineWindow) {
                PostQuitMessage(0);
            };
            break;
        default:
            break;
    };
    return DefWindowProc(hWnd, Msg, wParam, lParam);
};



/* **********************************************************************************************************************
   Run
   ---
   Main function of the map maker.
   #param  instance  The instance of the program.
   **********************************************************************************************************************/

void VDEngine::Run(HINSTANCE instance) {
    MSG message;
    POINT mousePosition;
    RECT windowDimensions;
    RECT renderDimensions;

    // initialize all the parts of the VDEngine
    mapEditor = new VDMapEditor();
    npcEditor = new VDNPCEditor();
    graphics = new VDDirect3D();
    input = new VDInput();
    world = new VDWorld();
    user = new VDEntity();
    worldOffset = new VDEntity();

    // initialize the user position
    user->SetCurrentLocation(0, 0);
    worldOffset->SetCurrentLocation(0, 0);

    // height/width of the window
    width = 896;
    height = 672;

    // register all of the windows
    RegisterWindows(instance);

    // create the application window
    vdEngineWindow = CreateWindowEx(NULL, "VDEngine", "VDEngine", WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE, 0, 0, width, height, NULL, NULL, instance, NULL);
    if (vdEngineWindow == NULL) {
        VDError("Failed to create the window.", __FILE__, __LINE__);
    };

    // initialize the graphics API
    graphics->InitializeDirect3D(vdEngineWindow);

    // initialize input
    input->InitializeInput(instance);
    input->InitializeKeyboard(vdEngineWindow);
    input->InitializeMouse(vdEngineWindow);

    // TODO: I moved this from where a new map editor map would be created... might want to refine the following:
    //       we'll want to repopulate the NPC's everytime we add an NPC, maybe??
    // load up the NPC's
    LoadNPCs();

    // load the Map Editor interface
    mapEditorWindow = mapEditor->CreateMapEditor(instance, vdEngineWindow, VD_MAP_EDITOR_CLASS_NAME);

    // populate the NPC list
    for (unsigned int n = 0; n < npcs.size(); n++) {
        (void) SendMessage(mapEditor->GetNPCListBox(), LB_ADDSTRING, 0, (LPARAM) npcs.at(n).GetName());
    };

    // main loop
    while (vdEngineRunning) {
        int choice = 0;

        // dispatch incoming sent messages, check the thread message queue, retrieve message
        while (PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) {
            if (message.message == WM_QUIT) {
                vdEngineRunning = FALSE;
            } else if (message.message == WM_COMMAND) {
                ProcessMenu(instance, message);
            };

            TranslateMessage(&message);
            DispatchMessage(&message);
        };

        // gather input
        input->RetrieveLostInput();
        input->UpdateInput();
        GetCursorPos(&mousePosition);

        // take a look at the current window location
        (void) GetWindowRect(vdEngineWindow, &windowDimensions);

        // acquire the real mouse position
        actualMousePosition.x = mousePosition.x - GetSystemMetrics(SM_CXFIXEDFRAME) - windowDimensions.left;
        actualMousePosition.y = mousePosition.y - (GetSystemMetrics(SM_CYFIXEDFRAME) + GetSystemMetrics(SM_CYCAPTION)) - windowDimensions.top;

        // check the various editors for messages
        if (mapEditorWindow != NULL && mapEditorWindow == GetForegroundWindow()) {
            if (mapEditorWindow == GetForegroundWindow()) {
                mapEditor->ProcessMapEditor(graphics->GetDirect3DRenderer(), input, &npcs);
            };
        };
        if (npcEditorWindow != NULL && npcEditorWindow == GetForegroundWindow()) {
            if (npcEditorWindow == GetForegroundWindow()) {
                npcEditor->ProcessNPCEditor(graphics->GetDirect3DRenderer(), &npcs);
            };
        };

        // editing the world
        if (mapEditorWindow != NULL) {
            if (vdEngineWindow == GetForegroundWindow()) {
                ProcessWorldEditing();
            };
        };

        // see if any keys were pressed
        if (input->GetKeyboardInput()[DIK_ESCAPE] & 0x80) {
            vdEngineRunning = FALSE;
        } else if (input->GetKeyboardInput()[DIK_LEFT] & 0x80) {
            choice = 1;
        } else if (input->GetKeyboardInput()[DIK_RIGHT] & 0x80) {
            choice = 2;
        } else if (input->GetKeyboardInput()[DIK_UP] & 0x80) {
            choice = 3;
        } else if (input->GetKeyboardInput()[DIK_DOWN] & 0x80) {
            choice = 4;
        };

        // user decided to move
        if (choice >= 1 && choice <= 4) {
            // update the user's location
            user->Move(choice);
        };

        // render map graphics
        if (vdEngineWindow != NULL) {
            renderDimensions.left = 0;
            renderDimensions.top = 0;
            renderDimensions.right = width - GetSystemMetrics(SM_CXFIXEDFRAME);
            renderDimensions.bottom = height - GetSystemMetrics(SM_CYFIXEDFRAME);
            graphics->Render(&renderDimensions, -1, 0, 0, user->GetCurrentLocationX(), user->GetCurrentLocationY());
        };

        // render Map Editor stuff
        if (mapEditorWindow != NULL) {
            RECT drawingArea;

            // draw the tile sheet
            (void) mapEditor->GetTileSheetDimensions(&drawingArea);
            graphics->RenderSprite(&drawingArea, mapEditor->GetWindow(), mapEditor->GetTileSheet());

            // draw the selected NPC sprite
            (void) mapEditor->GetNPCSpriteDimensions(&drawingArea);
            graphics->RenderSprite(&drawingArea, mapEditor->GetWindow(), mapEditor->GetNPCSprite());

            // draw the selection rectangle
            if (mapEditor->IsTileSelected() == TRUE) {
                HDC deviceContext = GetDC(mapEditor->GetWindow());
                if (deviceContext == NULL) {
                    VDError("Failed to retrieve a handle to the display device context.", __FILE__, __LINE__);
                };

                // change pen
                HPEN tempPen = (HPEN) SelectObject(deviceContext, CreatePen(PS_SOLID, 1, RGB(255, 0, 0)));
                if (tempPen == NULL) {
                    VDError("Failed to change the pen object.", __FILE__, __LINE__);
                };

                // change brush
                HBRUSH tempBrush = (HBRUSH) SelectObject(deviceContext, GetStockObject(NULL_BRUSH));
                if (tempBrush == NULL) {
                    VDError("Failed to change the brush object.", __FILE__, __LINE__);
                };

                // draw the selection rectangle
                RECT location, sheetSourceRect, actualSourceRect;
                (void) mapEditor->GetTileSheetDimensions(&location);
                (void) mapEditor->GetTileSheet()->GetSourceRectangle(&sheetSourceRect);
                (void) mapEditor->GetTileSheetSourceRectangle(&actualSourceRect);
                drawingArea.left = actualSourceRect.left - sheetSourceRect.left + location.left;
                drawingArea.top = actualSourceRect.top - sheetSourceRect.top + location.top;
                drawingArea.right = drawingArea.left + 32;
                drawingArea.bottom = drawingArea.top + 32;
                if (Rectangle(deviceContext, drawingArea.left, drawingArea.top, drawingArea.right, drawingArea.bottom) == FALSE) {
                    VDError("Failed to draw a rectangle.", __FILE__, __LINE__);
                };

                // free up memory
                if (DeleteObject(SelectObject(deviceContext, tempPen)) == FALSE) { 
                    VDError("Failed to delete the pen", __FILE__, __LINE__);
                };
                if (DeleteObject(SelectObject(deviceContext, tempBrush)) == FALSE) {
                    VDError("Failed to delete the brush", __FILE__, __LINE__);
                };
                if (ReleaseDC(mapEditor->GetWindow(), deviceContext) == FALSE) {
                    VDError("Failed to release the drawing device context.", __FILE__, __LINE__);
                };
            };
        };

        // render NPC Editor sprites
        if (npcEditorWindow != NULL) {
            RECT drawingArea;
            (void) npcEditor->GetNPCSpriteDimensions(&drawingArea);
            if (npcEditor->GetNPCSpriteSheet() != NULL) {
                graphics->RenderSprite(&drawingArea, npcEditor->GetWindow(), npcEditor->GetNPCSpriteSheet());
            };
            if (npcEditor->GetNPCSprite() != NULL) {
                graphics->RenderSprite(&drawingArea, npcEditor->GetWindow(), npcEditor->GetNPCSprite());
            };
            if (npcEditor->GetNPCSpriteSheet() == NULL && npcEditor->GetNPCSprite() == NULL) {
                graphics->RenderSprite(&drawingArea, npcEditor->GetWindow(), NULL);
            };
        };
    };
};



/* **********************************************************************************************************************
   RegisterWindows
   ---------------
   Registers the window classes that we'll be using in this application.
   #param  instance   Instance of the application.
   **********************************************************************************************************************/

void VDEngine::RegisterWindows(HINSTANCE instance) {
    WNDCLASSEX windowClassStructure;
    ATOM registerClassEx;

    // main window
    windowClassStructure.cbClsExtra = 0;                                   // number of extra bytes to allocate following the window-class structure
    windowClassStructure.cbSize = sizeof(WNDCLASSEX);                      // size of the structure (in bytes)
    windowClassStructure.cbWndExtra = 0;                                   // number of extra bytes to allocate following the window instance
    windowClassStructure.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);  // background brush
    windowClassStructure.hCursor = LoadCursor(NULL, IDC_ARROW);            // cursor
    windowClassStructure.hIcon = NULL;                                     // icon
    windowClassStructure.hIconSm = LoadIcon(NULL, IDI_APPLICATION);        // small icon
    windowClassStructure.hInstance = instance;                             // instance that has the window procedure
    windowClassStructure.lpfnWndProc = WindowProcedure;                    // window procedure
    windowClassStructure.lpszClassName = VD_ENGINE_CLASS_NAME;             // class name
    windowClassStructure.lpszMenuName = NULL;                              // menu resource name
    windowClassStructure.style = NULL;                                     // style
    registerClassEx = RegisterClassEx(&windowClassStructure);
    if (registerClassEx == 0) {
        VDError("Failed to register the window class.", __FILE__, __LINE__);
    };
    (void) SecureZeroMemory(&windowClassStructure, sizeof(windowClassStructure));

    // Map Editor
    windowClassStructure.cbClsExtra = 0;                                   // number of extra bytes to allocate following the window-class structure
    windowClassStructure.cbSize = sizeof(WNDCLASSEX);                      // size of the structure (in bytes)
    windowClassStructure.cbWndExtra = 0;                                   // number of extra bytes to allocate following the window instance
    windowClassStructure.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);  // background brush
    windowClassStructure.hCursor = LoadCursor(NULL, IDC_ARROW);            // cursor
    windowClassStructure.hIcon = NULL;                                     // icon
    windowClassStructure.hIconSm = LoadIcon(NULL, IDI_APPLICATION);        // small icon
    windowClassStructure.hInstance = instance;                             // instance that has the window procedure
    windowClassStructure.lpfnWndProc = WindowProcedure;                    // window procedure
    windowClassStructure.lpszClassName = VD_MAP_EDITOR_CLASS_NAME;         // class name
    windowClassStructure.lpszMenuName = MAKEINTRESOURCE(VD_MENU);          // menu resource name
    windowClassStructure.style = NULL;                                     // style
    registerClassEx = RegisterClassEx(&windowClassStructure);
    if (registerClassEx == 0) {
        VDError("Failed to register the window class.", __FILE__, __LINE__);
    };

    // NPC Editor
    windowClassStructure.cbClsExtra = 0;                                   // number of extra bytes to allocate following the window-class structure
    windowClassStructure.cbSize = sizeof(WNDCLASSEX);                      // size of the structure (in bytes)
    windowClassStructure.cbWndExtra = 0;                                   // number of extra bytes to allocate following the window instance
    windowClassStructure.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);  // background brush
    windowClassStructure.hCursor = LoadCursor(NULL, IDC_ARROW);            // cursor
    windowClassStructure.hIcon = NULL;                                     // icon
    windowClassStructure.hIconSm = LoadIcon(NULL, IDI_APPLICATION);        // small icon
    windowClassStructure.hInstance = instance;                             // instance that has the window procedure
    windowClassStructure.lpfnWndProc = WindowProcedure;                    // window procedure
    windowClassStructure.lpszClassName = VD_NPC_EDITOR_CLASS_NAME;         // class name
    windowClassStructure.lpszMenuName = NULL;                              // menu resource name
    windowClassStructure.style = NULL;                                     // style
    registerClassEx = RegisterClassEx(&windowClassStructure);
    if (registerClassEx == 0) {
        VDError("Failed to register the window class.", __FILE__, __LINE__);
    };
};



/* **********************************************************************************************************************
   ProcessMenu
   -----------
   Processes messages sent by the system menu.
   #param  instance  Instance of the program.
   #param  message   The message provided by PeekMessage.
   **********************************************************************************************************************/

void VDEngine::ProcessMenu(HINSTANCE instance, MSG message) {
    switch (LOWORD(message.wParam)) {

        // Menu -> File -> Open
        case VD_MENU_FILE_OPEN:
            OpenMap();
            break;

        // Menu -> File -> Close
        case VD_MENU_FILE_CLOSE:
            ClearMap();
            break;

        // Menu -> File -> Save
        case VD_MENU_FILE_SAVE:
            SaveMap();
            break;

        // Menu -> File -> Exit
        case VD_MENU_FILE_EXIT:
            PostQuitMessage(0);
            break;

        // Menu -> Tools -> NPC Editor
        case VD_MENU_TOOLS_NPC_EDITOR:
            if (npcEditorWindow == NULL) {
                // TODO: load up NPC's, on 'X' then WriteNPCs() I guess
                // load up the NPC's
                LoadNPCs();

                // load the NPC Editor interface
                npcEditorWindow = npcEditor->CreateNPCEditor(instance, vdEngineWindow, VD_NPC_EDITOR_CLASS_NAME);

                // populate the NPC list
                for (unsigned int n = 0; n < npcs.size(); n++) {
                    (void) SendMessage(npcEditor->GetNPCListBox(), LB_ADDSTRING, 0, (LPARAM) npcs.at(n).GetName());
                };
            };
            break;
        

        // Menu -> Help -> About
        case VD_MENU_HELP_ABOUT:
            (void) MessageBox(NULL, "Varkalas D`Lonovan's 2D Game Engine", "VDEngine", MB_OK);
            break;
        default:
            break;
    };
};



/* **********************************************************************************************************************
   OpenMap
   -------
   Opens a world map file.
   **********************************************************************************************************************/

void VDEngine::OpenMap() {
    char text[FILE_NAME_MAX_SIZE];

    // open a world file
    VDOpenFile(mapEditor->GetWindow(), "VDEngine Maps (*.vdm)\0*.vdm\0", text);

    // make sure the user selected a file
    if (strcmp(text, "") != 0) {
        // clear the current map
        ClearMap();

        // load the world
        world->LoadWorld(text);

        // clear the graphics
        graphics->ClearScreen();

        // add the tile sprites
        for (int x = 0; x < world->GetWorldSizeX(); x++) {
            for (int y = 0; y < world->GetWorldSizeY(); y++) {
                RECT sourceRect;
                (void) SetRect(&sourceRect, 0, 0, 32, 32);
                graphics->AddSprite(world->GetTileImageFileLocation(x, y),
                                    sourceRect,
                                    (float) x * 32.0f,
                                    (float) y * 32.0f,
                                    0xFF000000,
                                    FALSE,
                                    FALSE);
            };
        };

        // add the NPC sprites
        for (unsigned int n = 0; n < world->GetNPCsSize(); n++) {
            // figure out which direction the NPC is facing
            RECT sourceRect;
            (void) SetRect(&sourceRect, 0, 0, 0, 0);
            switch (world->GetNPCDirection(n)) {
                case 0:
                    (void) SetRect(&sourceRect, 0, 0, 32, 32);
                    break;
                case 1:
                    (void) SetRect(&sourceRect, 192, 0, 224, 32);
                    break;
                case 2:
                    (void) SetRect(&sourceRect, 288, 0, 320, 32);
                    break;
                case 3:
                    (void) SetRect(&sourceRect, 0, 0, 32, 32);
                    break;
                case 4:
                    (void) SetRect(&sourceRect, 96, 0, 128, 32);
                    break;
                default:
                    break;
            };

            // add the sprite
            graphics->AddSprite(world->GetNPCImageFileLocation(n),
                                sourceRect,
                                (float) world->GetNPCCurrentLocationX(n) * 32.0f,
                                (float) world->GetNPCCurrentLocationY(n) * 32.0f,
                                world->GetObjectColorKey(n),
                                FALSE,
                                TRUE);
        };
    };
};



/* **********************************************************************************************************************
   ClearMap
   --------
   Reset the map and the map editor.
   **********************************************************************************************************************/

void VDEngine::ClearMap() {
    // reset the window
    (void) SetWindowText(mapEditor->GetNewAreaStatic(), "");
    (void) SetWindowText(mapEditor->GetNewAreaStartingPointX(), "0");
    (void) SetWindowText(mapEditor->GetNewAreaStartingPointY(), "0");
    mapEditor->SetTileSheet(NULL);
    mapEditor->SetSelectedTile(NULL);
    mapEditor->SetNPCSprite(NULL);
    mapEditor->SetTileSelected(FALSE);
    mapEditor->SetTileSheetSourceRectangle(0, 0, 32, 32);
    mapEditor->SetTileSheetImageLocation("");
    mapEditor->SetSelectedTileImageLocation("");
    mapEditor->SetNewAreaFileLocation("");
    mapEditor->SetSelectedNPCImageLocation("");
    mapEditor->SetPreviousNPCSelection("");

    // reset the world
    user->SetCurrentLocation(0, 0);
    worldOffset->SetCurrentLocation(0, 0);
    world->ClearWorld();

    // clear the graphics
    graphics->ClearScreen();
};



/* **********************************************************************************************************************
   SaveMap
   -------
   Saves a world map to file.
   **********************************************************************************************************************/

void VDEngine::SaveMap() {
    char text[FILE_NAME_MAX_SIZE];

    // save a world file
    VDSaveFile(mapEditor->GetWindow(), "VDEngine Maps (*.vdm)\0*.vdm\0", text);

    // make sure the user selected a file
    if (strcmp(text, "") != 0) {
        world->WriteWorld(text);
    };
};



/* **********************************************************************************************************************
   ProcessWorldEditing
   -------------------
   Edits the world if the user clicked on the main map area.
   **********************************************************************************************************************/

void VDEngine::ProcessWorldEditing() {
    if (input->GetCurrentWindow() != vdEngineWindow) {
        input->ChangeMouseWindow(vdEngineWindow);
    };
    // cursor is on the map area
    if (actualMousePosition.x >= 0 && actualMousePosition.y < width) {
        // user clicks on the map
        if (input->GetMouseInput().rgbButtons[0] & 0x80) {
            int optionStatus = -1;

            // see if we're adding a tile or an NPC
            if (SendMessage(mapEditor->GetNullTileOption(), BM_GETCHECK, 0, 0) == BST_CHECKED) {
                optionStatus = 0;
            } else if (SendMessage(mapEditor->GetOpenTileOption(), BM_GETCHECK, 0, 0) == BST_CHECKED) {
                optionStatus = 1;
            } else if (SendMessage(mapEditor->GetImpassableTileOption(), BM_GETCHECK, 0, 0) == BST_CHECKED) {
                optionStatus = 2;
            } else if (SendMessage(mapEditor->GetNewAreaTileOption(), BM_GETCHECK, 0, 0) == BST_CHECKED) {
                optionStatus = 3;
            } else if (SendMessage(mapEditor->GetNullNPCOption(), BM_GETCHECK, 0, 0) == BST_CHECKED) {
                optionStatus = 4;
            } else if (SendMessage(mapEditor->GetNormalNPCOption(), BM_GETCHECK, 0, 0) == BST_CHECKED) {
                optionStatus = 5;
            } else if (SendMessage(mapEditor->GetStaticNPCOption(), BM_GETCHECK, 0, 0) == BST_CHECKED) {
                optionStatus = 6;
            };

            // get the current coordinates relative to the user's position (when they move with arrow keys)
            float onScreenX = (float) user->GetCurrentLocationX() * 32.0f + actualMousePosition.x - (actualMousePosition.x % 32);
            float onScreenY = (float) user->GetCurrentLocationY() * 32.0f + actualMousePosition.y - (actualMousePosition.y % 32);
            int worldX = 0, worldY = 0;

            // set the world coordinates in relation to the screen coordinates and the world view
            if (world->GetWorldSizeX() == 0 && world->GetWorldSizeY() == 0) {
                // first tile of the map, set the offset of the world coordinates
                worldOffset->SetCurrentLocation((int) onScreenX / 32, (int) onScreenY / 32);
            } else {
                worldX = (int) onScreenX / 32 - worldOffset->GetCurrentLocationX();
                worldY = (int) onScreenY / 32 - worldOffset->GetCurrentLocationY();
            };

            // TODO: REMOVE
            graphics->RemoveTexts();
            char testess[100];
            sprintf_s(testess, "World Size: %d, %d\n", world->GetWorldSizeX(), world->GetWorldSizeY());
            (void) graphics->AddText(testess, 10, 10, 80, 18, D3DCOLOR_ARGB(255, 255, 255, 255));
            char worldCoord[100];
            sprintf_s(worldCoord, "World Position: %d, %d\n", worldX, worldY);
            (void) graphics->AddText(worldCoord, 10, 30, 80, 48, D3DCOLOR_ARGB(255, 255, 255, 255));

            // reallocate the world if we have a sprite and it's not a Null option
            if ((mapEditor->IsTileSelected() == TRUE && optionStatus >= 1 && optionStatus <= 3)
             || (mapEditor->GetNPCSprite() != NULL && optionStatus >= 5 && optionStatus <= 6)) {
                // see if the tile is out of the current world bounds
                if (world->IsTileInBounds(worldX, worldY) == FALSE) {
                    // reallocate the world
                    int method = world->ReallocateWorld(worldX, worldY);

                    // we have to make sure the world view is offset since we changed the world
                    switch (method) {
                        case 1:
                            worldOffset->SetCurrentLocation(worldOffset->GetCurrentLocationX() + worldX,
                                                           worldOffset->GetCurrentLocationY());
                            break;
                        case 2:
                            worldOffset->SetCurrentLocation(worldOffset->GetCurrentLocationX(),
                                                           worldOffset->GetCurrentLocationY() + worldY);
                            break;
                        case 3:
                            worldOffset->SetCurrentLocation(worldOffset->GetCurrentLocationX() + worldX,
                                                           worldOffset->GetCurrentLocationY() + worldY);
                            break;
                        default:
                            break;
                    };
                };

                // the tile was in the negative world space, so it's 0 in the new world space
                if (worldX < 0) {
                    worldX = 0;
                };
                if (worldY < 0) {
                    worldY = 0;
                };
            };

            // setting the tile to NULL
            if (optionStatus == 0) {
                // make sure we're not out of bounds
                if (world->IsTileInBounds(worldX, worldY) == TRUE) {
                    // set the tile status
                    world->SetTileStatus(worldX, worldY, optionStatus);

                    // reset the tile data
                    world->SetTileImageFileLocation(worldX, worldY, "");
                    world->SetTileNewArea(worldX, worldY, "", 0, 0);

                    // check if there's a tile already in that spot and remove it if there is
                    graphics->RemoveTileSprite(onScreenX, onScreenY);
                };
            };

            // adding a tile
            if (mapEditor->IsTileSelected() == TRUE && optionStatus >= 1 && optionStatus <= 3) {
                // see if we have to recreate the tile texture in 32x32 form
                if (mapEditor->GetSelectedTile() == NULL) {
                    CreateTileTexture();
                };

                // set the tile status
                world->SetTileStatus(worldX, worldY, optionStatus);

                // set the name of the texture in world
                world->SetTileImageFileLocation(worldX, worldY, mapEditor->GetSelectedTileImageLocation());

                // set the name of the new area
                if (optionStatus == 3) {
                    if (strcmp(mapEditor->GetNewAreaFileLocation(), "") == 0) {
                        MessageBox(vdEngineWindow, "This tile will not be set as a new area as there is no new area location provided.\nPlease click on the Load New Area button and select a map first.", "VDEngine", MB_OK);
                        world->SetTileStatus(worldX, worldY, 2);
                    } else {
                        char location[10];
                        int startingPointX = 0, startingPointY = 0;

                        // get the new area starting point
                        (void) GetWindowText(mapEditor->GetNewAreaStartingPointX(), location, sizeof(location));
                        startingPointX = atoi(location);
                        (void) GetWindowText(mapEditor->GetNewAreaStartingPointY(), location, sizeof(location));
                        startingPointY = atoi(location);

                        // set the new area for the tile
                        world->SetTileNewArea(worldX, worldY, mapEditor->GetNewAreaFileLocation(), startingPointX, startingPointY);
                    };
                };

                // check if there's a tile already in that spot and remove it if there is
                graphics->RemoveTileSprite(onScreenX, onScreenY);

                // add the new sprite
                RECT drawingArea;
                (void) SetRect(&drawingArea, 0, 0, 32, 32);
                graphics->AddSprite(mapEditor->GetSelectedTileImageLocation(), drawingArea, onScreenX, onScreenY, 0xFF000000, FALSE, FALSE);
            };

            // setting the NPC to NULL
            if (optionStatus == 4) {
                // make sure we're not out of bounds
                if (world->IsTileInBounds(worldX, worldY) == TRUE) {
                    // remove the NPC from the world
                    world->RemoveNPC(worldX, worldY);

                    // check if there's an NPC already in that spot and remove it if there is
                    graphics->RemoveNPCSprite(onScreenX, onScreenY);
                };
            };

            // adding an NPC
            if (mapEditor->GetNPCSprite() != NULL && optionStatus >= 5 && optionStatus <= 6) {
                char npcName[FILE_NAME_MAX_SIZE];

                // get the selected NPC
                int currentNPCIndex = (int) SendMessage(mapEditor->GetNPCListBox(), LB_GETCURSEL, 0, 0);
                if (currentNPCIndex != LB_ERR) {
                    // get the NPC name
                    (void) SendMessage(mapEditor->GetNPCListBox(), LB_GETTEXT, (WPARAM) currentNPCIndex, (LPARAM) &npcName);

                    // check the direction the NPC is facing
                    RECT sourceRect;
                    (void) mapEditor->GetNPCSprite()->GetSourceRectangle(&sourceRect);
                    int direction = 0;
                    if (sourceRect.left >= 0 && sourceRect.left <= 64) {
                        direction = 1;
                    };
                    if (sourceRect.left >= 96 && sourceRect.left <= 160) {
                        direction = 2;
                    };
                    if (sourceRect.left >= 192 && sourceRect.left <= 256) {
                        direction = 3;
                    };
                    if (sourceRect.left >= 288 && sourceRect.left <= 352) {
                        direction = 4;
                    };

                    // get the transparent color
                    char colorKeyText[11];
                    int colorKey;
                    GetWindowText(mapEditor->GetTransparencyColor(), colorKeyText, sizeof(colorKeyText));
                    colorKey = strtoul(colorKeyText, NULL, 0);

                    // add the NPC to the world
                    world->AddNPC(npcName, worldX, worldY, mapEditor->GetSelectedNPCImageLocation(), optionStatus - 4, direction, colorKey);

                    // check if there's an NPC already in that spot and remove it if there is
                    graphics->RemoveNPCSprite(onScreenX, onScreenY);

                    // add the new sprite
                    mapEditor->GetNPCSprite()->GetSourceRectangle(&sourceRect);
                    graphics->AddSprite(mapEditor->GetSelectedNPCImageLocation(), sourceRect, onScreenX, onScreenY, colorKey, FALSE, TRUE);
                };
            };
        };
    };
};



/* **********************************************************************************************************************
   VDOpenFile
   ----------
   Creates an open file box and sets the file name parameter to the chosen file.
   #param  window    Handle to the window opening the box.
   #param  filter    The type of filter to distinguish which types of files to open.
   #param  fileName  (out) The chosen file location.
   **********************************************************************************************************************/

void VDEngine::VDOpenFile(HWND window, LPSTR filter, LPSTR fileName) {
    char file[FILE_NAME_MAX_SIZE];
    OPENFILENAME openFileBox;

    // zero out the open file box just to be on the safe side
    SecureZeroMemory(&openFileBox, sizeof(OPENFILENAME));

    // set up the open file structure
                openFileBox.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
            openFileBox.hwndOwner = window;
            openFileBox.lpstrFile = file;
         openFileBox.lpstrFile[0] = '\0';
       openFileBox.lpstrFileTitle = NULL;
          openFileBox.lpstrFilter = filter;
      openFileBox.lpstrInitialDir = NULL;
          openFileBox.lStructSize = sizeof(OPENFILENAME);
         openFileBox.nFilterIndex = 1;
             openFileBox.nMaxFile = sizeof(file);
        openFileBox.nMaxFileTitle = NULL;

    // open a file
    BOOL returnValue = GetOpenFileName(&openFileBox);
    if (returnValue != 0) {
        (void) strcpy_s(fileName, sizeof(file), file);
    } else {
        (void) strcpy_s(fileName, sizeof(file), "");
    };
};



/* **********************************************************************************************************************
   VDSaveFile
   ----------
   Creates a save file box and sets the file name parameter to the chosen file.
   #param  window    Handle to the window opening the box.
   #param  filter    The type of filter to distinguish which types of files to open.
   #param  fileName  (out) The chosen file location.
   **********************************************************************************************************************/

void VDEngine::VDSaveFile(HWND window, LPSTR filter, LPSTR fileName) {
    char file[FILE_NAME_MAX_SIZE];
    OPENFILENAME openFileBox;

    // zero out the open file box just to be on the safe side
    SecureZeroMemory(&openFileBox, sizeof(OPENFILENAME));

    // set up the open file structure
                openFileBox.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
            openFileBox.hwndOwner = window;
          openFileBox.lpstrDefExt = ".vdm";
            openFileBox.lpstrFile = file;
         openFileBox.lpstrFile[0] = '\0';
       openFileBox.lpstrFileTitle = NULL;
          openFileBox.lpstrFilter = filter;
      openFileBox.lpstrInitialDir = NULL;
          openFileBox.lStructSize = sizeof(OPENFILENAME);
         openFileBox.nFilterIndex = 1;
             openFileBox.nMaxFile = sizeof(file);
        openFileBox.nMaxFileTitle = NULL;

    // open a file
    BOOL returnValue = GetSaveFileName(&openFileBox);
    if (returnValue != 0) {
        // make it so the path name is the app directory iff we're opening images
        char * begin = file;
        char * end = strstr(file, "Images\\");
        if (end != NULL) {
            int n = begin - end, m = 0;
            if (n < 0) {
                n = -n;
            };
            while (file[n] != '\0') {
                fileName[m] = file[n];
                n++;
                m++;
            };
            fileName[m] = '\0';
            return;
        };
        end = strstr(file, "Maps\\");
        if (end != NULL) {
            int n = begin - end, m = 0;
            if (n < 0) {
                n = -n;
            };
            while (file[n] != '\0') {
                fileName[m] = file[n];
                n++;
                m++;
            };
            fileName[m] = '\0';
            return;
        };
        (void) strcpy_s(fileName, sizeof(file), file);
        MessageBox(NULL, "here, not a map or sprite but a", NULL, MB_OK);
        MessageBox(NULL, fileName, NULL, MB_OK);
    } else {
        (void) strcpy_s(fileName, sizeof(file), "");
    };
};



/* **********************************************************************************************************************
   LoadNPCs
   --------
   Loads the NPC's into the game.
   **********************************************************************************************************************/

void VDEngine::LoadNPCs() {
    HANDLE file = NULL;
    BOOL returnValue = FALSE;
    DWORD numberOfBytesRead = 0;
    size_t numberOfNPCs = 0;

    // clear the NPC data first
    npcs.clear();

    // open the file
    file = CreateFile("Data/NPCs.vd", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file == INVALID_HANDLE_VALUE) {
        return;
    };

    // get how many NPC's we're adding
    returnValue = ReadFile(file, &numberOfNPCs, sizeof(numberOfNPCs), &numberOfBytesRead, NULL);
    if (returnValue == FALSE) {
        VDError("Failed to read from the NPCs.vd file.", __FILE__, __LINE__);
    };

    // read all the NPC's
    for (unsigned int n = 0; n < numberOfNPCs; n++) {
        VDNPC newNPC;
        newNPC.LoadNPC(file);
        npcs.push_back(newNPC);
    };

    // close the file
    returnValue = CloseHandle(file);
    if (returnValue == FALSE) {
        VDError("Failed to close the NPCs.vd file.", __FILE__, __LINE__);
    };
};



/* **********************************************************************************************************************
   CreateTileTexture
   -----------------
   Loads a tile represented on the tile sheet if it exists, creates it otherwise.
   **********************************************************************************************************************/

void VDEngine::CreateTileTexture() {
    VDSprite *sprite = mapEditor->GetTileSheet();
    RECT sourceRect;
    (void) mapEditor->GetTileSheetSourceRectangle(&sourceRect);

    char newName[FILE_NAME_MAX_SIZE], tileSheetImageLocation[FILE_NAME_MAX_SIZE], tileSheetName[FILE_NAME_MAX_SIZE], extension[10];
    LPDIRECT3DTEXTURE9 tempTexture = NULL;

    // remove the path name and extension from the tile sheet location
    (void) strcpy_s(tileSheetImageLocation, mapEditor->GetTileSheetImageLocation());
    int n = 0, m = 0, p = 0, numSlashes = 0;
    while (tileSheetImageLocation[n] != '\0') {
        if (tileSheetImageLocation[n] == '\\') {
            numSlashes++;
        };
        n++;
    };
    n = 0;
    while (tileSheetImageLocation[n] != '.') {
        if (tileSheetImageLocation[n] == '\\') {
            m++;
        };
        n++;
        if (tileSheetImageLocation[n] != '.' && m == numSlashes) {
            tileSheetName[p] = tileSheetImageLocation[n];
            p++;
        };
    };
    tileSheetName[p] = '\0';
    m = 0;
    while (tileSheetImageLocation[n] != '\0') {
        extension[m] = tileSheetImageLocation[n];
        n++;
        m++;
    };
    extension[m] = '\0';
    (void) strcpy_s(newName, "Images/");
    (void) strcat_s(newName, tileSheetName);

    // now add the X,Y loc and file extension
    (void) strcat_s(newName, "-");
    char loc[10];
    (void) _itoa_s(sourceRect.left, loc, sizeof(loc));
    (void) strcat_s(newName, loc);
    (void) strcat_s(newName, ",");
    (void) strcpy_s(loc, "");
    (void) _itoa_s(sourceRect.top, loc, sizeof(loc));
    (void) strcat_s(newName, loc);
    (void) strcat_s(newName, extension);

    // see if the texture exists already
    HRESULT returnValue = D3DXCreateTextureFromFileEx(graphics->GetDirect3DRenderer(), newName, D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT, NULL, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 0xFFFFFFFF, NULL, NULL, &tempTexture);
    if (FAILED(returnValue)) {
        returnValue = D3DXCreateTexture(graphics->GetDirect3DRenderer(), 32, 32, D3DX_DEFAULT, 0, D3DFMT_X8R8G8B8, D3DPOOL_SCRATCH, &tempTexture);
        D3DLOCKED_RECT sourceSprite, destSprite;
        returnValue = sprite->GetTexture()->LockRect(0, &sourceSprite, NULL, D3DLOCK_READONLY);
        if (FAILED(returnValue)) {
	        VDDirectXError("Failed to lock the texture.", returnValue, __FILE__, __LINE__);
        }
        returnValue = tempTexture->LockRect(0, &destSprite, NULL, D3DLOCK_DISCARD);
        if (FAILED(returnValue)) {
            sprite->GetTexture()->UnlockRect(0);
            VDDirectXError("Failed to lock the texture.", returnValue, __FILE__, __LINE__);
        };
        UINT sourceSpriteWidth = sprite->GetSpriteWidth();

        UINT bytesPerPixel = 4;
        const BYTE* sourceLoc = (const BYTE*) sourceSprite.pBits + sourceRect.top * sourceSpriteWidth * bytesPerPixel
                                                                 + sourceRect.left * bytesPerPixel;
        for (int n = 0; n < 32; n++) {
            BYTE* destAddress = (BYTE *) destSprite.pBits + n * destSprite.Pitch;
            memcpy(destAddress, sourceLoc, 32 * bytesPerPixel);
            sourceLoc += sourceSprite.Pitch;
        };

        sprite->GetTexture()->UnlockRect(0);
        tempTexture->UnlockRect(0);

        // save the texture to hard disk - requires a directory Images to be in the app directory
        if (strcmp(extension, ".png") == 0) {
            returnValue = D3DXSaveTextureToFile(newName, D3DXIFF_PNG, tempTexture, NULL);
            if (FAILED(returnValue)) {
                VDDirectXError("Failed to save texture.", returnValue, __FILE__, __LINE__);
            };
        } else if (strcmp(extension, ".bmp") == 0) {
            returnValue = D3DXSaveTextureToFile(newName, D3DXIFF_BMP, tempTexture, NULL);
            if (FAILED(returnValue)) {
                VDDirectXError("Failed to save texture.", returnValue, __FILE__, __LINE__);
            };
        };
    };

    if (tempTexture != NULL) {
        tempTexture->Release();
        tempTexture = NULL;
    };

    RECT drawingArea;
    (void) SetRect(&drawingArea, 0, 0, 32, 32);
    mapEditor->SetSelectedTileImageLocation(newName);
//    mapEditor->SetSelectedTile(new VDSprite(graphics->GetDirect3DRenderer(), newName, drawingArea, D3DXVECTOR3((float) sourceRect.left, (float) sourceRect.top, 0.0f), FALSE, FALSE));
    mapEditor->SetSelectedTile(new VDSprite(graphics->GetDirect3DRenderer(), newName, drawingArea, D3DXVECTOR3(0.0f, 0.0f, 0.0f), 0xFF000000, FALSE, FALSE));
    // TODO: Note it doesn't even use selectedTile (EVER), only the selectedTileImageLocation
};



/* **********************************************************************************************************************
   Accessors and Modifiers
   **********************************************************************************************************************/

VDNPC VDEngine::GetNPC(LPCSTR name) {
    for (unsigned int n = 0; n < npcs.size(); n++) {
        if (strcmp(name, npcs.at(n).GetName()) == 0) {
            return npcs.at(n);
        };
    };

    return NULL;
};