/*Copyright (C) 2010-2023 Duncan Deveaux

This file is part of Hikou no mizu.

Hikou no mizu 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.

Hikou no mizu 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 Hikou no mizu.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Configuration.hpp"

#include "Configuration/ConfWriter.hpp"
#include "Configuration/ConfReader.hpp"
#include "Structs/Character.hpp"
#include "Network/Serialization/PeerData.hpp"
#include "Tools/Filesystem.hpp"
#include "Tools/Log.hpp"
#include <sstream>

namespace
{
    namespace Labels
    {
        const std::string displayWidth("display_width");
        const std::string displayHeight("display_height");
        const std::string displayMode("display_mode");
        const std::string vsyncMode("vsync_mode");
        const std::string framerate("framerate");
        const std::string masterVolume("master_volume");
        const std::string musicVolume("music_volume");
        const std::string ambientVolume("ambient_volume");
        const std::string soundVolume("sound_volume");
        const std::string networkAddress("net_address");
        const std::string networkPort("net_port");
        const std::string networkUsername("net_username");
        const std::string networkCharacter("net_character");
        const std::string dynamicCamera("dynamic_camera");
        const std::string cameraShake("camera_shake");
        const std::string showFps("show_fps");
        const std::string showHitboxes("show_hitboxes");
        const std::string locale_("locale");
    }
}

Configuration::Configuration() :
m_vsyncMode(Display::VSyncMode::OnAdaptive),
m_frameRate(60), m_masterVolume(70), m_musicVolume(20), m_ambientVolume(15),
m_soundVolume(100), m_enableCameraShake(true), m_showFps(true),
m_showHitboxes(false), m_cameraMode(FightCameraHandler::CameraMode::Dynamic),
m_networkAddress("game.hikounomizu.org"),
m_networkCharacter("Hikou"), m_networkPort(16026),
m_locale("en")
{
    m_playerKeys[1] = PlayerKeys(); //Player 1 : Default keys

    //Player 2 : Other keys
    m_playerKeys[2] = PlayerKeys(KeyboardKey(SDL_SCANCODE_I),
                                 KeyboardKey(SDL_SCANCODE_O),
                                 KeyboardKey(SDL_SCANCODE_P),
                                 KeyboardKey(SDL_SCANCODE_UP),
                                 KeyboardKey(SDL_SCANCODE_LEFT),
                                 KeyboardKey(SDL_SCANCODE_RIGHT),
                                 KeyboardKey(SDL_SCANCODE_DOWN));
}

void Configuration::load()
{
    std::string cfgDirectory;
    if (!Filesystem::getHNMHome(cfgDirectory))
    {
        Log::err("Configuration loading: "
                 "Could not retrieve the user configuration directory path");
        return;
    }

    //Main configuration
    ConfReader mainCfgReader;
    if (mainCfgReader.load(cfgDirectory + "main.cfg"))
    {
        //Video
        int displayWidth, displayHeight, displayMode, frameRate;
        int vsyncMode;

        if (mainCfgReader.getItem(Labels::displayWidth, displayWidth) &&
            mainCfgReader.getItem(Labels::displayHeight, displayHeight))
        {
            m_displayLayout.width = displayWidth > 0 ? displayWidth : 0;
            m_displayLayout.height = displayHeight > 0 ? displayHeight : 0;
        }

        if (mainCfgReader.getItem(Labels::displayMode, displayMode))
            m_displayLayout.mode = Display::readDisplayMode(displayMode);

        if (mainCfgReader.getItem(Labels::vsyncMode, vsyncMode))
            setVSyncMode(Display::readVSyncMode(vsyncMode));

        if (mainCfgReader.getItem(Labels::framerate, frameRate))
            setFrameRate(frameRate);

        //Audio
        int masterVolume, musicVolume, ambientVolume, soundVolume;

        if (mainCfgReader.getItem(Labels::masterVolume, masterVolume))
            setMasterVolume(masterVolume);

        if (mainCfgReader.getItem(Labels::musicVolume, musicVolume))
            setMusicVolume(musicVolume);

        if (mainCfgReader.getItem(Labels::ambientVolume, ambientVolume))
            setAmbientVolume(ambientVolume);

        if (mainCfgReader.getItem(Labels::soundVolume, soundVolume))
            setSoundVolume(soundVolume);

        //Network
        std::string networkAddress, networkUsername, networkCharacter;
        std::uint16_t networkPort = 16026;

        if (mainCfgReader.getItem(Labels::networkAddress, networkAddress))
            setNetworkAddress(networkAddress);

        if (mainCfgReader.getItem(Labels::networkPort, networkPort))
            setNetworkPort(networkPort);

        if (mainCfgReader.getItem(Labels::networkUsername, networkUsername))
            setNetworkUsername(networkUsername);

        if (mainCfgReader.getItem(Labels::networkCharacter, networkCharacter))
            setNetworkCharacter(networkCharacter);

        //Locale
        std::string localeName;
        if (mainCfgReader.getItem(Labels::locale_, localeName))
            setLocale(localeName);
        else //Could not read locale field: Initialize to user default
            setLocale();

        //Misc
        bool dynamicCamera, enableCameraShake, showFps, showHitboxes;

        if (mainCfgReader.getItem(Labels::dynamicCamera, dynamicCamera))
        {
            setCameraMode(dynamicCamera ? FightCameraHandler::CameraMode::Dynamic
                                        : FightCameraHandler::CameraMode::Static);
        }

        if (mainCfgReader.getItem(Labels::cameraShake, enableCameraShake))
            setEnableCameraShake(enableCameraShake);

        if (mainCfgReader.getItem(Labels::showFps, showFps))
            setShowFps(showFps);

        if (mainCfgReader.getItem(Labels::showHitboxes, showHitboxes))
            setShowHitboxes(showHitboxes);
    }
    else //Configuration file not opened: Initialize locale to user default
        setLocale();

    //Keys configuration
    ConfReader keysCfgReader;

    bool browsing = true;
    std::size_t i = 1;
    while (browsing)
    {
        std::ostringstream keysFilePath;
        keysFilePath << cfgDirectory << "keys/player" << i << "/main.cfg";

        if (keysCfgReader.load(keysFilePath.str()))
        {
            PlayerKeys keys;
            keysCfgReader.getPlayerKeysList(keys);

            setPlayerKeys(i, keys);

            //Joystick keys
            bool browsingJoystick = true;
            std::size_t j = 0;
            while (browsingJoystick)
            {
                std::ostringstream joyKeysFilePath;
                joyKeysFilePath << cfgDirectory << "keys/player" << i
                                                << "/joystick" << j << ".cfg";

                if (keysCfgReader.load(joyKeysFilePath.str()))
                {
                    std::string joystickName;
                    if (keysCfgReader.getItem("joystick", joystickName))
                    {
                        PlayerKeys joyKeys;
                        keysCfgReader.getPlayerKeysList(joyKeys);

                        setPlayerJoystickKeys(i, joystickName, joyKeys);
                    }
                }
                else
                    browsingJoystick = false;

                j++;
            }
        }
        else
            browsing = false;

        i++;
    }
}

void Configuration::save()
{
    std::string cfgDirectory;
    if (!Filesystem::getHNMHome(cfgDirectory))
    {
        Log::err("Configuration saving: "
                 "Could not retrieve the user configuration directory path");
        return;
    }

    //Create directories if needed
    Filesystem::makeDirectory(cfgDirectory);
    Filesystem::makeDirectory(cfgDirectory + "keys");

    //Main configuration
    ConfWriter mainCfgWriter;

    //Video
    mainCfgWriter.addItem(Labels::displayWidth, m_displayLayout.width);
    mainCfgWriter.addItem(Labels::displayHeight, m_displayLayout.height);
    mainCfgWriter.addItem(Labels::displayMode,
        Display::writeDisplayMode(m_displayLayout.mode));
    mainCfgWriter.addItem(Labels::vsyncMode,
        Display::writeVSyncMode(m_vsyncMode));
    mainCfgWriter.addItem(Labels::framerate, m_frameRate);

    //Audio
    mainCfgWriter.addItem(Labels::masterVolume, m_masterVolume);
    mainCfgWriter.addItem(Labels::musicVolume, m_musicVolume);
    mainCfgWriter.addItem(Labels::ambientVolume, m_ambientVolume);
    mainCfgWriter.addItem(Labels::soundVolume, m_soundVolume);

    //Network
    mainCfgWriter.addItem(Labels::networkAddress, m_networkAddress);
    mainCfgWriter.addItem(Labels::networkPort, m_networkPort);
    mainCfgWriter.addItem(Labels::networkUsername, m_networkUsername.get());
    mainCfgWriter.addItem(Labels::networkCharacter, m_networkCharacter);

    //Locale
    mainCfgWriter.addItem(Labels::locale_, m_locale);

    //Misc
    mainCfgWriter.addItem(Labels::dynamicCamera,
        (m_cameraMode == FightCameraHandler::CameraMode::Dynamic));
    mainCfgWriter.addItem(Labels::cameraShake, m_enableCameraShake);
    mainCfgWriter.addItem(Labels::showFps, m_showFps);
    mainCfgWriter.addItem(Labels::showHitboxes, m_showHitboxes);

    mainCfgWriter.writeFile(cfgDirectory + "main.cfg");

    //Keys configuration
    std::map<std::size_t, PlayerKeys>::iterator it;
    for (it = m_playerKeys.begin(); it != m_playerKeys.end(); ++it)
    {
        //Create directory if needed
        std::ostringstream keysDirectoryPath;
        keysDirectoryPath << cfgDirectory << "keys/player" << it->first << "/";
        Filesystem::makeDirectory(keysDirectoryPath.str());

        //Set elements
        ConfWriter keysCfgWriter;
        keysCfgWriter.addPlayerKeysList(it->second);

        std::ostringstream mainKeysFilePath; //Keyboard
        mainKeysFilePath << keysDirectoryPath.str() << "main.cfg";
        keysCfgWriter.writeFile(mainKeysFilePath.str());

        //Joysticks
        std::size_t i = 0;
        std::map<std::string, PlayerKeys> joyKeys = m_playerJoystickKeys[it->first];
        for (std::map<std::string, PlayerKeys>::const_iterator it2 = joyKeys.begin();
             it2 != joyKeys.end();
             ++it2)
        {
            ConfWriter joyKeysCfgWriter;
            joyKeysCfgWriter.addItem("joystick", it2->first);
            joyKeysCfgWriter.addPlayerKeysList(it2->second);

            std::ostringstream joyKeysFilePath; //Keyboard
            joyKeysFilePath << keysDirectoryPath.str() << "joystick" << i << ".cfg";
            joyKeysCfgWriter.writeFile(joyKeysFilePath.str());

            i++;
        }
    }
}

//Get and set methods
    //Video
void Configuration::setDisplayLayout(const Display::DisplayLayout &displayLayout)
{
    m_displayLayout = displayLayout;
}

const Display::DisplayLayout &Configuration::getDisplayLayout() const
{
    return m_displayLayout;
}

void Configuration::setVSyncMode(Display::VSyncMode vsyncMode)
{
    m_vsyncMode = vsyncMode;
}

Display::VSyncMode Configuration::getVSyncMode() const
{
    return m_vsyncMode;
}

void Configuration::setFrameRate(int frameRate)
{
    if (m_frameRate >= 0)
        m_frameRate = frameRate;
}

int Configuration::getFrameRate() const
{
    return m_frameRate;
}

    //Audio
void Configuration::setMasterVolume(int masterVolume)
{
    if (masterVolume >= 0 && masterVolume <= 100)
        m_masterVolume = masterVolume;
}

int Configuration::getMasterVolume() const
{
    return m_masterVolume;
}

void Configuration::setMusicVolume(int musicVolume)
{
    if (musicVolume >= 0 && musicVolume <= 100)
        m_musicVolume = musicVolume;
}

int Configuration::getMusicVolume() const
{
    return m_musicVolume;
}

void Configuration::setAmbientVolume(int ambientVolume)
{
    if (ambientVolume >= 0 && ambientVolume <= 100)
        m_ambientVolume = ambientVolume;
}

int Configuration::getAmbientVolume() const
{
    return m_ambientVolume;
}

void Configuration::setSoundVolume(int soundVolume)
{
    if (soundVolume >= 0 && soundVolume <= 100)
        m_soundVolume = soundVolume;
}

int Configuration::getSoundVolume() const
{
    return m_soundVolume;
}

float Configuration::outputMusicVolume() const
{
    return static_cast<float>(m_masterVolume * m_musicVolume) / 100.f;
}

float Configuration::outputAmbientVolume() const
{
    return static_cast<float>(m_masterVolume * m_ambientVolume) / 100.f;
}

float Configuration::outputSoundVolume() const
{
    return static_cast<float>(m_masterVolume * m_soundVolume) / 100.f;
}

    //Network
void Configuration::setNetworkAddress(const std::string &address)
{
    m_networkAddress = address.substr(0, Configuration::MaxAddressLength);
}

const std::string &Configuration::getNetworkAddress() const
{
    return m_networkAddress;
}

void Configuration::setNetworkPort(std::uint16_t port)
{
    if (port > 0)
        m_networkPort = port;
}

std::uint16_t Configuration::getNetworkPort() const
{
    return m_networkPort;
}

void Configuration::setNetworkUsername(const Utf8::utf8_string &username)
{
    m_networkUsername = username;
    m_networkUsername.clear_after(PeerData::MaxUsernameLength);
}

const Utf8::utf8_string &Configuration::getNetworkUsername() const
{
    return m_networkUsername;
}

void Configuration::setNetworkCharacter(const std::string &character)
{
    if (Character::validateName(Character::fetchNames(), character))
        m_networkCharacter = character;
}

const std::string &Configuration::getNetworkCharacter() const
{
    return m_networkCharacter;
}

    //Locale
void Configuration::setLocale(const std::string &localeName)
{
    m_locale = I18n::findLocale(localeName);
}

void Configuration::setLocale()
{
    const std::string userLocale = I18n::getUserLocale();
    Log::info("Detected user locale: " + userLocale);
    setLocale(userLocale);
}

const std::string &Configuration::getLocale() const
{
    return m_locale;
}

    //Misc
void Configuration::setEnableCameraShake(bool cameraShake)
{
    m_enableCameraShake = cameraShake;
}

bool Configuration::getEnableCameraShake() const
{
    return m_enableCameraShake;
}

void Configuration::setShowFps(bool showFps)
{
    m_showFps = showFps;
}

bool Configuration::getShowFps() const
{
    return m_showFps;
}

void Configuration::setShowHitboxes(bool showHitboxes)
{
    m_showHitboxes = showHitboxes;
}

bool Configuration::getShowHitboxes() const
{
    return m_showHitboxes;
}

void Configuration::setCameraMode(FightCameraHandler::CameraMode cameraMode)
{
    m_cameraMode = cameraMode;
}

FightCameraHandler::CameraMode Configuration::getCameraMode() const
{
    return m_cameraMode;
}

    //Keys binding
void Configuration::setPlayerKeys(std::size_t ix, const PlayerKeys &playerKeys)
{
    m_playerKeys[ix] = playerKeys;
}

bool Configuration::getPlayerKeys(std::size_t ix, PlayerKeys &target) const
{
    std::map<std::size_t, PlayerKeys>::const_iterator it =
        m_playerKeys.find(ix);

    if (it != m_playerKeys.end())
    {
        target = it->second;
        return true;
    }

    return false;
}

void Configuration::setPlayerJoystickKeys(std::size_t ix,
                                          const std::string &joystickName,
                                          const PlayerKeys &playerKeys)
{
    m_playerJoystickKeys[ix][joystickName] = playerKeys;
}

bool Configuration::getPlayerJoystickKeys(std::size_t ix,
                                          const std::string &joystickName,
                                          PlayerKeys &target) const
{
    std::map<std::size_t, std::map<std::string, PlayerKeys>>::const_iterator
        it = m_playerJoystickKeys.find(ix);

    if (it != m_playerJoystickKeys.end())
    {
        std::map<std::string, PlayerKeys>::const_iterator it2 =
            it->second.find(joystickName);
        if (it2 != it->second.end())
        {
            target = it2->second;
            return true;
        }
    }

    return false;
}

void Configuration::updatePlayerDevices(int removedDevice)
{
    std::map<std::size_t, int>::iterator it;
    for (it = m_playerDevices.begin(); it != m_playerDevices.end(); ++it)
    {
        if (it->second == removedDevice)
            it->second = DEVICE_KEYBOARD;
    }
}

void Configuration::setPlayerDevice(std::size_t ix, int deviceID)
{
    m_playerDevices[ix] = deviceID;
}

int Configuration::getPlayerDevice(std::size_t ix) const
{
    std::map<std::size_t, int>::const_iterator it = m_playerDevices.find(ix);

    if (it != m_playerDevices.end())
        return it->second;

    return DEVICE_KEYBOARD;
}
