//-----------------------------------------------------------------------------
// 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 "core/tokenizer.h"
#include "map2dif plus/editGeometry.h"
#include "interior/interior.h"
#include "map2dif plus/editInteriorRes.h"
#include "interior/floorPlanRes.h"
#include "map2dif plus/morianGame.h"
#include "core/frameAllocator.h"
#include "gui/core/guiCanvas.h"
#include "map2dif plus/lmapPacker.h"
#include "map2dif plus/convert.h"

#include <stdlib.h>

MorianGame GameObject;

// FOR SILLY LINK DEPENDANCY
bool gEditingMission = false;

#if defined(TORQUE_DEBUG)
const char* const gProgramVersion = "1.0d";
#else
const char* const gProgramVersion = "1.0r";
#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();

   ResManager::create();

   // Register known file types here
   ResourceManager->registerExtension(".jpg", constructBitmapJPEG);
   ResourceManager->registerExtension(".png", constructBitmapPNG);
   ResourceManager->registerExtension(".gif", constructBitmapGIF);
   ResourceManager->registerExtension(".dbm", constructBitmapDBM);
   ResourceManager->registerExtension(".bmp", constructBitmapBMP);
   ResourceManager->registerExtension(".bm8", constructBitmapBM8);
   ResourceManager->registerExtension(".gft", constructFont);
   ResourceManager->registerExtension(".dif", constructInteriorDIF);
   ResourceManager->registerExtension(".map", constructInteriorMAP);

   Con::init();

   Math::init();
   Platform::init();    // platform specific initialization

   // Create a log file
   Con::setLogMode(6);

   return(true);
}

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

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

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

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

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

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

   cleanSlashes(path);

   terminate(path);

   return path;
}   

char* getPath(const char* file)
{
   char* path = "\0";

   if (!dStrchr(file, '/') && !dStrchr(file, '\\'))
      return path;
   else
   {
      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;

   cleanSlashes(path);

   terminate(path);

   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;
}   

S32 MorianGame::main(int argc, const char** argv)
{
   // Set the memory manager page size to 64 megs...
   setMinimumAllocUnit((U32)( 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 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 'S':
            gTextureSearch = false;
            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, and Matthew Fairfax\n"
              "  Built: %s at %s\n\n"
              "Usage: map2dif [-s] [-l] [-h] [-e] [-o outputDirectory] [-t textureDirectory] <file>.map\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"
              "        -s : Don't search for textures in parent dir.\n"
              "        -o dir: Directory in which to place the .dif file\n"
              "        -t dir: Location of textures\n", gProgramVersion, __DATE__, __TIME__);
      shutdownLibraries();
      return -1;
   }
   else
      dPrintf("\nmap2dif - Torque .MAP file converter\n"
              "  Copyright (C) GarageGames.com, Inc.\n"
              "  Program version: %s\n"
              "  Programmers: John Folliard, Dave Moore, and Matthew Fairfax\n"
              "  Built: %s at %s\n\n", gProgramVersion, __DATE__, __TIME__);

   // 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;

   // Wad path
   gWadPath = wadPath;

   // Print out which file we are loading and what texture search path has been set
   Con::printf("Loading %s", mapFile);
   dPrintf("\nLoading %s\n", mapFile);
   Con::printf("Initial texture search path is set to \"%s\"\n", gWadPath);
   dPrintf("Initial texture search path is set to \"%s\"\n\n", gWadPath);

   dFflushStdout();

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

   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);
   }

   // Fix the slashes
   for (U32 i = 0; i < mapFileNames.size(); i++)
      cleanSlashes(mapFileNames[i]);

   gWorkingResource = new EditInteriorResource;
   
   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
      // First we need our object to load the file into
      InteriorMap map;

      // Set the texture path
      map.mTexPath = StringTable->insert(wadPath);

      // Set our path info
      map.setPath(mapFileNames[i]);

      // Load resource
      map.mInteriorRes = ResourceManager->load(mapFileNames[i], true);
      if (map.mInteriorRes)
      {
         Con::printf("Successfully opened map file: %s", mapFileNames[i]);
         dPrintf("Successfully opened map file: %s\n", mapFileNames[i]);
         dFflushStdout();
      }
      else
      {
         Con::printf("   Unable to load map file: %s", mapFileNames[i]);
         dPrintf("   Unable to load map file: %s\n", mapFileNames[i]);
         delete pTokenizer;
         delete gWorkingGeometry;
         delete gWorkingResource;
         shutdownLibraries();
         return -1;
      }

      // Little late for this but it looks nice
      dPrintf("  Parsing mapfile...");
      dFflushStdout();

      loadTextures(&map);

      // Reserve enough room for all the brushes
      // Not truly "dynamic" but it will do for now
      gBrushArena.setSize(map.mInteriorRes->mBrushes.size());

      convertInteriorMap(&map);

      delete pTokenizer;
      dPrintf("   done.\n");

      gWorkingGeometry->setGraphGeneration(false,extrusionTest);

      dPrintf("  Creating BSP...");
      dFflushStdout();
      if (gWorkingGeometry->createBSP() == false)
      {
         Con::printf("Error creating BSP for %s!", mapFileNames[i]);
         dPrintf("Error creating BSP for %s!\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();

      // Give status
      Con::printf("\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());
      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());

      // 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 )
{

}