/**
 * @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>
#include <string.h>

#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 "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. */
unsigned int time = 0; /**< USed to calculate FPS and movement, in pause.c */
static char version[VERSION_LEN];

/* 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. */
static void print_SDLversion(void);
static void load_screen(void);
static void load_all(void);
static void unload_all(void);
void main_loop(void);
static void display_fps(const double dt);
static void window_caption(void);
static void data_name(void);
/* Update. */
static void fps_control(void);
static void update_all(void);
static void update_routine(double dt);
static void render_all(void);


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

  /* Create the home directory if needed. */
  if(lfile_dirMakeExist("."))
    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);
  }
  window_caption();
  load_screen();
  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. */
  }
  gl_fontInit(NULL, NULL, FONT_SIZE); /* Init default font size. */
  gl_fontInit(&gl_smallFont, NULL, FONT_SIZE_SMALL); /* Small font. */
  gui_init(); /* Init the GUI crap. */
  toolkit_init(); /* Init the toolkit. */

  /* Data loading. */
  load_all();

  /* 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. */
  space_exit();     /* Clean up the universe!!! */
  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 load_scren(void)
 *
 * @brief Display a loading screen.
 */
void load_screen(void) {
  int i;
  glTexture* tex;
  char file_path[PATH_MAX];
  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;
  for(i = 0; i < (int)nfiles; i++) {
    if(strncmp(files[i], "../gfx/loading", len)==0)
      nload++;
    free(files[i]);
  }

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

  /* Load the texture. */
  snprintf(file_path, PATH_MAX, "../gfx/loading%03d.png", RNG(0, nload-1));
  tex = gl_newImage(file_path);

  /* Draw once, won't be redrawn. */
  glClear(GL_COLOR_BUFFER_BIT);
  gl_blitScale(tex, 0., 0., SCREEN_W, SCREEN_H, NULL);
  SDL_GL_SwapBuffers();

  /* Free the textures. */
  gl_freeTexture(tex);
}

/**
 * @fn void load_all(void)
 *
 * @brief Load all the data, makes main() simpler.
 */
void load_all(void) {
  /* Ordering of these is very important as they are interdependent. */
  commodity_load();
  factions_load(); /* Dep for fleet, space, missions. */
  missions_load(); /* No dep. */
  spfx_load();
  outfit_load();
  ships_load();
  fleet_load();
  space_load();
}

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

/**
 * @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) {
  /* Setup. */
  spfx_start(cur_dt);
  /* BG. */
  space_render(cur_dt);
  planets_render();
  player_renderBG();
  weapons_render(WEAPON_LAYER_BG);
  /* N. */
  pilots_render();
  weapons_render(WEAPON_LAYER_FG);
  spfx_render(SPFX_LAYER_BACK);
  /* FG. */
  player_render();
  spfx_render(SPFX_LAYER_FRONT);
  space_renderOverlay(cur_dt);
  player_renderGUI();
  display_fps(cur_dt);
}


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(pack_check(DATA)) {
    ERR("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) {
    WARN("Lephisto version and data module version differ!");
    WARN("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((node = node->next));

  xmlFreeDoc(doc);
  free(buf);
  xmlCleanupParser();
}

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

