// $Id$
//
//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2006 SuperTuxKart-Team
//  Modelled after Supertux's configfile.cpp
//
//  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.
#include <stdio.h>
#include <stdexcept>

// for mkdir:
#if !defined(WIN32) || defined(__CYGWIN__)
#  include <sys/stat.h>
#  include <sys/types.h>
#else
#  include <direct.h>
#endif

#include <plib/pw.h>
#include <plib/ul.h>

#include "config.hpp"
#include "lisp/lisp.hpp"
#include "lisp/parser.hpp"
#include "lisp/writer.hpp"

Config *config;

Config::Config() {
  setDefaults();
  loadConfig();
}   // Config

// -----------------------------------------------------------------------------
Config::Config(const std::string& filename) {
  setDefaults();
  loadConfig(filename);
}   // Config


// -----------------------------------------------------------------------------
Config::~Config() {
}

// -----------------------------------------------------------------------------
std::string Config::getConfigDir() {
  std::string dirname;
#ifdef WIN32
  // For now the old windows config way is used: store a config file
  // in the current directory (in other OS a special subdirectory is created)
  dirname=".";
#else
  if(getenv("HOME")!=NULL) {
    dirname = getenv("HOME");
  } else{
    dirname = ".";
  }
  dirname += "/";
  dirname += CONFIGDIR;
#endif
  return dirname;
}  // getConfigDir

// -----------------------------------------------------------------------------
/*set the config filename for this platform*/
void Config::setFilename() {
  filename = getConfigDir();
  filename += "/";
#ifdef WIN32
  filename += "supertuxkart.cfg";
#else
  filename += "config";
#endif
}   // setFilename

// -----------------------------------------------------------------------------
/*load default values for options*/
void Config::setDefaults() {
  setFilename();
  fullscreen       = false;
  noStartScreen    = false;
  sfx              = true;
  music            = true;
  smoke            = false;
  displayFPS       = false;
  singleWindowMenu = false;
  oldStatusDisplay = false;
  herringStyle     = "new";
  disableMagnet    = true;
  profile          = 0;
  useKPH           = false;
  improvedPhysics  = false;
  newKeyboardStyle = true;
  replayHistory    = false;
  width            = 800;
  height           = 600;
  karts            = 4;
  
  player[0].setName("Player 1");
  player[1].setName("Player 2");
  player[2].setName("Player 3");
  player[3].setName("Player 4");

  player[0].setUseJoystick(true);
  player[1].setUseJoystick(false);
  player[2].setUseJoystick(false);
  player[3].setUseJoystick(false);
  
  /*player 1 default keyboard settings*/
  player[0].setKey(KC_LEFT,    PW_KEY_LEFT);
  player[0].setKey(KC_RIGHT,   PW_KEY_RIGHT);
  player[0].setKey(KC_ACCEL,   PW_KEY_UP);
  player[0].setKey(KC_BRAKE,   PW_KEY_DOWN);
  player[0].setKey(KC_WHEELIE, 'a');
  player[0].setKey(KC_JUMP,    's');
  player[0].setKey(KC_RESCUE,  'd');
  player[0].setKey(KC_FIRE,    'f');
  /*player 1 default joystick settings*/
  player[0].setButton(KC_BRAKE,     1);
  player[0].setButton(KC_WHEELIE,   0x20);
  player[0].setButton(KC_JUMP,      0x10);
  player[0].setButton(KC_RESCUE,    0x04);
  player[0].setButton(KC_FIRE,      0x08);
  
  /*player 2 default keyboard settings*/
  player[1].setKey(KC_LEFT,      'j');
  player[1].setKey(KC_RIGHT,     'l');
  player[1].setKey(KC_ACCEL,     'i');
  player[1].setKey(KC_BRAKE,     'k');
  player[1].setKey(KC_WHEELIE,   'q');
  player[1].setKey(KC_JUMP,      'w');
  player[1].setKey(KC_RESCUE,    'e');
  player[1].setKey(KC_FIRE,      'r');
  /*player 2 default joystick settings*/
  player[1].setButton(KC_ACCEL,     0);
  player[1].setButton(KC_BRAKE,     1);
  player[1].setButton(KC_WHEELIE,   2);
  player[1].setButton(KC_JUMP,      3);
  player[1].setButton(KC_RESCUE,    4);
  player[1].setButton(KC_FIRE,      5);

  /*player 3 default joystick settings*/
  player[2].setButton(KC_ACCEL,     0);
  player[2].setButton(KC_BRAKE,     1);
  player[2].setButton(KC_WHEELIE,   2);
  player[2].setButton(KC_JUMP,      3);
  player[2].setButton(KC_RESCUE,    4);
  player[2].setButton(KC_FIRE,      5);
  /*player 4 default joystick settings*/
  player[3].setButton(KC_ACCEL,     0);
  player[3].setButton(KC_BRAKE,     1);
  player[3].setButton(KC_WHEELIE,   2);
  player[3].setButton(KC_JUMP,      3);
  player[3].setButton(KC_RESCUE,    4);
  player[3].setButton(KC_FIRE,      5);
}   // setDefaults


// -----------------------------------------------------------------------------
/*load default configuration file for this platform*/
void Config::loadConfig() {
  loadConfig(filename);
}   // loadConfig

// -----------------------------------------------------------------------------
// Checks for existance of the tuxkart configuration directory. If the 
// directory does not exist, it will be created. Return values:
// 1: config dir exists
// 2: does not exist, but was created
// 0: does not exist, and could not be created.
int Config::CheckAndCreateDir() {
  std::string dirname = getConfigDir();
  ulDir*      u       = ulOpenDir(dirname.c_str());
  if(u) {  // OK, directory exists
    ulCloseDir(u);
    return 1;
  }
  // The directory does not exist, try to create it
  int bError;
#if defined(WIN32) && !defined(__CYGWIN__)
  bError = _mkdir(dirname.c_str()      ) != 0;
#else
  bError = mkdir(dirname.c_str(), 0755) != 0;
#endif
  if(bError) {
    fprintf(stderr, "Couldn't create '%s', config files will not be saved.\n",
	    dirname.c_str());
    return 0;
  } else {
    printf("Config directory '%s' successfully created.\n",dirname.c_str());
    return 2;
  }

}   // CheckAndCreateDir

// -----------------------------------------------------------------------------
/*load configuration values from file*/
void Config::loadConfig(const std::string& filename) {
  std::string temp;
  const lisp::Lisp* root = 0;
  int i;
  int dirExist = CheckAndCreateDir();
  // Check if the config directory exists. If not, exit without an error
  // message, an appropriate message was printed by CheckAndCreateDir
  if (dirExist != 1) return;
  
  try {
    lisp::Parser parser;
    root = parser.parse(filename);
  } catch(std::exception& e) {
    std::cout << "Config file '"<<filename<<"' does not exist, it will be created.\n";
    delete root;
    return;
  }
  
  try {
    const lisp::Lisp* lisp = root->getLisp("tuxkart-config");
    if(!lisp)
      throw std::runtime_error("No tuxkart-config node");

    /*get toggles*/
    lisp->get("fullscreen",       fullscreen);
    lisp->get("sfx" ,             sfx);
    lisp->get("nostartscreen",    noStartScreen);
    lisp->get("music",            music);
    lisp->get("smoke",            smoke);
    lisp->get("displayFPS",       displayFPS);
    lisp->get("singlewindowmenu", singleWindowMenu);
    lisp->get("oldStatusDisplay", oldStatusDisplay);
    lisp->get("herringStyle",     herringStyle);
    lisp->get("disableMagnet",    disableMagnet);
    lisp->get("newKeyboardStyle", newKeyboardStyle);
    lisp->get("useKPH",           useKPH);
    lisp->get("improvedPhysics",  improvedPhysics);

    /*get resolution width/height*/
    lisp->get("width",            width);
    lisp->get("height",           height);

    /*get number of karts*/
    lisp->get("karts", karts);

    /*get player configurations*/
    for(i=0; i<PLAYERS; ++i) {
      temp = "player-";
      temp += i+'1';

      const lisp::Lisp* reader = lisp->getLisp(temp);
      if(!reader) {
        temp = "No " + temp + " node";
        throw std::runtime_error(temp);
      }
      std::string name;
      reader->get("name", name);
      player[i].setName(name);

      int tmp= 0;
      /*get keyboard configuration*/
      reader->get("left", tmp);    player[i].setKey(KC_LEFT, tmp);
      reader->get("right", tmp);   player[i].setKey(KC_RIGHT, tmp);
      reader->get("up", tmp);      player[i].setKey(KC_ACCEL, tmp);
      reader->get("down", tmp);    player[i].setKey(KC_BRAKE, tmp);
      reader->get("wheelie", tmp); player[i].setKey(KC_WHEELIE, tmp);
      reader->get("jump", tmp);    player[i].setKey(KC_JUMP, tmp);
      reader->get("rescue", tmp);  player[i].setKey(KC_RESCUE, tmp);
      reader->get("fire", tmp);    player[i].setKey(KC_FIRE, tmp);

      /*get joystick configuration*/
      reader->get("joy-up", tmp);      player[i].setButton(KC_ACCEL, tmp);
      reader->get("joy-down", tmp);    player[i].setButton(KC_BRAKE, tmp);
      reader->get("joy-wheelie", tmp); player[i].setButton(KC_WHEELIE, tmp);
      reader->get("joy-jump", tmp);    player[i].setButton(KC_JUMP, tmp);
      reader->get("joy-rescue", tmp);  player[i].setButton(KC_RESCUE, tmp);
      reader->get("joy-fire", tmp);    player[i].setButton(KC_FIRE, tmp);
    }
  } catch(std::exception& e) {
    std::cout << "Error while parsing config '" << filename << "': " << e.what() << "\n";
  }
  delete root;
}   // loadConfig


// -----------------------------------------------------------------------------
/*call saveConfig w/ the default filename for this platform*/
void Config::saveConfig() {
  saveConfig(filename);
}   // saveConfig

// -----------------------------------------------------------------------------
/*write settings to config file*/
void Config::saveConfig(const std::string& filename) {
  std::string temp;
  int i;

  int dirExist = CheckAndCreateDir();
  // Check if the config directory exists (again, since it was already checked 
  // when reading the config file - this is done in case that the problem was
  // fixed while tuxkart is running). If the directory does not exist and
  // can not be created, an error message was already printed to stderr,
  // and we can exit here without any further messages.
  if (dirExist == 0) return;

  try {
    lisp::Writer writer(filename);

    writer.beginList("tuxkart-config");
    writer.writeComment("the following options can be set to #t or #f:");
    writer.write("fullscreen\t", fullscreen);
    writer.write("sfx\t",   sfx);
    writer.write("music\t", music);
    writer.write("smoke\t", smoke);
    writer.writeComment("Display frame per seconds");
    writer.write("displayFPS\t", displayFPS);
    writer.writeComment("Use the old, one-window style menu at startup");
    writer.write("singleWindowMenu\t", singleWindowMenu);
    writer.writeComment("Display kart icons in bottom row instead of at the left");
    writer.write("oldStatusDisplay\t", oldStatusDisplay);
    writer.writeComment("Name of the .herring file to use.");
    writer.write("herringStyle\t", herringStyle);
    writer.writeComment("Allow players to disable a magnet");
    writer.write("disableMagnet\t", disableMagnet);
    writer.writeComment("Use of kilometers per hours (km/h) instead of mph");
    writer.write("useKPH\t", useKPH);
    writer.writeComment("With improved physics the gravity on a non-horizontal");
    writer.writeComment("plane will add an accelerating force on the kart");
    writer.write("improvedPhysics\t", improvedPhysics);
    writer.writeComment("#f: old 'digital' style, #t:new analog style, allows for better turning");
    writer.write("newKeyboardStyle\t", newKeyboardStyle);

    writer.writeComment("screen resolution");
    writer.write("width\t", width);
    writer.write("height\t", height);

    writer.writeComment("number of karts. -1 means use all");
    writer.write("karts\t", karts);

    /*write player configurations*/
    for(i=0; i<PLAYERS; ++i) {
      temp = "player ";
      temp += i+'1';
      temp += " settings";
      writer.writeComment(temp);
      temp = "player-";
      temp += i+'1';
      writer.beginList(temp);

      writer.write("name\t", player[i].getName());

      writer.writeComment("keyboard layout");
      writer.write("left\t",    player[i].getKey(KC_LEFT));
      writer.write("right\t",   player[i].getKey(KC_RIGHT));
      writer.write("up\t\t",    player[i].getKey(KC_ACCEL));
      writer.write("down\t",    player[i].getKey(KC_BRAKE));
      writer.write("wheelie\t", player[i].getKey(KC_WHEELIE));
      writer.write("jump\t",    player[i].getKey(KC_JUMP));
      writer.write("rescue\t",  player[i].getKey(KC_RESCUE));
      writer.write("fire\t",    player[i].getKey(KC_FIRE));

      writer.writeComment("joystick layout");
      writer.write("joy-up",        player[i].getButton(KC_ACCEL));
      writer.write("joy-down",      player[i].getButton(KC_BRAKE));
      writer.write("joy-wheelie\t", player[i].getButton(KC_WHEELIE));
      writer.write("joy-jump\t",    player[i].getButton(KC_JUMP));
      writer.write("joy-rescue\t",  player[i].getButton(KC_RESCUE));
      writer.write("joy-fire\t",    player[i].getButton(KC_FIRE));

      writer.endList(temp);
    }   // for i

    writer.endList("tuxkart-config");
  } catch(std::exception& e) {
    std::cout << "Couldn't write config: " << e.what() << "\n";
  }
}   // saveConfig
// -----------------------------------------------------------------------------
std::string Config::GetKeyAsString(int player_index, KartActions control) {
  int key         = player[player_index].getKey(control);
  int useJoystick = player[player_index].IsUsingJoystick();
  std::string ret;
  switch (key) {
    case PW_KEY_RIGHT     : ret="right"  ; break;
    case PW_KEY_LEFT      : ret="left"   ; break;
    case PW_KEY_UP        : ret="up"     ; break;
    case PW_KEY_DOWN      : ret="down"   ; break;
    case PW_KEY_F1        : ret="F1"     ; break;
    case PW_KEY_F2        : ret="F2"     ; break;
    case PW_KEY_F3        : ret="F3"     ; break;
    case PW_KEY_F4        : ret="F4"     ; break;
    case PW_KEY_F5        : ret="F5"     ; break;
    case PW_KEY_F6        : ret="F6"     ; break;
    case PW_KEY_F7        : ret="F7"     ; break;
    case PW_KEY_F8        : ret="F8"     ; break;
    case PW_KEY_F9        : ret="F9"     ; break;
    case PW_KEY_F10       : ret="F10"    ; break;
    case PW_KEY_F11       : ret="F11"    ; break;
    case PW_KEY_F12       : ret="F12"    ; break;
    case PW_KEY_PAGE_UP   : ret="pg up"  ; break;
    case PW_KEY_PAGE_DOWN : ret="pg down"; break;
    case PW_KEY_HOME      : ret="home"   ; break;
    case PW_KEY_END       : ret="end"    ; break;
    case PW_KEY_INSERT    : ret="insert" ; break;
    case 13               : ret="enter"  ; break;
    case 32               : ret="space"  ; break;
    default:                ret=" ";
                            ret[0]=key;
  }
  if (useJoystick) {
    char joyInfo[60];
    if      (control == KC_LEFT)  sprintf(joyInfo, " or stick left");
    else if (control == KC_RIGHT) sprintf(joyInfo, " or stick right");
    else if (control == KC_ACCEL) sprintf(joyInfo, " or stick up");
    else
    {
        const int TARGET_BUTTON = config->player[player_index].getButton(control);
        int button_number = 0;
        while((1 << button_number != TARGET_BUTTON) && (button_number < 16))
            ++button_number;

        if(button_number < 16)
            sprintf(joyInfo, " or joybutton %d", button_number + 1 );
        else
            sprintf(joyInfo, " or unassigned" );

    }
    ret += joyInfo;
  }
  return ret;
}   // GetKeyAsString

/*EOF*/
