#include "Map.h"

#include <stdint.h>

#include <string>
#include <vector>

#include <iostream>

#include "../util/fileUtils.h"
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>

using namespace std;

Map :: Map ()
{
  initialize();
}

Map :: ~Map ()
{
}

void Map :: initialize()
{
  setDimensions (2, 2);

  playerStartX=0;
  playerStartY=0;
  playerEndX = 1;
  playerEndY = 1;

  graphics.resize (MAP_TILE_NUM_TILES + 1, "NULL_INDEX");
  name="";
  tileSize=0;

  mapUnitEnemyGraphics.clear();
  enemyStartingPositionsX.clear();
  enemyStartingPositionsY.clear();
  enemyAnimatedStates.clear();
}

uint8_t Map :: setDimensions (uint8_t width, uint8_t height)
{
  //changes x & y dimensions. If the new dimensions are smaller, the upper left corner is preserved.
  //if player is offmap, they are moved to start point or (0,0)
  uint8_t i;

  terrain.resize (height);

  for (i = 0; i < terrain.size (); i++)
    {
      terrain.at(i).resize(width, MAP_TERRAIN_NULL);
    }

  fog.resize (height);

  for (i = 0; i < fog.size (); i++)
    {
      fog.at(i).resize(width, true);
    }

  if (height < terrain.size () || height < fog.size ())
    return 1;

  return 0;
}

void Map :: printMap ()
{
  //outputs map to cout.
  uint8_t i, j;

  cout << "MAP\n";
  for (i = 0; i < terrain.size (); i++)
    {
      for (j = 0; j < terrain[i].size (); j++)
        {
          switch (terrain[i][j])
            {
            case (MAP_TERRAIN_NULL):
              cout << " ";
              break;
            case (MAP_TERRAIN_GROUND):
              cout << "_";
              break;
            case (MAP_TERRAIN_UNPASSABLE):
              cout << "X";
              break;
            case (MAP_TERRAIN_ENDPOINT):
              cout << "E";
              break;
            case (MAP_TERRAIN_LOCKED):
              cout << "L";
              break;
            }
        }
      cout << "|" << endl;
    }

  cout << endl << "FOG\n";
  for (i = 0; i < fog.size (); i++)
    {
      for (j = 0; j < fog[i].size (); j++)
        {
          if (fog[i][j])
            cout << "F";
          else
            cout << " ";
        }
      cout << "|" << endl;
    }
  cout << endl;
}

void Map :: setStartPoint (uint8_t x, uint8_t y)
{
  playerStartX = x;
  playerStartY = y;
}

uint8_t Map :: getStartPointX ()
{
  return playerStartX;
}

uint8_t Map :: getStartPointY ()
{
  return playerStartY;
}

void Map :: setEndPoint (uint8_t x, uint8_t y)
{
  playerEndX = x;
  playerEndY = y;
}

void Map :: printMapWithPlayerVision (vector <vector <uint8_t> > & vact1, vector <vector <bool> > & vact2)
{
  //later: add fog. (transparency layer)
  //later: add vision.

  //copy vact so it matches the map.
  uint8_t i, j;

  vact1.resize (terrain.size());
  for (i = 0; i < terrain.size (); i ++)
    {
      vact1[i].resize(terrain[i].size());
      for (j = 0; j < terrain[i].size(); j ++)
        {
          vact1[i][j]=terrain[i][j];
        }
    }

  vact2.resize (fog.size());
  for (i = 0; i < fog.size (); i ++)
    {
      vact2[i].resize(fog[i].size());
      for (j = 0; j < fog[i].size(); j ++)
        {
          vact2[i][j]=fog[i][j];
        }
    }
}

void Map :: setMapTerrain (uint8_t x, uint8_t y, uint8_t terrainType)
{
  terrain[y][x]=terrainType;

  /*
  uint8_t i, j;

  for (i = 0; i < terrain.size (); i ++)
    {
      for (j = 0; j < terrain[i].size(); j ++)
        {
          cout << int(terrain[i][j]);
        }
      cout << endl;
    }
  */
}

uint8_t Map :: loadMap (const string & mapIndex)
{
  if (mapIndex.empty() || mapIndex == "NULL_INDEX")
    return 2;

  xmlDocPtr doc;
  xmlXPathContextPtr xpathCtx;

  string xpathStart="/MapManifest/Map[@ID='";
  xpathStart+=mapIndex;
  xpathStart+="']";

  if (!prepareXMLFile (doc, xpathCtx, "resources/game/MapManifest.xml", xpathStart))
    {
      return false;
    }

  bool tempBool = loadMapHelper (doc, xpathCtx, xpathStart);
  setName (mapIndex);

  xmlXPathFreeContext(xpathCtx);
  xmlFreeDoc(doc);

  if (!tempBool)
    return 3;
  return 0;
}

bool Map :: loadMapHelper (xmlDocPtr doc, xmlXPathContextPtr xpathCtx, const string & xpath)
{
  //A map needs:
  //1) dimensions. Must be at least 2 x 2.
  //2) player start point.
  //3) map endpoint.

  xmlChar * element;
  string tempString, tempString2, tempString3, tempString4;
  int temp, temp2;
  uint8_t w, h;
  bool boolCheck = false, done=false;
  uint8_t rowc, colc, enemyIndex;
  char indexStr [80];

  //dimensions.
  element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/Size/Width", "");
  if (element == NULL)
    temp = 0;
  else
    temp = int(xmlXPathCastStringToNumber(element));
  xmlFree (element);

  element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/Size/Height", "");
  if (element == NULL)
    temp2 = 0;
  else
    temp2 = int(xmlXPathCastStringToNumber(element));
  xmlFree (element);

  w = (uint8_t)temp;
  h = (uint8_t)temp2;
  setDimensions (w, h);

  //reject the map if it is too small.
  if (int(w * h) < 2)
    return false;

  //Now getting the map terrain.
  for (rowc = 0; rowc < h; rowc ++)
    {
      sprintf (indexStr, "Row%u", rowc+1);
      tempString2.assign (indexStr);
      element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/TerrainLayout/", tempString2);
      if (element != NULL)
        {
          tempString.assign ((const char *)element);
          xmlFree (element);
        }
      else
        tempString = "";

      for (colc = 0; colc < w; colc ++)
        {
          if (tempString.empty())
            setMapTerrain (colc, rowc, MAP_TERRAIN_NULL);
          else
            {
              if (tempString.find (" ", 0) == string::npos)
                tempString3 = tempString;
              else
                {
                  tempString3 = tempString.substr(0, tempString.find (" ", 0));
                  tempString = tempString.substr(tempString.find (" ", 0) + 1);
                }

              if (tempString3 == "null" || tempString3 == "NULL" || tempString3 == "Null")
                setMapTerrain (colc, rowc, MAP_TERRAIN_NULL);
              else if (tempString3 == "ground" || tempString3 == "GROUND" || tempString3 == "Ground")
                setMapTerrain (colc, rowc, MAP_TERRAIN_GROUND);
              else if (tempString3 == "unpassable" || tempString3 == "UNPASSABLE" || tempString3 == "Unpassable")
                setMapTerrain (colc, rowc, MAP_TERRAIN_UNPASSABLE);
              else if (tempString3 == "locked" || tempString3 == "LOCKED" || tempString3 == "Locked")
                setMapTerrain (colc, rowc, MAP_TERRAIN_LOCKED);
            }
        }
    }

  //player start point.
  boolCheck = true;

  element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/PlayerStart/X", "");
  if (element == NULL)
    boolCheck = false;
  else
    temp = int(xmlXPathCastStringToNumber(element));
  xmlFree (element);

  element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/PlayerStart/Y", "");
  if (element == NULL)
    boolCheck = false;
  else
    temp2 = int(xmlXPathCastStringToNumber(element));
  xmlFree (element);

  w = (uint8_t)temp;
  h = (uint8_t)temp2;

  setStartPoint(w, h);

  if (!boolCheck)
    return false;


  //player end point.
  boolCheck = true;

  element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/PlayerEnd/X", "");
  if (element == NULL)
    boolCheck = false;
  else
    temp = int(xmlXPathCastStringToNumber(element));
  xmlFree (element);

  element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/PlayerEnd/Y", "");
  if (element == NULL)
    boolCheck = false;
  else
    temp2 = int(xmlXPathCastStringToNumber(element));
  xmlFree (element);

  w = (uint8_t)temp;
  h = (uint8_t)temp2;

  setEndPoint(w, h);

  //bound check
  if (h >= terrain.size())
    boolCheck = false;
  else if (w >= terrain[h].size())
    boolCheck = false;

  if (!boolCheck)
    return false;

  //change terrain to match endpoint.
  setMapTerrain (w, h, MAP_TERRAIN_ENDPOINT);

  //Optionally, a map can have:
  //1) Background Image
  //2) Tileset Files

  //Backgroundimage
  element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/BackgroundImageIndex", "");
  if (element == NULL)
    {
      setBackgroundImageName ("NULL_INDEX");
    }
  else
    {
      setBackgroundImageName ((const char *)element);
      xmlFree (element);
    }

  //Tileset
  element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/TerrainTileset/TileSize", "");
  if (element==NULL)
    temp=20;
  else
    {
      temp = int(xmlXPathCastStringToNumber(element));
      xmlFree (element);
    }
  setTileSize (temp);

  element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/TerrainTileset/Ground", "");
  if (element == NULL)
    setTileImageName ("NULL_INDEX", MAP_TILE_GROUND);
  else
    {
      setTileImageName ((const char *)element, MAP_TILE_GROUND);
      xmlFree (element);
    }

  element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/TerrainTileset/Unpassable", "");
  if (element == NULL)
    setTileImageName ("NULL_INDEX", MAP_TILE_UNPASSABLE);
  else
    {
      setTileImageName ((const char *)element, MAP_TILE_UNPASSABLE);
      xmlFree (element);
    }

  element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/TerrainTileset/Locked", "");
  if (element == NULL)
    setTileImageName ("NULL_INDEX", MAP_TILE_LOCKED);
  else
    {
      setTileImageName ((const char *)element, MAP_TILE_LOCKED);
      xmlFree (element);
    }

  element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/TerrainTileset/Null", "");
  if (element == NULL)
    setTileImageName ("NULL_INDEX", MAP_TILE_NULL);
  else
    {
      setTileImageName ((const char *)element, MAP_TILE_NULL);
      xmlFree (element);
    }

  element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/TerrainTileset/Endpoint", "");
  if (element == NULL)
    setTileImageName ("NULL_INDEX", MAP_TILE_ENDPOINT);
  else
    {
      setTileImageName ((const char *)element, MAP_TILE_ENDPOINT);
      xmlFree (element);
    }

  element = getFirstElementUsingXPath (doc, xpathCtx, xpath, "/TerrainTileset/Fog", "");
  if (element == NULL)
    setTileImageName ("NULL_INDEX", MAP_TILE_FOG);
  else
    {
      setTileImageName ((const char *)element, MAP_TILE_FOG);
      xmlFree (element);
    }

  enemyIndex=0;
  done=false;
  while (!done)
    {
      sprintf (indexStr, "/Enemy[%u]/X", enemyIndex+1);
      tempString4.assign (indexStr);
      element = getFirstElementUsingXPath (doc, xpathCtx, xpath, tempString4, "");

      if (element == NULL)
        done = true;
      else
        {
          temp2 = int(xmlXPathCastStringToNumber(element));
          if (temp2 < 0)
            temp2=0;
          if (temp2 > 255)
            temp2=255;
          enemyStartingPositionsX.push_back(temp2);
        }
      xmlFree (element);

      enemyIndex++;
    }

  enemyIndex=0;
  done=false;
  while (!done)
    {
      sprintf (indexStr, "/Enemy[%u]/Y", enemyIndex+1);
      tempString4.assign (indexStr);
      element = getFirstElementUsingXPath (doc, xpathCtx, xpath, tempString4, "");

      if (element == NULL)
        done = true;
      else
        {
          temp2 = int(xmlXPathCastStringToNumber(element));
          if (temp2 < 0)
            temp2=0;
          if (temp2 > 255)
            temp2=255;
          enemyStartingPositionsY.push_back(temp2);
        }
      xmlFree (element);

      enemyIndex++;
    }

  enemyIndex=0;
  done=false;
  AnimatedState blankAnimState;
  while (!done)
    {
      sprintf (indexStr, "/Enemy[%u]/MapToon", enemyIndex+1);
      tempString4.assign (indexStr);
      element = getFirstElementUsingXPath (doc, xpathCtx, xpath, tempString4, "");

      if (element == NULL)
        done = true;
      else
        {
          tempString4=(const char *)element;
          mapUnitEnemyGraphics.push_back(tempString4);
          enemyAnimatedStates.push_back(blankAnimState);
        }
      xmlFree (element);

      enemyIndex++;
    }
  return true;
}

const string & Map :: getName ()
{
  return name;
}

void Map :: setName (const string & nam)
{
  name = nam;
}

void Map :: setBackgroundImageName (const string & str)
{
  graphics[0] = str;
}

void Map :: setTileImageName (const string & str, uint8_t tilename)
{
 if (tilename < MAP_TILE_NUM_TILES)
    graphics[tilename+1] = str;
}

const string & Map :: getBackgroundImageName ()
{
  return graphics[0];
}

const string & Map :: getTileImageName (uint8_t tilename)
{
  if (tilename < MAP_TILE_NUM_TILES)
    return graphics[tilename+1];

  return graphics[0];
}

void Map :: setTileSize (uint8_t ts)
{
  tileSize = ts;
}

uint8_t Map :: getTileSize ()
{
  return tileSize;
}

uint8_t Map::getMapWidth ()
{
  return terrain[0].size ();
}

uint8_t Map::getMapHeight ()
{
  return terrain.size ();
}

void Map::getMapTerrain (vector <vector <uint8_t> > & mapToFill)
{
  //This sets mapToFill to the map.

  uint8_t i, j;

  mapToFill.resize (terrain.size());
  for (i = 0; i < terrain.size (); i ++)
    {
      mapToFill[i].resize(terrain[i].size());
      for (j = 0; j < terrain[i].size(); j ++)
        {
          mapToFill[i][j]=terrain[i][j];
        }
    }
}

void Map::getMapFog (vector <vector <bool> > & mapToFill)
{
  //This sets mapToFill to the fog layout.

  uint8_t i, j;

  mapToFill.resize (fog.size());
  for (i = 0; i < fog.size (); i ++)
    {
      mapToFill[i].resize(fog[i].size());
      for (j = 0; j < fog[i].size(); j ++)
        {
          mapToFill[i][j]=fog[i][j];
        }
    }
}

uint8_t Map::getEndPointX ()
{
  return playerEndX;
}

uint8_t Map::getEndPointY ()
{
  return playerEndY;
}

vector <uint8_t> & Map::getEnemyStartingPositionsX ()
{
  return enemyStartingPositionsX;
}

vector <uint8_t> & Map::getEnemyStartingPositionsY ()
{
  return enemyStartingPositionsY;
}

vector <string> & Map::getEnemyMapUnitPicNames ()
{
  return mapUnitEnemyGraphics;
}

vector <AnimatedState> & Map::getEnemyAnimatedStates ()
{
  return enemyAnimatedStates;
}
