/*!
  Sss - a Slope Soaring Simulater.
  Copyright (C) 2002 Danny Chapman - flight@rowlhouse.freeserve.co.uk
  
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  
  \file main.cpp
*/
/*!
  \mainpage
  
  \section intro Introduction
  
  Sss/SSS is a simulator of radio-controlled gliders. Its
  distinguishing features are:
  
  <ul>
  
  <li> Extensive configuration at run-time through configuration files
  
  <li> Wind field is precalculated using a numerical simulation (using
  Metphomod) of airflow over the terrain, or can be calculated on the fly.
  
  <li> OpenGL is used for the rendering API, and GLUT as the event
  "engine".
  
  <li> Terrain is simplified/displayed using the Lindstrom/Pascucci (2001)
  view-dependant level-of-detail algorithm.
  
  <li> Currently support for component aerodynamics/graphics glider
  model (resulting in reasonable aerodynamics), which is itself very
  configurable at run-time. Also aerodynamics code imported from Crrcsim.
  
  <li> The code is designed to facilitate adding support for different
  aerodynamics/graphics etc models. For example, it would be easy to
  write a new aerodynamics module, and use it with the existing
  component model for graphical display etc.
  
  <li> Development is done on a low-end PC, under Linux and Windows,
  so there's a good chance it will work on the vast majority of
  x86 platforms.
  
  </ul>
  
  \section extending Extending the glider code
  
  It should be straightforward to extend the glider simulation code to
  add a new model type - e.g.
  <ul>
  
  <li> A graphical representation based on, say, 3D Studio Max output
  (on my todo list!)
  
  <li> A different aerodynamic representation - e.g. one that isn't
  component based
  
  <li> A more complicated engine representation - e.g. multiple
  engines, with torques etc (and maybe interactions with the
  aerodynamics).
  
  <li> A different structural representation - e.g. one more suited to
  collision detection/response.
  
  </ul>
  
  Let's say you want to do the second (already done!) - i.e. write a new aerodynamics
  representation based on the Crrcsim code - but you want to carry on
  using the existing graphical etc representations. To do this, just
  write two files, e.g. glider_aero_crrcsim.h and
  glider_aero_crrcsim.cpp, inheriting the new class
  Glider_aero_crrcsim from Glider_aero (exactly as in
  glider_aero_component.h). As well as adding the source file to the
  Makefile, you'll need to add it to the "factory" in glider_aero.cpp,
  so that when aero_type = "crrcsim", it creates an instance of your
  new class.
  
  Pretty easy, I think. When it comes to reading in configuration from
  a text file, the Config_file class can/should be used.
  
  \section note Notes
  
  Documentation generated by doxygen.
  
  Copyright (C) 2002 Danny Chapman - flight@rowlhouse.freeserve.co.uk */

#include "sss.h"
#include "config.h"
#include "misc.h"
#include "config_file.h"
#include "environment.h"
#include "glider.h"
#include "bludger.h"
#include "robot_pilot.h"
#include "body.h"
#include "joystick.h"
#include "remote_sss_iface.h"
#include "race_manager.h"

#include "sss_glut.h"

#include "sss_assert.h"

#include "log_trace.h"


#ifdef WIN32
#include <windows.h>
#include <wincon.h>
#endif

#include <iostream>
using namespace std;

const char * program_name = "Slope Soaring Simulator (v2.16.5) - Copyright 2004 Danny Chapman";

const char license[]="SSS by Danny Chapman:\n"
"    sss@rowlhouse.freeserve.co.uk\n"
"SSS comes with ABSOLUTELY NO WARRANTY;\n"
"This is free software, and you are welcome to redistribute it\n"
"under certain conditions; see the GNU General Public License\n";

//! Shows usage, then exits
void show_usage(int argc, char *argv[])
{
  cerr << "Usage: \n" << argv[0] << " <port to listen on> <ip-addr1> <port1>\n";
  cerr << "Or:\n";
  cerr << argv[0] << " <glider file>\n";
  exit(0);
}

/// Help function for an interactive startup.
int get_choice(string prompt,
               vector<string> choices)
{
  int choice = -1;
  while ( (choice < 1) || (choice > (int) choices.size()) )
  {
    cout << prompt << endl;
    for (unsigned i = 0 ; i < choices.size() ; ++i)
    {
      cout << i + 1 << "  " << choices[i] << endl;
    }
    cout << "Enter option number: ";
    cin >> choice;
  }
  return choice - 1;
}


void do_interactive_startup(Config & config)
{
  cout << 
    "\nEntering interactive startup - to disable, or to control the behaviour\n" <<
    "in detail, edit sss.cfg\n\n";
  
  int choice;
  vector<string> choices;
  
  // game mode
  choices.push_back("Demo: no control (except most keyboard actions work) - watch some robots");
  choices.push_back("Normal: just mooch around");
  choices.push_back("Race: Create some robots to race against (start when countdown exires)");
  choices.push_back("F3F: F3F-style timed race, normally with no opponents");
  choices.push_back("Sledge: Experimental 'sledge' mode");
  
  choice = get_choice("Choose game mode:",
                      choices);
  switch (choice)
  {
  case 0: config.sss_mode = Config::DEMO_MODE; break;
  case 1: config.sss_mode = Config::NORMAL_MODE; break;
  case 2: config.sss_mode = Config::RACE_MODE; break;
  case 3: config.sss_mode = Config::F3F_MODE; break;
  case 4: config.sss_mode = Config::SLEDGE_MODE; break;
  }
  
  cout << "\nHow many robot gliders (e.g. between 0 and 10): ";
  cin >> config.num_robots;
  
  cout << "Continuing with initialisation...\n";
#ifdef WIN32
  // Need to clean up input, else we don't wait for input on assert
  // I wonder who is right, MSVC or GNU...
//  getc(stdin);
#endif
}

/// Attempt to work out which timing option is the best by measuring
/// the number of clock changes in a set time. Only have to worry
/// about this under windows, where the QueryPerformanceCounter stuff
/// is sometimes better, and sometimes worse than GLUT!
void set_timing_method(Config::Timer_method & timer_method)
{
  if (timer_method != Config::TIMER_AUTO)
    return;
  
#ifdef WIN32
  const int total_millisec = 100;
  long unsigned increments = 0;
  long unsigned count = 0;
  
  // test GLUT.
  int last_glut_time = glutGet(GLUT_ELAPSED_TIME);
  int end_glut_time = last_glut_time + total_millisec;
  int current_glut_time = last_glut_time;
  while ( current_glut_time < end_glut_time )
  {
    if (current_glut_time != last_glut_time)
    {
      ++increments;
      last_glut_time = current_glut_time;
    }
    ++count;
    current_glut_time = glutGet(GLUT_ELAPSED_TIME);
  }
  float glut_resolution = (float) total_millisec / increments;
  
  TRACE("glut count = %ld, increments = %ld, resolution = %f\n",
        count, increments, glut_resolution);
  
  // test query stuff
  LARGE_INTEGER currentTime;
  LARGE_INTEGER frequency;
  bool use_perf = QueryPerformanceFrequency(&frequency);
  if (!use_perf)
  {
    TRACE("QueryPerformance not available - using GLUT for timing\n");
    timer_method = Config::TIMER_GLUT;
    return;
  }
  
  QueryPerformanceCounter(&currentTime);
  int last_perf_time = (int) (1000 * 
                              (float)currentTime.QuadPart / 
                              (float)frequency.QuadPart);
  int end_perf_time = last_perf_time + total_millisec;
  int current_perf_time = last_perf_time;
  increments = count = 0;
  while ( current_perf_time < end_perf_time )
  {
    if (current_perf_time != last_perf_time)
    {
      ++increments;
      last_perf_time = current_perf_time;
    }
    ++count;
    QueryPerformanceCounter(&currentTime);
    
    current_perf_time = (int) (1000 * 
                               (float)currentTime.QuadPart / 
                               (float)frequency.QuadPart);
    //TRACE("%d - end = %d\n", current_perf_time, end_perf_time);
  }
  float perf_resolution = (float) total_millisec / increments;
  
  TRACE("perf count = %ld, increments = %ld, resolution = %f\n",
        count, increments, perf_resolution);
  
  if (perf_resolution < glut_resolution)
  {
    TRACE("Using QueryPerformanceCounter for timing\n");
    timer_method = Config::TIMER_PERF;
  }
  else
  {
    TRACE("Using GLUT for timing\n");
    timer_method = Config::TIMER_GLUT;
  }
  return;
#else
  timer_method = Config::TIMER_GLUT;
#endif
}


//==================================================================
/// main creates the main objects, and passes them to Sss. 
///
/// We intentionally rely on the operating system to tidy up when we
/// exit - (a) we're using GLUT, so the only way to tidy up is to
/// either register with atexit, or use the stack rather carefully, and
/// (b) there's no guarantee that any cleanup code will get executed 
/// anyway - the user may just kill the process dead.
//==================================================================
int main(int argc, char *argv[])
{  
  TRACE("%s\n", program_name);
  TRACE("%s\n", license);
  
  string config_file_name("sss.cfg");
  if (argc == 2)
  {
    if ( (string(argv[1]) == string("-h")) ||
         (string(argv[1]) == string("-?")) ||
         (string(argv[1]) == string("-doc")) ||
         (string(argv[1]) == string("--h")) ||
         (string(argv[1]) == string("--help")) ||
         (string(argv[1]) == string("-help")) )
      show_usage(argc, argv);
    
    // treat first arg as a config file
    config_file_name = argv[1];
  }
  
  // initialise trace - used while doing config
  enable_trace(true);
  set_trace_level(0);
  enable_trace_all_strings(false);
  
  Config config(config_file_name.c_str());
  
  // now update trace settings
  enable_trace(config.trace_enabled);
  set_trace_level(config.trace_level);
  enable_trace_all_strings(config.trace_all_strings);
  add_trace_strings(config.trace_strings);
  
  TRACE_FILE_IF(1)
    {TRACE_FUNCTION(); TRACE("Loaded main config\n");}
  
  // screensaver?
  if (config.running_as_screensaver)
  {
    config.fullscreen = true;
    config.interactive_startup = false;
  }
  
  if (config.interactive_startup)
    do_interactive_startup(config);
  
  // Initialise GLUT
  glutInit(&argc, argv);
  
  // Choose the best timing method?
  set_timing_method(config.timer_method);
  
  // Override config if we're in a specific mode.
  if (config.sss_mode == Config::QUIDDITCH_MODE)
  {
    config.glider_file = "glider_wizard.dat";
  }
  else if ( (config.sss_mode == Config::F3F_MODE) ||
            (config.sss_mode == Config::RACE_MODE) )
  {
    config.terrain_file = "none";
    config.wind_file = "runtime";
    if (config.sss_mode == Config::F3F_MODE)
      config.wind_scale = 2.0f;
    else
      config.wind_scale = 1.5f;
    config.wind_dir = 270.0f;
    config.builtin_terrain_size = 129;
    config.builtin_terrain_dx = 10;
    config.builtin_terrain_type = Config::TERRAIN_PLATEAU;
    config.builtin_terrain_ridge_height = 80;
    config.builtin_terrain_ridge_width = 80;
    config.builtin_terrain_plateau_width = 200;
  }
  else if ((config.sss_mode == Config::SLEDGE_MODE) && (config.interactive_startup))
  {
    config.glider_file = "glider_sledge.dat";
    config.terrain_file = "none";
    config.wind_file = "runtime";
    config.wind_scale = 0.0f;
    config.builtin_terrain_size = 1025;
    config.builtin_terrain_dx = 2;
    config.builtin_terrain_type = Config::TERRAIN_PISTE;
    config.builtin_terrain_piste_slope = 0.5;
  }
  
  // Check for multiplayer.
  if (argc > 2)
  {
    TRACE_FILE_IF(1)
      {TRACE_FUNCTION(); TRACE("Multiplayer\n");}
    
    if (argc != 4)
      show_usage(argc, argv);
    
    // the arguments are OK... read them
    string server_port(argv[1]);
    string client_addr(argv[2]);
    string client_port(argv[3]);
    Remote_sss_iface::create_instance(server_port,
                                      client_addr,
                                      client_port);
  }
  
  // Create Sss, the repository of everything.
  Sss * sss = Sss::create_instance(config);
  
  // Now start creating things to put in Sss.
  Environment * environment = Environment::instance();
  
  // tweak the body_y position if we are a server (really should be more 
  // careful and tweak in the direction perp to the wind.
  if (Remote_sss_iface::instance() &&
      Remote_sss_iface::instance()->is_server())
  {
    config.start_y -= 2;
  }
  
  Joystick * joystick = new Joystick(10);
  
  if ( (config.sss_mode == Config::F3F_MODE) ||
       (config.sss_mode == Config::RACE_MODE) ) 
  {
//     Checkpoint_sphere * checkpoint_sphere_right = 
//       new Checkpoint_sphere(Position(-120, 50, 80), 5.0f);
//     Checkpoint_sphere * checkpoint_sphere_left = 
//       new Checkpoint_sphere(Position(-120, -50, 80), 5.0f);
//     Checkpoint_cylinder * checkpoint_cylinder_mid = 
//       new Checkpoint_cylinder(-140, 0, 5.0f);
    Checkpoint_plane * checkpoint_plane_right = 
      new Checkpoint_plane(-120, 50, -115, 50);
    Checkpoint_plane * checkpoint_plane_left = 
      new Checkpoint_plane(-115, -50, -120, -50);
    
    std::vector<Checkpoint *> checkpoints;
    checkpoints.push_back(checkpoint_plane_left);
    checkpoints.push_back(checkpoint_plane_right);
    checkpoints.push_back(checkpoint_plane_left);
    checkpoints.push_back(checkpoint_plane_right);
    checkpoints.push_back(checkpoint_plane_left);
    checkpoints.push_back(checkpoint_plane_right);
    // make F3F a longer longer course
    if (config.sss_mode == Config::F3F_MODE)
    {
      checkpoints.push_back(checkpoint_plane_left);
      checkpoints.push_back(checkpoint_plane_right);
      checkpoints.push_back(checkpoint_plane_left);
      checkpoints.push_back(checkpoint_plane_right);
      checkpoints.push_back(checkpoint_plane_left);
    }
    if (config.sss_mode == Config::F3F_MODE)
      Race_manager::create_instance(F3F_RACE, checkpoints);
    else
      Race_manager::create_instance(NORMAL_RACE, checkpoints);
  }
  else if (config.sss_mode == Config::SLEDGE_MODE)
  {
    // checkpoints get sorted out when the terrain is generated
    std::vector<Checkpoint *> checkpoints;
    Race_manager::create_instance(TIMED_RACE, checkpoints);
  }
  
  // create the primary glider... but note that it might be controlled
  // directly, or it might be owned by a robot (though the user can
  // always take over later).
  //
  //! \todo - use the Pilot class
  Glider * glider;
  Body * body;
  
  if (config.sss_mode == Config::DEMO_MODE)
  {
    bool success;
    string real_robot_file = "robots/" + config.robot_file;
    Config_file robot_config_file(real_robot_file,
                                  success);
    Robot_pilot * pilot;
    Position pos(config.start_x, 
                 config.start_y,
                 0);
    environment->set_z(pos);
    pos[2] += 10;
    
    if (success)
    {
      TRACE_FILE_IF(2)
        TRACE("Creating Robot pilot\n");
      pilot = new Robot_pilot(robot_config_file,
                              pos,
                              0,
                              false); // do not pause on startup
      TRACE("Created robot pilot as main glider\n");
    }
    else
    {
      assert1(!"Unable to get robot pilot config for main glider\n");
    }
    glider = pilot->get_glider();
    
  }
  else // not demo
  {
    bool success;
    string real_glider_file = "gliders/" + config.glider_file;
    Config_file glider_config_file(real_glider_file,
                                   success);
    assert2(success, "Cannot open the requested glider file");
    
    glider = new Glider(glider_config_file,
                        Position(config.start_x, 
                                 config.start_y, 
                                 0),
                        config.glider_speed, 
                        joystick);
  }
  body = new Body(Position(config.start_x, 
                           config.start_y,
                           0), // body sets its own z
                  glider);
  body->calc_new_pos_and_orient(0);
  
  // Pass all these things to Sss.
  sss->initialise(glider,
                  environment,
                  body,
                  joystick);  
  
  // Now create any non-fundamental objects
  if (config.sss_mode == Config::QUIDDITCH_MODE)
  {
    for (int i = 0 ; i < 2 ; ++i)
    {
      Bludger * bludger = 
        new Bludger(glider->get_pos() + Position(i, 0, 50));
      bludger->set_vel(Velocity(ranged_random(-10, 10), 
                                ranged_random(-10, 10), 
                                ranged_random(-10, 10)));
    }
  }
  
  // add any initial robots
  for (int i = 0 ; i < config.num_robots ; ++i)
  {
    sss->add_robot();
  }
  
  // the following never returns
  sss->start();
  
  TRACE("The GLUT main loop returned - should never happen!\n");
  
  return(0);
}

// Some random stuff from misc.h - not used at the moment

/*! Indicates if the machine is little-endian */
bool is_little_endian()
{
  enum Result 
    {
      NOT_READY, ENDIAN_TRUE, ENDIAN_FALSE
    };
  static Result result = NOT_READY;
  if (result == NOT_READY)
  {
    int val = 0x33221100;
    unsigned char * a = (unsigned char *) &val;
    unsigned char * b = a+1;
    unsigned char * c = a+2;
    unsigned char * d = a+3;
    cout << (int) *a << " " << (int) *b << " "
         << (int) *c << " "<< (int) *d << " " << endl;
    result = (*d > *a) ? ENDIAN_TRUE : ENDIAN_FALSE;
  }
  return (result != ENDIAN_TRUE);
}


//! Takes a 4-byte word and converts it to little endian (if necessary)
void convert_word_to_little_endian(void * orig)
{
  if (true == is_little_endian())
    return;
  unsigned char * a = (unsigned char *) orig;
  unsigned char * b = a+1;
  unsigned char * c = a+2;
  unsigned char * d = a+3;
  
  unsigned char t;
  // swap a and d
  t = *d; *d = *a; *a = t;
  // and b and c
  t = *b; *b = *c; *c = t;
}

//! Takes a 4-byte word and converts it from little endian to whatever
//! is this machine is (if necessary)
void convert_word_from_little_endian(void * orig)
{
  convert_word_to_little_endian(orig);
}

