/***************************************************************************
 *   Copyright (C) 2007 by A.J. Tavakoli                                   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdexcept>

#include "Q3Map.h"
#include "TGAFile.h"
#include "Video.h"
#include "common.h"

#define Q3_BSP_IDENT   (('P'<<24)+('S'<<16)+('B'<<8)+'I')
#define Q3_BSP_VER     0x2e
#define FIX_ENDIAN(x)  fixEndian( (void *)&x, sizeof(x) )


// set static big endian flag to true if platform is big endian
bool Q3Map::bigEndian = Q3Map::isBigEndian();


Q3Map::Q3Map():
     ents(NULL),
     textures(NULL),
     planes(NULL),
     nodes(NULL),
     leafs(NULL),
     leafFaces(NULL),
     leafBrushes(NULL),
     models(NULL),
     brushes(NULL),
     brushSides(NULL),
     vertices(NULL),
     meshVerts(NULL),
     effects(NULL),
     faces(NULL),
     lightMaps(NULL),
     lightVols(NULL),
     numVisVecs(0),
     visVecSize(0),
     visVecs(NULL) {
      
  // zero out lump pointers
  memset( lumps, 0, sizeof(lumps) );
}


// clear all dynamically allocated map data
Q3Map::~Q3Map() {
  clear(); 
}


// clear all dynamically allocated map data and reset
// all state variables
void Q3Map::clear() {
  for ( int i=0; i < NUM_LUMP_ENTRIES; i++ ) {
    if ( lumps[i] ) {
      delete [] lumps[i];
      lumps[i] = NULL;
    }
  }

  ents = NULL;
  textures = NULL;
  planes = NULL;
  nodes = NULL;
  leafs = NULL;
  leafFaces = NULL;
  leafBrushes = NULL;
  models = NULL;
  brushes = NULL;
  brushSides = NULL;
  vertices = NULL;
  meshVerts = NULL;
  effects = NULL;
  faces = NULL;
  lightMaps = NULL;
  lightVols = NULL;  
  
  numTextures = 0;
  numPlanes = 0;
  numNodes = 0;
  numLeafs = 0;
  numLeafFaces = 0;
  numLeafBrushes = 0;
  numModels = 0;
  numBrushes = 0;
  numBrushSides = 0;
  numVertices = 0;
  numMeshVerts = 0;
  numEffects = 0;
  numFaces = 0;
  numLightMaps = 0;
  numLightVols = 0;
  
  numVisVecs = 0;
  visVecSize = 0;
  visVecs = NULL;
}


// loads a quake 3 map from disk.  returns true if load was successful,
// false otherwise.
bool Q3Map::load(const char *filename) {
  FILE *fin = fopen(filename, "rb");
    
  if ( NULL == fin )
    return false;

  // read in header
  MapHeader header;
  fread(&header, sizeof(header), 1, fin);
  
  // make sure this is a quake 3 map
  Sint32 magic = Q3_BSP_IDENT;
  Sint32 ver   = Q3_BSP_VER;
  FIX_ENDIAN(magic);
  FIX_ENDIAN(ver);
  
  // check magic # and version to make sure this is a quake 3 map
  if ( magic != header.magic || ver != header.version ) {    
    fclose(fin);
    return false;
  }

  // clear out any map data before loading new data from file
  clear();
  
  // read in lumps
  for ( int i=0; i < NUM_LUMP_ENTRIES; i++ ) {
    FIX_ENDIAN(header.direntries[i].length);
    FIX_ENDIAN(header.direntries[i].offset);
    
    // allocate storage for this lump
    lumps[i] = new Sint8[header.direntries[i].length];
    
    // make sure memory was allocated successfully
    if ( !lumps[i] ) {
      // clear all lumps that were allocated before this one
      for ( int j=0; j < i; j++ )
        if ( lumps[j] )
          delete [] lumps[i];
          
      fclose(fin);
      return false; // indicate error
    }  
    
    // fseek to start of lump in file
    fseek(fin, header.direntries[i].offset, SEEK_SET);
    
    // read in lump
    fread(lumps[i], 1, size_t(header.direntries[i].length), fin);
  }    

  // close file  
  fclose(fin);

  return readLumps(lumps, header);
}


// extracts data from lumps read from quake 3 bsp file
bool Q3Map::readLumps(Sint8 *lumps[], const MapHeader &header) {
  numTextures    = int(header.direntries[LUMP_TEXTURES_INDEX].length)/sizeof(Texture);
  numPlanes      = int(header.direntries[LUMP_PLANES_INDEX].length)/sizeof(Plane);
  numNodes       = int(header.direntries[LUMP_NODES_INDEX].length)/sizeof(Node);
  numLeafs       = int(header.direntries[LUMP_LEAFS_INDEX].length)/sizeof(Leaf);
  numLeafFaces   = int(header.direntries[LUMP_LEAFFACES_INDEX].length)/4;
  numLeafBrushes = int(header.direntries[LUMP_LEAFBRUSHES_INDEX].length)/4;
  numModels      = int(header.direntries[LUMP_MODELS_INDEX].length)/sizeof(Model);
  numBrushes     = int(header.direntries[LUMP_BRUSHES_INDEX].length)/sizeof(Brush);  
  numBrushSides  = int(header.direntries[LUMP_BRUSHSIDES_INDEX].length)/sizeof(BrushSide);
  numVertices    = int(header.direntries[LUMP_VERTICES_INDEX].length)/sizeof(Vertex);
  numMeshVerts   = int(header.direntries[LUMP_MESHVERTS_INDEX].length)/4;
  numEffects     = int(header.direntries[LUMP_EFFECTS_INDEX].length)/sizeof(Effect);
  numFaces       = int(header.direntries[LUMP_FACES_INDEX].length)/sizeof(Face);
  numLightMaps   = int(header.direntries[LUMP_LIGHTMAPS_INDEX].length)/sizeof(LightMap);
  numLightVols   = int(header.direntries[LUMP_LIGHTVOLS_INDEX].length)/sizeof(LightVol);
  numVisVecs     = int(header.direntries[LUMP_VISDATA_INDEX].length);

  ents        = (char *)lumps[LUMP_ENTITIES_INDEX];
  textures    = (Texture *)lumps[LUMP_TEXTURES_INDEX];
  planes      = (Plane *)lumps[LUMP_PLANES_INDEX];
  nodes       = (Node *)lumps[LUMP_NODES_INDEX];  
  leafs       = (Leaf *)lumps[LUMP_LEAFS_INDEX];
  leafFaces   = (Sint32 *)lumps[LUMP_LEAFFACES_INDEX];
  leafBrushes = (Sint32 *)lumps[LUMP_LEAFBRUSHES_INDEX];
  models      = (Model *)lumps[LUMP_MODELS_INDEX];
  brushes     = (Brush *)lumps[LUMP_BRUSHES_INDEX];
  brushSides  = (BrushSide *)lumps[LUMP_BRUSHSIDES_INDEX];
  vertices    = (Vertex *)lumps[LUMP_VERTICES_INDEX];
  meshVerts   = (Sint32 *)lumps[LUMP_MESHVERTS_INDEX];
  effects     = (Effect *)lumps[LUMP_EFFECTS_INDEX];
  faces       = (Face *)lumps[LUMP_FACES_INDEX];
  lightMaps   = (LightMap *)lumps[LUMP_FACES_INDEX];
  lightVols   = (LightVol *)lumps[LUMP_LIGHTVOLS_INDEX];
  
  // visibility information
  if ( header.direntries[LUMP_VISDATA_INDEX].length > 0 ) {
    Sint32 *visLump = (Sint32 *)lumps[LUMP_VISDATA_INDEX];
    numVisVecs = visLump[0];
    visVecSize = visLump[1];
    visVecs = (Uint8 *)&visLump[2];
  }
  
  if ( bigEndian ) {
    //// begin code to correct endianness (yuck!) ////
    // (does nothing on little endian machines)
    
    for ( int i=0; i < numTextures; i++ ) {
      FIX_ENDIAN(textures[i].flags);
      FIX_ENDIAN(textures[i].contents);
    }
      
    for ( int i=0; i < numPlanes; i++ ) {
      FIX_ENDIAN(planes[i].normal[0]);
      FIX_ENDIAN(planes[i].normal[1]);
      FIX_ENDIAN(planes[i].normal[2]);
      FIX_ENDIAN(planes[i].d);    
    }
      
    for ( int i=0; i < numNodes; i++ ) {
      FIX_ENDIAN(nodes[i].plane);
      FIX_ENDIAN(nodes[i].min[0]);
      FIX_ENDIAN(nodes[i].min[1]);
      FIX_ENDIAN(nodes[i].min[2]);
      FIX_ENDIAN(nodes[i].max[0]);
      FIX_ENDIAN(nodes[i].max[1]);
      FIX_ENDIAN(nodes[i].max[2]);
      FIX_ENDIAN(nodes[i].children[0]);
      FIX_ENDIAN(nodes[i].children[1]);
    }
      
    for ( int i=0; i < numLeafs; i++ ) {
      FIX_ENDIAN(leafs[i].area);
      FIX_ENDIAN(leafs[i].cluster);
      FIX_ENDIAN(leafs[i].min[0]);
      FIX_ENDIAN(leafs[i].min[1]);
      FIX_ENDIAN(leafs[i].min[2]);
      FIX_ENDIAN(leafs[i].max[0]);
      FIX_ENDIAN(leafs[i].max[1]);
      FIX_ENDIAN(leafs[i].max[2]);
      FIX_ENDIAN(leafs[i].leafFace);
      FIX_ENDIAN(leafs[i].leafBrush);
      FIX_ENDIAN(leafs[i].numLeafBrushes);
    }
       
    for ( int i=0; i < numLeafFaces; i++ )
      FIX_ENDIAN(leafFaces[i]);
      
    for ( int i=0; i < numLeafBrushes; i++ )
      FIX_ENDIAN(leafBrushes[i]);
      
    for ( int i=0; i < numModels; i++ ) {
      FIX_ENDIAN(models[i].min[0]);
      FIX_ENDIAN(models[i].min[1]);
      FIX_ENDIAN(models[i].min[2]);
      FIX_ENDIAN(models[i].max[0]);
      FIX_ENDIAN(models[i].max[1]);
      FIX_ENDIAN(models[i].max[2]);
      FIX_ENDIAN(models[i].face);
      FIX_ENDIAN(models[i].numFaces);
      FIX_ENDIAN(models[i].brush);
      FIX_ENDIAN(models[i].numBrushes);
    }
    
    for ( int i=0; i < numBrushes; i++ ) {        
      FIX_ENDIAN(brushes[i].brushSide);
      FIX_ENDIAN(brushes[i].numBrushSides);
      FIX_ENDIAN(brushes[i].texture);      
    }
    
    for ( int i=0; i < numBrushSides; i++ ) {
      FIX_ENDIAN(brushSides[i].plane);    
      FIX_ENDIAN(brushSides[i].texture);
    }
    
    for ( int i=0; i < numVertices; i++ ) {
      FIX_ENDIAN(vertices[i].pos[0]);
      FIX_ENDIAN(vertices[i].pos[1]);
      FIX_ENDIAN(vertices[i].pos[2]);
      FIX_ENDIAN(vertices[i].surfaceTC[0]);
      FIX_ENDIAN(vertices[i].surfaceTC[1]);
      FIX_ENDIAN(vertices[i].lightMapTC[0]);
      FIX_ENDIAN(vertices[i].lightMapTC[1]);
      FIX_ENDIAN(vertices[i].normal[0]);
      FIX_ENDIAN(vertices[i].normal[1]);
      FIX_ENDIAN(vertices[i].normal[2]);      
    }
    
    for ( int i=0; i < numMeshVerts; i++ )
      FIX_ENDIAN(meshVerts[i]);
    
    for ( int i=0; i < numEffects; i++ ) {
      FIX_ENDIAN(effects[i].brush);
      FIX_ENDIAN(effects[i].unknown);
    }
    
    for ( int i=0; i < numFaces; i++ ) {
      FIX_ENDIAN(faces[i].texture);
      FIX_ENDIAN(faces[i].effect);
      FIX_ENDIAN(faces[i].type);
      FIX_ENDIAN(faces[i].vertex);
      FIX_ENDIAN(faces[i].numVertices);
      FIX_ENDIAN(faces[i].meshVert);
      FIX_ENDIAN(faces[i].numMeshVerts);
      FIX_ENDIAN(faces[i].lightMapIndex);
      FIX_ENDIAN(faces[i].lightMapStart[0]);
      FIX_ENDIAN(faces[i].lightMapStart[1]);
      FIX_ENDIAN(faces[i].lightMapSize[0]);
      FIX_ENDIAN(faces[i].lightMapSize[1]);
      FIX_ENDIAN(faces[i].lightMapOrigin[0]);
      FIX_ENDIAN(faces[i].lightMapOrigin[1]);
      FIX_ENDIAN(faces[i].lightMapOrigin[2]);    
      FIX_ENDIAN(faces[i].lightMapVecs[0][0]);
      FIX_ENDIAN(faces[i].lightMapVecs[0][1]);
      FIX_ENDIAN(faces[i].lightMapVecs[0][2]);
      FIX_ENDIAN(faces[i].lightMapVecs[1][0]);
      FIX_ENDIAN(faces[i].lightMapVecs[1][1]);
      FIX_ENDIAN(faces[i].lightMapVecs[1][2]);
      FIX_ENDIAN(faces[i].normal[0]);
      FIX_ENDIAN(faces[i].normal[1]);
      FIX_ENDIAN(faces[i].normal[2]);
      FIX_ENDIAN(faces[i].size[0]);
      FIX_ENDIAN(faces[i].size[1]);
    }
    
    FIX_ENDIAN(numVisVecs);
    FIX_ENDIAN(visVecSize);

    //// end of code to correct endianness ////
  } // if (big endian)

  return true;
}


// returns true if platform is big endian
bool Q3Map::isBigEndian() {
  int bytes = 1;
  Uint8 *s = (unsigned char *)&bytes;
  
  return s[0] != 1; 
}


Q3Map::ENT_TOKEN Q3Map::getEntToken(size_t &pos,
                                    size_t len,
                                    std::string *strVal) const {

  for ( ; pos < len; pos++ ) {
    if ( ents[pos] == '{' ) {
      pos++;
      return ENT_TOKEN_LBRACE;
    }
    if ( ents[pos] == '}' ) {
      pos++;
      return ENT_TOKEN_RBRACE;
    }
    if ( ents[pos] == '"' ) {
      pos++;
      if ( strVal != NULL )
        (*strVal) = "";
      // this is a string, so read until next "
      for ( ; pos < len && ents[pos] != '"'; pos++ ) {
        if ( strVal != NULL )
          (*strVal) += ents[pos];
      }
      if ( pos < len && ents[pos] == '"' ) {
        pos++;
        return ENT_TOKEN_STRING;
      }

      // reached end of entities before " was found
      return ENT_TOKEN_INVALID;
    }
  }

  return ENT_TOKEN_END;
}


// parses entities and creates one map for each entity, each map contains
// names and values of each attribute.  returns true on success, false
// on error.  any existing data in entMaps will be cleared.
bool Q3Map::parseEnts(Q3Map::EntMaps &entMaps) const {
  // make sure entity data has been loaded
  if ( NULL == ents )
    return false; // error

  // clear map
  entMaps.clear();

  size_t len = strlen(ents),
         pos = 0;
  ENT_TOKEN tok = ENT_TOKEN_INVALID;

  
  for ( tok = getEntToken(pos, len);
        tok != ENT_TOKEN_END;
        tok = getEntToken(pos, len) ) {
    // expecting '{'  
    if ( tok != ENT_TOKEN_LBRACE )
      return false;

    // create map for this entity
    entMaps.push_back( std::map<std::string, std::string>() );
    std::map<std::string, std::string> &currMap = entMaps[entMaps.size()-1];

    // read in each attribute assignment    
    std::string attrName, attrValue;
    for ( tok = getEntToken(pos, len, &attrName);
          tok != ENT_TOKEN_RBRACE;
          tok = getEntToken(pos, len, &attrName) ) {
      // expecting attribute name
      if ( tok != ENT_TOKEN_STRING )
        return false;

      // get attribute value
      tok = getEntToken(pos, len, &attrValue);
      if ( tok != ENT_TOKEN_STRING )
        return false;

      // map this attribute to its value
      currMap[attrName] = attrValue;
    }
  }

  return true;
}
