//
//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2006-2013 SuperTuxKart-Team
//  Modelled after Supertux's configfile.h
//
//  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 3
//  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.

#ifndef HEADER_USER_CONFIG_HPP
#define HEADER_USER_CONFIG_HPP

/* The following config versions are currently used:
   0: the 0.2 release config file, without config-version number
      (so that defaults to 0)
   1: Removed singleWindowMenu, newKeyboardStyle, oldStatusDisplay,
      added config-version number
      Version 1 can read version 0 without any problems, so
      SUPPORTED_CONFIG_VERSION is 0.
   2: Changed to SDL keyboard bindings
   3: Added username (userid was used for ALL players)
   4: Added username per player
   5: Enabled jumping, which might cause a problem with old
      config files (which have an unused entry for jump defined
      --> if a kart control for (say) player 2 uses the same key as
      jump for player 1, this problem is not noticed in 0.3, but will
      cause an undefined game action now
   6: Added stick configurations.
*/
#include <string>
#include <map>
#include <vector>
#include <fstream>

#include <irrString.h>
using irr::core::stringc;
using irr::core::stringw;

#include "../utils/constants.h"
#include "../utils/no_copy.h"
#include "../utils/ptr_vector.h"
#include "../utils/time.h"



class XMLNode;
class UTFWriter;

/**
 *  The base of a set of small utilities to enable quickly adding/removing
 *  stuff to/from config painlessly.
 */
class UserConfigParam
{
    friend class GroupUserConfigParam;
protected:
    std::string m_param_name;
    std::string m_comment;
public:
    virtual     ~UserConfigParam();
    virtual void write(std::ofstream & stream) const = 0;
    virtual void writeInner(std::ofstream & stream, int level = 0) const;
    virtual void findYourDataInAChildOf(const XMLNode* node) = 0;
    virtual void findYourDataInAnAttributeOf(const XMLNode* node) = 0;
    virtual irr::core::stringc toString() const = 0;
};   // UserConfigParam

// ============================================================================
class GroupUserConfigParam : public UserConfigParam
{
    std::vector<UserConfigParam*> m_attributes;
    std::vector<GroupUserConfigParam*> m_children;
public:
    GroupUserConfigParam(const char* name, const char* comment=NULL);
    GroupUserConfigParam(const char* param_name,
                       GroupUserConfigParam* group,
                       const char* comment = NULL);
    void write(std::ofstream& stream) const;
    void writeInner(std::ofstream& stream, int level = 0) const;
    void findYourDataInAChildOf(const XMLNode* node);
    void findYourDataInAnAttributeOf(const XMLNode* node);

    void addChild(UserConfigParam* child);
    void addChild(GroupUserConfigParam* child);
    void clearChildren();

    irr::core::stringc toString() const;
};   // GroupUserConfigParam

// ============================================================================
template<typename T, typename U>
class ListUserConfigParam : public UserConfigParam
{
    std::vector<T> m_elements;

public:
    ListUserConfigParam(const char* param_name,
                         const char* comment = NULL);
    ListUserConfigParam(const char* param_name,
                         const char* comment,
                         int nb_elts,
                         ...);
    ListUserConfigParam(const char* param_name,
                         GroupUserConfigParam* group,
                         const char* comment = NULL);
    ListUserConfigParam(const char* param_name,
                         GroupUserConfigParam* group,
                         const char* comment,
                         int nb_elts,
                         ...);

    void write(std::ofstream& stream) const;
    void findYourDataInAChildOf(const XMLNode* node);
    void findYourDataInAnAttributeOf(const XMLNode* node);

    void addElement(T element);

    irr::core::stringc toString() const;

    operator std::vector<T>() const
            { return m_elements; }
    float& operator=(const std::vector<T>& v)
            { m_elements = std::vector<T>(v); return m_elements; }
    float& operator=(const ListUserConfigParam& v)
            { m_elements = std::vector<T>(v); return m_elements; }
};   // ListUserConfigParam
typedef ListUserConfigParam<std::string, const char*>    StringListUserConfigParam;

// ============================================================================
class IntUserConfigParam : public UserConfigParam
{
    int m_value;
    int m_default_value;

public:

    IntUserConfigParam(int default_value, const char* param_name,
                       const char* comment = NULL);
    IntUserConfigParam(int default_value, const char* param_name,
                       GroupUserConfigParam* group,
                       const char* comment = NULL);

    void write(std::ofstream& stream) const;
    void findYourDataInAChildOf(const XMLNode* node);
    void findYourDataInAnAttributeOf(const XMLNode* node);

    irr::core::stringc toString() const;
    void revertToDefaults()      { m_value = m_default_value;        }

    operator int() const         { return m_value;                   }
    int& operator++(int dummy)   { m_value++; return m_value;        }
    int& operator=(const int& v) { m_value = v; return m_value;      }
    int& operator=(const IntUserConfigParam& v)
                                 { m_value = (int)v; return m_value; }
};   // IntUserConfigParam

// ============================================================================
/*
class TimeUserConfigParam : public UserConfigParam
{
    StkTime::TimeType m_value;
    StkTime::TimeType m_default_value;

public:

    TimeUserConfigParam(StkTime::TimeType default_value, const char* param_name,
                        const char* comment = NULL);
    TimeUserConfigParam(StkTime::TimeType default_value, const char* param_name,
                        GroupUserConfigParam* group, const char* comment=NULL);

    void write(std::ofstream& stream) const;
    void findYourDataInAChildOf(const XMLNode* node);
    void findYourDataInAnAttributeOf(const XMLNode* node);

    irr::core::stringc toString() const;
    void revertToDefaults()               { m_value = m_default_value;        }
    operator StkTime::TimeType() const       { return m_value;                   }
    StkTime::TimeType& operator=(const StkTime::TimeType& v)
                                          { m_value = v; return m_value;      }
    StkTime::TimeType& operator=(const TimeUserConfigParam& v)
                                          { m_value = (int)v; return m_value; }
};   // TimeUserConfigParam

// ============================================================================
*/
class StringUserConfigParam : public UserConfigParam
{
    std::string m_value;
    std::string m_default_value;

public:

    StringUserConfigParam(const char* default_value, const char* param_name,
                          const char* comment = NULL);
    StringUserConfigParam(const char* default_value, const char* param_name,
                          GroupUserConfigParam* group,
                          const char* comment = NULL);

    void write(std::ofstream& stream) const;
    void findYourDataInAChildOf(const XMLNode* node);
    void findYourDataInAnAttributeOf(const XMLNode* node);

    void revertToDefaults() { m_value = m_default_value; }

    std::string getDefaultValue() const { return m_default_value; }

    irr::core::stringc toString() const { return m_value.c_str(); }

    operator std::string() const  { return m_value; }
    std::string& operator=(const std::string& v)
                                  { m_value = v; return m_value; }
    std::string& operator=(const StringUserConfigParam& v)
                                  { m_value = (std::string)v; return m_value; }

    const char* c_str() const { return m_value.c_str(); }
};   // StringUserConfigParam

// ============================================================================
class BoolUserConfigParam : public UserConfigParam
{
    bool m_value;
    bool m_default_value;

public:
    BoolUserConfigParam(bool default_value, const char* param_name,
                        const char* comment = NULL);
    BoolUserConfigParam(bool default_value, const char* param_name,
                        GroupUserConfigParam* group,
                        const char* comment = NULL);
    void write(std::ofstream& stream) const;
    void findYourDataInAChildOf(const XMLNode* node);
    void findYourDataInAnAttributeOf(const XMLNode* node);

    irr::core::stringc toString() const;
    void revertToDefaults() { m_value = m_default_value; }

    operator bool() const { return m_value; }
    bool& operator=(const bool& v) { m_value = v; return m_value; }
    bool& operator=(const BoolUserConfigParam& v)
                              { m_value = (bool)v; return m_value; }
};   // BoolUserConfigParam

// ============================================================================
class FloatUserConfigParam : public UserConfigParam
{
    float m_value;
    float m_default_value;

public:
    FloatUserConfigParam(float default_value, const char* param_name,
                         const char* comment = NULL);
    FloatUserConfigParam(float default_value, const char* param_name,
                         GroupUserConfigParam* group,
                         const char* comment = NULL);

    void write(std::ofstream& stream) const;
    void findYourDataInAChildOf(const XMLNode* node);
    void findYourDataInAnAttributeOf(const XMLNode* node);

    irr::core::stringc toString() const;
    void revertToDefaults() { m_value = m_default_value; }

    operator float() const { return m_value; }
    float& operator=(const float& v) { m_value = v; return m_value; }
    float& operator=(const FloatUserConfigParam& v)
                              { m_value = (float)v; return m_value; }
};   // FloatUserConfigParam

// ============================================================================
enum AnimType {ANIMS_NONE         = 0,
               ANIMS_PLAYERS_ONLY = 1,
               ANIMS_ALL          = 2 };

/** Using X-macros for setting-possible values is not very pretty, but it's a
 *  no-maintenance case :
 *  when you want to add a new parameter, just add one signle line below and
 *  everything else automagically works (including default value, saving to
 *  file, loading from file)
 */

#ifndef PARAM_PREFIX
#define PARAM_PREFIX extern
#endif

#ifndef PARAM_DEFAULT
#define PARAM_DEFAULT(X)
#endif

// ============================================================================
/** \brief Contains all parameters that are stored in the user's config file
 *  \ingroup config
 */
namespace UserConfigParams
{




    PARAM_PREFIX GroupUserConfigParam        m_graphics_group
            PARAM_DEFAULT( GroupUserConfigParam("Graphics", "Graphic Settings") );

    //PARAM_PREFIX BoolUserConfigParam         m_fullscreen
    //        PARAM_DEFAULT( BoolUserConfigParam(true, "fullscreen_on", &m_graphics_group,
    //        "Whether fullscreen enabled or not (true or false)") );

    // ---- Video
    PARAM_PREFIX GroupUserConfigParam        m_video_group
        PARAM_DEFAULT( GroupUserConfigParam("Video", "Video Settings") );

    PARAM_PREFIX IntUserConfigParam         m_width
            PARAM_DEFAULT(  IntUserConfigParam(1024, "width", &m_video_group,
                                            "Screen/window width in pixels") );
    PARAM_PREFIX IntUserConfigParam         m_height
            PARAM_DEFAULT(  IntUserConfigParam(768, "height", &m_video_group,
                                           "Screen/window height in pixels") );
    PARAM_PREFIX BoolUserConfigParam        m_fullscreen
            PARAM_DEFAULT(  BoolUserConfigParam(false, "fullscreen",
                                                &m_video_group) );
    PARAM_PREFIX IntUserConfigParam         m_prev_width
            PARAM_DEFAULT(  IntUserConfigParam(1024, "prev_width",
                            &m_video_group, "Previous screen/window width") );
    PARAM_PREFIX IntUserConfigParam         m_prev_height
            PARAM_DEFAULT(  IntUserConfigParam(768, "prev_height",
                            &m_video_group,"Previous screen/window height") );
    PARAM_PREFIX BoolUserConfigParam        m_prev_fullscreen
            PARAM_DEFAULT(  BoolUserConfigParam(false, "prev_fullscreen",
                            &m_video_group) );
    PARAM_PREFIX BoolUserConfigParam        m_remember_window_location
            PARAM_DEFAULT(  BoolUserConfigParam(false, "remember_window_location",
                            &m_video_group) );
    PARAM_PREFIX IntUserConfigParam         m_window_x
            PARAM_DEFAULT(  IntUserConfigParam(-1, "window_x",
                            &m_video_group,"If remember_window_location is true") );
    PARAM_PREFIX IntUserConfigParam         m_window_y
            PARAM_DEFAULT(  IntUserConfigParam(-1, "window_y",
                            &m_video_group,"If remember_window_location is true") );
    // ---- - - -

    PARAM_PREFIX BoolUserConfigParam        m_display_fps
            PARAM_DEFAULT(  BoolUserConfigParam(false, "show_fps",
                            &m_video_group, "Display frame per seconds") );
    PARAM_PREFIX IntUserConfigParam         m_max_fps
            PARAM_DEFAULT(  IntUserConfigParam(120, "max_fps",
                       &m_video_group, "Maximum fps, should be at least 60") );

    PARAM_PREFIX BoolUserConfigParam        m_texture_compression
        PARAM_DEFAULT(BoolUserConfigParam(true, "enable_texture_compression",
        &m_video_group, "Enable Texture Compression"));
    /** This is a bit flag: bit 0: enabled (1) or disabled(0).
     *  Bit 1: setting done by default(0), or by user choice (2). This allows
     *  to e.g. disable h.d. textures on hd3000 as default, but still allow the
     *  user to enable it. */

     PARAM_PREFIX IntUserConfigParam        m_high_definition_textures
        PARAM_DEFAULT(IntUserConfigParam(1, "enable_high_definition_textures",
        &m_video_group, "Enable high definition textures. Bit flag: "
                        "bit 0 = enabled/disabled; bit 1 = set by user/set as default"));

    PARAM_PREFIX int  m_verbosity         PARAM_DEFAULT( 0 );
        PARAM_PREFIX BoolUserConfigParam        m_force_legacy_device
        PARAM_DEFAULT(BoolUserConfigParam(false, "force_legacy_device",
        &m_video_group, "Force OpenGL 2 context, even if OpenGL 3 is available."));

        /** Some constants to bitmask to enable various messages to be printed. */
    enum { LOG_MEMORY  = 0x0001,
           LOG_GUI     = 0x0002,
           LOG_ADDONS  = 0x0004,
           LOG_MISC    = 0x0008,
           LOG_FLYABLE = 0x0010,
           LOG_ALL     = 0xffff };

    /** Returns true if the user want additional messages for memory usage. */
    bool   logMemory();
    /** Returns true if the user want additional messages related to GUI. */
    bool   logGUI   ();
    /** Returns true if the user want additional messages related to addons. */
    bool   logAddons();
    /** Returns true if the user want additional debug info for flyables */
    bool   logFlyable();
    /** Returns true if the user want additional messages for general items. */
    bool   logMisc  ();

    // ---- Graphic Quality
    PARAM_PREFIX GroupUserConfigParam        m_graphics_quality
            PARAM_DEFAULT( GroupUserConfigParam("GFX",
                                                "Graphics Quality Settings") );


    PARAM_PREFIX BoolUserConfigParam         m_vsync
            PARAM_DEFAULT( BoolUserConfigParam(false, "vsync",
                           &m_graphics_quality,
                           "Whether vertical sync is enabled") );

#if defined(WIN32) && !defined(__CYGWIN__)
    // No console on windows
#  define CONSOLE_DEFAULT false
#else
#  define CONSOLE_DEFAULT true
#endif




    PARAM_PREFIX StringUserConfigParam      m_skin_file
            PARAM_DEFAULT(  StringUserConfigParam("Default.lsskin", "skin_file",
                                                  "Name of the skin to use") );
        // No console on windows
    PARAM_PREFIX BoolUserConfigParam        m_log_errors_to_console
            PARAM_DEFAULT(  BoolUserConfigParam(
            CONSOLE_DEFAULT, "log_errors", "Enable logging to console.") );

                // TODO : is this used with new code? does it still work?
    PARAM_PREFIX BoolUserConfigParam        m_crashed
            PARAM_DEFAULT(  BoolUserConfigParam(false, "crashed") );

    PARAM_PREFIX GroupUserConfigParam       m_hw_report_group
            PARAM_DEFAULT( GroupUserConfigParam("HWReport",
                                          "Everything related to hardware configuration.") );

    PARAM_PREFIX IntUserConfigParam        m_last_hw_report_version
            PARAM_DEFAULT(  IntUserConfigParam(0, "report-version", &m_hw_report_group,
                                                  "Version of hardware report "
                                                  "that was reported last") );
    PARAM_PREFIX IntUserConfigParam        m_random_identifier
            PARAM_DEFAULT(  IntUserConfigParam(0, "random-identifier", &m_hw_report_group,
                                                  "A random number to avoid duplicated reports.") );
        /** True if graphical profiler should be displayed */
    PARAM_PREFIX bool m_profiler_enabled  PARAM_DEFAULT( false );


}
#undef PARAM_PREFIX
#undef PARAM_SUFFIX

// ============================================================================
/**
  * \brief Class for managing general STK user configuration data.
  * \ingroup config
  */
class UserConfig : public NoCopy
{
private:

    /** Filename of the user config file. */
    std::string        m_filename;
    irr::core::stringw m_warning;

    static const int m_current_config_version;

public:
          /** Create the user config object; does not actually load it,
           *  UserConfig::loadConfig needs to be called. */
          UserConfig();
         ~UserConfig();

    bool  loadConfig();
    void  saveConfig();

    const irr::core::stringw& getWarning()        { return m_warning;  }
    void  resetWarning()                          { m_warning="";      }
    void  setWarning(irr::core::stringw& warning) { m_warning=warning; }

};   // UserConfig


extern UserConfig *user_config;

#endif

/*EOF*/
