/*  LilyPad - Pad plugin for PS2 Emulator
 *  Copyright (C) 2002-2014  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 "Config.h"

#include "usb.h"
#include "HidDevice.h"


#define VID 0x054c
#define PID 0x0268

// Unresponsive period required before calling DS3Check().
#define DEVICE_CHECK_DELAY 2000
// Unresponsive period required before calling DS3Enum().  Note that enum is always called on first check.
#define DEVICE_ENUM_DELAY 10000

// Delay between when DS3Check() and DS3Enum() actually do stuff.
#define DOUBLE_CHECK_DELAY 1000
#define DOUBLE_ENUM_DELAY 20000

// Send at least one message every 3 seconds - basically just makes sure the right light(s) are on.
// Not really necessary.
#define UPDATE_INTERVAL 3000

unsigned int lastDS3Check = 0;
unsigned int lastDS3Enum = 0;

typedef void(__cdecl *_usb_init)(void);
typedef int(__cdecl *_usb_close)(usb_dev_handle *dev);
typedef int(__cdecl *_usb_get_string_simple)(usb_dev_handle *dev, int index, char *buf, size_t buflen);
typedef usb_dev_handle *(__cdecl *_usb_open)(struct usb_device *dev);
typedef int(__cdecl *_usb_find_busses)(void);
typedef int(__cdecl *_usb_find_devices)(void);
typedef struct usb_bus *(__cdecl *_usb_get_busses)(void);
typedef usb_dev_handle *(__cdecl *_usb_open)(struct usb_device *dev);
typedef int(__cdecl *_usb_control_msg)(usb_dev_handle *dev, int requesttype, int request, int value, int index, char *bytes, int size, int timeout);

_usb_init pusb_init;
_usb_close pusb_close;
_usb_get_string_simple pusb_get_string_simple;
_usb_open pusb_open;
_usb_find_busses pusb_find_busses;
_usb_find_devices pusb_find_devices;
_usb_get_busses pusb_get_busses;
_usb_control_msg pusb_control_msg;

HMODULE hModLibusb = 0;

void UninitLibUsb()
{
    if (hModLibusb) {
        FreeLibrary(hModLibusb);
        hModLibusb = 0;
    }
}

void TryInitDS3(usb_device *dev)
{
    while (dev) {
        if (dev->descriptor.idVendor == VID && dev->descriptor.idProduct == PID) {
            usb_dev_handle *handle = pusb_open(dev);
            if (handle) {
                char junk[20];
                // This looks like HidD_GetFeature with a feature report id of 0xF2 to me and a length of 17.
                // That doesn't work, however, and 17 is shorter than the report length.
                pusb_control_msg(handle, 0xa1, 1, 0x03f2, dev->config->interface->altsetting->bInterfaceNumber, junk, 17, 1000);
                pusb_close(handle);
            }
        }
        if (dev->num_children) {
            for (int i = 0; i < dev->num_children; i++) {
                TryInitDS3(dev->children[i]);
            }
        }
        dev = dev->next;
    }
}

void DS3Enum(unsigned int time)
{
    if (time - lastDS3Enum < DOUBLE_ENUM_DELAY) {
        return;
    }
    lastDS3Enum = time;
    pusb_find_busses();
    pusb_find_devices();
}

void DS3Check(unsigned int time)
{
    if (time - lastDS3Check < DOUBLE_CHECK_DELAY) {
        return;
    }
    if (!lastDS3Check) {
        DS3Enum(time);
    }
    lastDS3Check = time;

    usb_bus *bus = pusb_get_busses();
    while (bus) {
        TryInitDS3(bus->devices);
        bus = bus->next;
    }
}

int InitLibUsb()
{
    if (hModLibusb) {
        return 1;
    }
    hModLibusb = LoadLibraryA("C:\\windows\\system32\\libusb0.dll");
    if (hModLibusb) {
        if ((pusb_init = (_usb_init)GetProcAddress(hModLibusb, "usb_init")) &&
            (pusb_close = (_usb_close)GetProcAddress(hModLibusb, "usb_close")) &&
            (pusb_get_string_simple = (_usb_get_string_simple)GetProcAddress(hModLibusb, "usb_get_string_simple")) &&
            (pusb_open = (_usb_open)GetProcAddress(hModLibusb, "usb_open")) &&
            (pusb_find_busses = (_usb_find_busses)GetProcAddress(hModLibusb, "usb_find_busses")) &&
            (pusb_find_devices = (_usb_find_devices)GetProcAddress(hModLibusb, "usb_find_devices")) &&
            (pusb_get_busses = (_usb_get_busses)GetProcAddress(hModLibusb, "usb_get_busses")) &&
            (pusb_control_msg = (_usb_control_msg)GetProcAddress(hModLibusb, "usb_control_msg"))) {
            pusb_init();
            return 1;
        }
        UninitLibUsb();
    }
    return 0;
}

int DualShock3Possible()
{
    return InitLibUsb();
}

#include <pshpack1.h>

struct MotorState
{
    unsigned char duration;
    unsigned char force;
};

struct LightState
{
    // 0xFF makes it stay on.
    unsigned char duration;
    // Have to make one or the other non-zero to turn on light.
    unsigned char dunno[2];
    // 0 is fully lit.
    unsigned char dimness;
    // Have to make non-zero to turn on light.
    unsigned char on;
};

// Data sent to DS3 to set state.
struct DS3Command
{
    unsigned char id;
    unsigned char unsure;
    // Small is first, then big.
    MotorState motors[2];
    unsigned char noClue[4];
    // 2 is pad 1 light, 4 is pad 2, 8 is pad 3, 16 is pad 4.  No clue about the others.
    unsigned char lightFlags;
    // Lights are in reverse order.  pad 1 is last.
    LightState lights[4];
    unsigned char dunno[18];
};

#include <poppack.h>

int CharToAxis(unsigned char c)
{
    int v = (int)c + ((unsigned int)c >> 7);
    return ((c - 128) * FULLY_DOWN) >> 7;
}

int CharToButton(unsigned char c)
{
    int v = (int)c + ((unsigned int)c >> 7);
    return (v * FULLY_DOWN) >> 8;
}

class DualShock3Device : public Device
{
    // Cached last vibration values by pad and motor.
    // Need this, as only one value is changed at a time.
    int ps2Vibration[2][4][2];
    int vibration[2];

public:
    int index;
    HANDLE hFile;
    DS3Command sendState;
    unsigned char getState[49];
    OVERLAPPED readop;
    OVERLAPPED writeop;
    int writeCount;
    int lastWrite;

    unsigned int dataLastReceived;

    int writeQueued;
    int writing;

    int StartRead()
    {
        int res = ReadFile(hFile, &getState, sizeof(getState), 0, &readop);
        return (res || GetLastError() == ERROR_IO_PENDING);
    }

    void QueueWrite()
    {
        // max of 2 queued writes allowed, one for either motor.
        if (writeQueued < 2) {
            writeQueued++;
            StartWrite();
        }
    }

    int StartWrite()
    {
        if (!writing && writeQueued) {
            lastWrite = GetTickCount();
            writing++;
            writeQueued--;
            sendState.motors[0].duration = 0x50;
            sendState.motors[1].duration = 0x50;

            int bigForce = vibration[0] * 256 / FULLY_DOWN;
            if (bigForce > 255)
                bigForce = 255;
            sendState.motors[1].force = (unsigned char)bigForce;
            sendState.motors[0].force = (unsigned char)(vibration[1] >= FULLY_DOWN / 2);
            // Can't seem to have them both non-zero at once.
            if (sendState.motors[writeCount & 1].force) {
                sendState.motors[(writeCount & 1) ^ 1].force = 0;
                sendState.motors[(writeCount & 1) ^ 1].duration = 0;
            }

            writeCount++;
            int res = WriteFile(hFile, &sendState, sizeof(sendState), 0, &writeop);
            return (res || GetLastError() == ERROR_IO_PENDING);
        }
        return 1;
    }

    DualShock3Device(int index, wchar_t *name, wchar_t *path)
        : Device(DS3, OTHER, name, path, L"DualShock 3")
    {
        writeCount = 0;
        writing = 0;
        writeQueued = 0;
        memset(&readop, 0, sizeof(readop));
        memset(&writeop, 0, sizeof(writeop));
        memset(&sendState, 0, sizeof(sendState));
        sendState.id = 1;
        int temp = (index & 4);
        sendState.lightFlags = (1 << (temp + 1));
        sendState.lights[3 - temp].duration = 0xFF;
        sendState.lights[3 - temp].dunno[0] = 1;
        sendState.lights[3 - temp].on = 1;
        memset(ps2Vibration, 0, sizeof(ps2Vibration));
        vibration[0] = vibration[1] = 0;
        this->index = index;
        int i;
        for (i = 0; i < 16; i++) {
            if (i != 14 && i != 15 && i != 8 && i != 9) {
                AddPhysicalControl(PRESSURE_BTN, i, 0);
            } else {
                AddPhysicalControl(PSHBTN, i, 0);
            }
        }
        for (; i < 23; i++) {
            AddPhysicalControl(ABSAXIS, i, 0);
        }
        AddFFAxis(L"Big Motor", 0);
        AddFFAxis(L"Small Motor", 1);
        AddFFEffectType(L"Constant Effect", L"Constant", EFFECT_CONSTANT);
        hFile = INVALID_HANDLE_VALUE;
    }

    wchar_t *GetPhysicalControlName(PhysicalControl *c)
    {
        const static wchar_t *names[] = {
            L"Square",
            L"Cross",
            L"Circle",
            L"Triangle",
            L"R1",
            L"L1",
            L"R2",
            L"L2",
            L"R3",
            L"L3",
            L"Left",
            L"Down",
            L"Right",
            L"Up",
            L"Start",
            L"Select",
            L"L-Stick X",
            L"L-Stick Y",
            L"R-Stick X",
            L"R-Stick Y",
            L"Left/Right Tilt",
            L"Forward/Back Tilt",
            L"???",
        };
        unsigned int i = (unsigned int)(c - physicalControls);
        if (i < sizeof(names) / sizeof(names[0])) {
            return (wchar_t *)names[i];
        }
        return Device::GetPhysicalControlName(c);
    }

    int Activate(InitInfo *initInfo)
    {
        if (active)
            Deactivate();
        // Give grace period before get mad.
        lastWrite = dataLastReceived = GetTickCount();
        readop.hEvent = CreateEvent(0, 0, 0, 0);
        writeop.hEvent = CreateEvent(0, 0, 0, 0);
        hFile = CreateFileW(instanceID, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
        if (!readop.hEvent || !writeop.hEvent || hFile == INVALID_HANDLE_VALUE ||
            !StartRead()) {
            Deactivate();
            return 0;
        }
        active = 1;
        AllocState();
        return 1;
    }

    int Update()
    {
        if (!active)
            return 0;
        HANDLE h[2] = {
            readop.hEvent,
            writeop.hEvent};
        unsigned int time = GetTickCount();
        if (time - lastWrite > UPDATE_INTERVAL) {
            QueueWrite();
        }
        while (1) {
            DWORD res = WaitForMultipleObjects(2, h, 0, 0);
            if (res == WAIT_OBJECT_0) {
                dataLastReceived = time;
                if (!StartRead()) {
                    Deactivate();
                    return 0;
                }

                physicalControlState[0] = CharToButton(getState[25]);
                physicalControlState[1] = CharToButton(getState[24]);
                physicalControlState[2] = CharToButton(getState[23]);
                physicalControlState[3] = CharToButton(getState[22]);
                physicalControlState[4] = CharToButton(getState[21]);
                physicalControlState[5] = CharToButton(getState[20]);
                physicalControlState[6] = CharToButton(getState[19]);
                physicalControlState[7] = CharToButton(getState[18]);
                physicalControlState[10] = CharToButton(getState[17]);
                physicalControlState[11] = CharToButton(getState[16]);
                physicalControlState[12] = CharToButton(getState[15]);
                physicalControlState[13] = CharToButton(getState[14]);
                physicalControlState[8] = ((getState[2] & 4) / 4) * FULLY_DOWN;
                physicalControlState[9] = ((getState[2] & 2) / 2) * FULLY_DOWN;
                physicalControlState[15] = ((getState[2] & 1) / 1) * FULLY_DOWN;
                physicalControlState[14] = ((getState[2] & 8) / 8) * FULLY_DOWN;
                physicalControlState[16] = CharToAxis(getState[6]);
                physicalControlState[17] = CharToAxis(getState[7]);
                physicalControlState[18] = CharToAxis(getState[8]);
                physicalControlState[19] = CharToAxis(getState[9]);
                physicalControlState[20] = CharToAxis(getState[42] + 128);
                physicalControlState[21] = CharToAxis(getState[44] + 128);
                physicalControlState[22] = CharToAxis(getState[46] + 128);
                continue;
            } else if (res == WAIT_OBJECT_0 + 1) {
                writing = 0;
                if (!writeQueued && (vibration[0] | vibration[1])) {
                    QueueWrite();
                }
                if (!StartWrite()) {
                    Deactivate();
                    return 0;
                }
            } else {
                if (time - dataLastReceived >= DEVICE_CHECK_DELAY) {
                    if (time - dataLastReceived >= DEVICE_ENUM_DELAY) {
                        DS3Enum(time);
                    }
                    DS3Check(time);
                    QueueWrite();
                }
            }
            break;
        }
        return 1;
    }

    void SetEffects(unsigned char port, unsigned int slot, unsigned char motor, unsigned char force)
    {
        ps2Vibration[port][slot][motor] = force;
        vibration[0] = vibration[1] = 0;
        for (int p = 0; p < 2; p++) {
            for (int s = 0; s < 4; s++) {
                int padtype = config.padConfigs[p][s].type;
                for (int i = 0; i < pads[p][s][padtype].numFFBindings; i++) {
                    // Technically should also be a *65535/BASE_SENSITIVITY, but that's close enough to 1 for me.
                    ForceFeedbackBinding *ffb = &pads[p][s][padtype].ffBindings[i];
                    vibration[0] += (int)((ffb->axes[0].force * (__int64)ps2Vibration[p][s][ffb->motor]) / 255);
                    vibration[1] += (int)((ffb->axes[1].force * (__int64)ps2Vibration[p][s][ffb->motor]) / 255);
                }
            }
        }
        // Make sure at least 2 writes are queued, to update both motors.
        QueueWrite();
        QueueWrite();
    }

    void SetEffect(ForceFeedbackBinding *binding, unsigned char force)
    {
        PadBindings pBackup = pads[0][0][0];
        pads[0][0][0].ffBindings = binding;
        pads[0][0][0].numFFBindings = 1;
        SetEffects(0, 0, binding->motor, 255);
        pads[0][0][0] = pBackup;
    }

    void Deactivate()
    {
        if (hFile != INVALID_HANDLE_VALUE) {
            CancelIo(hFile);
            CloseHandle(hFile);
            hFile = INVALID_HANDLE_VALUE;
        }
        if (readop.hEvent) {
            CloseHandle(readop.hEvent);
        }
        if (writeop.hEvent) {
            CloseHandle(writeop.hEvent);
        }
        writing = 0;
        writeQueued = 0;
        memset(ps2Vibration, 0, sizeof(ps2Vibration));
        vibration[0] = vibration[1] = 0;

        FreeState();
        active = 0;
    }

    ~DualShock3Device()
    {
    }
};

void EnumDualShock3s()
{
    if (!InitLibUsb())
        return;

    HidDeviceInfo *foundDevs = 0;

    int numDevs = FindHids(&foundDevs, VID, PID);
    if (!numDevs)
        return;
    int index = 0;
    for (int i = 0; i < numDevs; i++) {
        if (foundDevs[i].caps.FeatureReportByteLength == 49 &&
            foundDevs[i].caps.InputReportByteLength == 49 &&
            foundDevs[i].caps.OutputReportByteLength == 49) {
            wchar_t temp[100];
            wsprintfW(temp, L"DualShock 3 #%i", index + 1);
            dm->AddDevice(new DualShock3Device(index, temp, foundDevs[i].path));
            index++;
        }
        free(foundDevs[i].path);
    }
    free(foundDevs);
}