//-----------------------------------------------------------------------------
// Torque Game Engine 
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "platform/platform.h"
#include "platform/event.h"
#include "platform/platformAssert.h"
#include "platform/platformVideo.h"
#include "math/mMath.h"
#include "console/console.h"
#include "dgl/gBitmap.h"
#include "core/tVector.h"
#include "core/fileStream.h"
#include "dgl/gTexManager.h"
#include "console/consoleTypes.h"
#include "math/mathTypes.h"
#include "map2dif/tokenizer.h"
#include "map2dif/editGeometry.h"
#include "interior/interior.h"
#include "map2dif/editInteriorRes.h"
#include "map2dif/entityTypes.h"
#include "interior/floorPlanRes.h"
#include "map2dif/morianGame.h"
#include "core/frameAllocator.h"
#include "gui/core/guiCanvas.h"
#include "map2dif/lmapPacker.h"

#include <stdlib.h>

MorianGame GameObject;


// FOR SILLY LINK DEPENDANCY
bool gEditingMission = false;

#if defined(TORQUE_DEBUG)
const char* const gProgramVersion = "0.900r-beta";
#else
const char* const gProgramVersion = "0.900d-beta";
#endif

//bool gRenderPreview       = false;
bool gSpecifiedDetailOnly = false;
bool gBuildAsLowDetail    = false;
bool gVerbose             = false;
const char* gWadPath = "base/textures/";
bool gTextureSearch = true;
int gQuakeVersion = 2;

U32 gMaxPlanesConsidered  = 32;

EditInteriorResource* gWorkingResource = NULL;

//#if defined(TORQUE_OS_WIN32) // huger hack
// huge hack
//GuiCanvas *Canvas;
//void GuiCanvas::paint() {}
//#endif

//
static bool initLibraries()
{
   // asserts should be created FIRST
   PlatformAssert::create();
   FrameAllocator::init(2 << 20);


   _StringTable::create();
   TextureManager::create();

   Con::init();

   Math::init();
   Platform::init();    // platform specific initialization
   return(true);
}

static void shutdownLibraries()
{
   // shut down
   Platform::shutdown();
   Con::shutdown();

   TextureManager::destroy();
   _StringTable::destroy();

   // asserts should be destroyed LAST
   FrameAllocator::destroy();
   PlatformAssert::destroy();
}

S32 getGraphNodes( char * mapFileName ) 
{
   // setup the tokenizer
   Tokenizer* pTokenizer = new Tokenizer();
   if (pTokenizer->openFile(mapFileName) == false) {
      dPrintf("getGraphNodes(): Error opening map file: %s", mapFileName);
      delete pTokenizer;
      shutdownLibraries();
      return -1;
   }
   
   // Create a geometry object
   AssertFatal(gWorkingGeometry == NULL, "EditGeometry already exists");
   gWorkingGeometry = new EditGeometry;

   // configure graph for generation.  not doing extrusion approach now.  
   gWorkingGeometry->setGraphGeneration(true, false);

   // parse and create the geometry
   dPrintf("Map file opened for graph work: %s\n"
           "  Parsing mapfile...", mapFileName);  dFflushStdout();
   if (gWorkingGeometry->parseMapFile(pTokenizer) == false) {
      dPrintf("getGraphNodes(): Error parsing map file: %s\n", mapFileName);
      delete pTokenizer;
      delete gWorkingGeometry;
      shutdownLibraries();
      return -1;
   }
   delete pTokenizer;
   dPrintf("done.\n");

   // The code that gives us the node list is down in the createBSP() 
   // call tree.  Kind of klunky but simpler for now.  
   dPrintf("  Creating graph node list");    dFflushStdout();
   gWorkingGeometry->xferDetailToStructural();
   bool  result = gWorkingGeometry->createBSP();
   
   if( result )
      gWorkingGeometry->writeGraphInfo();
   
   delete gWorkingGeometry;      gWorkingGeometry = NULL;
   delete gWorkingResource;      gWorkingResource = NULL;
   shutdownLibraries();
   
   if( result == false ){
      dPrintf( "getGraphNodes(): Error in BSP processing (%s)!\n", mapFileName);
      return -1;
   }
   else{
      dPrintf( "getGraphNodes(): Seemed to work... \n" );
      return 0;
   }
}

char* cleanPath(const char* _path)
{
   char* path = new char[dStrlen(_path) + 2];
   dStrcpy(path, _path);

   // Clean up path char.
   for (char* ptr = path; *ptr != '\0'; ptr++)
      if (*ptr == '\\')
         *ptr = '/';

   // Check termination
   char* end = &path[dStrlen(path) - 1];
   if (*end != '/')  {
      end[1] = '/';
      end[2] = 0;      
   }

   return path;
}   

char* getPath(const char* file)
{
   char* path = new char[dStrlen(file) + 2];
   dStrcpy(path, file);

   // Strip back to first path char.
   char* slash = dStrrchr(path, '/');
   if (!slash)
      slash = dStrrchr(path, '\\');
   if (slash)
      *slash = 0;

   // Clean up path char.
   char* ptr = path;
   for (; *ptr != '\0'; ptr++)
      if (*ptr == '\\')
         *ptr = '/';

   // Check termination
   ptr--;
   if (*ptr != '/') {
      ptr[1] = '/';
      ptr[2] = 0;
   }

   return path;
}

char* getBaseName(const char* file)
{
   // Get rid of path
   const char* slash = dStrrchr(file, '/');
   if (!slash)
      slash = dStrrchr(file, '\\');
   if (!slash)
      slash = file;
   else
      slash++;
   char* name = new char[dStrlen(slash) + 1];
   dStrcpy(name, slash);

   // Strip extension & trailing _N
   char* dot = dStrrchr(name, '.') - 2;
   if (dot[0] == '_' && (dot[1] >= '0' && dot[1] <= '9'))
      dot[0] = '\0';
   else
      dot[2] = '\0';

   return name;
}   

char* mergePath(const char* path1, const char* path2)
{
   // Will merge and strip off leading ".." from path2
   char* base = new char[dStrlen(path1) + dStrlen(path2) + 2];
   dStrcpy(base,path1);

   // Strip off ending path char.
   char* end = &base[dStrlen(base) - 1];
   if (*end == '/' || *end == '\\')
      *end = 0;

   // Deal with lead ./ and ../
   while (path2[0] == '.')
      if (path2[1] == '.') {
         // Chop off ../ and remove the trailing dir from base
         path2 += 2;
         if (*path2 == '/' || *path2 == '\\')
            path2++;
         char *ptr = dStrrchr(base, '/');
         if (!ptr)
            ptr = dStrrchr(base, '\\');
         AssertISV(ptr,"Error, could not merge relative path past root");
         *ptr = 0;
      }
      else {
         // Simply swallow the ./
         path2 += 1;
         if (*path2 == '/' || *path2 == '\\')
            path2++;
      }

   dStrcat(base,"/");
   dStrcat(base,path2);
   return base;
}   


S32 MorianGame::main(int argc, const char** argv)
{
   // Set the memory manager page size to 64 megs...
   setMinimumAllocUnit(64 << 20);

   if(!initLibraries())
      return 0;

   // Set up the command line args for the console scripts...
   Con::setIntVariable("Game::argc", argc);
   for (S32 i = 0; i < argc; i++)
      Con::setVariable(avar("Game::argv%d", i), argv[i]);

   // Parse command line args...
   bool isForNavigation = false, extrusionTest = false;
   const char* wadPath = 0;
   const char* difPath = 0;
   S32 i = 1;
   for (; i < argc; i++) {
      if (argv[i][0] != '-')
         break;
      switch(dToupper(argv[i][1])) {
         case 'D':
            gSpecifiedDetailOnly = true;
            break;
         case 'L':
            gSpecifiedDetailOnly = true;
            gBuildAsLowDetail = true;
            break;
         case 'H':
            gMaxPlanesConsidered = U32(1 << 30);
            break;
         case 'N':
            gVerbose = true;
            break;
         case 'G':
            isForNavigation = true;
            extrusionTest = true;
            break;
         case 'E':
            extrusionTest = true;
            break;
         case 'S':
            gTextureSearch = false;
            break;

         case 'Q':
            gQuakeVersion = atoi (argv[++i]);
            break;

         case 'T':
            wadPath = cleanPath(argv[++i]);
            break;
         case 'O':
            difPath = cleanPath(argv[++i]);
            break;
      }
   }
   U32 args = argc - i;
   if (args != 1) {
      dPrintf("\nmap2dif - Torque .MAP file converter\n"
              "  Copyright (C) GarageGames.com, Inc.\n"
              "  Program version: %s\n"
              "  Programmers: John Folliard & Dave Moore\n"
              "  Built: %s at %s\n\n"
              "Usage: map2dif [-v] [-p] [-s] [-l] [-h] [-g] [-e] [-n] [-q ver] [-o outputDirectory] [-t textureDirectory] <file>.map\n"
              "        -p : Include a preview bitmap in the interior file\n"
              "        -d : Process only the detail specified on the command line\n"
              "        -l : Process as a low detail shape (implies -s)\n"
              "        -h : Process for final build (exhaustive BSP search)\n"
              "        -g : Generate navigation graph info\n"
              "        -e : Do extrusion test\n"
              "        -s : Don't search for textures in parent dir.\n"
              "        -n : Noisy error/statistic reporting\n"
              "        -q ver: Quake map file version (2, 3)\n"
              "        -o dir: Directory in which to place the .dif file\n"
              "        -t dir: Location of textures\n", gProgramVersion, __DATE__, __TIME__);
      shutdownLibraries();
      return -1;
   }

   // Check map file extension
   const char* mapFile = argv[i];
   const char* pDot = dStrrchr(mapFile, '.');
   AssertISV(pDot && ((dStricmp(pDot, ".map") == 0)),
         "Error, the map file must have a .MAP extension.");

   // Get path and file name arguments
   const char* mapPath = getPath(mapFile);
   const char* baseName = getBaseName(mapFile);

   if (!wadPath)
      wadPath = mapPath;
   if (!difPath)
      difPath = mapPath;

   // Old relative path merge, should think about what to do with it.
   // wadPath = mergePath(mapPath,wadPath);
   // difPath = mergePath(mapPath,difPath);

   // Dif file name
   char* pOutputName = new char[dStrlen(difPath) + dStrlen(baseName) + 5];
   dStrcpy(pOutputName,difPath);
   dStrcat(pOutputName,baseName);
   dStrcat(pOutputName,".dif");

   // Wad path
   gWadPath = wadPath;

   //
   Vector<char*> mapFileNames;
   if (gSpecifiedDetailOnly == false) {
      const char* pDot = dStrrchr(mapFile, '.');

      if (pDot && *(pDot - 2) == '_') {
         // This is a detail based interior
         char buffer[1024];
         dStrcpy(buffer, mapFile);
         char* pBufDot = dStrrchr(buffer, '.');
         AssertFatal(pBufDot, "Error, why isn't it in this buffer too?");
         *(pBufDot-1) = '\0';

         for (U32 i = 0; i <= 9; i++) {
            mapFileNames.push_back(new char[1024]);
            dSprintf(mapFileNames.last(), 1023, "%s%d%s", buffer, i, pDot);
         }

         // Now, eliminate all mapFileNames that aren't actually map files
         for (S32 i = S32(mapFileNames.size() - 1); i >= 0; i--) {
            Tokenizer* pTokenizer = new Tokenizer();
            if (pTokenizer->openFile(mapFileNames[i]) == false) {
               delete [] mapFileNames[i];
               mapFileNames.erase(i);
            }
            delete pTokenizer;
         }
      } else {
         // normal interior
         mapFileNames.push_back(new char[dStrlen(mapFile) + 1]);
         dStrcpy(mapFileNames.last(), mapFile);
      }
   } else {
      mapFileNames.push_back(new char[dStrlen(mapFile) + 1]);
      dStrcpy(mapFileNames.last(), mapFile);
   }

   gWorkingResource = new EditInteriorResource;
   
   if( isForNavigation ){
      S32   retCode = getGraphNodes( mapFileNames[0] );
      delete [] pOutputName;
      for (U32 i = 0; i < mapFileNames.size(); i++)
         delete [] mapFileNames[i];
      return retCode;
   }
   
   for (U32 i = 0; i < mapFileNames.size(); i++) {
      // setup the tokenizer
      Tokenizer* pTokenizer = new Tokenizer();
      if (pTokenizer->openFile(mapFileNames[i]) == false) {
         dPrintf("Error opening map file: %s", mapFileNames[i]);
         delete pTokenizer;
         shutdownLibraries();
         return -1;
      }

      // Create a geometry object
      AssertFatal(gWorkingGeometry == NULL, "Already working?");
      gWorkingGeometry = new EditGeometry;

      // parse and create the geometry
      dPrintf("Successfully opened map file: %s\n"
              "  Parsing mapfile...", mapFileNames[i]);
      dFflushStdout();
      if (gWorkingGeometry->parseMapFile(pTokenizer) == false) {
         dPrintf("Error parsing map file: %s\n", mapFileNames[i]);
         delete pTokenizer;
         delete gWorkingGeometry;
         delete gWorkingResource;
         shutdownLibraries();
         return -1;
      }
      delete pTokenizer;
      dPrintf("done.\n");
         
      gWorkingGeometry->setGraphGeneration(false,extrusionTest);

      dPrintf("  Creating BSP...");
      dFflushStdout();
      if (gWorkingGeometry->createBSP() == false) {
         dPrintf("Error creating BSP!\n", mapFileNames[i]);
         // delete pTokenizer;  (already)
         delete gWorkingGeometry;
         delete gWorkingResource;
         shutdownLibraries();
         return -1;
      }

      dPrintf("done.\n  Marking active zones...");
      gWorkingGeometry->markEmptyZones();
      dPrintf("done\n  Creating surfaces..."); dFflushStdout();
      gWorkingGeometry->createSurfaces();
      dPrintf("done.\n  Lightmaps: Normal...");
      dFflushStdout();
      gWorkingGeometry->computeLightmaps(false);
      dPrintf("Alarm...");
      dFflushStdout();
      gWorkingGeometry->computeLightmaps(true);
      dPrintf("done.\n  Resorting and Packing LightMaps..."); dFflushStdout();
      gWorkingGeometry->preprocessLighting();
      gWorkingGeometry->sortLitSurfaces();
      gWorkingGeometry->packLMaps();
      dPrintf("done.\n");
      dFflushStdout();

      // Process any special entitys...
      for (U32 i = 0; i < gWorkingGeometry->mEntities.size(); i++)
      {
         if (dynamic_cast<DoorEntity*>(gWorkingGeometry->mEntities[i]) != NULL) {
             DoorEntity* pDoor = static_cast<DoorEntity*>(gWorkingGeometry->mEntities[i]);
             pDoor->process();
         }
//          else if (dynamic_cast<ForceFieldEntity*>(gWorkingGeometry->mEntities[i]) != NULL) {
//             ForceFieldEntity* pForceField = static_cast<ForceFieldEntity*>(gWorkingGeometry->mEntities[i]);
//             pForceField->process();
//          }
      }

      // Give status
      dPrintf("\n  STATISTICS\n"
              "   - Total brushes:      %d\n"
              "     + structural:       %d\n"
              "     + detail:           %d\n"
              "     + portal:           %d\n"
              "   - Number of zones:    %d\n"
              "   - Number of surfaces: %d\n", gWorkingGeometry->getTotalNumBrushes(),
              gWorkingGeometry->getNumStructuralBrushes(),
              gWorkingGeometry->getNumDetailBrushes(),
              gWorkingGeometry->getNumPortalBrushes(),
              gWorkingGeometry->getNumZones(),
              gWorkingGeometry->getNumSurfaces());

      if (gWorkingGeometry->getNumAmbiguousBrushes() != 0 ||
          gWorkingGeometry->getNumOrphanPolys() != 0) {
         dPrintf(
            "\n  ** ***  WARNING WARNING WARNING  *** **\n"
            "  *** **  WARNING WARNING WARNING  ** ***\n"
            "\n   Errors exists in this interior.  Please use the debug rendering modes\n"
            "    to find and correct the following problems:\n"
            "\n   * Ambiguous brushes: %d"
            "\n   * Orphaned Polygons: %d\n"
            "\n  *** **  WARNING WARNING WARNING  ** ***\n"
            "  ** ***  WARNING WARNING WARNING  *** **\n",
            gWorkingGeometry->getNumAmbiguousBrushes(),
            gWorkingGeometry->getNumOrphanPolys());
      }

      // DMMTODO: store new geometry in the correct instance location
      Interior* pRuntime = new Interior;

      //
      // Support for interior light map border sizes.
      //
      pRuntime->setLightMapBorderSize(SG_LIGHTMAP_BORDER_SIZE);

      dPrintf("\n  Exporting to runtime..."); dFflushStdout();
      gWorkingGeometry->exportToRuntime(pRuntime, gWorkingResource);
      dPrintf("done.\n\n");
      dFflushStdout();
      gWorkingResource->addDetailLevel(pRuntime);

      delete gWorkingGeometry;
      gWorkingGeometry = NULL;
   }

   if (gWorkingResource->getNumDetailLevels() > 0) {
      dPrintf(" Writing Resource: "); dFflushStdout();

      dPrintf("persist..(%s) ", pOutputName); dFflushStdout();
      gWorkingResource->sortDetailLevels();

      gWorkingResource->getDetailLevel(0)->processHullPolyLists();
      gWorkingResource->getDetailLevel(0)->processVehicleHullPolyLists();
      for (U32 i = 1; i < gWorkingResource->getNumDetailLevels(); i++)
         gWorkingResource->getDetailLevel(i)->purgeLODData();

      FileStream fws;
      fws.open(pOutputName, FileStream::Write);
      gWorkingResource->write(fws);
      fws.close();

      dPrintf("Done.\n\n");
      dFflushStdout();

      delete gWorkingResource;
   }

   delete [] pOutputName;
   for (U32 i = 0; i < mapFileNames.size(); i++)
      delete [] mapFileNames[i];

   shutdownLibraries();
   return 0;
}

void GameReactivate()
{

}

void GameDeactivate( bool )
{

}
