//
//  Lol Engine
//
//  Copyright © 2010—2015 Benjamin Litzelmann
//            © 2017—2018 Sam Hocevar <sam@hocevar.net>
//
//  Lol Engine is free software. It comes without any warranty, to
//  the extent permitted by applicable law. You can redistribute it
//  and/or modify it under the terms of the Do What the Fuck You Want
//  to Public License, Version 2, as published by the WTFPL Task Force.
//  See http://www.wtfpl.net/ for more details.
//

#include <lol/engine-internal.h>

#include <string>

namespace lol
{

///////////////////////////////////////////////////////////////////////////////
// KeyBinding

void KeyBinding::Bind(const std::string& device_name, const std::string& key_name)
{
    const InputDevice* device = InputDevice::Get(device_name);

    if (!device)
    {
        msg::warn("trying to bind key to nonexistent input device %s\n",
                  device_name.c_str());
        return;
    }

    int keyindex = device->GetKeyIndex(key_name);
    if (keyindex < 0)
    {
        msg::warn("trying to bind nonexistent key %s.%s\n",
                  device_name.c_str(), key_name.c_str());
        return;
    }

    m_keybindings.push(device, keyindex);
}

bool KeyBinding::Unbind(const std::string& device_name, const std::string& key_name)
{
    for (int i = 0; i < m_keybindings.count(); ++i)
    {
        if (m_keybindings[i].m1->GetName() == device_name)
        {
            if (m_keybindings[i].m2 == m_keybindings[i].m1->GetKeyIndex(key_name))
            {
                m_keybindings.remove(i);
                return true;
            }
        }
    }
    return false;
}

void KeyBinding::ClearBindings()
{
    m_keybindings.clear();
}

///////////////////////////////////////////////////////////////////////////////
// AxisBinding

void AxisBinding::Bind(const std::string& device_name, const std::string& axis_name)
{
    const InputDevice* device = InputDevice::Get(device_name);
    if (!device)
    {
        msg::warn("trying to bind axis to nonexistent input device %s\n",
                  device_name.c_str());
        return;
    }

    int axisindex = device->GetAxisIndex(axis_name);
    if (axisindex < 0)
    {
        msg::warn("trying to bind nonexistent axis %s.%s\n",
                  device_name.c_str(), axis_name.c_str());
        return;
    }

    m_axisbindings.push(device, axisindex);
}

void AxisBinding::BindKey(const std::string& device_name, const std::string& key_name)
{
    const InputDevice* device = InputDevice::Get(device_name);
    if (!device)
    {
        msg::warn("trying to bind axis key to nonexistent input device %s\n",
                  device_name.c_str());
        return;
    }

    int keyindex = device->GetKeyIndex(key_name);
    if (keyindex < 0)
    {
        msg::warn("trying to bind nonexistent axis key %s.%s\n",
                  device_name.c_str(), key_name.c_str());
        return;
    }

    m_keybindings.push(device, -1, keyindex);
}

void AxisBinding::BindKeys(const std::string& device_name, const std::string& min_key_name, const std::string& max_key_name)
{
    const InputDevice* device = InputDevice::Get(device_name);
    if (!device)
    {
        msg::warn("trying to bind axis keys to nonexistent input device %s\n",
                  device_name.c_str());
        return;
    }

    int minkeyindex = device->GetKeyIndex(min_key_name);
    if (minkeyindex < 0)
    {
        msg::warn("trying to bind nonexistent axis key %s.%s\n",
                  device_name.c_str(), min_key_name.c_str());
        return;
    }

    int maxkeyindex = device->GetKeyIndex(max_key_name);
    if (maxkeyindex < 0)
    {
        msg::warn("trying to bind nonexistent axis key %s.%s\n",
                  device_name.c_str(), max_key_name.c_str());
        return;
    }

    m_keybindings.push(device, minkeyindex, maxkeyindex);
}

bool AxisBinding::Unbind(const std::string& device_name, const std::string& axis_name)
{
    for (int i = 0; i < m_keybindings.count(); ++i)
    {
        if (m_axisbindings[i].m1->GetName() == device_name)
        {
            if (m_axisbindings[i].m2 == m_axisbindings[i].m1->GetAxisIndex(axis_name))
            {
                m_axisbindings.remove(i);
                return true;
            }
        }
    }
    return false;
}

bool AxisBinding::UnbindKey(const std::string& device_name, const std::string& key_name)
{
    for (int i = 0; i < m_keybindings.count(); ++i)
    {
        if (m_keybindings[i].m1->GetName() == device_name)
        {
            if (m_keybindings[i].m2 == -1 && m_keybindings[i].m3 == m_keybindings[i].m1->GetKeyIndex(key_name))
            {
                m_keybindings.remove(i);
                return true;
            }
        }
    }
    return false;
}

bool AxisBinding::UnbindKeys(const std::string& device_name, const std::string& min_key_name, const std::string& max_key_name)
{
    for (int i = 0; i < m_keybindings.count(); ++i)
    {
        if (m_keybindings[i].m1->GetName() == device_name)
        {
            if (m_keybindings[i].m2 == m_keybindings[i].m1->GetKeyIndex(min_key_name)
                && m_keybindings[i].m3 == m_keybindings[i].m1->GetKeyIndex(max_key_name))
            {
                m_keybindings.remove(i);
                return true;
            }
        }
    }
    return false;

}

void AxisBinding::ClearBindings()
{
    m_axisbindings.clear();
    m_keybindings.clear();
}

float AxisBinding::RetrieveCurrentValue()
{
    float max_positive = 0.0f;
    float max_negative = 0.0f;

    for (int i = 0; i < m_axisbindings.count(); ++i)
    {
        float value = m_axisbindings[i].m1->GetAxis(m_axisbindings[i].m2);
        if (value > max_positive)
            max_positive = value;
        if (value < max_negative)
            max_negative = value;
    }

    for (int i = 0; i < m_keybindings.count(); ++i)
    {
        float value = 0.0f;
        m_keybindings[i].m1->GetKey(m_keybindings[i].m2);
        value += m_keybindings[i].m1->GetKey(m_keybindings[i].m3) ? 1.0f : 0.0f;
        if (m_keybindings[i].m2 != -1)
            value += m_keybindings[i].m1->GetKey(m_keybindings[i].m2) ? -1.0f : 0.0f;

        if (value > max_positive)
            max_positive = value;
        if (value < max_negative)
            max_negative = value;
    }

    return max_negative + max_positive;
}

///////////////////////////////////////////////////////////////////////////////
// Controller

array<Controller*> Controller::controllers;
uint32_t Controller::m_active_layer = ~((uint32_t)0);

//-----------------------------------------------------------------------------
Controller::Controller(std::string const &name)
{
    m_gamegroup = GAMEGROUP_INPUT;
    m_name = name;
    m_activate_nextframe = true;
    m_deactivate_nextframe = false;
    m_active = false;
    if (Get(name) != nullptr)
    {
        msg::warn("controller “%s” has already been registered\n", name.c_str());
    }
    controllers.push(this);
}

Controller::Controller(std::string const &name, InputProfile const& profile)
    : Controller(name)
{
    Init(profile);
}

Controller::~Controller()
{
    ClearProfile();
    for (int i = 0; i < controllers.count(); ++i)
    {
        if (controllers[i] == this)
        {
            controllers.remove(i);
            break;
        }
    }
}

//Init mode 1: Input profile system -------------------------------------------
void Controller::Init(InputProfile const& profile)
{
    UnbindProfile();
    BindProfile(profile);
}
void Controller::ClearProfile()
{
    UnbindProfile();
}

//Init mode 2: By hand, key/axis by key/axis ----------------------------------
void Controller::SetInputCount(int nb_keys, int nb_axis)
{
    m_keys.resize(nb_keys);
    m_axis.resize(nb_axis);
}

//Layer mask stuff ------------------------------------------------------------
void Controller::SetLayerMask(uint32_t layer_mask)
{
    m_layer_mask = layer_mask;
}
uint32_t Controller::GetLayerMask()
{
    return m_layer_mask;
}
bool Controller::IsLayerActive()
{
    return !!(m_layer_mask & m_active_layer);
}

//GetKeys/Axis stuff ----------------------------------------------------------
KeyBinding& Controller::GetKey(int index)
{
    return m_keys[index];
}
KeyBinding const& Controller::GetKey(int index) const
{
    return m_keys[index];
}
AxisBinding& Controller::GetAxis(int index)
{
    return m_axis[index];
}
AxisBinding const& Controller::GetAxis(int index) const
{
    return m_axis[index];
}

//Key methods: should not go directly to binding ------------------------------
bool Controller::IsKeyPressed(int index) const
{
//#error do something better IsLayerActive()
    return GetKey(index).IsPressed();
}
bool Controller::IsKeyReleased(int index) const
{
    return GetKey(index).IsReleased();
}
bool Controller::WasKeyPressedThisFrame(int index) const
{
    return GetKey(index).WasPressedThisFrame();
}
bool Controller::WasKeyReleasedThisFrame(int index) const
{
    return GetKey(index).WasReleasedThisFrame();
}

//Axis methods: should not go directly to binding -----------------------------
float Controller::GetAxisValue(int index) const
{
    return GetAxis(index).GetValue();
}
float Controller::GetAxisDelta(int index) const
{
    return GetAxis(index).GetDelta();
}

//-----------------------------------------------------------------------------
Controller* Controller::Get(std::string const &name)
{
    for (auto controller : controllers)
        if (controller->m_name == name)
            return controller;
    return nullptr;
}

//Input profile system --------------------------------------------------------
void Controller::UnbindProfile()
{
    if (m_profile.IsEmpty())
        return;

    m_mutex.lock();

    //Keyboard
    if (m_keyboard)
    {
        for (InputProfile::Keyboard& key : m_profile.m_keys)
            GetKey(key.m_idx).UnbindKeyboard(key.m_name);
        m_keyboard = nullptr;
    }

    //Mouse
    if (m_mouse)
    {
        for (InputProfile::MouseKey& key : m_profile.m_mouse_keys)
            GetKey(key.m_idx).UnbindMouse(key.m_name);
        for (InputProfile::MouseAxis& axis : m_profile.m_mouse_axis)
            GetAxis(axis.m_idx).UnbindMouse(axis.m_name);
        m_mouse = nullptr;
    }

    //Joystick
    for (InputProfile::JoystickKey& key : m_profile.m_joystick_keys)
    {
        if (m_joystick_idx.find(key.m_joy) != INDEX_NONE)
            GetKey(key.m_idx).UnbindJoystick(key.m_joy, key.m_name);
    }
    for (InputProfile::JoystickAxis& axis : m_profile.m_joystick_axis)
    {
        if (m_joystick_idx.find(axis.m_joy) != INDEX_NONE)
            GetAxis(axis.m_idx).UnbindJoystick(axis.m_joy, axis.m_name);
    }
    m_joystick.clear();
    m_joystick_idx.clear();

    m_mutex.unlock();
}
//Input profile system --------------------------------------------------------
void Controller::BindProfile(InputProfile const& setup)
{
    ASSERT(!setup.IsEmpty());

    m_mutex.lock();
    m_profile = setup;

    m_keys.resize(m_profile.GetKeyCount());
    m_axis.resize(m_profile.GetAxisCount());

    //Keyboard
    m_keyboard = InputDevice::GetKeyboard();
    if (m_keyboard)
    {
        for (InputProfile::Keyboard& key : m_profile.m_keys)
            GetKey(key.m_idx).BindKeyboard(key.m_name);
    }

    //Mouse
    m_mouse = InputDevice::GetMouse();
    if (m_mouse)
    {
        for (InputProfile::MouseKey& key : m_profile.m_mouse_keys)
            GetKey(key.m_idx).BindMouse(key.m_name);
        for (InputProfile::MouseAxis& axis : m_profile.m_mouse_axis)
            GetAxis(axis.m_idx).BindMouse(axis.m_name);
    }

    //Joystick
    for (uint64_t joy_idx : m_profile.m_joystick)
    {
        class InputDevice* joystick = InputDevice::GetJoystick(joy_idx);
        if (joystick)
        {
            m_joystick << joystick;
            m_joystick_idx << joy_idx;
        }
    }
    for (InputProfile::JoystickKey& key : m_profile.m_joystick_keys)
    {
        if (m_joystick_idx.find(key.m_joy) != INDEX_NONE)
            GetKey(key.m_idx).BindJoystick(key.m_joy, key.m_name);
    }
    for (InputProfile::JoystickAxis& axis : m_profile.m_joystick_axis)
    {
        if (m_joystick_idx.find(axis.m_joy) != INDEX_NONE)
            GetAxis(axis.m_idx).BindJoystick(axis.m_joy, axis.m_name);
    }

    m_mutex.unlock();
}

//-----------------------------------------------------------------------------
void Controller::tick_game(float seconds)
{
    Entity::tick_game(seconds);

    if (m_active)
    {
        for (int i = 0; i < m_keys.count(); ++i)
            m_keys[i].Update();

        for (int i = 0; i < m_axis.count(); ++i)
            m_axis[i].Update();
    }

    if (m_activate_nextframe)
        m_active = true;

    if (m_deactivate_nextframe)
        m_active = false;

    m_activate_nextframe = false;
    m_deactivate_nextframe = false;
}

//-----------------------------------------------------------------------------
void Controller::Activate()
{
    m_activate_nextframe = true;
    m_deactivate_nextframe = false;
}

void Controller::Deactivate()
{
    m_deactivate_nextframe = true;
    m_activate_nextframe = false;
}

//-----------------------------------------------------------------------------
array<Controller*> Controller::DeactivateAll()
{
    array<Controller*> result;

    for (int i = 0; i < controllers.count(); ++i)
    {
        if (controllers[i]->m_active || controllers[i]->m_activate_nextframe)
        {
            result.push(controllers[i]);
            controllers[i]->Deactivate();
        }
    }

    return result;
}

} /* namespace lol */