/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2020 Jon Evans <jon@craftyjon.com>
 * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

#ifndef _JSON_SETTINGS_H
#define _JSON_SETTINGS_H

#include <nlohmann/json.hpp>
#include <utility>
#include <wx/string.h>

#include <core/optional.h>

class wxConfigBase;
class NESTED_SETTINGS;
class PARAM_BASE;

enum class SETTINGS_LOC {
    USER,       ///< The main config directory (e.g. ~/.config/kicad/)
    PROJECT,    ///< The settings directory inside a project folder
    COLORS,     ///< The color scheme directory (e.g. ~/.config/kicad/colors/)
    NESTED,     ///< Not stored in a file, but inside another JSON_SETTINGS
};


class JSON_SETTINGS : public nlohmann::json
{
public:
    JSON_SETTINGS( const std::string& aFilename, SETTINGS_LOC aLocation, int aSchemaVersion,
                   nlohmann::json aDefaultJson = nlohmann::json( {} ) ) :
            JSON_SETTINGS( aFilename, aLocation, aSchemaVersion, true, true, aDefaultJson ) {}

    JSON_SETTINGS( const std::string& aFilename, SETTINGS_LOC aLocation, int aSchemaVersion,
                   bool aCreateIfMissing, bool aWriteFile,
                   nlohmann::json aDefaultJson = nlohmann::json( {} ) );

    virtual ~JSON_SETTINGS();

    std::string GetFilename() const { return m_filename; }

    SETTINGS_LOC GetLocation() const { return m_location; }

    void SetLegacyFilename( const std::string& aFilename ) { m_legacy_filename = aFilename; }

    /**
     * Updates the parameters of this object based on the current JSON document contents
     */
    virtual void Load();

    /**
     * Stores the current parameters into the JSON document represented by this object
     * Note: this doesn't do any writing to disk; that's handled by SETTINGS_MANAGER
     */
    virtual void Store();

    /**
     * Loads the backing file from disk and then calls Load()
     * @param aDirectory is the path to the file
     */
    virtual void LoadFromFile( const std::string& aDirectory );

    /**
     * Calls Store() and then writes the contents of the JSON document to a file
     * @param aDirectory is the directory to save to, including trailing separator
     */
    virtual void SaveToFile( const std::string& aDirectory );

    /**
     * Resets all parameters to default values.  Does NOT write to file or update underlying JSON.
     */
    void ResetToDefaults();

    /**
     * Fetches a JSON object that is a subset of this JSON_SETTINGS object, using a path of the
     * form "key1.key2.key3" to refer to nested objects.
     * @param aPath is a string containing one or more keys separated by '.'
     * @return a JSON object from within this one
     */
    OPT<nlohmann::json> GetJson( std::string aPath ) const;

    /**
     * Fetches a value from within the JSON document.
     * Will return an empty optional if the value is not found or a mismatching type.
     * @tparam ValueType is the type to cast to
     * @param aPath is the path within the document to retrieve
     * @return a value from within this document
     */
    template<typename ValueType>
    OPT<ValueType> Get( std::string aPath ) const
    {
        if( OPT<nlohmann::json> ret = GetJson( std::move( aPath ) ) )
        {
            try
            {
                return ret->get<ValueType>();
            }
            catch( ... )
            {
            }
        }

        return NULLOPT;
    }

    /**
     * Stores a value into the JSON document
     * Will throw an exception if ValueType isn't something that the library can handle
     * @tparam ValueType is the type to store
     * @param aPath is a path to store in the form "key1.key2.key3"
     * @param aVal is the value to store
     */
    template<typename ValueType>
    void Set( std::string aPath, ValueType aVal )
    {
        ( *this )[PointerFromString( std::move( aPath ) ) ] = aVal;
    }

    /**
     * Migrates the schema of this settings from the version in the file to the latest version
     *
     * Schema migration doesn't need to be used every time a setting is added!  This is intended
     * to be more of an "escape hatch" in the event that we decide to move settings around or make
     * other changes to a settings file format that can't simply be handled by loading a new default
     *
     * @return true if migration was successful
     */
    virtual bool Migrate();

    /**
     * Migrates from wxConfig to JSON-based configuration.  Should be implemented by any subclasses
     * of JSON_SETTINGS that map to a legacy (wxConfig-based) config file.
     * @param aLegacyConfig is a wxConfigBase holding a loaded configuration to migrate
     * @return true if migration was successful
     */
    virtual bool MigrateFromLegacy( wxConfigBase* aLegacyConfig );

    /**
     * Transfers ownership of a given NESTED_SETTINGS to this object.
     * Can be used to construct a NESTED_SETTINGS without the parent object needing to know about
     * the implementation of the nested object;
     *
     * @param aSettings is the settings object to take ownership of
     * @param aTarget is a pointer to update to the passed in settings
     */
    void AddNestedSettings( NESTED_SETTINGS* aSettings );

    /**
     * Saves and frees a nested settings object, if it exists within this one
     * @param aSettings is a pointer to a NESTED_SETTINGS that has already been added to this one
     */
    void ReleaseNestedSettings( NESTED_SETTINGS* aSettings );

    /**
    * Builds a JSON pointer based on a given string
    * @param aPath is the path in the form "key1.key2.key3"
    * @return a JSON pointer that can be used to index into a JSON object
    */
    static nlohmann::json::json_pointer PointerFromString( std::string aPath );

protected:

    /**
    * Translates a legacy wxConfig value to a given JSON pointer value
    * @tparam ValueType is the basic type of the value
    * @param aConfig is the legacy config to read from
    * @param aKey is the key (within the current path) to read
    * @param aDest is a string that will form a JSON pointer (key1.key2.key3) to write to
    */
    template<typename ValueType>
    bool fromLegacy( wxConfigBase* aConfig, const std::string& aKey, const std::string& aDest );

    /**
     * Translates a legacy wxConfig string value to a given JSON pointer value
     * @param aConfig is the legacy config to read from
     * @param aKey is the key (within the current path) to read
     * @param aDest is a string that will form a JSON pointer (key1.key2.key3) to write to
     */
    bool fromLegacyString( wxConfigBase* aConfig, const std::string& aKey,
                           const std::string& aDest );

    /**
    * Translates a legacy COLOR4D stored in a wxConfig string to a given JSON pointer value
    * @param aConfig is the legacy config to read from
    * @param aKey is the key (within the current path) to read
    * @param aDest is a string that will form a JSON pointer (key1.key2.key3) to write to
    */
    bool fromLegacyColor( wxConfigBase* aConfig, const std::string& aKey,
                          const std::string& aDest );

    /// The filename (not including path) of this settings file
    std::string m_filename;

    /// The filename of the wxConfig legacy file (if different from m_filename)
    std::string m_legacy_filename;

    /// The location of this settings file (@see SETTINGS_LOC)
    SETTINGS_LOC m_location;

    /// The list of parameters (owned by this object)
    std::vector<PARAM_BASE*> m_params;

    /// Nested settings files that live inside this one, if any
    std::vector<NESTED_SETTINGS*> m_nested_settings;

    /// Whether or not the backing store file should be created it if doesn't exist
    bool m_createIfMissing;

    /// Whether or not the backing store file should be written
    bool m_writeFile;

    /// Version of this settings schema.
    int m_schemaVersion;

    /// A list of JSON pointers that are preserved during a read-update-write to disk
    std::vector<nlohmann::json::json_pointer> m_preserved_paths;
};

// Specializations to allow conversion between wxString and std::string via JSON_SETTINGS API

template<> OPT<wxString> JSON_SETTINGS::Get( std::string aPath ) const;

template<> void JSON_SETTINGS::Set<wxString>( std::string aPath, wxString aVal );

// Specializations to allow directly reading/writing wxStrings from JSON

void to_json( nlohmann::json& aJson, const wxString& aString );

void from_json( const nlohmann::json& aJson, wxString& aString );

#endif
