//  SPDX-License-Identifier: MIT
//
//  EmulationStation Desktop Edition
//  InputConfig.cpp
//
//  Input device configuration functions.
//

#include "InputConfig.h"

#include "Log.h"

#include <pugixml.hpp>

// Some utility functions.
std::string inputTypeToString(InputType type)
{
    switch (type) {
    case TYPE_AXIS:
        return "axis";
    case TYPE_BUTTON:
        return "button";
    case TYPE_HAT:
        return "hat";
    case TYPE_KEY:
        return "key";
    case TYPE_CEC_BUTTON:
        return "cec-button";
    default:
        return "error";
    }
}

InputType stringToInputType(const std::string& type)
{
    if (type == "axis")
        return TYPE_AXIS;
    if (type == "button")
        return TYPE_BUTTON;
    if (type == "hat")
        return TYPE_HAT;
    if (type == "key")
        return TYPE_KEY;
    if (type == "cec-button")
        return TYPE_CEC_BUTTON;
    return TYPE_COUNT;
}

std::string toLower(std::string str)
{
    for (unsigned int i = 0; i < str.length(); i++)
        str[i] = static_cast<char>(tolower(str[i]));

    return str;
}
// End of utility functions.

InputConfig::InputConfig(
        int deviceId,
        const std::string& deviceName,
        const std::string& deviceGUID)
        : mDeviceId(deviceId),
        mDeviceName(deviceName),
        mDeviceGUID(deviceGUID),
        mDefaultConfigFlag(false)
{
}

void InputConfig::clear()
{
    mNameMap.clear();
}

bool InputConfig::isConfigured()
{
    return mNameMap.size() > 0;
}

void InputConfig::mapInput(const std::string& name, Input input)
{
    mNameMap[toLower(name)] = input;
}

void InputConfig::unmapInput(const std::string& name)
{
    auto it = mNameMap.find(toLower(name));
    if (it != mNameMap.cend())
        mNameMap.erase(it);
}

bool InputConfig::getInputByName(const std::string& name, Input* result)
{
    auto it = mNameMap.find(toLower(name));
    if (it != mNameMap.cend()) {
        *result = it->second;
        return true;
    }
    return false;
}

int InputConfig::getInputIDByName(const std::string& name)
{
    auto it = mNameMap.find(toLower(name));
    if (it != mNameMap.cend()) {
        return it->second.id;
    }
    return -1;
}

bool InputConfig::isMappedTo(const std::string& name, Input input)
{
    Input comp;
    if (!getInputByName(name, &comp))
        return false;

    if (comp.configured && comp.type == input.type && comp.id == input.id) {
        if (comp.type == TYPE_HAT)
            return (input.value == 0 || input.value & comp.value);

        if (comp.type == TYPE_AXIS)
            return input.value == 0 || comp.value == input.value;
        else
            return true;
    }
    return false;
}

bool InputConfig::isMappedLike(const std::string& name, Input input)
{
    if (name == "left") {
        return isMappedTo("left", input) || isMappedTo("leftanalogleft", input) ||
                isMappedTo("rightanalogleft", input);
    }
    else if (name == "right") {
        return isMappedTo("right", input) || isMappedTo("leftanalogright", input) ||
                isMappedTo("rightanalogright", input);
    }
    else if (name == "up") {
        return isMappedTo("up", input) || isMappedTo("leftanalogup", input) ||
                isMappedTo("rightanalogup", input);
    }
    else if (name == "down") {
        return isMappedTo("down", input) || isMappedTo("leftanalogdown", input) ||
                isMappedTo("rightanalogdown", input);
    }
    else if (name == "leftshoulder") {
        return isMappedTo("leftshoulder", input) || isMappedTo("pageup", input);
    }
    else if (name == "rightshoulder") {
        return isMappedTo("rightshoulder", input) || isMappedTo("pagedown", input);
    }
    else if (name == "lefttrigger") {
        return isMappedTo("lefttrigger", input) || isMappedTo("home", input);
    }
    else if (name == "righttrigger") {
        return isMappedTo("righttrigger", input) || isMappedTo("end", input);
    }
    return isMappedTo(name, input);
}

std::vector<std::string> InputConfig::getMappedTo(Input input)
{
    std::vector<std::string> maps;

    typedef std::map<std::string, Input>::const_iterator it_type;
    for (it_type iterator = mNameMap.cbegin(); iterator != mNameMap.cend(); iterator++) {
        Input chk = iterator->second;

        if (!chk.configured)
            continue;

        if (chk.device == input.device && chk.type == input.type && chk.id == input.id) {
            if (chk.type == TYPE_HAT) {
                if (input.value == 0 || input.value & chk.value)
                    maps.push_back(iterator->first);
                continue;
            }

            if (input.type == TYPE_AXIS) {
                if (input.value == 0 || chk.value == input.value)
                    maps.push_back(iterator->first);
            }
            else {
                maps.push_back(iterator->first);
            }
        }
    }
    return maps;
}

void InputConfig::loadFromXML(pugi::xml_node& node)
{
    clear();

    for (pugi::xml_node input = node.child("input"); input; input = input.next_sibling("input")) {
        std::string name = input.attribute("name").as_string();
        std::string type = input.attribute("type").as_string();
        InputType typeEnum = stringToInputType(type);

        if (typeEnum == TYPE_COUNT) {
            LOG(LogError) << "InputConfig load error - input of type \"" << type <<
                    "\" is invalid! Skipping input \"" << name << "\".\n";
            continue;
        }

        int id = input.attribute("id").as_int();
        int value = input.attribute("value").as_int();

        if (value == 0) {
            LOG(LogWarning) << "InputConfig value is 0 for " <<
                    type << " " << id << "!\n";
        }

        mNameMap[toLower(name)] = Input(mDeviceId, typeEnum, id, value, true);
    }
}

void InputConfig::writeToXML(pugi::xml_node& parent)
{
    pugi::xml_node cfg = parent.append_child("inputConfig");

    if (mDeviceId == DEVICE_KEYBOARD) {
        cfg.append_attribute("type") = "keyboard";
        cfg.append_attribute("deviceName") = "Keyboard";
    }
    else if (mDeviceId == DEVICE_CEC) {
        cfg.append_attribute("type") = "cec";
        cfg.append_attribute("deviceName") = "CEC";
    }
    else {
        cfg.append_attribute("type") = "joystick";
        cfg.append_attribute("deviceName") = mDeviceName.c_str();
    }

    cfg.append_attribute("deviceGUID") = mDeviceGUID.c_str();

    typedef std::map<std::string, Input>::const_iterator it_type;
    for (it_type iterator = mNameMap.cbegin(); iterator != mNameMap.cend(); iterator++) {
        if (!iterator->second.configured)
            continue;

        pugi::xml_node input = cfg.append_child("input");
        input.append_attribute("name") = iterator->first.c_str();
        input.append_attribute("type") = inputTypeToString(iterator->second.type).c_str();
        input.append_attribute("id").set_value(iterator->second.id);
        input.append_attribute("value").set_value(iterator->second.value);
    }
}