/**
 * @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(DEBUGGING)
#include <signal.h>
#include <execinfo.h>
#include <stdlib.h>
#include <unistd.h>
#endif /* defined(LINUX) && !defined(DEBUGGING) */

/* 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 "ldata.h"
#include "weapon.h"
#include "faction.h"
#include "lxml.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 "llua_misn.h"
#include "lfile.h"
#include "unidiff.h"
#include "nebulae.h"


#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. */

/* Some defaults. */
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 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 */


/**
 *  @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("\n       "APPNAME" - Dark Tides v%s", version);
  LOG("       ----------------------------\n");

  /* 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());

  /* Must be initialized befre input init is called. */
  if(SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) {
    WARN("Unable to initialize SDL video: %s", SDL_GetError());
    return -1;
  }

  /* 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. */

  /* Open the data. */
  if(ldata_open() != 0)
    ERR("Failed to open ldata.");

  /* Load the data basics. */
  LOG(" %s", ldata_name());
  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();

  /* 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();
      }
  }
  
  /* 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");

  /* 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: */

  /* Unload data. */
  unload_all();

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

  /* Close data. */
  ldata_close();

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

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

  /* Count the loading screens. */
  loadscreens = ldata_list("../gfx/loading/", &nload);

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

  /* Load the texture. */
  snprintf(file_path, PATH_MAX, "../gfx/loading/%s",
      loadscreens[RNG_SANE(0, nload-1)]);
  loading = gl_newImage(file_path, 0);

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

/**
 * @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();
}

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

/**
 * @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. */
}

/**
 * @brief Unloads all data, simplifies main().
 */
void unload_all(void) {
  /* Data unloading - Inverse load_all is a good order. */
  economy_destroy();  /* Must be called before space_exit. */
  space_exit();       /* Cleans up the universe. */
  fleet_free();
  ships_free();
  outfit_free();
  spfx_free();      /* Remove the special effects. */
  missions_free();
  factions_free();
  commodity_free();
  var_cleanup();    /* Clean up mission variables. */
}

/**
 * @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.;  /**< Display fps accumulator. */
static double cur_dt = 0.;  /**< Current deltatick. */
/**
 * @brief Controls the FPS.
 */
static void fps_control(void) {
  unsigned int t;
  double delay;

  /* dt in s. */
  t = SDL_GetTicks();
  cur_dt =  (double)(t - time); /* Get the elapsed ms. */
  cur_dt *= dt_mod;             /* Apply the modifier. */
  cur_dt /= 1000.;              /* Convert to seconds. */
  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;
/**
 * @brief Updates the game itself (player flying around etc).
 */
static void update_all(void) {
  double tmpdt;

  if(cur_dt > 0.25/**dt_mod*/) { /* 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);
}

/**
 * @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);
}

/**
 * @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(dt);
  display_fps(dt); /* Exception. */
}


static double fps = 0.;     /**< FPS to finally display. */
static double fps_cur = 0.; /**< FPS accumulator to trigger change. */
/**
 * @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 / dt_mod;
  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);
    y -= gl_defFont.h + 5.;
  }
  if(dt_mod != 1.)
    gl_print(NULL, x, y, NULL, "%3.1fx", dt_mod);
}

/**
 * @brief Set the window caption.
 */
static void window_caption(void) {
  char buf[PATH_MAX];

  snprintf(buf, PATH_MAX, APPNAME" - %s", ldata_name());
  SDL_WM_SetCaption(buf, NULL);
}

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

/**
 * @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, ldata_name());

  return human_version;
}

/**
 * @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(DEBUGGING)
/**
 * @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 == SIGABRT)
    switch(sig_code) {
      case SI_USER:     return "SIGABRT (raised by program)";
      default:          return "SIGABRT";
    }
  /* 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(DEBUGGING)
  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(SIGABRT, &sa, &so);
  if(so.sa_handler == SIG_IGN)
    DEBUG("Unable to get set up SIGABRT signal handler.");
#endif /* #if defined(LINUX) && !defined(DEBUGGING) */
}