#ifdef HAVE_LIBEVDEV

#include "evdev_joystick_handler.h"
#include "Utilities/Thread.h"
#include "Utilities/Log.h"

#include <functional>
#include <algorithm>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <cstring>
#include <cstdio>
#include <cmath>

evdev_joystick_config g_evdev_joystick_config;

evdev_joystick_handler::evdev_joystick_handler() {}

evdev_joystick_handler::~evdev_joystick_handler() { Close(); }

bool evdev_joystick_handler::Init()
{
    g_evdev_joystick_config.load();

    axistrigger = static_cast<bool>(g_evdev_joystick_config.axistrigger);

    revaxis.emplace_back(g_evdev_joystick_config.lxreverse);
    revaxis.emplace_back(g_evdev_joystick_config.lyreverse);
    revaxis.emplace_back(g_evdev_joystick_config.rxreverse);
    revaxis.emplace_back(g_evdev_joystick_config.ryreverse);

    return true;
}

void evdev_joystick_handler::update_devs()
{
    for (u32 i = 0; i < joy_devs.size(); ++i)
        try_open_dev(i);
}

inline u16 Clamp0To255(f32 input)
{
    if (input > 255.f)
        return 255;
    else if (input < 0.f)
        return 0;
    else return static_cast<u16>(input);
}

std::tuple<u16, u16> evdev_joystick_handler::ConvertToSquirclePoint(u16 inX, u16 inY)
{
    // convert inX and Y to a (-1, 1) vector;
    const f32 x = (inX - 127) / 127.f;
    const f32 y = ((inY - 127) / 127.f);

    // compute angle and len of given point to be used for squircle radius
    const f32 angle = std::atan2(y, x);
    const f32 r = std::sqrt(std::pow(x, 2.f) + std::pow(y, 2.f));

    // now find len/point on the given squircle from our current angle and radius in polar coords
    // https://thatsmaths.com/2016/07/14/squircles/
    const f32 newLen = (1 + std::pow(std::sin(2 * angle), 2.f) / (g_evdev_joystick_config.squirclefactor / 1000.f)) * r;

    // we now have len and angle, convert to cartisian

    const int newX = Clamp0To255(((newLen * std::cos(angle)) + 1) * 127);
    const int newY = Clamp0To255(((newLen * std::sin(angle)) + 1) * 127);
    return std::tuple<u16, u16>(newX, newY);
}

bool evdev_joystick_handler::try_open_dev(u32 index)
{
    libevdev*& dev = joy_devs[index];
    bool was_connected = dev != nullptr;

    if (index >= joy_paths.size()) return false;
    const auto& path = joy_paths[index];

    if (access(path.c_str(), R_OK) == -1)
    {
        if (was_connected)
        {
            // It was disconnected.
            pads[index]->m_port_status |= CELL_PAD_STATUS_ASSIGN_CHANGES;

            int fd = libevdev_get_fd(dev);
            libevdev_free(dev);
            close(fd);
            dev = nullptr;
        }
        pads[index]->m_port_status &= ~CELL_PAD_STATUS_CONNECTED;
        LOG_ERROR(GENERAL, "Joystick %s is not present or accessible [previous status: %d]", path.c_str(),
                  was_connected ? 1 : 0);
        return false;
    }

    if (was_connected) return true;  // It's already been connected, and the js is still present.
    int fd = open(path.c_str(), O_RDONLY | O_NONBLOCK);

    if (fd == -1)
    {
        int err = errno;
        LOG_ERROR(GENERAL, "Failed to open joystick #%d: %s [errno %d]", index, strerror(err), err);
        return false;
    }

    int ret = libevdev_new_from_fd(fd, &dev);
    if (ret < 0)
    {
        LOG_ERROR(GENERAL, "Failed to initialize libevdev for joystick #%d: %s [errno %d]", index, strerror(-ret), -ret);
        return false;
    }

    LOG_NOTICE(GENERAL, "Opened joystick #%d '%s' at %s (fd %d)", index, libevdev_get_name(dev), path, fd);

    if (!was_connected)
        // Connection status changed from disconnected to connected.
        pads[index]->m_port_status |= CELL_PAD_STATUS_ASSIGN_CHANGES;
    pads[index]->m_port_status |= CELL_PAD_STATUS_CONNECTED;

    int buttons=0;
    for (u32 i=BTN_JOYSTICK; i<KEY_MAX; i++)
        if (libevdev_has_event_code(dev, EV_KEY, i))
        {
            LOG_NOTICE(GENERAL, "Joystick #%d has button %d as %d", index, i, buttons);
            joy_button_maps[index][i - BTN_MISC] = buttons++;
        }

    int axes=0;
    for (u32 i=ABS_X; i<=ABS_RZ; i++)
    {

        if (libevdev_has_event_code(dev, EV_ABS, i))
        {
            LOG_NOTICE(GENERAL, "Joystick #%d has axis %d as %d", index, i, axes);

            axis_ranges[i].first = libevdev_get_abs_minimum(dev, i);
            axis_ranges[i].second = libevdev_get_abs_maximum(dev, i);

            // Skip ABS_Z and ABS_RZ on controllers where it's used for the triggers.
            if (axistrigger && (i == ABS_Z || i == ABS_RZ)) continue;
            joy_axis_maps[index][i - ABS_X] = axes++;
        }
    }

    for (u32 i=ABS_HAT0X; i<=ABS_HAT3Y; i+=2)
        if (libevdev_has_event_code(dev, EV_ABS, i) ||
            libevdev_has_event_code(dev, EV_ABS, i+1))
        {
            LOG_NOTICE(GENERAL, "Joystick #%d has hat %d", index, i);
            joy_hat_ids[index] = i - ABS_HAT0X;
        }

    return true;
}

void evdev_joystick_handler::Close()
{
    for (auto& dev : joy_devs)
    {
        if (dev != nullptr)
        {
            int fd = libevdev_get_fd(dev);
            libevdev_free(dev);
            close(fd);
        }
    }
}

inline float deadzone(f32 input)
{
    //when we're just above the deadzone, the output should be 128, not 128+deadzone
    //otherwise, there'll be a jump. So, we need to recalculate the output with a linear function
    float deadzone_slope, deadzone_origin;
    if (input >= 127.5 - g_evdev_joystick_config.deadzone && input <= 127.5 + g_evdev_joystick_config.deadzone)
    {
        return 127.5;
    }
    else
    {
        if (input > 127.5 + g_evdev_joystick_config.deadzone)
        {
            deadzone_slope = 127.5 / (127.5 - g_evdev_joystick_config.deadzone);
            deadzone_origin = 255.0 * (1.0 - deadzone_slope);
            return (deadzone_slope * input + deadzone_origin);
        }
        if (input < 127.5 - g_evdev_joystick_config.deadzone)
        {
            return (127.5 / (127.5 - g_evdev_joystick_config.deadzone)) * input;
        }
    }
}

int evdev_joystick_handler::scale_axis(int axis, int value)
{
    auto range = axis_ranges[axis];
    // Check if scaling is needed.
    if (range.first != 0 || range.second != 255)
    {
        if (range.first < 0)
        {
            // Move the ranges up to make the following calculation actually *work*
            value += -range.first;
            range.second += -range.first;
            range.first = 0;
        }
        return (deadzone(((static_cast<float>(value) - range.first) / range.second) * 255));
    }
    else
    {
        return (deadzone(static_cast<float>(value)));
    }
}

std::vector<std::string> evdev_joystick_handler::ListDevices()
{
    Init();
    std::vector<std::string> evdev_joystick_list;
    fs::dir devdir{"/dev/input/"};
    fs::dir_entry et;

    while (devdir.read(et))
    {
        // Check if the entry starts with event (a 5-letter word)
        if (et.name.size() > 5 && et.name.compare(0, 5,"event") == 0)
        {
            int fd = open(("/dev/input/" + et.name).c_str(), O_RDONLY|O_NONBLOCK);
            struct libevdev *dev = NULL;
            int rc = libevdev_new_from_fd(fd, &dev);
            if (rc < 0)
            {
                // If it's just a bad file descriptor, don't bother logging, but otherwise, log it.
                if (rc != -9)
                    LOG_WARNING(GENERAL, "Failed to connect to device at %s, the error was: %s", "/dev/input/" + et.name, strerror(-rc));
                libevdev_free(dev);
                close(fd);
                continue;
            }
            if (libevdev_has_event_type(dev, EV_KEY) &&
                libevdev_has_event_code(dev, EV_ABS, ABS_X) &&
                libevdev_has_event_code(dev, EV_ABS, ABS_Y))
            {
                // It's a joystick.
                evdev_joystick_list.push_back(libevdev_get_name(dev));
            }
            libevdev_free(dev);
            close(fd);
        }
    }
    return evdev_joystick_list;
}

bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr<Pad> pad, const std::string& device)
{
        Init();

        // Now we need to find the device with the same name, and make sure not to grab any duplicates.
        fs::dir devdir{"/dev/input/"};
        fs::dir_entry et;
        while (devdir.read(et))
        {
            // Check if the entry starts with event (a 5-letter word)
            if (et.name.size() > 5 && et.name.compare(0, 5,"event") == 0)
            {
                int fd = open(("/dev/input/" + et.name).c_str(), O_RDONLY|O_NONBLOCK);
                struct libevdev *dev = NULL;
                int rc = libevdev_new_from_fd(fd, &dev);
                if (rc < 0)
                {
                    // If it's just a bad file descriptor, don't bother logging, but otherwise, log it.
                    if (rc != -9)
                        LOG_WARNING(GENERAL, "Failed to connect to device at %s, the error was: %s", "/dev/input/" + et.name, strerror(-rc));
                    libevdev_free(dev);
                    close(fd);
                    continue;
                }
                const std::string name = libevdev_get_name(dev);
                if (libevdev_has_event_type(dev, EV_KEY) &&
                    libevdev_has_event_code(dev, EV_ABS, ABS_X) &&
                    libevdev_has_event_code(dev, EV_ABS, ABS_Y) &&
                    name == device)
                {
                    // It's a joystick.

                    // Now let's make sure we don't already have this one.
                    bool alreadyIn = false;
                    for (int i = 0; i < joy_paths.size(); i++)
                        if (joy_paths[i] == fmt::format("/dev/input/%s", et.name))
                        {
                            alreadyIn = true;
                            break;
                        }
                    if (alreadyIn == true)
                    {
                        libevdev_free(dev);
                        close(fd);
                        continue;
                    }

                    // Alright, now that we've confirmed we haven't added this joystick yet, les do dis.
                    joy_paths.emplace_back(fmt::format("/dev/input/%s", et.name));
                }
                libevdev_free(dev);
                close(fd);
            }
        }

        joy_devs.push_back(nullptr);
        joy_axis_maps.emplace_back(ABS_RZ - ABS_X, -1);
        joy_axis.emplace_back(ABS_RZ - ABS_X, -1);
        joy_button_maps.emplace_back(KEY_MAX - BTN_JOYSTICK, -1);
        joy_hat_ids.emplace_back(-1);
        pad->Init(
            CELL_PAD_STATUS_DISCONNECTED,
            CELL_PAD_SETTING_PRESS_OFF | CELL_PAD_SETTING_SENSOR_OFF,
            CELL_PAD_CAPABILITY_PS3_CONFORMITY | CELL_PAD_CAPABILITY_PRESS_MODE,
            CELL_PAD_DEV_TYPE_STANDARD
        );
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.triangle, CELL_PAD_CTRL_TRIANGLE);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.circle, CELL_PAD_CTRL_CIRCLE);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.cross, CELL_PAD_CTRL_CROSS);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.square, CELL_PAD_CTRL_SQUARE);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.l2, CELL_PAD_CTRL_L2);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.r2, CELL_PAD_CTRL_R2);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.l1, CELL_PAD_CTRL_L1);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, g_evdev_joystick_config.r1, CELL_PAD_CTRL_R1);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.start, CELL_PAD_CTRL_START);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.select, CELL_PAD_CTRL_SELECT);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.l3, CELL_PAD_CTRL_L3);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.r3, CELL_PAD_CTRL_R3);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2, 0, 0x100/*CELL_PAD_CTRL_PS*/);// TODO: PS button support

        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.up, CELL_PAD_CTRL_UP);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.down, CELL_PAD_CTRL_DOWN);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.left, CELL_PAD_CTRL_LEFT);
        pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, g_evdev_joystick_config.right, CELL_PAD_CTRL_RIGHT);

        pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X+g_evdev_joystick_config.lxstick, 0, 0);
        pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X+g_evdev_joystick_config.lystick, 0, 0);
        pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X+g_evdev_joystick_config.rxstick, 0, 0);
        pad->m_sticks.emplace_back(CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X+g_evdev_joystick_config.rystick, 0, 0);
        pad->m_vibrateMotors.emplace_back(true, 0);
        pad->m_vibrateMotors.emplace_back(false, 0);
        pads.emplace_back(pad);
        update_devs();
        return true;
}

void evdev_joystick_handler::ThreadProc()
{
    update_devs();

    for (int i=0; i<joy_devs.size(); i++)
    {
        auto pad = pads[i];
        auto& dev = joy_devs[i];
        if (dev == nullptr) continue;

        // Try to query the latest event from the joystick.
        input_event evt;
        int ret = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &evt);

        // Grab any pending sync event.
        if (ret == LIBEVDEV_READ_STATUS_SYNC)
        {
            LOG_NOTICE(GENERAL, "Captured sync event");
            ret = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL | LIBEVDEV_READ_FLAG_SYNC, &evt);
        }

        if (ret < 0)
        {
            // -EAGAIN signifies no available events, not an actual *error*.
            if (ret != -EAGAIN)
                LOG_ERROR(GENERAL, "Failed to read latest event from joystick #%d: %s [errno %d]", i, strerror(-ret), -ret);
            continue;
        }

        switch (evt.type)
        {
        case EV_SYN:
            LOG_NOTICE(GENERAL, "Joystick #%d sent EV_SYN", i);
            break;
        case EV_MSC:
            LOG_NOTICE(GENERAL, "Joystick #%d sent EV_MSC", i);
            break;
        case EV_KEY:
        {
            LOG_NOTICE(GENERAL, "Joystick #%d EV_KEY: %d %d", i, evt.code, evt.value);
            if (evt.code < BTN_MISC)
            {
                LOG_NOTICE(GENERAL, "Joystick #%d sent non-button key event %d", i, evt.code);
                break;
            }

            int button_code = joy_button_maps[i][evt.code - BTN_MISC];
            if (button_code == -1)
            {
                LOG_ERROR(GENERAL, "Joystick #%d sent invalid button code %d", i, evt.code);
            }

            auto which_button = std::find_if(
                pad->m_buttons.begin(), pad->m_buttons.end(),
                [&](const Button& bt) { return bt.m_keyCode == button_code; });
            if (which_button == pad->m_buttons.end())
            {
                LOG_ERROR(GENERAL, "Joystick #%d sent button event for unmapped button %d", i, evt.code);
                break;
            }

            which_button->m_pressed = evt.value;
            which_button->m_value = evt.value ? 255 : 0;
            break;
        }
        case EV_ABS:
            LOG_NOTICE(GENERAL, "Joystick #%d EV_ABS: %d %d", i, evt.code, evt.value);

            if (evt.code >= ABS_HAT0X && evt.code <= ABS_HAT3Y) {
                int hat = evt.code - ABS_HAT0X;
                if (hat != joy_hat_ids[i] && hat-1 != joy_hat_ids[i])
                {
                    LOG_ERROR(GENERAL, "Joystick #%d sent HAT event for invalid hat %d (expected %d)", i, hat, joy_hat_ids[i]);
                    break;
                }

                int source_axis = hat == joy_hat_ids[i] ? EVDEV_DPAD_HAT_AXIS_X : EVDEV_DPAD_HAT_AXIS_Y;

                for (Button& bt : pad->m_buttons)
                {
                    if (bt.m_keyCode != source_axis) continue;

                    if (evt.value == 0)
                    {
                        bt.m_pressed = false;
                        bt.m_value = 0;
                    }
                    else
                    {
                        int code;
                        if (source_axis == EVDEV_DPAD_HAT_AXIS_X)
                        {
                            code = evt.value > 0 ? CELL_PAD_CTRL_RIGHT : CELL_PAD_CTRL_LEFT;
                        }
                        else
                        {
                            code = evt.value > 0 ? CELL_PAD_CTRL_DOWN : CELL_PAD_CTRL_UP;
                        }

                        if (bt.m_outKeyCode == code)
                        {
                            bt.m_pressed = true;
                            bt.m_value = 255;
                        }
                    }
                }
            }
            else if (axistrigger && (evt.code == ABS_Z || evt.code == ABS_RZ))
            {
                // For Xbox controllers, a third axis represent the left/right triggers.
                int which_trigger=0;

                if (evt.code == ABS_Z)
                {
                    which_trigger = CELL_PAD_CTRL_L2;
                }
                else if (evt.code == ABS_RZ)
                {
                    which_trigger = CELL_PAD_CTRL_R2;
                }
                else
                {
                    LOG_ERROR(GENERAL, "Joystick #%d sent invalid event code %d for 3rd axis", i, evt.code);
                    break;
                }

                auto which_button = std::find_if(
                    pad->m_buttons.begin(), pad->m_buttons.end(),
                    [&](const Button& bt) { return bt.m_outKeyCode == which_trigger; });
                if (which_button == pad->m_buttons.end())
                {
                    LOG_ERROR(GENERAL, "Joystick #%d's pad has no trigger %d", i, which_trigger);
                    break;
                }

                int value = scale_axis(evt.code, evt.value);
                which_button->m_pressed = value > 0;
                which_button->m_value = value;
            }
            else if (evt.code >= ABS_X && evt.code <= ABS_RZ)
            {
                int axis = joy_axis_maps[i][evt.code - ABS_X];

                if (axis > pad->m_sticks.size())
                {
                    LOG_ERROR(GENERAL, "Joystick #%d sent axis event for invalid axis %d", i, axis);
                    break;
                }

                int value = evt.value;
                if (revaxis[axis])
                {
                    // Reverse the value in the range.
                    value = (axis_ranges[evt.code].second + axis_ranges[evt.code].first) - value;
                }

                if (g_evdev_joystick_config.squirclejoysticks)
                {
                    joy_axis[i][axis] = value;
                    if (evt.code == ABS_X || evt.code == ABS_Y)
                    {
                        int Xaxis = joy_axis_maps[i][ABS_X];
                        int Yaxis = joy_axis_maps[i][ABS_Y];
                        pad->m_sticks[Xaxis].m_value = scale_axis(ABS_X, joy_axis[i][Xaxis]);
                        pad->m_sticks[Yaxis].m_value = scale_axis(ABS_Y, joy_axis[i][Yaxis]);

                        std::tie(pad->m_sticks[Xaxis].m_value, pad->m_sticks[Yaxis].m_value) =
                            ConvertToSquirclePoint(pad->m_sticks[Xaxis].m_value, pad->m_sticks[Yaxis].m_value);
                    }
                    else
                    {
                        int Xaxis = joy_axis_maps[i][ABS_RX];
                        int Yaxis = joy_axis_maps[i][ABS_RY];
                        pad->m_sticks[Xaxis].m_value = scale_axis(ABS_RX, joy_axis[i][Xaxis]);
                        pad->m_sticks[Yaxis].m_value = scale_axis(ABS_RY, joy_axis[i][Yaxis]);

                        std::tie(pad->m_sticks[Xaxis].m_value, pad->m_sticks[Yaxis].m_value) =
                            ConvertToSquirclePoint(pad->m_sticks[Xaxis].m_value, pad->m_sticks[Yaxis].m_value);
                    }
                }
                else
                    pad->m_sticks[axis].m_value = scale_axis(evt.code, value);
            }
            break;
        default:
            LOG_ERROR(GENERAL, "Unknown joystick #%d event %d", i, evt.type);
            break;
        }
    }
}

#endif
