#include <QApplication>

#include "joypad.h"

//for actually interacting with the joystick devices
#include <linux/joystick.h>
#include <linux/input.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>

JoyPad::JoyPad( int i, int dev, QObject *parent )
    : QObject(parent), joydev(-1), axisCount(0), buttonCount(0), jpw(0), readNotifier(0), errorNotifier(0) {
    debug_mesg("Constructing the joypad device with index %d and fd %d\n", i, dev);
    //remember the index,
    index = i;

    //load data from the joystick device, if available.
    if (dev >= 0) {
        debug_mesg("Valid file handle, setting up handlers and reading axis configs...\n");
        open(dev);
        debug_mesg("done resetting and setting up device index %d\n", i);
    } else {
        debug_mesg("This joypad does not have a valid file handle, not setting up event listeners\n");
    }
    debug_mesg("Done constructing the joypad device %d\n", i);
}

JoyPad::~JoyPad() {
    close();
}

void JoyPad::close() {
    if (readNotifier) {
        disconnect(readNotifier, 0, 0, 0);

        readNotifier->blockSignals(true);
        readNotifier->setEnabled(false);

        delete readNotifier;
        readNotifier = 0;
    }
    if (errorNotifier) {
        disconnect(errorNotifier, 0, 0, 0);

        errorNotifier->blockSignals(true);
        errorNotifier->setEnabled(false);

        delete errorNotifier;
        errorNotifier = 0;
    }
    if (joydev >= 0) {
        if (::close(joydev) != 0) {
            debug_mesg("close(js%d %d): %s\n", index, joydev, strerror(errno));
        }
        joydev = -1;
    }
}

void JoyPad::open(int dev) {
    debug_mesg("resetting to dev\n");
    //remember the device file descriptor
    close();
    joydev = dev;

    char id[256];
    memset(id, 0, sizeof(id));
    if (ioctl(joydev, JSIOCGNAME(sizeof(id)), id) < 0) {
        deviceId = "Unknown";
    }
    else {
        deviceId = id;
    }

    //read in the number of axes / buttons
    axisCount = 0;
    ioctl (joydev, JSIOCGAXES, &axisCount);
    buttonCount = 0;
    ioctl (joydev, JSIOCGBUTTONS, &buttonCount);
    //make sure that we have the axes we need.
    //if one that we need doesn't yet exist, add it in.
    //Note: if the current layout has a key assigned to an axis that did not
    //have a real joystick axis mapped to it, and this function suddenly brings
    //that axis into use, the key assignment will not be lost because the axis
    //will already exist and no new axis will be created.
    for (int i = axes.size(); i < axisCount; i++) {
        axes.append(new Axis( i, this ));
    }
    for (int i = buttons.size(); i < buttonCount; i++) {
        buttons.append(new Button( i, this ));
    }
    debug_mesg("Setting up joyDeviceListeners\n");
    readNotifier = new QSocketNotifier(joydev, QSocketNotifier::Read, this);
    connect(readNotifier, SIGNAL(activated(int)), this, SLOT(handleJoyEvents()));
    errorNotifier = new QSocketNotifier(joydev, QSocketNotifier::Exception, this);
    connect(errorNotifier, SIGNAL(activated(int)), this, SLOT(handleJoyEvents()));
    debug_mesg("Done setting up joyDeviceListeners\n");
    debug_mesg("done resetting to dev\n");
}

const QString &JoyPad::getDeviceId() const {
    return deviceId;
}

QString JoyPad::getName() const {
    return tr("Joystick %1 (%2)").arg(index+1).arg(deviceId);
}

int JoyPad::getIndex() const {
    return index;
}

void JoyPad::toDefault() {
    //to reset the whole, reset all the parts.
    foreach (Axis *axis, axes) {
        axis->toDefault();
    }
    foreach (Button *button, buttons) {
        button->toDefault();
    }
}

bool JoyPad::isDefault() {
    //if any of the parts are not at default, then the whole isn't either.
    foreach (Axis *axis, axes) {
        if (!axis->isDefault()) return false;
    }
    foreach (Button *button, buttons) {
        if (!button->isDefault()) return false;
    }
    return true;
}

bool JoyPad::readConfig( QTextStream &stream ) {
    toDefault();

    QString word;
    QChar ch = 0;
    int num = 0;

    stream >> word;
    while (!word.isNull() && word != "}") {
        word = word.toLower();
        if (word == "button") {
            stream >> num;
            if (num > 0) {
                stream >> ch;
                if (ch != ':') {
                    errorBox(tr("Layout file error"), tr("Expected ':', found '%1'.").arg(ch));
                    return false;
                }
                for (int i = buttons.size(); i < num; ++ i) {
                    buttons.append(new Button(i, this));
                }
                if (!buttons[num-1]->read( stream )) {
                    errorBox(tr("Layout file error"), tr("Error reading Button %1").arg(num));
                    return false;
                }
            }
            else {
                stream.readLine();
            }
        }
        else if (word == "axis") {
            stream >> num;
            if (num > 0) {
                stream >> ch;
                if (ch != ':') {
                    errorBox(tr("Layout file error"), tr("Expected ':', found '%1'.").arg(ch));
                    return false;
                }
                for (int i = axes.size(); i < num; ++ i) {
                    axes.append(new Axis(i, this));
                }
                if (!axes[num-1]->read(stream)) {
                    errorBox(tr("Layout file error"), tr("Error reading Axis %1").arg(num));
                    return false;
                }
            }
        }
        else {
            errorBox(tr("Layout file error"), tr("Error while reading layout. Unrecognized word: %1").arg(word));
            return false;
        }
        stream >> word;
    }
    return true;
}

//only actually writes something if this JoyPad is NON DEFAULT.
void JoyPad::write( QTextStream &stream ) {
    if (!axes.empty() || !buttons.empty()) {
        stream << "Joystick " << (index+1) << " {\n";
        foreach (Axis *axis, axes) {
            if (!axis->isDefault()) {
                axis->write(stream);
            }
        }
        foreach (Button *button, buttons) {
            if (!button->isDefault()) {
                button->write(stream);
            }
        }
        stream << "}\n\n";
    }
}

void JoyPad::release() {
    foreach (Axis *axis, axes) {
        axis->release();
    }
    foreach (Button *button, buttons) {
        button->release();
    }
}

void JoyPad::jsevent(const js_event &msg) {
    //if there is a JoyPadWidget around, ie, if the joypad is being edited
    if (jpw != NULL && hasFocus) {
        //tell the dialog there was an event. It will use this to flash
        //the appropriate button, if necesary.
        jpw->jsevent(msg);
        return;
    }
    //if the dialog is open, stop here. We don't want to signal ourselves with
    //the input we generate.
    if (qApp->activeWindow() != 0 && qApp->activeModalWidget() != 0) return;

    //otherwise, lets create us a fake event! Pass on the event to whichever
    //Button or Axis was pressed and let them decide what to do with it.
    unsigned int type = msg.type & ~JS_EVENT_INIT;
    if (type == JS_EVENT_AXIS) {
        debug_mesg("DEBUG: passing on an axis event\n");
        debug_mesg("DEBUG: %d %d\n", msg.number, msg.value);
        if (msg.number < axes.size()) axes[msg.number]->jsevent(msg.value);
        else debug_mesg("DEBUG: axis index out of range: %d\n", msg.value);
    }
    else if (type == JS_EVENT_BUTTON) {
        debug_mesg("DEBUG: passing on a button event\n");
        debug_mesg("DEBUG: %d %d\n", msg.number, msg.value);
        if (msg.number < buttons.size()) buttons[msg.number]->jsevent(msg.value);
        else debug_mesg("DEBUG: button index out of range: %d\n", msg.value);
    }
}

JoyPadWidget* JoyPad::widget( QWidget* parent, int i) {
    //create the widget and remember it.
    jpw = new JoyPadWidget(this, i, parent);
    return jpw;
}

void JoyPad::handleJoyEvents() {
    js_event msg;
    ssize_t len = read(joydev, &msg, sizeof(js_event));
    //if there was a real event waiting,
    if (len == sizeof(js_event)) {
        //pass that event on to the joypad!
        jsevent(msg);
    }
}

void JoyPad::releaseWidget() {
    //this is how we know that there is no longer a JoyPadWidget around.
    jpw = 0;
}

void JoyPad::errorRead() {
    debug_mesg("There was an error reading off of the device with fd %d, disabling\n", joydev);
    close();
    debug_mesg("Done disabling device with fd %d\n", joydev);
}

void JoyPad::focusChange(bool focusState) {
	hasFocus = !focusState;
}
