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

#include "VDWorld.h"
#include "VDError.h"

#include <crtdbg.h>
#ifdef _DEBUG
    #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
    #define new DEBUG_NEW
#else
    #define DEBUG_CLIENTBLOCK
#endif  // _DEBUG

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

VDWorld::VDWorld() {
    world = NULL;
    worldSizeX = 0;
    worldSizeY = 0;
    npcs.clear();
};



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

VDWorld::~VDWorld() {
    for (int x = 0; x < worldSizeX; x++) {
        delete[] world[x];
        world[x] = NULL;
    };
    if (world != NULL) {
        delete[] world;
        world = NULL;
    };
};



/* **********************************************************************************************************************
   IsTileInBounds
   --------------
   Checks if the tile is in the world bounds.
   #param  locationX  X location of the tile.
   #param  locationY  Y location of the tile.
   #return  TRUE if the x,y location is within the world bounds, FALSE otherwise.
   **********************************************************************************************************************/

BOOL VDWorld::IsTileInBounds(int locationX, int locationY) {
    if (locationX >= 0 && locationX < worldSizeX && locationY >= 0 && locationY < worldSizeY) {
        return TRUE;
    } else {
        return FALSE;
    };
};



/* **********************************************************************************************************************
   IsTileVacant
   ------------
   Checks if the tile is vacant (if there's something on it or not)
   #param  locationX  X location of the tile.
   #param  locationY  Y location of the tile.
   #return  TRUE if the x,y location is vacant, FALSE otherwise.
   **********************************************************************************************************************/

BOOL VDWorld::IsTileVacant(int locationX, int locationY) {
    for (unsigned int n = 0; n < npcs.size(); n++) {
        if (npcs.at(n).GetCurrentLocationX() == locationX && npcs.at(n).GetCurrentLocationY() == locationY) {
            return FALSE;
        };
    };

    return TRUE;
};



/* **********************************************************************************************************************
   ReallocateWorld
   ---------------
   Reallocates the map given the new coordinates.
   #param  newWorldLocationX  X coordinate attained from mouse position.
   #param  newWorldLocationY  Y coordinate attained from mouse position.
   #return  The method in which we reallocated the map (1 = shifted right, 2 = shifted down, 3 = shifted right + down)
   **********************************************************************************************************************/

int VDWorld::ReallocateWorld(int newWorldLocationX, int newWorldLocationY) {
    int method = 0;
    int oldWorldSizeX = 0, oldWorldSizeY = 0;

    // copy world
    VDTile **oldWorld;
    oldWorldSizeX = worldSizeX;
    oldWorldSizeY = worldSizeY;
    oldWorld = new VDTile*[oldWorldSizeX]();
    for (int x = 0; x < oldWorldSizeX; x++) {
        oldWorld[x] = new VDTile[oldWorldSizeY]();
    };
    for (int x = 0; x < oldWorldSizeX; x++) {
        for (int y = 0; y < oldWorldSizeY; y++) {
            oldWorld[x][y].SetStatus(world[x][y].GetStatus());
            oldWorld[x][y].SetImageFileLocation(world[x][y].GetImageFileLocation());
            //oldWorld[x][y].SetSpawns(world[x][y].GetSpawns());
        };
    };

    // deallocate the world
    for (int x = 0; x < worldSizeX; x++) {
        delete[] world[x];
        world[x] = NULL;
    };
    delete[] world;
    world = NULL;

    // see how we're going to allocate the new world
    if (newWorldLocationX < 0 && newWorldLocationY >= 0) {
        // old tiles shift to the right
        method = 1;
        worldSizeX -= newWorldLocationX;
        if (newWorldLocationY >= worldSizeY) {
            worldSizeY = newWorldLocationY + 1;
        };
    } else if (newWorldLocationY < 0 && newWorldLocationX >= 0) {
        // old tiles shift down
        method = 2;
        worldSizeY -= newWorldLocationY;
        if (newWorldLocationX >= worldSizeX) {
            worldSizeX = newWorldLocationX + 1;
        };
    } else if (newWorldLocationX < 0 && newWorldLocationY < 0) {
        // old tiles shift down and to the right
        method = 3;
        worldSizeX -= newWorldLocationX;
        worldSizeY -= newWorldLocationY;
    } else {
        // old tiles stay where they are
        method = 4;
        if (newWorldLocationX >= worldSizeX) {
            worldSizeX = newWorldLocationX + 1;
        };
        if (newWorldLocationY >= worldSizeY) {
            worldSizeY = newWorldLocationY + 1;
        };
    };

    // allocate the new world tiles
    world = new VDTile*[worldSizeX]();
    for (int x = 0; x < worldSizeX; x++) {
        world[x] = new VDTile[worldSizeY]();
    };

    // populate the new world with the old world tiles
    for (int x = 0; x < oldWorldSizeX; x++) {
        for (int y = 0; y < oldWorldSizeY; y++) {
            switch (method) {
                case 1:
                    world[x - newWorldLocationX][y].SetStatus(oldWorld[x][y].GetStatus());
                    world[x - newWorldLocationX][y].SetImageFileLocation(oldWorld[x][y].GetImageFileLocation());
                    //world[x - newWorldLocationX][y].SetSpawns(oldWorld[x][y].GetSpawns());
                    break;
                case 2:
                    world[x][y - newWorldLocationY].SetStatus(oldWorld[x][y].GetStatus());
                    world[x][y - newWorldLocationY].SetImageFileLocation(oldWorld[x][y].GetImageFileLocation());
                    //world[x][y - newWorldLocationY].SetSpawns(oldWorld[x][y].GetSpawns());
                    break;
                case 3:
                    world[x - newWorldLocationX][y - newWorldLocationY].SetStatus(oldWorld[x][y].GetStatus());
                    world[x - newWorldLocationX][y - newWorldLocationY].SetImageFileLocation(oldWorld[x][y].GetImageFileLocation());
                    //world[x - newWorldLocationX][y - newWorldLocationY].SetSpawns(oldWorld[x][y].GetSpawns());
                    break;
                case 4:
                    world[x][y].SetStatus(oldWorld[x][y].GetStatus());
                    world[x][y].SetImageFileLocation(oldWorld[x][y].GetImageFileLocation());
                    //world[x][y].SetSpawns(oldWorld[x][y].GetSpawns());
                    break;
                default:
                    break;
            };
        };
    };

    // deallocate the old world
    for (int x = 0; x < oldWorldSizeX; x++) {
        delete[] oldWorld[x];
        oldWorld[x] = NULL;
    };
    delete[] oldWorld;
    oldWorld = NULL;

    return method;
};



/* **********************************************************************************************************************
   AddNPC
   ------
   Adds the NPC to the list of NPCs in the world.
   #param  name               The name of the NPC.
   #param  locationX          X location of the NPC.
   #param  locationY          Y location of the NPC.
   #param  imageFileLocation  The image file location string.
   #param  status             The status of the NPC.
   #param  facingDirection    The direction the NPC is facing.
   #param  colorKey           Used for transparent colors.
   **********************************************************************************************************************/

void VDWorld::AddNPC(LPCSTR name, int locationX, int locationY, LPCSTR imageFileLocation, int status, int facingDirection, D3DCOLOR colorKey) {
    // see if we're trying to add an NPC that already exists in the same location
    for (unsigned int n = 0; n < npcs.size(); n++) {
        if (npcs.at(n).GetCurrentLocationX() == locationX && npcs.at(n).GetCurrentLocationY() == locationY) {
            // only change the name, image, and status -- not the location
            npcs.at(n).SetName(name);
            npcs.at(n).SetImageFileLocation(imageFileLocation);
            npcs.at(n).SetStatus(status);
            npcs.at(n).SetDirection(facingDirection);
            npcs.at(n).SetColorKey(colorKey);
            return;
        };
    };

    // add the NPC
    VDNPC newNPC = VDNPC(name);
    newNPC.SetCurrentLocation(locationX, locationY);
    newNPC.SetImageFileLocation(imageFileLocation);
    newNPC.SetStatus(status);
    newNPC.SetDirection(facingDirection);
    newNPC.SetColorKey(colorKey);
    npcs.push_back(newNPC);
};



/* **********************************************************************************************************************
   RemoveNPC
   ---------
   Removes the NPC from the list of NPCs in the world.
   #param  locationX  X location of the NPC.
   #param  locationY  Y location of the NPC.
   **********************************************************************************************************************/

void VDWorld::RemoveNPC(int locationX, int locationY) {
    for (unsigned int n = 0; n < npcs.size(); n++) {
        if (npcs.at(n).GetCurrentLocationX() == locationX && npcs.at(n).GetCurrentLocationY() == locationY) {
            npcs.erase(npcs.begin() + n);
        };
    };
};



/* **********************************************************************************************************************
   RunNPCLogic  (TODO - change name?)
   -----------
   Starts the NPC logic.
   #param  index        Index of the vector of NPCs.
   #param  currentTime  Current time of the game.
   **********************************************************************************************************************/

int VDWorld::RunNPCLogic(unsigned int index, double currentTime) {
    return npcs.at(index).RunLogic(currentTime);
};



/* **********************************************************************************************************************
   MoveNPC
   -------
   Moves an NPC.
   #param  index      Index of the vector of NPCs.
   #param  direction  Direction in which to move the NPC.
   **********************************************************************************************************************/

void VDWorld::MoveNPC(unsigned int index, int direction) {
    npcs.at(index).Move(direction);
};



/* **********************************************************************************************************************
   ClearWorld
   ----------
   Clear all the current world data, resetting the world.
   **********************************************************************************************************************/

void VDWorld::ClearWorld() {
    for (int x = 0; x < worldSizeX; x++) {
        delete[] world[x];
        world[x] = NULL;
    };
    if (world != NULL) {
        delete[] world;
        world = NULL;
    };
    worldSizeX = 0;
    worldSizeY = 0;
    npcs.clear();
};



/* **********************************************************************************************************************
   WriteWorld
   ----------
   Saves the current world to hard disk.
   #param  fileName  The name of the world file.
   **********************************************************************************************************************/

void VDWorld::WriteWorld(LPCSTR fileName) {
    HANDLE file = NULL;
    BOOL returnValue = FALSE;
    DWORD numberOfBytesWritten = 0;
    size_t numberOfNPCs = 0;

    // open the file
    file = CreateFile(fileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file == INVALID_HANDLE_VALUE) {
        VDError("Failed to create the world file.", __FILE__, __LINE__);
    };

    // make sure we let it know how many tiles we're adding
    returnValue = WriteFile(file, &worldSizeX, sizeof(worldSizeX), &numberOfBytesWritten, NULL);
    if (returnValue == FALSE) {
        VDError("Failed to write world data to file.", __FILE__, __LINE__);
    };
    returnValue = WriteFile(file, &worldSizeY, sizeof(worldSizeY), &numberOfBytesWritten, NULL);
    if (returnValue == FALSE) {
        VDError("Failed to write world data to file.", __FILE__, __LINE__);
    };

    // write all the tiles
    for (int x = 0; x < worldSizeX; x++) {
        for (int y = 0; y < worldSizeY; y++) {
            world[x][y].WriteTile(file);
        };
    };

    // TODO: write Spawn data

    // make sure we let it know how many NPCs we're adding 
    numberOfNPCs = npcs.size();
    returnValue = WriteFile(file, &numberOfNPCs, sizeof(numberOfNPCs), &numberOfBytesWritten, NULL);
    if (returnValue == FALSE) {
        VDError("Failed to write world data to file.", __FILE__, __LINE__);
    };

    // write all the NPCs
    for (unsigned int n = 0; n < npcs.size(); n++) {
        npcs.at(n).WriteNPC(file);
    };

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



/* **********************************************************************************************************************
   LoadWorld
   ---------
   Loads a world into the game.
   #param  fileName  The name of the world file.
   **********************************************************************************************************************/

void VDWorld::LoadWorld(LPCSTR fileName) {
    HANDLE file = NULL;
    BOOL returnValue = FALSE;
    DWORD numberOfBytesRead = 0;
    unsigned int numberOfNPCs = 0;

    // open the file
    file = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file == INVALID_HANDLE_VALUE) {
        VDError("Failed to open the world file.", __FILE__, __LINE__);
    };

    // get how many tiles we're adding
    returnValue = ReadFile(file, &worldSizeX, sizeof(worldSizeX), &numberOfBytesRead, NULL);
    if (returnValue == FALSE) {
        VDError("Failed to read from the world file.", __FILE__, __LINE__);
    };
    returnValue = ReadFile(file, &worldSizeY, sizeof(worldSizeY), &numberOfBytesRead, NULL);
    if (returnValue == FALSE) {
        VDError("Failed to read from the world file.", __FILE__, __LINE__);
    };

    // allocate the size of the world
    world = new VDTile*[worldSizeX]();
    for (int x = 0; x < worldSizeX; x++) {
        world[x] = new VDTile[worldSizeY]();
    };

    // read all the tiles
    for (int x = 0; x < worldSizeX; x++) {
        for (int y = 0; y < worldSizeY; y++) {
            world[x][y].ReadTile(file);
        };
    };

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

    // read all the NPCs
    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 world file.", __FILE__, __LINE__);
    };
};



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

int VDWorld::GetTileStatus(int x, int y) {
    return world[x][y].GetStatus();
};

LPCSTR VDWorld::GetTileImageFileLocation(int x, int y) {
    return world[x][y].GetImageFileLocation();
};
void VDWorld::SetTileStatus(int x, int y, int theStatus) {
    world[x][y].SetStatus(theStatus);
};

void VDWorld::SetTileImageFileLocation(int x, int y, LPCSTR theImageFileLocation) {
    world[x][y].SetImageFileLocation(theImageFileLocation);
};

LPCSTR VDWorld::GetTileNewAreaName(int x, int y) {
    return world[x][y].GetNewAreaName();
};

int VDWorld::GetTileNewAreaStartingPointX(int x, int y) {
    return world[x][y].GetNewAreaStartingPointX();
};

int VDWorld::GetTileNewAreaStartingPointY(int x, int y) {
    return world[x][y].GetNewAreaStartingPointY();
};

void VDWorld::SetTileNewArea(int x, int y, LPCSTR newAreaName, int startingLocationX, int startingLocationY) {
    world[x][y].SetNewAreaName(newAreaName);
    world[x][y].SetNewAreaStartingPoint(startingLocationX, startingLocationY);
};

int VDWorld::GetWorldSizeX() {
    return worldSizeX;
};

int VDWorld::GetWorldSizeY() {
    return worldSizeY;
};

size_t VDWorld::GetNPCsSize() {
    return npcs.size();
};
LPCSTR VDWorld::GetNPCName(unsigned int index) {
    return npcs.at(index).GetName();
};
int VDWorld::GetNPCCurrentLocationX(unsigned int index) {
    return npcs.at(index).GetCurrentLocationX();
};

int VDWorld::GetNPCCurrentLocationY(unsigned int index) {
    return npcs.at(index).GetCurrentLocationY();
};

void VDWorld::SetNPCCurrentLocation(unsigned int index, int x, int y) {
    npcs.at(index).SetCurrentLocation(x, y);
};

int VDWorld::GetNPCDirection(unsigned int index) {
    return npcs.at(index).GetDirection();
};

LPCSTR VDWorld::GetNPCImageFileLocation(unsigned int index) {
    return npcs.at(index).GetImageFileLocation();
};

/*LPCSTR VDWorld::GetNPCDialogue(unsigned int npcIndex, unsigned int dialogueIndex) {
    return npcs.at(npcIndex).GetDialogueText(dialogueIndex);
};*/

// TODO: currently only gets an NPC
D3DCOLOR VDWorld::GetObjectColorKey(unsigned int npcIndex) {
    return npcs.at(npcIndex).GetColorKey();
};