/*  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 "KeyboardQueue.h"
#include "Config.h"

InputDeviceManager *dm = 0;

InputDeviceManager::InputDeviceManager()
{
    memset(this, 0, sizeof(*this));
}

void InputDeviceManager::ClearDevices()
{
    for (int i = 0; i < numDevices; i++) {
        delete devices[i];
    }
    free(devices);
    devices = 0;
    numDevices = 0;
}

InputDeviceManager::~InputDeviceManager()
{
    ClearDevices();
}

Device::Device(DeviceAPI api, DeviceType d, const wchar_t *displayName, const wchar_t *instanceID, const wchar_t *productID)
{
    memset(pads, 0, sizeof(pads));
    this->api = api;
    type = d;
    this->displayName = wcsdup(displayName);
    if (instanceID)
        this->instanceID = wcsdup(instanceID);
    else
        this->instanceID = wcsdup(displayName);
    this->productID = 0;
    if (productID)
        this->productID = wcsdup(productID);
    active = 0;
    attached = 1;
    enabled = 0;

#ifdef _MSC_VER
    hWndProc = 0;
#endif

    virtualControls = 0;
    numVirtualControls = 0;
    virtualControlState = 0;
    oldVirtualControlState = 0;

    physicalControls = 0;
    numPhysicalControls = 0;
    physicalControlState = 0;

    ffEffectTypes = 0;
    numFFEffectTypes = 0;
    ffAxes = 0;
    numFFAxes = 0;
}

void Device::FreeState()
{
    if (virtualControlState)
        free(virtualControlState);
    virtualControlState = 0;
    oldVirtualControlState = 0;
    physicalControlState = 0;
}

Device::~Device()
{
    Deactivate();
    // Generally called by deactivate, but just in case...
    FreeState();
    int i;
    for (int port = 0; port < 2; port++) {
        for (int slot = 0; slot < 4; slot++) {
            for (int padtype = 0; padtype < numPadTypes; padtype++) {
                free(pads[port][slot][padtype].bindings);
                for (i = 0; i < pads[port][slot][padtype].numFFBindings; i++) {
                    free(pads[port][slot][padtype].ffBindings[i].axes);
                }
                free(pads[port][slot][padtype].ffBindings);
            }
        }
    }
    free(virtualControls);

    for (i = numPhysicalControls - 1; i >= 0; i--) {
        if (physicalControls[i].name)
            free(physicalControls[i].name);
    }
    free(physicalControls);

    free(displayName);
    free(instanceID);
    free(productID);
    if (ffAxes) {
        for (i = 0; i < numFFAxes; i++) {
            free(ffAxes[i].displayName);
        }
        free(ffAxes);
    }
    if (ffEffectTypes) {
        for (i = 0; i < numFFEffectTypes; i++) {
            free(ffEffectTypes[i].displayName);
            free(ffEffectTypes[i].effectID);
        }
        free(ffEffectTypes);
    }
}

void Device::AddFFEffectType(const wchar_t *displayName, const wchar_t *effectID, EffectType type)
{
    ffEffectTypes = (ForceFeedbackEffectType *)realloc(ffEffectTypes, sizeof(ForceFeedbackEffectType) * (numFFEffectTypes + 1));
    ffEffectTypes[numFFEffectTypes].displayName = wcsdup(displayName);
    ffEffectTypes[numFFEffectTypes].effectID = wcsdup(effectID);
    ffEffectTypes[numFFEffectTypes].type = type;
    numFFEffectTypes++;
}

void Device::AddFFAxis(const wchar_t *displayName, int id)
{
    ffAxes = (ForceFeedbackAxis *)realloc(ffAxes, sizeof(ForceFeedbackAxis) * (numFFAxes + 1));
    ffAxes[numFFAxes].id = id;
    ffAxes[numFFAxes].displayName = wcsdup(displayName);
    numFFAxes++;
    int bindingsExist = 0;
    for (int port = 0; port < 2; port++) {
        for (int slot = 0; slot < 4; slot++) {
            for (int padtype = 0; padtype < numPadTypes; padtype++) {
                for (int i = 0; i < pads[port][slot][padtype].numFFBindings; i++) {
                    ForceFeedbackBinding *b = pads[port][slot][padtype].ffBindings + i;
                    b->axes = (AxisEffectInfo *)realloc(b->axes, sizeof(AxisEffectInfo) * (numFFAxes));
                    memset(b->axes + (numFFAxes - 1), 0, sizeof(AxisEffectInfo));
                    bindingsExist = 1;
                }
            }
        }
    }
    // Generally the case when not loading a binding file.
    if (!bindingsExist) {
        int i = numFFAxes - 1;
        ForceFeedbackAxis temp = ffAxes[i];
        while (i && temp.id < ffAxes[i - 1].id) {
            ffAxes[i] = ffAxes[i - 1];
            i--;
        }
        ffAxes[i] = temp;
    }
}

void Device::AllocState()
{
    FreeState();
    virtualControlState = (int *)calloc(numVirtualControls + numVirtualControls + numPhysicalControls, sizeof(int));
    oldVirtualControlState = virtualControlState + numVirtualControls;
    physicalControlState = oldVirtualControlState + numVirtualControls;
}

void Device::FlipState()
{
    memcpy(oldVirtualControlState, virtualControlState, sizeof(int) * numVirtualControls);
}

void Device::PostRead()
{
    FlipState();
}

void Device::CalcVirtualState()
{
    for (int i = 0; i < numPhysicalControls; i++) {
        PhysicalControl *c = physicalControls + i;
        int index = c->baseVirtualControlIndex;
        int val = physicalControlState[i];
        if (c->type & BUTTON) {
            virtualControlState[index] = val;
            // DirectInput keyboard events only.
            if (this->api == DI && this->type == KEYBOARD) {
                if (!(virtualControlState[index] >> 15) != !(oldVirtualControlState[index] >> 15) && c->vkey) {
                    // Check for alt-F4 to avoid toggling skip mode incorrectly.
                    if (c->vkey == VK_F4) {
                        int i;
                        for (i = 0; i < numPhysicalControls; i++) {
                            if (virtualControlState[physicalControls[i].baseVirtualControlIndex] &&
                                (physicalControls[i].vkey == VK_MENU ||
                                 physicalControls[i].vkey == VK_RMENU ||
                                 physicalControls[i].vkey == VK_LMENU)) {
                                break;
                            }
                        }
                        if (i < numPhysicalControls)
                            continue;
                    }
                    int event = KEYPRESS;
                    if (!(virtualControlState[index] >> 15))
                        event = KEYRELEASE;
                    QueueKeyEvent(c->vkey, event);
                }
            }
        } else if (c->type & ABSAXIS) {
            virtualControlState[index] = (val + FULLY_DOWN) / 2;
            // Positive.  Overkill.
            virtualControlState[index + 1] = (val & ~(val >> 31));
            // Negative
            virtualControlState[index + 2] = (-val & (val >> 31));
        } else if (c->type & RELAXIS) {
            int delta = val - oldVirtualControlState[index];
            virtualControlState[index] = val;
            // Positive
            virtualControlState[index + 1] = (delta & ~(delta >> 31));
            // Negative
            virtualControlState[index + 2] = (-delta & (delta >> 31));
        } else if (c->type & POV) {
            virtualControlState[index] = val;
            int iSouth = 0;
            int iEast = 0;
            if ((unsigned int)val <= 37000) {
                double angle = val * (3.141592653589793 / 18000.0);
                double East = sin(angle);
                double South = -cos(angle);
                // Normalize so greatest direction is 1.
                double mul = FULLY_DOWN / std::max(fabs(South), fabs(East));

                iEast = (int)floor(East * mul + 0.5);
                iSouth = (int)floor(South * mul + 0.5);
            }
            // N
            virtualControlState[index + 1] = (-iSouth & (iSouth >> 31));
            // S
            virtualControlState[index + 3] = (iSouth & ~(iSouth >> 31));
            // E
            virtualControlState[index + 2] = (iEast & ~(iEast >> 31));
            // W
            virtualControlState[index + 4] = (-iEast & (iEast >> 31));
        }
    }
}

VirtualControl *Device::GetVirtualControl(unsigned int uid)
{
    for (int i = 0; i < numVirtualControls; i++) {
        if (virtualControls[i].uid == uid)
            return virtualControls + i;
    }
    return 0;
}

VirtualControl *Device::AddVirtualControl(unsigned int uid, int physicalControlIndex)
{
    // Not really necessary, as always call AllocState when activated, but doesn't hurt.
    FreeState();

    if (numVirtualControls % 16 == 0) {
        virtualControls = (VirtualControl *)realloc(virtualControls, sizeof(VirtualControl) * (numVirtualControls + 16));
    }
    VirtualControl *c = virtualControls + numVirtualControls;

    c->uid = uid;
    c->physicalControlIndex = physicalControlIndex;

    numVirtualControls++;
    return c;
}

PhysicalControl *Device::AddPhysicalControl(ControlType type, unsigned short id, unsigned short vkey, const wchar_t *name)
{
    // Not really necessary, as always call AllocState when activated, but doesn't hurt.
    FreeState();

    if (numPhysicalControls % 16 == 0) {
        physicalControls = (PhysicalControl *)realloc(physicalControls, sizeof(PhysicalControl) * (numPhysicalControls + 16));
    }
    PhysicalControl *control = physicalControls + numPhysicalControls;

    memset(control, 0, sizeof(PhysicalControl));
    control->type = type;
    control->id = id;
    if (name)
        control->name = wcsdup(name);
    control->baseVirtualControlIndex = numVirtualControls;
    unsigned int uid = id | (type << 16);
    if (type & BUTTON) {
        AddVirtualControl(uid, numPhysicalControls);
        control->vkey = vkey;
    } else if (type & AXIS) {
        AddVirtualControl(uid | UID_AXIS, numPhysicalControls);
        AddVirtualControl(uid | UID_AXIS_POS, numPhysicalControls);
        AddVirtualControl(uid | UID_AXIS_NEG, numPhysicalControls);
    } else if (type & POV) {
        AddVirtualControl(uid | UID_POV, numPhysicalControls);
        AddVirtualControl(uid | UID_POV_N, numPhysicalControls);
        AddVirtualControl(uid | UID_POV_E, numPhysicalControls);
        AddVirtualControl(uid | UID_POV_S, numPhysicalControls);
        AddVirtualControl(uid | UID_POV_W, numPhysicalControls);
    }
    numPhysicalControls++;
    return control;
}

void Device::SetEffects(unsigned char port, unsigned int slot, unsigned char motor, unsigned char force)
{
    int padtype = config.padConfigs[port][slot].type;
    for (int i = 0; i < pads[port][slot][padtype].numFFBindings; i++) {
        ForceFeedbackBinding *binding = pads[port][slot][padtype].ffBindings + i;
        if (binding->motor == motor) {
            SetEffect(binding, force);
        }
    }
}

wchar_t *GetDefaultControlName(unsigned short id, int type)
{
    static wchar_t name[20];
    if (type & BUTTON) {
        wsprintfW(name, L"Button %i", id);
    } else if (type & AXIS) {
        wsprintfW(name, L"Axis %i", id);
    } else if (type & POV) {
        wsprintfW(name, L"POV %i", id);
    } else {
        wcscpy(name, L"Unknown");
    }
    return name;
}

wchar_t *Device::GetVirtualControlName(VirtualControl *control)
{
    static wchar_t temp[100];
    wchar_t *baseName = 0;
    if (control->physicalControlIndex >= 0) {
        baseName = physicalControls[control->physicalControlIndex].name;
        if (!baseName)
            baseName = GetPhysicalControlName(&physicalControls[control->physicalControlIndex]);
    }
    unsigned int uid = control->uid;
    if (!baseName) {
        baseName = GetDefaultControlName(uid & 0xFFFF, (uid >> 16) & 0x1F);
    }
    uid &= 0xFF000000;
    int len = (int)wcslen(baseName);
    if (len > 99)
        len = 99;
    memcpy(temp, baseName, len * sizeof(wchar_t));
    temp[len] = 0;
    if (uid) {
        if (len > 95)
            len = 95;
        wchar_t *out = temp + len;
        if (uid == UID_AXIS_POS) {
            wcscpy(out, L" +");
        } else if (uid == UID_AXIS_NEG) {
            wcscpy(out, L" -");
        } else if (uid == UID_POV_N) {
            wcscpy(out, L" N");
        } else if (uid == UID_POV_E) {
            wcscpy(out, L" E");
        } else if (uid == UID_POV_S) {
            wcscpy(out, L" S");
        } else if (uid == UID_POV_W) {
            wcscpy(out, L" W");
        }
    }
    return temp;
}

wchar_t *Device::GetPhysicalControlName(PhysicalControl *control)
{
    if (control->name)
        return control->name;
    return GetDefaultControlName(control->id, control->type);
}

void InputDeviceManager::AddDevice(Device *d)
{
    devices = (Device **)realloc(devices, sizeof(Device *) * (numDevices + 1));
    devices[numDevices++] = d;
}

void InputDeviceManager::Update(InitInfo *info)
{
    for (int i = 0; i < numDevices; i++) {
        if (devices[i]->enabled) {
            if (!devices[i]->active) {
                if (!devices[i]->Activate(info) || !devices[i]->Update())
                    continue;
                devices[i]->CalcVirtualState();
                devices[i]->PostRead();
            }
            if (devices[i]->Update())
                devices[i]->CalcVirtualState();
        }
    }
}

void InputDeviceManager::PostRead()
{
    for (int i = 0; i < numDevices; i++) {
        if (devices[i]->active)
            devices[i]->PostRead();
    }
}

Device *InputDeviceManager::GetActiveDevice(InitInfo *info, unsigned int *uid, int *index, int *value)
{
    int i, j;
    Update(info);
    int bestDiff = FULLY_DOWN / 2;
    Device *bestDevice = 0;
    for (i = 0; i < numDevices; i++) {
        if (devices[i]->active) {
            for (j = 0; j < devices[i]->numVirtualControls; j++) {
                if (devices[i]->virtualControlState[j] == devices[i]->oldVirtualControlState[j])
                    continue;
                if (devices[i]->virtualControls[j].uid & UID_POV)
                    continue;
                // Fix for releasing button used to click on bind button
                if (!((devices[i]->virtualControls[j].uid >> 16) & (POV | RELAXIS | ABSAXIS))) {
                    if (abs(devices[i]->oldVirtualControlState[j]) > abs(devices[i]->virtualControlState[j])) {
                        devices[i]->oldVirtualControlState[j] = 0;
                    }
                }
                int diff = abs(devices[i]->virtualControlState[j] - devices[i]->oldVirtualControlState[j]);
                // Make it require a bit more work to bind relative axes.
                if (((devices[i]->virtualControls[j].uid >> 16) & 0xFF) == RELAXIS) {
                    diff = diff / 4 + 1;
                }
                // Less pressure needed to bind DS3/SCP buttons.
                if ((devices[i]->api == DS3 || devices[i]->api == XINPUT) && (((devices[i]->virtualControls[j].uid >> 16) & 0xFF) & BUTTON)) {
                    diff *= 4;
                }
                if (diff > bestDiff) {
                    if (devices[i]->virtualControls[j].uid & UID_AXIS) {
                        if ((((devices[i]->virtualControls[j].uid >> 16) & 0xFF) != ABSAXIS))
                            continue;
                        // Very picky when binding entire axes.  Prefer binding half-axes.
                        if (!((devices[i]->oldVirtualControlState[j] < FULLY_DOWN / 32 && devices[i]->virtualControlState[j] > FULLY_DOWN / 8) ||
                              (devices[i]->oldVirtualControlState[j] > 31 * FULLY_DOWN / 32 && devices[i]->virtualControlState[j] < 7 * FULLY_DOWN / 8))) {
                            continue;
                        }
                    } else if ((((devices[i]->virtualControls[j].uid >> 16) & 0xFF) == ABSAXIS)) {
                        if (devices[i]->oldVirtualControlState[j] > 15 * FULLY_DOWN / 16)
                            continue;
                    }
                    bestDiff = diff;
                    *uid = devices[i]->virtualControls[j].uid;
                    *index = j;
                    bestDevice = devices[i];
                    if (value) {
                        if ((devices[i]->virtualControls[j].uid >> 16) & RELAXIS) {
                            *value = devices[i]->virtualControlState[j] - devices[i]->oldVirtualControlState[j];
                        } else {
                            *value = devices[i]->virtualControlState[j];
                        }
                    }
                }
            }
        }
    }
    // Don't call when binding.
    // PostRead();
    return bestDevice;
}

void InputDeviceManager::ReleaseInput()
{
    for (int i = 0; i < numDevices; i++) {
        if (devices[i]->active)
            devices[i]->Deactivate();
    }
}

void InputDeviceManager::EnableDevices(DeviceType type, DeviceAPI api)
{
    for (int i = 0; i < numDevices; i++) {
        if (devices[i]->api == api && devices[i]->type == type) {
            EnableDevice(i);
        }
    }
}

void InputDeviceManager::DisableAllDevices()
{
    for (int i = 0; i < numDevices; i++) {
        DisableDevice(i);
    }
}

void InputDeviceManager::DisableDevice(int index)
{
    devices[index]->enabled = 0;
    if (devices[index]->active) {
        devices[index]->Deactivate();
    }
}

ForceFeedbackEffectType *Device::GetForcefeedbackEffect(wchar_t *id)
{
    for (int i = 0; i < numFFEffectTypes; i++) {
        if (!wcsicmp(id, ffEffectTypes[i].effectID)) {
            return &ffEffectTypes[i];
        }
    }
    return 0;
}

ForceFeedbackAxis *Device::GetForceFeedbackAxis(int id)
{
    for (int i = 0; i < numFFAxes; i++) {
        if (ffAxes[i].id == id)
            return &ffAxes[i];
    }
    return 0;
}

void InputDeviceManager::CopyBindings(int numOldDevices, Device **oldDevices)
{
    int *oldMatches = (int *)malloc(sizeof(int) * numOldDevices);
    int *matches = (int *)malloc(sizeof(int) * numDevices);
    int i, j, port, slot;
    Device *old, *dev;
    for (i = 0; i < numDevices; i++) {
        matches[i] = -1;
    }
    for (i = 0; i < numOldDevices; i++) {
        oldMatches[i] = -2;
        old = oldDevices[i];
        for (port = 0; port < 2; port++) {
            for (slot = 0; slot < 4; slot++) {
                for (int padtype = 0; padtype < numPadTypes; padtype++) {
                    if (old->pads[port][slot][padtype].numBindings + old->pads[port][slot][padtype].numFFBindings) {
                        // Means that there are bindings.
                        oldMatches[i] = -1;
                    }
                }
            }
        }
    }
    // Loops through ids looking for match, from most specific to most general.
    for (int id = 0; id < 3; id++) {
        for (i = 0; i < numOldDevices; i++) {
            if (oldMatches[i] >= 0)
                continue;
            for (j = 0; j < numDevices; j++) {
                if (matches[j] >= 0) {
                    continue;
                }
                wchar_t *id1 = devices[j]->IDs[id];
                wchar_t *id2 = oldDevices[i]->IDs[id];
                if (!id1 || !id2) {
                    continue;
                }
                if (!wcsicmp(id1, id2)) {
                    matches[j] = i;
                    oldMatches[i] = j;
                    break;
                }
            }
        }
    }

    for (i = 0; i < numOldDevices; i++) {
        if (oldMatches[i] == -2)
            continue;
        old = oldDevices[i];
        if (oldMatches[i] < 0) {
            dev = new Device(old->api, old->type, old->displayName, old->instanceID, old->productID);
            dev->attached = 0;
            AddDevice(dev);
            for (j = 0; j < old->numVirtualControls; j++) {
                VirtualControl *c = old->virtualControls + j;
                dev->AddVirtualControl(c->uid, -1);
            }
            for (j = 0; j < old->numFFEffectTypes; j++) {
                ForceFeedbackEffectType *effect = old->ffEffectTypes + j;
                dev->AddFFEffectType(effect->displayName, effect->effectID, effect->type);
            }
            for (j = 0; j < old->numFFAxes; j++) {
                ForceFeedbackAxis *axis = old->ffAxes + j;
                dev->AddFFAxis(axis->displayName, axis->id);
            }
            // Just steal the old bindings directly when there's no matching device.
            // Indices will be the same.
            memcpy(dev->pads, old->pads, sizeof(old->pads));
            memset(old->pads, 0, sizeof(old->pads));
        } else {
            dev = devices[oldMatches[i]];
            for (port = 0; port < 2; port++) {
                for (slot = 0; slot < 4; slot++) {
                    for (int padtype = 0; padtype < numPadTypes; padtype++) {
                        if (old->pads[port][slot][padtype].numBindings) {
                            dev->pads[port][slot][padtype].bindings = (Binding *)malloc(old->pads[port][slot][padtype].numBindings * sizeof(Binding));
                            for (int j = 0; j < old->pads[port][slot][padtype].numBindings; j++) {
                                Binding *bo = old->pads[port][slot][padtype].bindings + j;
                                Binding *bn = dev->pads[port][slot][padtype].bindings + dev->pads[port][slot][padtype].numBindings;
                                VirtualControl *cn = dev->GetVirtualControl(old->virtualControls[bo->controlIndex].uid);
                                if (cn) {
                                    *bn = *bo;
                                    bn->controlIndex = cn - dev->virtualControls;
                                    dev->pads[port][slot][padtype].numBindings++;
                                }
                            }
                        }
                        if (old->pads[port][slot][padtype].numFFBindings) {
                            dev->pads[port][slot][padtype].ffBindings = (ForceFeedbackBinding *)malloc(old->pads[port][slot][padtype].numFFBindings * sizeof(ForceFeedbackBinding));
                            for (int j = 0; j < old->pads[port][slot][padtype].numFFBindings; j++) {
                                ForceFeedbackBinding *bo = old->pads[port][slot][padtype].ffBindings + j;
                                ForceFeedbackBinding *bn = dev->pads[port][slot][padtype].ffBindings + dev->pads[port][slot][padtype].numFFBindings;
                                ForceFeedbackEffectType *en = dev->GetForcefeedbackEffect(old->ffEffectTypes[bo->effectIndex].effectID);
                                if (en) {
                                    *bn = *bo;
                                    bn->effectIndex = en - dev->ffEffectTypes;
                                    bn->axes = (AxisEffectInfo *)calloc(dev->numFFAxes, sizeof(AxisEffectInfo));
                                    for (int k = 0; k < old->numFFAxes; k++) {
                                        ForceFeedbackAxis *newAxis = dev->GetForceFeedbackAxis(old->ffAxes[k].id);
                                        if (newAxis) {
                                            bn->axes[newAxis - dev->ffAxes] = bo->axes[k];
                                        }
                                    }
                                    dev->pads[port][slot][padtype].numFFBindings++;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    free(oldMatches);
    free(matches);
}

void InputDeviceManager::SetEffect(unsigned char port, unsigned int slot, unsigned char motor, unsigned char force)
{
    for (int i = 0; i < numDevices; i++) {
        Device *dev = devices[i];
        if (dev->enabled && dev->numFFEffectTypes) {
            dev->SetEffects(port, slot, motor, force);
        }
    }
}
