/*  LilyPad - Pad plugin for PS2 Emulator
 *  Copyright (C) 2002-2015  PCSX2 Dev Team/ChickenLiver
 *
 *  PCSX2 is free software: you can redistribute it and/or modify it under the
 *  terms of the GNU Lesser General Public License as published by the Free
 *  Software Found- ation, either version 3 of the License, or (at your option)
 *  any later version.
 *
 *  PCSX2 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 PCSX2.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "Global.h"
#include "InputManager.h"
#include "Linux/JoyEvdev.h"
#include "Linux/bitmaskros.h"

JoyEvdev::JoyEvdev(int fd, bool ds3, const wchar_t *id)
    : Device(LNX_JOY, OTHER, id, id)
    , m_fd(fd)
{
    // XXX LNX_JOY => DS3 or ???

    m_abs.clear();
    m_btn.clear();
    m_rel.clear();
    int last = 0;

    uint8_t abs_bitmap[nUcharsForNBits(ABS_CNT)] = {0};
    uint8_t btn_bitmap[nUcharsForNBits(KEY_CNT)] = {0};
    uint8_t rel_bitmap[nUcharsForNBits(REL_CNT)] = {0};

    // Add buttons
    if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(btn_bitmap)), btn_bitmap) >= 0) {
        for (int bit = BTN_MISC; bit < KEY_CNT; bit++) {
            if (testBit(bit, btn_bitmap)) {
                AddPhysicalControl(PSHBTN, last, 0);
                m_btn.push_back(bit);
                last++;
            }
        }
    }

    // Add Absolute axis
    if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bitmap)), abs_bitmap) >= 0) {
        for (int bit = 0; bit < ABS_CNT; bit++) {
            ControlType type = ABSAXIS; // FIXME DS3

            if (testBit(bit, abs_bitmap)) {
                input_absinfo info;
                if (ioctl(m_fd, EVIOCGABS(bit), &info) < 0) {
                    fprintf(stderr, "Invalid IOCTL EVIOCGID\n");
                    continue;
                }

                AddPhysicalControl(ABSAXIS, last, 0);
                last++;
                if (std::abs(info.value - 127) < 2) {
                    fprintf(stderr, "HALF Axis info %d=>%d, current %d, flat %d, resolution %d\n", info.minimum, info.maximum, info.value, info.flat, info.resolution);

                    // Half axis must be split into 2 parts...
                    AddPhysicalControl(ABSAXIS, last, 0);
                    last++;

                    m_abs.push_back(abs_info(bit, info.minimum, info.value, type));
                    m_abs.push_back(abs_info(bit, info.value, info.maximum, type));
                } else {
                    fprintf(stderr, "FULL Axis info %d=>%d, current %d, flat %d, resolution %d\n", info.minimum, info.maximum, info.value, info.flat, info.resolution);

                    m_abs.push_back(abs_info(bit, info.minimum, info.maximum, type));
                }
            }
        }
    }

    // Add relative axis
    if (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(rel_bitmap)), rel_bitmap) >= 0) {
        for (int bit = 0; bit < REL_CNT; bit++) {
            if (testBit(bit, rel_bitmap)) {
                AddPhysicalControl(RELAXIS, last, last);
                m_rel.push_back(bit);
                last++;

                fprintf(stderr, "Add relative nb %d\n", bit);
            }
        }
    }

    fprintf(stderr, "New device created. Found axe:%zu, buttons:%zu, m_rel:%zu\n\n", m_abs.size(), m_btn.size(), m_rel.size());
}

JoyEvdev::~JoyEvdev()
{
    close(m_fd);
}

int JoyEvdev::Activate(InitInfo *args)
{
    AllocState();

    uint16_t size = m_abs.size() + m_rel.size() + m_btn.size();
    memset(physicalControlState, 0, sizeof(int) * size);

    active = 1;
    return 1;
}

int JoyEvdev::Update()
{
    struct input_event events[32];
    int len;
    int status = 0;
    //fprintf(stderr, "Update was called\n");

    // Do a big read to reduce kernel validation
    while ((len = read(m_fd, events, (sizeof events))) > 0) {
        int evt_nb = len / sizeof(input_event);
        //fprintf(stderr, "Poll %d events available\n", evt_nb);
        for (int i = 0; i < evt_nb; i++) {
            switch (events[i].type) {
                case EV_ABS: {
                    for (size_t idx = 0; idx < m_abs.size(); idx++) {
                        if (m_abs[idx].code == events[i].code) {
                            // XXX strict or not ?
                            if ((events[i].value >= m_abs[idx].min) && (events[i].value <= m_abs[idx].max)) {
                                // XXX FIX shitty api
                                int scale = m_abs[idx].scale(events[i].value);
                                fprintf(stderr, "axis value %d scaled to %d\n", events[i].value, scale);
                                physicalControlState[idx + m_btn.size()] = scale;
                                status = 1;
                            }
                        }
                    }
                } break;
                case EV_KEY: {
                    for (size_t idx = 0; idx < m_btn.size(); idx++) {
                        if (m_btn[idx] == events[i].code) {
                            fprintf(stderr, "Event KEY:%d detected with value %d\n", events[i].code, events[i].value);
                            physicalControlState[idx] = FULLY_DOWN * events[i].value;
                            status = 1;
                            break;
                        }
                    }

                } break;
                case EV_REL:
                    // XXX
                    break;
                default:
                    break;
            }
        }
    }

    return status;
}


static std::wstring CorrectJoySupport(int fd)
{
    struct input_id id;
    if (ioctl(fd, EVIOCGID, &id) < 0) {
        fprintf(stderr, "Invalid IOCTL EVIOCGID\n");
        return L"";
    }

    char dev_name[128];
    if (ioctl(fd, EVIOCGNAME(128), dev_name) < 0) {
        fprintf(stderr, "Invalid IOCTL EVIOCGNAME\n");
        return L"";
    }

    fprintf(stderr, "Found input device => bustype:%x, vendor:%x, product:%x, version:%x\n", id.bustype, id.vendor, id.product, id.version);
    fprintf(stderr, "\tName:%s\n", dev_name);

    std::string s(dev_name);
    return std::wstring(s.begin(), s.end());
}

void EnumJoystickEvdev()
{
    // Technically it must be done with udev but another lib for
    // avoid a loop is too much for me (even if udev is mandatory
    // so maybe later)
    std::string input_root("/dev/input/event");
    for (int i = 0; i < 32; i++) {
        std::string dev = input_root + std::to_string(i);

        int fd = open(dev.c_str(), O_RDWR | O_NONBLOCK);
        if (fd < 0) {
            continue;
        }

        std::wstring id = CorrectJoySupport(fd);
        if (id.size() != 0) {
            bool ds3 = id.find(L"PLAYSTATION(R)3") != std::string::npos;
            if (ds3) {
                fprintf(stderr, "DS3 device detected !!!\n");
            }
            dm->AddDevice(new JoyEvdev(fd, ds3, id.c_str()));
        } else if (fd >= 0)
            close(fd);
    }
}
