/**
 * @mainpage LEPHISTO
 *
 * Doxygen documentation for the Lephisto project. (c) SaraCraft.
 */
/**
 * @file lephisto.c
 *
 * @brief Control the overall game flow: data loading/unloading and game loop.
 */

#include <SDL/SDL.h>

/* Global. */
#include <string.h>
#if defined(LINUX) && !defined(NODEBUG)
#include <signal.h>
#include <execinfo.h>
#include <stdlib.h>
#include <unistd.h>
#endif /* defined(LINUX) && !defined(NODEBUG) */

/* Local. */
#include "lephisto.h"
#include "conf.h"
#include "log.h"
#include "physics.h"
#include "opengl.h"
#include "font.h"
#include "ship.h"
#include "pilot.h"
#include "player.h"
#include "input.h"
#include "joystick.h"
#include "space.h"
#include "rng.h"
#include "ai.h"
#include "outfit.h"
#include "pack.h"
#include "weapon.h"
#include "faction.h"
#include "xml.h"
#include "pause.h"
#include "toolkit.h"
#include "pilot.h"
#include "sound.h"
#include "music.h"
#include "spfx.h"
#include "economy.h"
#include "menu.h"
#include "mission.h"
#include "misn_lua.h"
#include "lfile.h"
#include "unidiff.h"
#include "nebulae.h"


#define XML_START_ID    "Start"            /**< XML document tag of module start file. */
#define START_DATA      "../dat/start.xml" /**< Path to module start file. */

#define CONF_FILE       "conf"             /**< Configuration file by default. */
#define VERSION_FILE    "VERSION"          /**< Version file by default. */
#define VERSION_LEN     10                 /**< Max length of the version file. */
#define FONT_SIZE       12                 /**< Normal font size. */
#define FONT_SIZE_SMALL 10                 /**< Small font size. */

#define LEPHISTO_INIT_DELAY 3000 /**< Minimum amount of time to wait with loading screen. */

static int quit = 0;              /**< For primary loop. */
static unsigned int time = 0;     /**< Used to calculate FPS and movement, in pause.c */
static char version[VERSION_LEN]; /**< Contains version. */
static glTexture* loading;        /**< Loading screen. */

/* Just some default crap. */
char* data = NULL;                  /**< Path to datafile. */
char dataname[DATA_NAME_LEN] = "";  /**< Name of data file. */
int nosound = 0;                    /**< Disable sound when loaded. */
int show_fps = 1;                   /**< Shows fps - default true. */
int max_fps = 0;                    /**< Default FPS limit, 0 is no limit. */
int indjoystick = -1;               /**< Index of joystick to use, -1 is none. */
char* namjoystick = NULL;           /**< Name of joystick to use, NULL is none. */

/* Prototypes. */
/* Loading. */
static void print_SDLversion(void);
static void loadscreen_load(void);
void loadscreen_render(double done, const char* msg); /* nebulae.c */
static void loadscreen_unload(void);
static void load_all(void);
static void unload_all(void);
static void display_fps(const double dt);
static void window_caption(void);
static void data_name(void);
static void debug_sigInit(void);
/* Update. */
static void fps_control(void);
static void update_all(void);
static void update_routine(double dt);
static void render_all(void);
/* Misc. */
void main_loop(void); /* dialogue.c */


/**
 * @fn int main(int argc, char** argv)
 *
 *  @brief The entry point of Lephisto.
 *
 *    @param[in] argc Number of arguments.
 *    @param[in] argv Array of argc arguments.
 *    @return EXIT_SUCCESS on success.
 */
#ifdef WIN32
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine,
                   int nCmdShow) {
  int argc = 0;
  char *argv[] = { NULL };
#else
int main(int argc, char** argv) {
#endif

  char buf[PATH_MAX];

  /* Print the version. */
  snprintf(version, VERSION_LEN, "%d.%d.%d", VMAJOR, VMINOR, VREV);
  LOG(" "APPNAME" v%s", version);

  /* Initialize SDL for possible warnings. */
  SDL_Init(0);

  /* Set up debug signal handlers. */
  debug_sigInit();

  /* Create the home directory if needed. */
  if(lfile_dirMakeExist("%s", lfile_basePath()))
    WARN("Unable to create lephisto directory '%s'", lfile_basePath());

  /* Input must be initialized for config to work. */
  input_init();

  /* Set the configuration. */
  snprintf(buf, PATH_MAX, "%s"CONF_FILE, lfile_basePath());
  conf_setDefaults(); /* Default config values. */
  conf_loadConfig(buf); /* Have Lua parse config. */
  conf_parseCLI(argc, argv); /* Parse CLI arguments. */

  /* Load the data basics. */
  data_name();
  LOG(" %s", dataname);
  DEBUG();

  /* Display the SDL Version. */
  print_SDLversion();
  DEBUG();

  /* Random numbers. */
  rng_init();

  /* OpenGL */
  if(gl_init()) { /* Init video output. */
    ERR("Initializing video output failed, exiting...");
    SDL_Quit();
    exit(EXIT_FAILURE);
  }

  gl_fontInit(NULL, NULL, FONT_SIZE); /* Initializes default font size. */
  gl_fontInit(&gl_smallFont, NULL, FONT_SIZE_SMALL);  /* Small font. */
  window_caption();

  /* Display the load screen. */
  loadscreen_load();
  loadscreen_render(0., "Initializing subsystems...");
  time = SDL_GetTicks();

  /* OpenAL sound. */
  if(nosound) {
    LOG("Sound is disabled!");
    sound_disabled = 1;
    music_disabled = 1;
  }
  if(sound_init()) WARN("Problem setting up sound!");
  music_choose("load");

  /* Input. */
  if((indjoystick >= 0) || (namjoystick != NULL)) {
    if(joystick_init())
      WARN("Error initializing joystick input");
    if(namjoystick != NULL) {
      /* Use a joystick name to find joystick. */
      if(joystick_use(joystick_get(namjoystick))) {
        WARN("Failure to open any joystick, falling back to default keybinds");
        input_setDefault();
      }
      free(namjoystick);
    }
    else if(indjoystick >= 0)
      /* Must be using an id instead. */
      if(joystick_use(indjoystick)) {
        WARN("Failure to open any joystick, falling back to default keybinds");
        input_setDefault();
      }
  }

  /* Misc. */
  if(ai_init())
    WARN("Error initializing AI");

  /* Misc graphics init stuff. */
  if(nebu_init() != 0) { /* Init the nebulae. */
    /* An error happened. */
    ERR("Unable to init the Nebulae subsystem!");
    /* Weirdness will accur. */
  }
  gui_init(); /* Init the GUI crap. */
  toolkit_init(); /* Init the toolkit. */

  /* Data loading. */
  load_all();

  /* Unload load screen. */
  loadscreen_unload();

  /* Start menu. */
  menu_main();

  /* Force a minimum delay with loading screen. */
  if((SDL_GetTicks() - time) < LEPHISTO_INIT_DELAY)
    SDL_Delay(LEPHISTO_INIT_DELAY - (SDL_GetTicks() - time));
  time = SDL_GetTicks(); /* Init the time. */

  /* Main loop. */
  SDL_Event event;
  /* flushes the event loop, since I notices that when the joystick is loaded, it */
  /* creates button events that results in the player starting out accelerating. */
  while(SDL_PollEvent(&event));
  while(!quit) {
    /* Event loop. */
    while(SDL_PollEvent(&event)) {
      if(event.type == SDL_QUIT) quit = 1; /* Handle quit. */

      input_handle(&event); /* handles all the events the player keybinds. */
    }
    main_loop();
  }

  /* Clean up some stuff. */
  player_cleanup(); /* Cleans up the player stuff. */
  gui_free();       /* Free up the gui. */
  weapon_exit();    /* Destroy all active weapons. */
  pilots_free();    /* Free the pilots, they where locked up D: */
  space_exit();     /* Cleans up the universe itself. */

  /* Unload data. */
  unload_all();

  /* Cleanup opengl fonts. */
  gl_freeFont(NULL);
  gl_freeFont(&gl_smallFont);

  /* Exit subsystems. */
  toolkit_exit();   /* Kill the toolkit. */
  ai_exit();        /* Stop the Lua AI magicness. */
  joystick_exit();  /* Release joystick. */
  input_exit();     /* Clean up keybindings. */
  nebu_exit();      /* Destroys the nebulae. */
  gl_exit();        /* Kills video output. */
  sound_exit();     /* Kills the sound. */
  SDL_Quit();       /* Quits SDL. */

  /* All is good. */
  exit(EXIT_SUCCESS);
}

/**
 * @fn void loadscreen_load(void)
 *
 * @brief Display a loading screen.
 */
void loadscreen_load(void) {
  int i;
  char file_path[PATH_MAX];
  char** loadscreens;
  char** files;
  uint32_t nfiles;
  size_t len;
  int nload;

  /* Count the loading screens. */
  files = pack_listfiles(data, &nfiles);
  len = strlen("../gfx/loading/");
  nload = 0;
  loadscreens = malloc(sizeof(char*) * nfiles);
  for(i = 0; i < (int)nfiles; i++) {
    if(strncmp(files[i], "../gfx/loading/", len)==0) {
      loadscreens[nload] = files[i];
      nload++;
    } else
      free(files[i]);
  }

  /* Must have loading screens. */
  if(nload == 0) {
    WARN("No loading screens found!");
    return;
  }

  /* Load the texture. */
  strncpy(file_path, loadscreens[RNG(0, nload-1)], PATH_MAX);
  loading = gl_newImage(file_path);

  /* Clean up. */
  for(i = 0; i < nload; i++)
    free(loadscreens[i]);
  free(loadscreens);
}

/**
 * @fn void loadscreen_render(double done, const char* msg)
 *
 * @brief Render the load screen with message.
 *    @param done Amount done (1. == completed).
 *    @param msg Loading screen message.
 */
void loadscreen_render(double done, const char* msg) {
  double x, y, w, h, rh;

  /* Clear background. */
  glClear(GL_COLOR_BUFFER_BIT);

  /* Draw loading screen image. */
  gl_blitScale(loading, 0., 0., SCREEN_W, SCREEN_H, NULL);

  /* Draw progress bar. */
  w = gl_screen.w * 0.4;
  h = gl_screen.h * 0.02;
  rh = h + gl_defFont.h + 4.;
  x = -w/2.;
  y = -h/2.;
  /* BG. */
  ACOLOUR(cBlack, 0.7);
  glBegin(GL_QUADS);
    glVertex2d(x-2.,        y-2. + 0.);
    glVertex2d(x-2.,        y+2. + rh);
    glVertex2d(x-2. + w+4., y+2. + rh);
    glVertex2d(x-2. + w+4., y-2. + 0.);
  glEnd();
  /* FG. */
  ACOLOUR(cConsole, 0.7);
  glBegin(GL_QUADS);
    glVertex2d(x,           y + 0.);
    glVertex2d(x,           y + h);
    glVertex2d(x + done*w,  y + h);
    glVertex2d(x + done*w,  y + 0.);
  glEnd();

  /* Draw text. */
  gl_print(&gl_defFont, x + gl_screen.w/2., y + gl_screen.h/2 + 2. + h,
      &cConsole, msg);

  /* Flip buffers. */
  SDL_GL_SwapBuffers();
}

/**
 * @fn static void loadscreen_unload(void)
 *
 * @brief Frees the loading screen.
 */
static void loadscreen_unload(void) {
  /* Free the textures. */
  gl_freeTexture(loading);
  loading = NULL;
}

/**
 * @fn void load_all(void)
 *
 * @brief Load all the data, makes main() simpler.
 */
#define LOADING_STAGES  9. /**< Amount of loading stages. */
void load_all(void) {
  /* Ordering of these is very important as they are interdependent. */
  loadscreen_render(1./LOADING_STAGES, "Loading the pilot's food surplus...");
  commodity_load();
  loadscreen_render(2./LOADING_STAGES, "Loading the powers that be...");
  factions_load(); /* Dep for fleet, space, missions. */
  loadscreen_render(3./LOADING_STAGES, "Loading things to do...");
  missions_load(); /* No dep. */
  loadscreen_render(4./LOADING_STAGES, "Loading firework displays...");
  spfx_load();
  loadscreen_render(5./LOADING_STAGES, "Loading laser beams...");
  outfit_load();
  loadscreen_render(6./LOADING_STAGES, "Loading floating homes...");
  ships_load();
  loadscreen_render(7./LOADING_STAGES, "Loading rich snobs...");
  fleet_load();
  loadscreen_render(8./LOADING_STAGES, "Loading the entire universe");
  space_load();
  loadscreen_render(1./LOADING_STAGES, "Load Complete!!");
  xmlCleanupParser(); /* Only needed to be run after all the loading is done. */
}

/**
 * @fn void unload_all(void)
 *
 * @brief Unloads all data, simplifies main().
 */
void unload_all(void) {
  /* Data unloading - order should not matter, but inverse load_all is good. */
  fleet_free();
  ships_free();
  outfit_free();
  spfx_free();      /* Remove the special effects. */
  missions_free();
  factions_free();
  commodity_free();
  var_cleanup();    /* Clean up mission variables. */
}

/**
 * @fn void main_loop(void)
 *
 * @brief Split main loop from main() for secondary loop hack in toolkit.c.
 */
void main_loop(void) {
  glClear(GL_COLOR_BUFFER_BIT);

  fps_control(); /* Everyone loves fps control.. */

  sound_update(); /* Update sounds. */
  if(toolkit) toolkit_update(); /* To simulate key repetition. */
  if(!menu_isOpen(MENU_MAIN)) {
    if(!paused) update_all(); /* Update game. */
    render_all();
  }
  if(toolkit) toolkit_render();

  gl_checkErr(); /* Check error every loop. */

  SDL_GL_SwapBuffers();
}

static double fps_dt = 1.;
static double cur_dt = 0.;
/**
 * @fn static void fps_control(void)
 *
 * @brief Controls the FPS.
 */
static void fps_control(void) {
  unsigned int t;
  double delay;

  /* dt in ms/1000. */
  t = SDL_GetTicks();
  cur_dt = (double)(t-time)/1000.;
  time = t;

  if(paused) SDL_Delay(10); /* Drop paused FPS to be nice to the CPU. */

  /* If the fps is limited.. */
  if((max_fps != 0) && (cur_dt < 1./max_fps)) {
    delay = 1./max_fps - cur_dt;
    SDL_Delay((unsigned int)(delay*1000));
    fps_dt += delay; /* Make sure it displays the propper FPS. */
  }
}

static const double fps_min = 1./50.0;
/**
 * @fn static void update_all(void)
 *
 * @brief Updates the game itself (player flying around etc).
 */
static void update_all(void) {
  double tmpdt;

  if(cur_dt > 0.25) { /* Slow timers down and rerun calculations */
    pause_delay((unsigned int)cur_dt*1000.);
    return;
  }
  /* We'll force a minimum of 50 FPS. */
  else if(cur_dt > fps_min) {
    tmpdt = cur_dt - fps_min;
    pause_delay((unsigned int)(tmpdt*1000));
    update_routine(fps_min);

    /* Run as many cycles of dt=fps_min as needed. */
    while(tmpdt > fps_min) {
      pause_delay((unsigned int)(-fps_min*1000)); /* Increment counters */
      update_routine(fps_min);
      tmpdt -= fps_min;
    }
    /* Note we don't touch cur_dt so that fps_delay works well. */
    update_routine(tmpdt); /* Leftovers. */
  } else /* Standard, just update with the last dt. */
    update_routine(cur_dt);
}

/**
 * @fn static void update_routine(double dt)
 *
 * @brief Actually runs the update.
 *    @param[in] dt Current delta tick.
 */
static void update_routine(double dt) {
  space_update(dt);
  weapons_update(dt);
  spfx_update(dt);
  pilots_update(dt);
  missions_update(dt);
}

/**
 * @fn static void render_all(void)
 *
 * @brief Renders the game itself (player flying around etc).
 *
 * Blitting order. (layers)
 *
 * - BG
 *   -- Stars and planets.
 *   -- Background player stuff (planet targetting)
 *   -- Background particles.
 *   -- Back layer weapons.
 *  - N
 *    -- NPC ships.
 *    -- Front layer weapons.
 *    -- Normal layer particles (above ships).
 *  - FG
 *    -- Player.
 *    -- Foreground particles.
 *    -- Text and GUI.
 */
static void render_all(void) {
  double dt;

  dt = (paused) ? 0. : cur_dt;
  /* Setup. */
  spfx_start(dt);
  /* BG. */
  space_render(dt);
  planets_render();
  player_renderBG();
  weapons_render(WEAPON_LAYER_BG, dt);
  /* N. */
  pilots_render();
  weapons_render(WEAPON_LAYER_FG, dt);
  spfx_render(SPFX_LAYER_BACK);
  /* FG. */
  player_render();
  spfx_render(SPFX_LAYER_FRONT);
  space_renderOverlay(dt);
  player_renderGUI();
  display_fps(dt); /* Exception. */
}


static double fps = 0.;
static double fps_cur = 0.;
/**
 * @fn static void display_fps(const double dt)
 *
 * @brief Displays FPS on the screen.
 *
 *    @param[in] dt Current delta tick.
 */
static void display_fps(const double dt) {
  double x, y;
  fps_dt += dt;
  fps_cur += 1.;
  if(fps_dt > 1.) {
    /* Recalculate every second. */
    fps = fps_cur / fps_dt;
    fps_dt = fps_cur = 0.;
  }

  x = 10.;
  y = (double)(gl_screen.h - 20);
  if(show_fps)
    gl_print(NULL, x, y, NULL, "%3.2f", fps);
}

/**
 * @fn static void data_name(void)
 *
 * @brief Set the data module's name.
 */
static void data_name(void) {
  uint32_t bufsize;
  char* buf;

  /* Check if data file is valid. */
  if((DATA == NULL) || (pack_check(DATA))) {
    WARN("Data file '%s' not found", DATA);
    WARN("You should specify which data file to use with '-d'");
    WARN("See -h or --help for more information");
    SDL_Quit();
    exit(EXIT_FAILURE);
  }

  /* Check the version. */
  buf = pack_readfile(DATA, VERSION_FILE, &bufsize);

  if(strncmp(buf, version, bufsize) != 0) {
    /* 0.3.4 release will use 0.3.3 ldata, we don't to warn.
    DEBUG("Lephisto version and data module version differ!");
    DEBUG("Lephisto is v%s, data is for v%s", version, buf);
    */
  }

  free(buf);

  /* Load the datafiles name. */
  buf = pack_readfile(DATA, START_DATA, &bufsize);

  xmlNodePtr node;
  xmlDocPtr doc = xmlParseMemory(buf, bufsize);

  node = doc->xmlChildrenNode;
  if(!xml_isNode(node, XML_START_ID)) {
    ERR("Maformed '"START_DATA"' file: missing root element '"XML_START_ID"'");
    return;
  }

  node = node->xmlChildrenNode; /* First node. */
  if(node == NULL) {
    ERR("Malformed '"START_DATA"' file: does not contain elements");
    return;
  }
  do {
    if(xml_isNode(node, "name")) {
      strncpy(dataname, xml_get(node), DATA_NAME_LEN);
      dataname[DATA_NAME_LEN-1] = '\0';
    }
  } while(xml_nextNode(node));

  xmlFreeDoc(doc);
  free(buf);
}

/**
 * @fn static void window_caption(void)
 *
 * @brief Set the window caption.
 */
static void window_caption(void) {
  char tmp[DATA_NAME_LEN+10];

  snprintf(tmp, DATA_NAME_LEN+10, APPNAME" - %s", dataname);
  SDL_WM_SetCaption(tmp, NULL);
}

static char human_version[50];  /**< Stores human readable version string. */

/**
 * @fn char* lephisto_version(void)
 *
 * @brief Return the version in a human readable string.
 *
 *    @return The human readable version string.
 */
char* lephisto_version(void) {
  if(human_version[0] == '\0')
    snprintf(human_version, 50, " "APPNAME" v%s - %s", version, dataname);

  return human_version;
}

/**
 * @fn static void print print_SDLversion.
 *
 * @bief Print the SDL version to console.
 */
static void print_SDLversion(void) {
  const SDL_version*  linked;
  SDL_version         compiled;
  SDL_VERSION(&compiled);
  linked = SDL_Linked_Version();

  DEBUG("SDL: %d.%d.%d [compiled: %d.%d.%d]",
      linked->major, linked->minor, linked->patch,
      compiled.major, compiled.minor, compiled.patch);

  /* Check if major/minor version differ. */
  if((linked->major*100 + linked->minor) > compiled.major*100 + compiled.minor)
    WARN("SDL is newer than compiled version.");
  if((linked->major*100 + linked->minor) < compiled.major*100 + compiled.minor)
    WARN("SDL is older than compiled version.");
}

#if defined(LINUX) && !defined(NODEBUG)
/**
 * @brief Get the string related to the signal code.
 *    @param sig Signal to which code belongs.
 *    @param sig_code Signal code to get string of.
 *    @return String of signal code.
 */
static const char* debug_sigCodeToStr(int sig, int sig_code) {
  if(sig == SIGFPE)
    switch(sig_code) {
      case SI_USER:     return "SIGFPE (raised by program)";
      case FPE_INTDIV:  return "SIGFPE (integer divide by zero)";
      case FPE_INTOVF:  return "SIGFPE (integer overflow)";
      case FPE_FLTDIV:  return "SIGFPE (floating-point divide by zero)";
      case FPE_FLTOVF:  return "SIGFPE (floating-point overflow)";
      case FPE_FLTUND:  return "SIGFPE (floating-point underflow)";
      case FPE_FLTRES:  return "SIGFPE (floating-point inexact result)";
      case FPE_FLTINV:  return "SIGFPE (floating-point invalid operation)";
      case FPE_FLTSUB:  return "SIGFPE (subscript out of range)";
      default:          return "SIGFPE";
    }
  else if(sig == SIGSEGV)
    switch(sig_code) {
      case SI_USER:     return "SIGSEGV (raised by program)";
      case SEGV_MAPERR: return "SIGEGV (address not mapped to object)";
      case SEGV_ACCERR: return "SIGEGV (invalid permissions for mapped object)";
      default: return "SIGSEGV";
    }
  else if(sig == SIGTRAP)
    switch(sig_code) {
      case SI_USER:     return "SIGTRAP (raised by program)";
      case TRAP_BRKPT:  return "SIGTRAP (process breakpoint)";
      case TRAP_TRACE:  return "SIGTRAP (process trace trap)";
      default:          return "SIGTRAP";
    }
  /* No suitable code found. */
  return strsignal(sig);
}

/**
 * @brief Backtrace signal handler for linux.
 *    @param sig Signal.
 *    @param info Signal information.
 *    @param unused Unused.
 */
static void debug_sigHandler(int sig, siginfo_t* info, void* unused) {
  (void) sig;
  (void) unused;
  int i, num;
  void* buf[64];
  char** symbols;

  num = backtrace(buf, 64);
  symbols = backtrace_symbols(buf, num);

  DEBUG("LEPHISTO recieved %s!",
      debug_sigCodeToStr(info->si_signo, info->si_code));
  for(i = 0; i < num; i++)
    DEBUG("   %s", symbols[i]);
  DEBUG("Report this to project maintainer with the backtrace.");

  exit(1);
}
#endif /* defined(LINUX) && !defined(DEBUG) */

/**
 * @brief Set up the SignalHandler for Linux.
 */
static void debug_sigInit(void) {
#if defined(LINUX) && !defined(NODEBUG)
  struct sigaction sa, so;

  /* Set up handler. */
  sa.sa_handler   = NULL;
  sa.sa_sigaction = debug_sigHandler;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags     = SA_SIGINFO;

  /* Attach signals. */
  sigaction(SIGSEGV, &sa, &so);
  if(so.sa_handler == SIG_IGN)
    DEBUG("Unable to set up SIGSEGV signal handler.");
  sigaction(SIGFPE, &sa, &so);
  if(so.sa_handler == SIG_IGN)
    DEBUG("Unable to set up SIGFPE signal handler.");
  sigaction(SIGTRAP, &sa, &so);
  if(so.sa_handler == SIG_IGN)
    DEBUG("Unable to get set up SIGTRAP signal handler.");
#endif /* #if defined(LINUX) && !defined(NODEBUG) */
}

