/*
    Linux Input Device support.
    Copyright (c) 2004 stefan kersten.
    modifications by Marije Baalman 2006-9

    ====================================================================

    SuperCollider real time audio synthesis system
    Copyright (c) 2002 James McCartney. All rights reserved.
    http://www.audiosynth.com

    This program 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 2 of the License, or
    (at your option) any later version.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA

    Linux Input Device interface, 2004 <sk>
*/

#include <atomic>

#include "SCBase.h"
#include "VMGlobals.h"
#include "PyrSymbolTable.h"
#include "PyrInterpreter.h"
#include "PyrKernel.h"

#include "PyrPrimitive.h"
#include "PyrObjectProto.h"
#include "PyrPrimitiveProto.h"
#include "PyrKernelProto.h"
#include "SC_InlineUnaryOp.h"
#include "SC_InlineBinaryOp.h"
#include "PyrSched.h"
#include "GC.h"
#include "SC_LanguageClient.h"

#if HAVE_LID
#    include <errno.h>
#    include <fcntl.h>
#    include <linux/input.h>
#    include <stdio.h>
#    include <string.h>
#    include <sys/select.h>
#    include <sys/types.h>
#    include <unistd.h>


#    define BITS_PER_LONG (sizeof(long) * 8)
#    define NBITS(x) ((((x)-1) / BITS_PER_LONG) + 1)
#    define OFF(x) ((x) % BITS_PER_LONG)
#    define BIT(x) (1UL << OFF(x))
#    define LONG(x) ((x) / BITS_PER_LONG)
#    define TEST_BIT(array, bit) (((array)[LONG(bit)] >> OFF(bit)) & 1)

extern bool compiledOK;

static PyrSymbol* s_inputDeviceClass = nullptr;
static PyrSymbol* s_inputDeviceInfoClass = nullptr;
static PyrSymbol* s_absInfoClass = nullptr;
static PyrSymbol* s_handleEvent = nullptr;
static PyrSymbol* s_readError = nullptr;

// =====================================================================
// SC_LID

struct SC_LID {
    SC_LID(PyrObject* obj);
    ~SC_LID();

    int open(const char* path);
    int close();

    bool isEventTypeSupported(int evtType);
    bool isEventCodeSupported(int evtType, int evtCode);

    int getName(char* buf, size_t size);
    int getInfo(struct input_id* info, char* bufPhys, size_t sizePhys, char* bufUniq, size_t sizeUniq);
    int getKeyState(int evtCode);
    int getAbsInfo(int evtCode, struct input_absinfo* info);

    int setLedState(int evtCode, int evtValue, int evtType);

    int grab(int flag);
    void handleEvent(struct input_event& evt, std::atomic<bool> const& shouldBeRunning);
    void readError(std::atomic<bool> const& shouldBeRunning);

    static PyrObject* getObject(PyrSlot* slot) {
        return isKindOfSlot(slot, s_inputDeviceClass->u.classobj) ? slotRawObject(slot) : nullptr;
    }

    static SC_LID* getDevice(PyrObject* obj) { return (SC_LID*)slotRawPtr(&obj->slots[0]); }

    SC_LID* m_next;
    PyrObject* m_obj;
    int m_fd;
    int m_lastEventType;
    std::uint64_t m_eventTypeCaps[NBITS(EV_MAX)];
    std::uint64_t m_eventCodeCaps[NBITS(KEY_MAX)];
    std::uint64_t m_keyState[NBITS(KEY_MAX)];
};

// =====================================================================
// SC_LIDManager

struct SC_LIDManager {
public:
    static SC_LIDManager& instance();

    int start();
    int stop();

    int add(SC_LID* dev);
    int remove(SC_LID* dev);

private:
    SC_LIDManager();
    ~SC_LIDManager();

    enum { kStop, kAdd, kRemove };

    struct Command {
        int id;
        union {
            SC_LID* dev;
        } arg;
    };

    int sendCommand(const Command& cmd);
    void devicesChanged();
    bool asyncAddDevice(SC_LID* dev);
    bool asyncRemoveDevice(SC_LID* dev);
    void loop();

    static void* threadFunc(void*);

    pthread_t m_thread;
    pthread_mutex_t m_mutex;
    std::atomic<bool> m_running;
    std::atomic<bool> mShouldBeRunning;
    int m_cmdFifo[2];
    int m_nfds;
    fd_set m_fds;
    SC_LID* m_devices;
};

// =====================================================================
// SC_LID

SC_LID::SC_LID(PyrObject* obj): m_next(nullptr), m_obj(obj), m_fd(-1), m_lastEventType(-1) {
    SetPtr(obj->slots + 0, this);
}

SC_LID::~SC_LID() {
    if (m_fd != -1)
        ::close(m_fd);
}

int SC_LID::open(const char* path) {
    m_fd = ::open(path, O_RDWR);

    if (m_fd == -1) {
        error("LID (1): %s\n", strerror(errno));
        return errFailed;
    }

    memset(m_eventTypeCaps, 0, sizeof(m_eventTypeCaps));
    if (ioctl(m_fd, EVIOCGBIT(0, EV_MAX), m_eventTypeCaps) == -1) {
        error("LID (2): %s\n", strerror(errno));
        return errFailed;
    }

    memset(m_keyState, 0, sizeof(m_keyState));
    if (ioctl(m_fd, EVIOCGKEY(sizeof(m_keyState)), m_keyState) == -1) {
        error("LID (3): %s\n", strerror(errno));
        return errFailed;
    }

    return SC_LIDManager::instance().add(this);
}

int SC_LID::close() {
    SetNil(m_obj->slots + 0);
    return SC_LIDManager::instance().remove(this);
}

bool SC_LID::isEventTypeSupported(int evtType) { return TEST_BIT(m_eventTypeCaps, evtType); }

bool SC_LID::isEventCodeSupported(int evtType, int evtCode) {
    if (evtType != m_lastEventType) {
        m_lastEventType = evtType;
        memset(m_eventCodeCaps, 0, sizeof(m_eventCodeCaps));
        if (ioctl(m_fd, EVIOCGBIT(evtType, KEY_MAX), m_eventCodeCaps) == -1) {
            post("LID failed to check event code (error %s)\n", strerror(errno));
            return false;
        }
    }
    return TEST_BIT(m_eventCodeCaps, evtCode);
}

int SC_LID::getName(char* buf, size_t size) {
    if (ioctl(m_fd, EVIOCGNAME(size), buf) == -1) {
        error("LID (5): %s\n", strerror(errno));
        return errFailed;
    }
    return errNone;
}

int SC_LID::getInfo(struct input_id* info, char* bufPhys, size_t sizePhys, char* bufUniq, size_t sizeUniq) {
    if (ioctl(m_fd, EVIOCGID, info) == -1) {
        error("LID (6): %s\n", strerror(errno));
        return errFailed;
    }
    if (ioctl(m_fd, EVIOCGPHYS(sizePhys), bufPhys) == -1) {
        // 		strcpy( sizePhys, strerror(errno));
        post("LID could not retrieve physical location (error: %s)\n", strerror(errno));
        // 		return errFailed;
    }
    if (ioctl(m_fd, EVIOCGUNIQ(sizeUniq), bufUniq) == -1) {
        // 		strcpy( strerror(errno), sizeof( strerror(errno)), sizeUniq );
        post("LID could not get unique identifier (error: %s)\n", strerror(errno));
        // 		return errFailed;
    }

    return errNone;
}

int SC_LID::getKeyState(int evtCode) { return TEST_BIT(m_keyState, evtCode); }

int SC_LID::getAbsInfo(int evtCode, struct input_absinfo* info) {
    if (ioctl(m_fd, EVIOCGABS(evtCode), info) == -1) {
        error("LID (9): %s\n", strerror(errno));
        return errFailed;
    }
    return errNone;
}

int SC_LID::setLedState(int evtCode, int evtValue, int evtType) { // added by marije baalman
    struct input_event ev;
    // 	post( "set led state called, putting event" );
    ev.code = evtCode;
    ev.value = evtValue;
    ev.type = evtType;
    // 	post( "m_fd %i", m_fd );
    // 	post( "code %i, value %i ", evtCode, evtValue );
    if (write(m_fd, &ev, sizeof(struct input_event)) == -1) {
        // 		post( "error writing event" );
        return errFailed;
    }
    return errNone;
}

int SC_LID::grab(int flag) {
    if (ioctl(m_fd, EVIOCGRAB, flag) == -1) {
        error("LID (10): %s\n", strerror(errno));
        return errFailed;
    }
    return errNone;
}

void SC_LID::handleEvent(struct input_event& evt, std::atomic<bool> const& shouldBeRunning) {
    if (evt.type != EV_SYN) {
        int status = lockLanguageOrQuit(shouldBeRunning);
        if (status == EINTR)
            return;
        if (status) {
            postfl("error when locking language (%d)\n", status);
            return;
        }

        if (compiledOK) {
            VMGlobals* g = gMainVMGlobals;
            g->canCallOS = false;
            ++g->sp;
            SetObject(g->sp, m_obj);
            ++g->sp;
            SetInt(g->sp, evt.type);
            ++g->sp;
            SetInt(g->sp, evt.code);
            ++g->sp;
            SetInt(g->sp, evt.value);
            runInterpreter(g, s_handleEvent, 4);
            g->canCallOS = false;
        }
        gLangMutex.unlock();
    }
}

void SC_LID::readError(std::atomic<bool> const& shouldBeRunning) {
    int status = lockLanguageOrQuit(shouldBeRunning);
    if (status == EINTR)
        return;
    if (status) {
        postfl("error when locking language (%d)\n", status);
        return;
    }

    if (compiledOK) {
        VMGlobals* g = gMainVMGlobals;
        g->canCallOS = false;
        ++g->sp;
        SetObject(g->sp, m_obj);
        runInterpreter(g, s_readError, 1);
        g->canCallOS = false;
    }
    gLangMutex.unlock();
}

// =====================================================================
// SC_LIDManager

SC_LIDManager& SC_LIDManager::instance() {
    static SC_LIDManager instance;
    return instance;
}

SC_LIDManager::SC_LIDManager(): m_running(false), m_devices(nullptr) {
    if (pipe(m_cmdFifo) == -1) {
        m_cmdFifo[0] = m_cmdFifo[1] = -1;
    }
    devicesChanged();
}

SC_LIDManager::~SC_LIDManager() {
    close(m_cmdFifo[0]);
    close(m_cmdFifo[1]);
}

int SC_LIDManager::start() {
    mShouldBeRunning = true;
    int err = pthread_create(&m_thread, nullptr, &threadFunc, this);
    if (err != 0)
        return errFailed;
    return errNone;
}

int SC_LIDManager::stop() {
    if (m_running == false)
        return errNone;

    Command cmd;

    cmd.id = kStop;
    int err = sendCommand(cmd);
    if (err)
        return err;

    mShouldBeRunning = false;
    err = pthread_join(m_thread, nullptr);
    if (err != 0)
        return errFailed;

    return errNone;
}

int SC_LIDManager::add(SC_LID* dev) {
    Command cmd;
    cmd.id = kAdd;
    cmd.arg.dev = dev;
    return sendCommand(cmd);
}

int SC_LIDManager::remove(SC_LID* dev) {
    Command cmd;
    cmd.id = kRemove;
    cmd.arg.dev = dev;
    return sendCommand(cmd);
}

int SC_LIDManager::sendCommand(const Command& cmd) {
    return write(m_cmdFifo[1], &cmd, sizeof(cmd)) == sizeof(cmd) ? errNone : errFailed;
}

void SC_LIDManager::devicesChanged() {
    int fdMax = m_cmdFifo[0];

    FD_ZERO(&m_fds);
    FD_SET(fdMax, &m_fds);

    SC_LID* dev = m_devices;
    while (dev) {
        int fd = dev->m_fd;
        if (fd != -1) {
            FD_SET(fd, &m_fds);
            if (fd > fdMax)
                fdMax = fd;
        }
        dev = dev->m_next;
    }

    m_nfds = fdMax + 1;
}

bool SC_LIDManager::asyncAddDevice(SC_LID* dev) {
    if (dev->m_next)
        return false;
    dev->m_next = m_devices;
    m_devices = dev;
    devicesChanged();
    return true;
}

bool SC_LIDManager::asyncRemoveDevice(SC_LID* dev) {
    SC_LID *prev = nullptr, *cur = m_devices;

    while (cur) {
        if (cur == dev) {
            if (prev)
                prev->m_next = dev->m_next;
            else
                m_devices = dev->m_next;
            dev->m_next = nullptr;
            delete dev;
            devicesChanged();
            return true;
        }
        prev = cur;
        cur = cur->m_next;
    }

    return false;
}

void* SC_LIDManager::threadFunc(void* arg) {
    ((SC_LIDManager*)arg)->loop();
    return nullptr;
}

void SC_LIDManager::loop() {
    m_running = true;
    post("LID: event loop started\n");

    while (true) {
        fd_set fds;
        memcpy(&fds, &m_fds, sizeof(fd_set));
        int n = select(m_nfds, &fds, nullptr, nullptr, nullptr);
        if (n == -1) {
            if (errno == EINTR)
                continue;
            post("LID: error in input handler: %s\n", strerror(errno));
            goto quit;
        } else if (n > 0) {
            if (FD_ISSET(m_cmdFifo[0], &fds)) {
                Command cmd;
                --n;
                int err = read(m_cmdFifo[0], &cmd, sizeof(cmd));
                if (err == -1) {
                    if (errno != EINTR) {
                        post("LID: error in input handler: %s\n", strerror(errno));
                        goto quit;
                    }
                } else {
                    switch (cmd.id) {
                    case kStop:
                        goto quit;
                    case kAdd:
                        if (asyncAddDevice(cmd.arg.dev)) {
                            post("LID: added device %p\n", cmd.arg);
                        } else {
                            post("LID: cannot add device\n");
                        }
                        break;
                    case kRemove:
                        if (asyncRemoveDevice(cmd.arg.dev)) {
                            post("LID: removed device %p\n", cmd.arg);
                        } else {
                            post("LID: couldn't remove device\n");
                        }
                        break;
                    default:
                        post("LID: unknown command in input handler\n");
                    }
                }
            }
            if (n > 0) {
                SC_LID* dev = m_devices;
                while (dev) {
                    int fd = dev->m_fd;
                    if (FD_ISSET(fd, &fds)) {
                        struct input_event evt;
                        if (read(fd, &evt, sizeof(evt)) == sizeof(evt)) {
                            dev->handleEvent(evt, mShouldBeRunning);
                        } else {
                            dev->readError(mShouldBeRunning);
                        }
                    }
                    if (!mShouldBeRunning)
                        goto quit;
                    dev = dev->m_next;
                }
            }
        }
    }

quit:
    m_running = false;
    post("LID: event loop stopped\n");
}

// =====================================================================
// Primitive Interface

int prLID_Open(VMGlobals* g, int numArgsPushed) {
    PyrSlot* args = g->sp - 1;
    int err;

    PyrObject* obj = SC_LID::getObject(args + 0);
    if (!obj)
        return errWrongType;

    char path[PATH_MAX];
    err = slotStrVal(args + 1, path, sizeof(path));
    if (err)
        return err;

    SC_LID* dev = new SC_LID(obj);
    err = dev->open(path);
    if (err) {
        delete dev;
        return err;
    }

    return errNone;
}

int prLID_Close(VMGlobals* g, int numArgsPushed) {
    PyrSlot* args = g->sp;

    PyrObject* obj = SC_LID::getObject(args + 0);
    if (!obj)
        return errWrongType;

    SC_LID* dev = SC_LID::getDevice(obj);
    if (!dev)
        return errFailed;

    return dev->close();
}

int prLID_EventTypeSupported(VMGlobals* g, int numArgsPushed) {
    PyrSlot* args = g->sp - 1;
    int evtType;
    int err;

    if (!g->canCallOS)
        return errCantCallOS;

    PyrObject* obj = SC_LID::getObject(args + 0);
    if (!obj)
        return errWrongType;

    err = slotIntVal(args + 1, &evtType);
    if (err)
        return err;

    SC_LID* dev = SC_LID::getDevice(obj);
    if (!dev)
        return errFailed;

    SetBool(args, dev->isEventTypeSupported(evtType));

    return errNone;
}

int prLID_EventCodeSupported(VMGlobals* g, int numArgsPushed) {
    PyrSlot* args = g->sp - 2;
    int evtType, evtCode;
    int err;

    if (!g->canCallOS)
        return errCantCallOS;

    PyrObject* obj = SC_LID::getObject(args + 0);
    if (!obj)
        return errWrongType;

    err = slotIntVal(args + 1, &evtType);
    if (err)
        return err;

    err = slotIntVal(args + 2, &evtCode);
    if (err)
        return err;

    SC_LID* dev = SC_LID::getDevice(obj);
    if (!dev)
        return errFailed;

    SetBool(args, dev->isEventCodeSupported(evtType, evtCode));

    return errNone;
}

int prLID_GetInfo(VMGlobals* g, int numArgsPushed) {
    PyrSlot* args = g->sp - 1;
    int err;

    PyrObject* obj = SC_LID::getObject(args + 0);
    if (!obj)
        return errWrongType;

    if (!isKindOfSlot(args + 1, s_inputDeviceInfoClass->u.classobj))
        return errWrongType;
    PyrObject* infoObj = slotRawObject(&args[1]);

    SC_LID* dev = SC_LID::getDevice(obj);
    if (!dev)
        return errFailed;

    char name[128];
    err = dev->getName(name, sizeof(name));
    if (err)
        return err;

    struct input_id info;
    char namePhys[128];
    char nameUniq[128];
    err = dev->getInfo(&info, namePhys, sizeof(namePhys), nameUniq, sizeof(nameUniq));
    if (err)
        return err;

    SetSymbol(infoObj->slots + 0, getsym(name));
    SetInt(infoObj->slots + 1, info.bustype);
    SetInt(infoObj->slots + 2, info.vendor);
    SetInt(infoObj->slots + 3, info.product);
    SetInt(infoObj->slots + 4, info.version);
    SetSymbol(infoObj->slots + 5, getsym(namePhys));
    SetSymbol(infoObj->slots + 6, getsym(nameUniq));

    slotCopy(&args[0], &args[1]);

    return errNone;
}

int prLID_GetKeyState(VMGlobals* g, int numArgsPushed) {
    PyrSlot* args = g->sp - 1;
    int evtCode;
    int err;

    PyrObject* obj = SC_LID::getObject(args + 0);
    if (!obj)
        return errWrongType;

    err = slotIntVal(args + 1, &evtCode);
    if (err)
        return err;

    SC_LID* dev = SC_LID::getDevice(obj);
    if (!dev)
        return errFailed;

    SetInt(args, dev->getKeyState(evtCode));

    return errNone;
}

int prLID_GetAbsInfo(VMGlobals* g, int numArgsPushed) {
    PyrSlot* args = g->sp - 2;
    int evtCode;
    int err;

    PyrObject* obj = SC_LID::getObject(args + 0);
    if (!obj)
        return errWrongType;

    err = slotIntVal(args + 1, &evtCode);
    if (err)
        return err;

    if (!isKindOfSlot(args + 2, s_absInfoClass->u.classobj))
        return errWrongType;
    PyrObject* infoObj = slotRawObject(&args[2]);

    SC_LID* dev = SC_LID::getDevice(obj);
    if (!dev)
        return errFailed;

    struct input_absinfo info;
    err = dev->getAbsInfo(evtCode, &info);
    if (err)
        return err;

    SetInt(infoObj->slots + 0, info.value);
    SetInt(infoObj->slots + 1, info.minimum);
    SetInt(infoObj->slots + 2, info.maximum);
    SetInt(infoObj->slots + 3, info.fuzz);
    SetInt(infoObj->slots + 4, info.flat);

    slotCopy(&args[0], &args[2]);

    return errNone;
}

int prLID_Grab(VMGlobals* g, int numArgsPushed) {
    PyrSlot* args = g->sp - 1;

    PyrObject* obj = SC_LID::getObject(args + 0);
    if (!obj)
        return errWrongType;

    SC_LID* dev = SC_LID::getDevice(obj);
    if (!dev)
        return errFailed;

    return dev->grab(IsTrue(args + 1));
}

int prLID_Start(VMGlobals* g, int numArgsPushed) {
    // 	if (!g->canCallOS) return errCantCallOS;
    return SC_LIDManager::instance().start();
}

int prLID_Stop(VMGlobals* g, int numArgsPushed) {
    // 	if (!g->canCallOS) return errCantCallOS;
    return SC_LIDManager::instance().stop();
}

int prLID_SetLedState(VMGlobals* g, int numArgsPushed) {
    // 	post( "set led state primitive called" );
    PyrSlot* args = g->sp - 2;
    int evtCode, evtValue;
    int err;

    PyrObject* obj = SC_LID::getObject(args + 0);
    if (!obj)
        return errWrongType;

    err = slotIntVal(args + 1, &evtCode);
    if (err)
        return err;

    err = slotIntVal(args + 2, &evtValue);
    if (err)
        return err;

    SC_LID* dev = SC_LID::getDevice(obj);
    if (!dev)
        return errFailed;

    SetInt(args, dev->setLedState(evtCode, evtValue, EV_LED));
    return errNone;
}

int prLID_SetMscState(VMGlobals* g, int numArgsPushed) {
    // 	post( "set msc state primitive called\n" );
    PyrSlot* args = g->sp - 2;
    int evtCode, evtValue;
    int err;

    PyrObject* obj = SC_LID::getObject(args + 0);
    if (!obj)
        return errWrongType;

    err = slotIntVal(args + 1, &evtCode);
    if (err)
        return err;

    err = slotIntVal(args + 2, &evtValue);
    if (err)
        return err;

    SC_LID* dev = SC_LID::getDevice(obj);
    if (!dev)
        return errFailed;

    SetInt(args, dev->setLedState(evtCode, evtValue, EV_MSC));
    return errNone;
}

void SC_LIDInit() {
    int base, index;

    s_inputDeviceClass = getsym("LID");
    s_inputDeviceInfoClass = getsym("LIDInfo");
    s_absInfoClass = getsym("LIDAbsInfo");
    s_handleEvent = getsym("prHandleEvent");
    s_readError = getsym("prReadError");

    base = nextPrimitiveIndex();
    index = 0;

    definePrimitive(base, index++, "_LID_Open", prLID_Open, 2, 0);
    definePrimitive(base, index++, "_LID_Close", prLID_Close, 1, 0);
    definePrimitive(base, index++, "_LID_EventTypeSupported", prLID_EventTypeSupported, 2, 0);
    definePrimitive(base, index++, "_LID_EventCodeSupported", prLID_EventCodeSupported, 3, 0);
    definePrimitive(base, index++, "_LID_GetInfo", prLID_GetInfo, 2, 0);
    definePrimitive(base, index++, "_LID_GetKeyState", prLID_GetKeyState, 2, 0);
    definePrimitive(base, index++, "_LID_GetAbsInfo", prLID_GetAbsInfo, 3, 0);
    definePrimitive(base, index++, "_LID_Grab", prLID_Grab, 2, 0);
    definePrimitive(base, index++, "_LID_Start", prLID_Start, 1, 0);
    definePrimitive(base, index++, "_LID_Stop", prLID_Stop, 1, 0);
    definePrimitive(base, index++, "_LID_SetLedState", prLID_SetLedState, 3, 0); // added by Marije Baalman
    definePrimitive(base, index++, "_LID_SetMscState", prLID_SetMscState, 3, 0);
}

#else // !HAVE_LID
int prLID_Start(VMGlobals* g, int numArgsPushed) { return errNone; }

int prLID_Stop(VMGlobals* g, int numArgsPushed) { return errNone; }

void SC_LIDInit() {
    int base, index;

    base = nextPrimitiveIndex();
    index = 0;

    definePrimitive(base, index++, "_LID_Start", prLID_Start, 1, 0);
    definePrimitive(base, index++, "_LID_Stop", prLID_Stop, 1, 0);
}

#endif // HAVE_LID

void initLIDPrimitives() { SC_LIDInit(); }

// EOF
