/* antimicrox Gamepad to KB+M event mapper
 * Copyright (C) 2015 Travis Nickles <nickles.travis@gmail.com>
 * Copyright (C) 2020 Jagoda Górska <juliagoda.pl@protonmail>
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include "joybutton.h"

#include "event.h"
#include "inputdevice.h"
#include "logger.h"
#include "setjoystick.h"
#include "vdpad.h"

#include "SDL2/SDL_events.h"
#include "eventhandlerfactory.h"

#include <QDebug>
//#include <QThread>
#include <QSharedPointer>
#include <QStringList>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QtConcurrent>
#include <chrono>

const JoyButton::JoyMouseCurve JoyButton::DEFAULTMOUSECURVE = JoyButton::EnhancedPrecisionCurve;
const JoyButton::SetChangeCondition JoyButton::DEFAULTSETCONDITION = JoyButton::SetChangeDisabled;
const JoyButton::JoyMouseMovementMode JoyButton::DEFAULTMOUSEMODE = JoyButton::MouseCursor;
const JoyButton::TurboMode JoyButton::DEFAULTTURBOMODE = JoyButton::NormalTurbo;
const JoyButton::JoyExtraAccelerationCurve JoyButton::DEFAULTEXTRAACCELCURVE = JoyButton::LinearAccelCurve;

JoyButtonSlot *JoyButton::lastActiveKey = nullptr;

// Keep track of active Mouse Speed Mod slots.
QList<JoyButtonSlot *> JoyButton::mouseSpeedModList;

// Lists used for cursor mode calculations.
QList<JoyButton::mouseCursorInfo> JoyButton::cursorXSpeeds;
QList<JoyButton::mouseCursorInfo> JoyButton::cursorYSpeeds;

// Lists used for spring mode calculations.
QList<PadderCommon::springModeInfo> JoyButton::springXSpeeds;
QList<PadderCommon::springModeInfo> JoyButton::springYSpeeds;

// Temporary test object to test old mouse time behavior.
QElapsedTimer JoyButton::testOldMouseTime;

// time when minislots next to each other in thread pool are waiting to execute function
// at the same time
int JoyButton::timeBetweenMiniSlots = 55;

int JoyButton::allSlotTimeBetweenSlots = 0;

// Helper object to have a single mouse event for all JoyButton
// instances.
JoyButtonMouseHelper JoyButton::mouseHelper;

QTimer JoyButton::staticMouseEventTimer;
QList<JoyButton *> JoyButton::pendingMouseButtons;

// IT CAN BE HERE
// LOOK FOR JoyCycle and put JoyMix next to the slots types
JoyButton::JoyButton(int sdl_button_index, int originset, SetJoystick *parentSet, QObject *parent)
    : QObject(parent)
{
    m_vdpad = nullptr;
    slotiter = nullptr;

    threadPool = QThreadPool::globalInstance();

    turboTimer.setParent(this);
    pauseTimer.setParent(this);
    holdTimer.setParent(this);
    pauseWaitTimer.setParent(this);
    createDeskTimer.setParent(this);
    releaseDeskTimer.setParent(this);
    mouseWheelVerticalEventTimer.setParent(this);
    mouseWheelHorizontalEventTimer.setParent(this);
    setChangeTimer.setParent(this);
    keyPressTimer.setParent(this);
    delayTimer.setParent(this);
    slotSetChangeTimer.setParent(this);
    setChangeTimer.setSingleShot(true);
    slotSetChangeTimer.setSingleShot(true);
    m_parentSet = parentSet;

    connect(&pauseWaitTimer, &QTimer::timeout, this, &JoyButton::pauseWaitEvent);
    connect(&keyPressTimer, &QTimer::timeout, this, &JoyButton::keyPressEvent);
    connect(&holdTimer, &QTimer::timeout, this, &JoyButton::holdEvent);
    connect(&delayTimer, &QTimer::timeout, this, &JoyButton::delayEvent);
    connect(&createDeskTimer, &QTimer::timeout, this, &JoyButton::waitForDeskEvent);
    connect(&releaseDeskTimer, &QTimer::timeout, this, &JoyButton::waitForReleaseDeskEvent);
    connect(&turboTimer, &QTimer::timeout, this, &JoyButton::turboEvent);
    connect(&mouseWheelVerticalEventTimer, &QTimer::timeout, this, &JoyButton::wheelEventVertical);
    connect(&mouseWheelHorizontalEventTimer, &QTimer::timeout, this, &JoyButton::wheelEventHorizontal);
    connect(&setChangeTimer, &QTimer::timeout, this, &JoyButton::checkForSetChange);
    connect(&slotSetChangeTimer, &QTimer::timeout, this, &JoyButton::slotSetChange);

    // Will only matter on the first call
    establishMouseTimerConnections();

    // Make sure to call before calling reset
    resetAllProperties();

    m_index_sdl = sdl_button_index;
    m_originset = originset;
    quitEvent = true;
    DEBUG() << "Created button with ID: " << m_index_sdl << " For set: " << originset << " Name: " << getName();
}

JoyButton::~JoyButton()
{ // threadPool->clear();

    reset();
    // resetPrivVars();
}

void JoyButton::queuePendingEvent(bool pressed, bool ignoresets)
{
    updatePendingParams(false, false, false);

    if (m_vdpad != nullptr)
        vdpadPassEvent(pressed, ignoresets);
    else
        updatePendingParams(true, pressed, ignoresets);
}

void JoyButton::activatePendingEvent()
{
    if (pendingEvent)
    {
        joyEvent(pendingPress, pendingIgnoreSets);
        updatePendingParams(false, false, false);
    }
}

bool JoyButton::hasPendingEvent() { return pendingEvent; }

void JoyButton::clearPendingEvent() { updatePendingParams(false, false, false); }

void JoyButton::vdpadPassEvent(bool pressed, bool ignoresets)
{
    if ((m_vdpad != nullptr) && (pressed != isButtonPressed))
    {
        isButtonPressed = pressed;

        if (isButtonPressed)
            emit clicked(m_index_sdl);
        else
            emit released(m_index_sdl);

        if (!ignoresets)
            m_vdpad->queueJoyEvent(ignoresets);
        else
            m_vdpad->joyEvent(pressed, ignoresets);
    }
}

/**
 * @brief Activates mapped slots and generates QT events
 *  which highlight pressed controller buttons.
 */
void JoyButton::joyEvent(bool pressed, bool ignoresets)
{
    if (Logger::isDebugEnabled())
        DEBUG() << "Processing JoyButton::joyEvent for: " << getName() << " SDL index: " << m_index_sdl
                << " className: " << metaObject()->className();

    if ((m_vdpad != nullptr) && !pendingEvent)
    {
        vdpadPassEvent(pressed, ignoresets);
    } else if (ignoreEvents && (pressed != isButtonPressed))
    {
        isButtonPressed = pressed;
        if (isButtonPressed)
            emit clicked(m_index_sdl);
        else
            emit released(m_index_sdl);
    } else
    {
        if (pressed != isDown)
        {
            if (pressed)
            {
                emit clicked(m_index_sdl);
                if (updateInitAccelValues)
                    oldAccelMulti = updateOldAccelMulti = accelTravel = 0.0;
            } else
            {
                emit released(m_index_sdl);
            }

            bool activePress = pressed;
            setChangeTimer.stop();

            if (m_toggle && pressed)
            {
                isDown = true;
                toggleActiveState = !toggleActiveState;

                if (!isButtonPressed)
                {
                    m_ignoresets = ignoresets;
                    isButtonPressed = !isButtonPressed;

                    ignoreSetQueue.enqueue(ignoresets);
                    isButtonPressedQueue.enqueue(isButtonPressed);
                } else
                {
                    activePress = false;
                }
            } else if (m_toggle && !pressed && isDown)
            {
                isDown = false;

                if (!toggleActiveState)
                {
                    m_ignoresets = ignoresets;
                    isButtonPressed = !isButtonPressed;

                    ignoreSetQueue.enqueue(ignoresets);
                    isButtonPressedQueue.enqueue(isButtonPressed);
                }
            } else
            {
                m_ignoresets = ignoresets;
                isButtonPressed = isDown = pressed;

                ignoreSetQueue.enqueue(ignoresets);
                isButtonPressedQueue.enqueue(isButtonPressed);
            }

            if (m_useTurbo)
            {
                if (isButtonPressed && activePress && !turboTimer.isActive())
                {
                    startSequenceOfPressActive(true, tr("Processing turbo for #%1 - %2"));
                    turboEvent();
                } else if (!isButtonPressed && !activePress && turboTimer.isActive())
                {
                    turboTimer.stop();

                    Q_ASSERT(!m_parentSet.isNull());
                    qDebug() << tr("Finishing turbo for button #%1 - %2")
                                    .arg(m_parentSet->getInputDevice()->getRealJoyNumber())
                                    .arg(getPartialName());

                    if (isKeyPressed)
                        turboEvent();
                    else
                        lastDistance = getMouseDistanceFromDeadZone();
                }
            }
            // Toogle is enabled and a controller button change has occurred.
            // Switch to a different distance zone if appropriate
            else if (m_toggle && !activePress && isButtonPressed)
            {
                updateParamsAfterDistEvent();
            } else if (isButtonPressed && activePress)
            {
                startSequenceOfPressActive(false, "Processing press for button #%1 - %2");

                if (!keyPressTimer.isActive())
                {
                    checkForPressedSetChange();

                    if (!setChangeTimer.isActive())
                        waitForDeskEvent();
                }
            } else if (!isButtonPressed && !activePress)
            {
                qDebug() << QString("Processing release for button #%1 - %2")
                                .arg(m_parentSet->getInputDevice()->getRealJoyNumber())
                                .arg(getPartialName());

                waitForReleaseDeskEvent();
            }

            if (updateInitAccelValues)
                updateMouseParams(false, false, 0.0);
        } else if (!m_useTurbo && isButtonPressed)
        {
            resetAccelerationDistances();
            currentAccelerationDistance = getAccelerationDistance();

            if (!setChangeTimer.isActive())
                updateParamsAfterDistEvent();
        }
    }

    updateInitAccelValues = true;
}

void JoyButton::updateParamsAfterDistEvent()
{
    if (distanceEvent())
    {
        qDebug() << tr("Distance change for button #%1 - %2")
                        .arg(m_parentSet->getInputDevice()->getRealJoyNumber())
                        .arg(getPartialName());

        quitEvent = true;
        buttonHold.restart();
        buttonHeldRelease.restart();
        keyPressHold.restart();
        releaseDeskTimer.stop();

        if (!keyPressTimer.isActive())
            waitForDeskEvent();
    }
}

void JoyButton::startSequenceOfPressActive(bool isTurbo, QString debugText)
{
    if (cycleResetActive && (cycleResetHold.elapsed() >= cycleResetInterval) && (slotiter != nullptr))
    {
        slotiter->toFront();
        currentCycle = nullptr;
        previousCycle = nullptr;
    }

    buttonHold.restart();
    buttonHeldRelease.restart();
    keyPressHold.restart();
    cycleResetHold.restart();
    if (isTurbo)
        turboTimer.start();
    else
        releaseDeskTimer.stop();

    // Newly activated button. Just entered safe zone.
    if (updateInitAccelValues)
        initializeDistanceValues();

    currentAccelerationDistance = getAccelerationDistance();

    Q_ASSERT(!m_parentSet.isNull());
    qDebug() << debugText.arg(m_parentSet->getInputDevice()->getRealJoyNumber()).arg(getPartialName());
}

/**
 * @brief Get 0 indexed number of SDL button index
 * @return 0 indexed button index number
 */
int JoyButton::getJoyNumber() { return m_index_sdl; }

/**
 * @brief Get a 1 indexed number of button
 * @return 1 indexed button index number
 */
int JoyButton::getRealJoyNumber() const { return m_index_sdl + 1; }

void JoyButton::setJoyNumber(int index) { m_index_sdl = index; }

void JoyButton::setToggle(bool toggle)
{
    if (toggle != m_toggle)
    {
        m_toggle = toggle;
        emit toggleChanged(toggle);
        emit propertyUpdated();
    }
}

void JoyButton::setTurboInterval(int interval)
{
    if ((interval < 10) && (interval != this->turboInterval))
    {
        this->setUseTurbo(false);
        interval = 0;
    }

    this->turboInterval = interval;
    emit turboIntervalChanged(interval);
    emit propertyUpdated();
}

void JoyButton::reset() { resetPrivVars(); }

void JoyButton::resetPrivVars()
{
    disconnectPropertyUpdatedConnections();
    stopTimers(true);
    releaseActiveSlots();
    clearAssignedSlots();
    clearQueues();
    resetAllProperties(); // quitEvent changed here
}

void JoyButton::reset(int index)
{
    JoyButton::reset();
    m_index_sdl = index;
}

bool JoyButton::getToggleState() { return m_toggle; }

int JoyButton::getTurboInterval() { return turboInterval; }

void JoyButton::turboEvent() { changeTurboParams(isKeyPressed, isButtonPressed); }

void JoyButton::changeTurboParams(bool _isKeyPressed, bool isButtonPressed)
{
    if (!isButtonPressedQueue.isEmpty())
    {
        if (!_isKeyPressed)
        {
            ignoreSetQueue.clear();
            isButtonPressedQueue.clear();
            isButtonPressedQueue.enqueue(isButtonPressed);
        } else
        {
            isButtonPressedQueue.enqueue(!isButtonPressed);
        }

        ignoreSetQueue.enqueue(false);
    }

    if (!_isKeyPressed)
        createDeskEvent();
    else
        releaseDeskEvent();
    isKeyPressed = !_isKeyPressed;

    if (turboTimer.isActive())
    {
        int tempInterval = turboInterval / 2;

        if (turboTimer.interval() != tempInterval)
            turboTimer.start(tempInterval);
    }
}

bool JoyButton::distanceEvent()
{
    bool released = false;

    if (slotiter != nullptr)
    {
        QReadLocker tempLocker(&assignmentsLock);

        if (containsDistanceSlots())
        {
            double currentDistance = getDistanceFromDeadZone();
            double tempDistance = 0.0;
            JoyButtonSlot *previousDistanceSlot = nullptr;
            QListIterator<JoyButtonSlot *> iter(*getAssignedSlots());

            if (previousCycle != nullptr)
            {
                qDebug() << "find previous Cycle in next steps in assignments and skip to it";

                iter.findNext(previousCycle);
            }

            while (iter.hasNext())
            {
                JoyButtonSlot *slot = iter.next();
                int tempcode = slot->getSlotCode();

                if (slot->getSlotMode() == JoyButtonSlot::JoyDistance)
                {
                    tempDistance += (tempcode / 100.0);

                    if (currentDistance < tempDistance)
                        iter.toBack();
                    else
                        previousDistanceSlot = slot;
                } else if (slot->getSlotMode() == JoyButtonSlot::JoyCycle)
                {
                    tempDistance = 0.0;
                    iter.toBack();
                }
            }

            // No applicable distance slot
            if (!previousDistanceSlot)
            {
                if (m_currentDistance != nullptr)
                {
                    // Distance slot is currently active.
                    // Release slots, return iterator to
                    // the front, and nullify currentDistance
                    pauseWaitTimer.stop();
                    holdTimer.stop();

                    // Release stuff
                    releaseActiveSlots();
                    currentPause = currentHold = nullptr;

                    slotiter->toFront();
                    if (previousCycle != nullptr)
                        slotiter->findNext(previousCycle);

                    m_currentDistance = nullptr;
                    released = true;
                }
            }
            // An applicable distance slot was found
            else
            {
                if (m_currentDistance != previousDistanceSlot)
                {
                    // Active distance slot is not the applicable slot.
                    // Deactive slots in previous distance range and
                    // activate new slots. Set currentDistance to
                    // new slot.
                    pauseWaitTimer.stop();
                    holdTimer.stop();

                    // Release stuff
                    releaseActiveSlots();
                    currentPause = currentHold = nullptr;

                    qDebug() << "Deactive slots in previous range and activate new slots";

                    slotiter->toFront();

                    if (previousCycle != nullptr)
                    {
                        qDebug() << "Find previous Cycle in slotiter starting from beginning";

                        slotiter->findNext(previousCycle);
                    }

                    slotiter->findNext(previousDistanceSlot);

                    m_currentDistance = previousDistanceSlot;
                    released = true;
                }
            }
        }
    }

    return released;
}

void JoyButton::createDeskEvent()
{
    quitEvent = false;

    if (slotiter == nullptr)
    {
        assignmentsLock.lockForRead();
        slotiter = new QListIterator<JoyButtonSlot *>(*getAssignedSlots());
        assignmentsLock.unlock();

        distanceEvent();
    } else if (!slotiter->hasPrevious())
    {
        distanceEvent();
    } else if (currentCycle != nullptr)
    {
        currentCycle = nullptr;
        distanceEvent();
    }

    assignmentsLock.lockForRead();
    activateSlots();
    assignmentsLock.unlock();

    if (currentCycle != nullptr)
        quitEvent = true;
    else if ((currentPause == nullptr) && (currentHold == nullptr) && !keyPressTimer.isActive())
        quitEvent = true;
}

void JoyButton::activateSlots()
{
    bool countForAllTime = false;

    if (allSlotTimeBetweenSlots == 0)
        countForAllTime = true;

    if (slotiter != nullptr)
    {
        QWriteLocker tempLocker(&activeZoneLock);

        bool exit = false;
        bool delaySequence = false;

        int i = 0;

        while (slotiter->hasNext() && !exit)
        {
            JoyButtonSlot *slot = slotiter->next();

            if (slot->getSlotMode() == JoyButtonSlot::JoyMix)
            {
                qDebug() << "JOYMIX IN ACTIVATESLOTS";

                if (slot->getMixSlots() != nullptr)
                {
                    QListIterator<JoyButtonSlot *> mini_it(*slot->getMixSlots());
                    QListIterator<JoyButtonSlot *> *it(&mini_it);

                    int countMinis = slot->getMixSlots()->count();
                    int timeX = countMinis;

                    std::chrono::time_point<std::chrono::high_resolution_clock> t1, t2;
                    t1 = std::chrono::high_resolution_clock::now();

                    while (it->hasNext())
                    {
                        JoyButtonSlot *slotmini = it->next();
                        qDebug() << "Run activated mini slot - name - deviceCode - mode: " << slotmini->getSlotString()
                                 << " - " << slotmini->getSlotCode() << " - " << slotmini->getSlotMode();

                        MiniSlotRun *minijob = new MiniSlotRun(slot, slotmini, this, timeBetweenMiniSlots * timeX);

                        minijob->setAutoDelete(false);

                        threadPool->start(minijob);

                        timeX--;

                        if (timeBetweenMiniSlots == 55)
                        {
                            t2 = std::chrono::high_resolution_clock::now();
                            timeBetweenMiniSlots = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
                        }
                    }

                    if (countForAllTime)
                    {
                        allSlotTimeBetweenSlots += countMinis * timeBetweenMiniSlots;
                    }

                    threadPool->waitForDone();

                    i++;
                    if (!slotiter->hasNext())
                        break;
                }
            } else
            {
                qDebug() << "Check now simple slots";
                addEachSlotToActives(slot, i, delaySequence, exit, slotiter);
            }
        }

        if (delaySequence && !getActiveSlots().isEmpty())
        {
            keyPressHold.restart();
            keyPressEvent();
        }
    }
}

void JoyButton::activateMiniSlots(JoyButtonSlot *slot, JoyButtonSlot *mix)
{
    int tempcode = slot->getSlotCode();
    JoyButtonSlot::JoySlotInputAction mode = slot->getSlotMode();

    if (mode == JoyButtonSlot::JoyKeyboard)
    {
        sendKeybEvent(slot, true);

        getActiveSlotsLocal().append(slot);
        int oldvalue = GlobalVariables::JoyButton::JoyButton::activeKeys.value(tempcode, 0) + 1;
        GlobalVariables::JoyButton::JoyButton::activeKeys.insert(tempcode, oldvalue);

        if (!slot->isModifierKey())
        {
            qDebug() << "There has been assigned a lastActiveKey " << slot->getSlotString();

            lastActiveKey = mix;
        } else
        {
            qDebug() << "It's not modifier key. lastActiveKey is null pointer";

            lastActiveKey = nullptr;
        }
    }
}

void JoyButton::addEachSlotToActives(JoyButtonSlot *slot, int &i, bool &delaySequence, bool &exit,
                                     QListIterator<JoyButtonSlot *> *slotiter)
{
    int tempcode = slot->getSlotCode();
    JoyButtonSlot::JoySlotInputAction mode = slot->getSlotMode();

    switch (mode)
    {
    case JoyButtonSlot::JoyKeyboard: {
        i++;

        qDebug() << i << ": It's a JoyKeyboard with code: " << tempcode << " and name: " << slot->getSlotString();

        sendevent(slot, true);

        getActiveSlotsLocal().append(slot);
        int oldvalue = GlobalVariables::JoyButton::JoyButton::activeKeys.value(tempcode, 0) + 1;
        GlobalVariables::JoyButton::JoyButton::activeKeys.insert(tempcode, oldvalue);

        if (!slot->isModifierKey())
        {
            qDebug() << "There has been assigned a lastActiveKey " << slot->getSlotString();

            lastActiveKey = slot;
        } else
        {
            qDebug() << "It's not modifier key. lastActiveKey is null pointer";

            lastActiveKey = nullptr;
        }

        break;
    }
    case JoyButtonSlot::JoyMouseButton: {
        i++;

        qDebug() << i << ": It's a JoyMouseButton with code: " << tempcode << " and name: " << slot->getSlotString();

        if ((tempcode == static_cast<int>(JoyButtonSlot::MouseWheelUp)) ||
            (tempcode == static_cast<int>(JoyButtonSlot::MouseWheelDown)))
        {
            slot->getMouseInterval()->restart();
            wheelVerticalTime.restart();
            currentWheelVerticalEvent = slot;
            getActiveSlotsLocal().append(slot);
            wheelEventVertical();
            currentWheelVerticalEvent = nullptr;
        } else if ((tempcode == static_cast<int>(JoyButtonSlot::MouseWheelLeft)) ||
                   (tempcode == static_cast<int>(JoyButtonSlot::MouseWheelRight)))
        {
            slot->getMouseInterval()->restart();
            wheelHorizontalTime.restart();
            currentWheelHorizontalEvent = slot;
            getActiveSlotsLocal().append(slot);
            wheelEventHorizontal();
            currentWheelHorizontalEvent = nullptr;
        } else
        {
            sendevent(slot, true);
            getActiveSlotsLocal().append(slot);
            int oldvalue = GlobalVariables::JoyButton::JoyButton::activeMouseButtons.value(tempcode, 0) + 1;
            GlobalVariables::JoyButton::JoyButton::activeMouseButtons.insert(tempcode, oldvalue);
        }

        break;
    }
    case JoyButtonSlot::JoyMouseMovement: {
        i++;

        qDebug() << i << ": It's a JoyMouseMovement with code: " << tempcode << " and name: " << slot->getSlotString();

        slot->getMouseInterval()->restart();

        getActiveSlotsLocal().append(slot);

        if (pendingMouseButtons.size() == 0)
            mouseHelper.setFirstSpringStatus(true);

        pendingMouseButtons.append(this);
        mouseEventQueue.enqueue(slot);

        // Temporarily lower timer interval. Helps improve mouse control
        // precision on the lower end of an axis.
        if (!staticMouseEventTimer.isActive() || (staticMouseEventTimer.interval() != 0))
        {
            if (!staticMouseEventTimer.isActive() ||
                (staticMouseEventTimer.interval() == GlobalVariables::JoyButton::IDLEMOUSEREFRESHRATE))
            {
                int tempRate =
                    qBound(0, GlobalVariables::JoyButton::mouseRefreshRate - GlobalVariables::JoyButton::gamepadRefreshRate,
                           GlobalVariables::JoyButton::MAXIMUMMOUSEREFRESHRATE);
                staticMouseEventTimer.start(tempRate);
                testOldMouseTime.restart();
                accelExtraDurationTime.restart();
            }
        }

        break;
    }
    case JoyButtonSlot::JoyPause: {
        i++;

        qDebug() << i << ": It's a JoyPause with code: " << tempcode << " and name: " << slot->getSlotString();

        if (!getActiveSlots().isEmpty())
        {
            qDebug() << "active slots QHash is not empty";

            if (slotiter->hasPrevious())
            {
                i--;
                slotiter->previous();
            }

            delaySequence = true;
            exit = true;
        }

        // Segment can be ignored on a 0 interval pause
        else if (tempcode > 0)
        {
            qDebug() << "active slots QHash is empty";

            currentPause = slot;
            pauseHold.restart();
            inpauseHold.restart();
            pauseWaitTimer.start(0);
            exit = true;
        }

        break;
    }
    case JoyButtonSlot::JoyHold: {
        i++;

        qDebug() << i << ": It's a JoyHold with code: " << tempcode << " and name: " << slot->getSlotString();

        currentHold = slot;
        holdTimer.start(0);
        exit = true;
        break;
    }
    case JoyButtonSlot::JoyDelay: {
        i++;

        qDebug() << i << ": It's a JoyDelay with code: " << tempcode << " and name: " << slot->getSlotString();

        currentDelay = slot;
        buttonDelay.restart();
        delayTimer.start(0);
        exit = true;
        break;
    }
    case JoyButtonSlot::JoyCycle: {
        i++;

        qDebug() << i << ": It's a JoyCycle with code: " << tempcode << " and name: " << slot->getSlotString();

        currentCycle = slot;
        exit = true;
        break;
    }
    case JoyButtonSlot::JoyDistance: {
        i++;

        qDebug() << i << ": It's a JoyDistance with code: " << tempcode << " and name: " << slot->getSlotString();

        exit = true;
        break;
    }
    case JoyButtonSlot::JoyRelease: {
        i++;

        qDebug() << i << ": It's a JoyRelease with code: " << tempcode << " and name: " << slot->getSlotString();

        if (currentRelease == nullptr)
        {
            findJoySlotsEnd(slotiter);
        } else if ((currentRelease != nullptr) && getActiveSlots().isEmpty())
        {
            qDebug() << "current is release but activeSlots is empty";

            exit = true;
        } else if ((currentRelease != nullptr) && !getActiveSlots().isEmpty())
        {
            qDebug() << "current is release and activeSlots is not empty";

            if (slotiter->hasPrevious())
            {
                qDebug() << "Back to previous slotiter from release";

                i--;
                slotiter->previous();
            }

            delaySequence = true;
            exit = true;
        }

        break;
    }
    case JoyButtonSlot::JoyMouseSpeedMod: {
        i++;

        qDebug() << i << ": It's a JoyMouseSpeedMod with code: " << tempcode << " and name: " << slot->getSlotString();

        GlobalVariables::JoyButton::mouseSpeedModifier = tempcode * 0.01;
        mouseSpeedModList.append(slot);
        getActiveSlotsLocal().append(slot);

        break;
    }
    case JoyButtonSlot::JoyKeyPress: {
        i++;

        qDebug() << i << ": It's a JoyKeyPress with code: " << tempcode << " and name: " << slot->getSlotString();

        if (getActiveSlots().isEmpty())
        {
            qDebug() << "activeSlots is empty. It's a true delaySequence and assigned currentKeyPress";

            delaySequence = true;
            currentKeyPress = slot;
        } else
        {
            qDebug() << "activeSlots is not empty. It's a true delaySequence and exit";

            if (slotiter->hasPrevious())
            {
                qDebug() << "Back to previous slotiter from JoyKeyPress";

                i--;
                slotiter->previous();
            }

            delaySequence = true;
            exit = true;
        }

        break;
    }
    case JoyButtonSlot::JoyLoadProfile: {
        i++;

        qDebug() << i << ": It's a JoyLoadProfile with code: " << tempcode << " and name: " << slot->getSlotString();

        releaseActiveSlots();
        slotiter->toBack();
        exit = true;

        QString location = slot->getTextData();

        if (!location.isEmpty())
            m_parentSet->getInputDevice()->sendLoadProfileRequest(location);

        break;
    }
    case JoyButtonSlot::JoySetChange: {
        i++;

        qDebug() << i << ": It's a JoySetChange with code: " << tempcode << " and name: " << slot->getSlotString();

        getActiveSlotsLocal().append(slot);

        break;
    }
    case JoyButtonSlot::JoyTextEntry:
    case JoyButtonSlot::JoyExecute: {
        i++;

        qDebug() << i << ": It's a JoyExecute or JoyTextEntry with code: " << tempcode
                 << " and name: " << slot->getSlotString();

        sendevent(slot, true);

        break;
    }
    default:
        break;
    }
}

void JoyButton::slotSetChange()
{
    if (currentSetChangeSlot != nullptr)
    {
        // Get set change slot and then remove reference.
        int setChangeIndex = currentSetChangeSlot->getSlotCode();
        currentSetChangeSlot = nullptr;

        // Ensure that a change to the current set is not attempted.
        if (setChangeIndex != m_originset)
        {
            emit released(m_index_sdl);
            emit setChangeActivated(setChangeIndex);
        }
    }
}

/**
 * @brief Calculate mouse movement coordinates for mouse movement slots
 *     currently active.
 */
void JoyButton::mouseEvent()
{
    JoyButtonSlot *buttonslot = nullptr;
    bool singleShot = false;

    if (currentMouseEvent != nullptr)
    {
        buttonslot = currentMouseEvent;
        singleShot = true;
    }

    if ((buttonslot != nullptr) || !mouseEventQueue.isEmpty())
    {
        updateMouseParams(true, true, 0.0);

        QQueue<JoyButtonSlot *> tempQueue;

        if (buttonslot == nullptr)
            buttonslot = mouseEventQueue.dequeue();

        int timeElapsed = testOldMouseTime.elapsed();

        // Presumed initial mouse movement. Use full duration rather than
        // partial.
        if (staticMouseEventTimer.interval() < GlobalVariables::JoyButton::mouseRefreshRate)
        {
            timeElapsed = GlobalVariables::JoyButton::mouseRefreshRate + (timeElapsed - staticMouseEventTimer.interval());
        }

        while (buttonslot != nullptr)
        {
            QElapsedTimer *mouseInterval = buttonslot->getMouseInterval();

            int mousedirection = buttonslot->getSlotCode();
            JoyButton::JoyMouseMovementMode mousemode = getMouseMode();

            bool isActive = getActiveSlots().contains(buttonslot);

            if (isActive)
            {
                int mousespeed = 0;

                if (mousemode == JoyButton::MouseCursor)
                {
                    switch (mousedirection)
                    {
                    case 1:
                    case 2:
                        mousespeed = mouseSpeedY;
                        break;

                    case 3:
                    case 4:
                        mousespeed = mouseSpeedX;
                        break;
                    }

                    double difference = getMouseDistanceFromDeadZone();
                    double mouse1 = 0;
                    double mouse2 = 0;
                    double sumDist = buttonslot->getMouseDistance();
                    JoyMouseCurve currentCurve = getMouseCurve();

                    switch (currentCurve)
                    {
                    case QuadraticCurve: {
                        difference = difference * difference;
                        break;
                    }
                    case CubicCurve: {
                        difference = difference * difference * difference;
                        break;
                    }
                    case QuadraticExtremeCurve: {
                        double temp = difference;
                        difference = difference * difference;
                        difference = (temp >= 0.95) ? (difference * 1.5) : difference;
                        break;
                    }
                    case PowerCurve: {
                        double tempsensitive = qMin(qMax(sensitivity, 1.0e-3), 1.0e+3);
                        double temp = qMin(qMax(pow(difference, 1.0 / tempsensitive), 0.0), 1.0);
                        difference = temp;
                        break;
                    }
                    case EnhancedPrecisionCurve: {
                        // Perform different forms of acceleration depending on
                        // the range of the element from its assigned dead zone.
                        // Useful for more precise controls with an axis.
                        double temp = difference;
                        if (temp <= 0.4)
                        {
                            // Low slope value for really slow acceleration
                            difference = (difference * 0.37);
                        } else if (temp <= 0.75)
                        {
                            // Perform Linear accleration with an appropriate
                            // offset.
                            difference = (difference - 0.252);
                        } else
                        {
                            // Perform mouse acceleration. Make up the difference
                            // due to the previous two segments. Maxes out at 1.0.
                            difference = (difference * 2.008) - 1.008;
                        }

                        break;
                    }
                    case EasingQuadraticCurve:
                    case EasingCubicCurve: {
                        // Perform different forms of acceleration depending on
                        // the range of the element from its assigned dead zone.
                        // Useful for more precise controls with an axis.
                        double temp = difference;
                        if (temp <= 0.4)
                        {
                            // Low slope value for really slow acceleration
                            difference = (difference * 0.38);

                            // Out of high end. Reset easing status.
                            if (buttonslot->isEasingActive())
                            {
                                buttonslot->setEasingStatus(false);
                                buttonslot->getEasingTime()->restart();
                            }
                        } else if (temp <= 0.75)
                        {
                            // Perform Linear accleration with an appropriate
                            // offset.
                            difference = (difference - 0.248);

                            // Out of high end. Reset easing status.
                            if (buttonslot->isEasingActive())
                            {
                                buttonslot->setEasingStatus(false);
                                buttonslot->getEasingTime()->restart();
                            }
                        } else
                        {
                            // Gradually increase the mouse speed until the specified elapsed duration
                            // time has passed.
                            int easingElapsed = buttonslot->getEasingTime()->elapsed();
                            double easingDuration = m_easingDuration; // Time in seconds
                            if (!buttonslot->isEasingActive())
                            {
                                buttonslot->setEasingStatus(true);
                                buttonslot->getEasingTime()->restart();
                                easingElapsed = 0;
                            }

                            // Determine the multiplier to use for the current maximum mouse speed
                            // based on how much time has passed.
                            double elapsedDiff;
                            if ((easingDuration > 0.0) && ((easingElapsed * .001) < easingDuration))
                            {
                                elapsedDiff = ((easingElapsed * .001) / easingDuration);
                                if (currentCurve == EasingQuadraticCurve)
                                {
                                    elapsedDiff = (1.5 - 1.0) * elapsedDiff * elapsedDiff + 1.0;
                                } else
                                {
                                    elapsedDiff = (1.5 - 1.0) * (elapsedDiff * elapsedDiff * elapsedDiff) + 1.0;
                                }
                            } else
                            {
                                elapsedDiff = 1.5;
                            }

                            // Allow gradient control on the high end of an axis.
                            difference = (elapsedDiff * difference);
                            difference = (difference * 1.33067 - 0.496005);
                        }
                        break;
                    }
                    default:
                        break;
                    }

                    double distance = 0;
                    difference = (GlobalVariables::JoyButton::mouseSpeedModifier == 1.0)
                                     ? difference
                                     : (difference * GlobalVariables::JoyButton::mouseSpeedModifier);

                    double mintravel = minMouseDistanceAccelThreshold * 0.01;
                    double minstop = qMax(0.05, mintravel);

                    // Last check ensures that acceleration is only applied for the same direction.
                    if (extraAccelerationEnabled && isPartRealAxis() &&
                        (fabs(getAccelerationDistance() - lastAccelerationDistance) >= mintravel) &&
                        (((getAccelerationDistance() - lastAccelerationDistance) >= 0) == (getAccelerationDistance() >= 0)))
                    {
                        double magfactor = extraAccelerationMultiplier;
                        double minfactor = qMax((GlobalVariables::JoyButton::DEFAULTSTARTACCELMULTIPLIER * 0.001) + 1.0,
                                                magfactor * (startAccelMultiplier * 0.01));
                        double maxtravel = maxMouseDistanceAccelThreshold * 0.01;
                        double slope = (magfactor - minfactor) / (maxtravel - mintravel);
                        double intercept = minfactor - (slope * mintravel);

                        double intermediateTravel =
                            qMin(maxtravel, fabs(getAccelerationDistance() - lastAccelerationDistance));
                        if ((currentAccelMulti > 1.0) && (oldAccelMulti == 0.0))
                        {
                            intermediateTravel = qMin(maxtravel, intermediateTravel + mintravel);
                        }

                        double currentAccelMultiTemp = (slope * intermediateTravel + intercept);
                        if (extraAccelCurve == EaseOutSineCurve)
                        {
                            double getMultiDiff2 =
                                ((currentAccelMultiTemp - minfactor) / (extraAccelerationMultiplier - minfactor));
                            currentAccelMultiTemp = (extraAccelerationMultiplier - minfactor) *
                                                        sin(getMultiDiff2 * (GlobalVariables::JoyControlStick::PI / 2.0)) +
                                                    minfactor;
                        } else if (extraAccelCurve == EaseOutQuadAccelCurve)
                        {
                            double getMultiDiff2 =
                                ((currentAccelMultiTemp - minfactor) / (extraAccelerationMultiplier - minfactor));
                            currentAccelMultiTemp =
                                -(extraAccelerationMultiplier - minfactor) * (getMultiDiff2 * (getMultiDiff2 - 2)) +
                                minfactor;
                        } else if (extraAccelCurve == EaseOutCubicAccelCurve)
                        {
                            double getMultiDiff =
                                ((currentAccelMultiTemp - minfactor) / (extraAccelerationMultiplier - minfactor)) - 1;
                            currentAccelMultiTemp = (extraAccelerationMultiplier - minfactor) *
                                                        ((getMultiDiff) * (getMultiDiff) * (getMultiDiff) + 1) +
                                                    minfactor;
                        }

                        difference = difference * currentAccelMultiTemp;
                        currentAccelMulti = currentAccelMultiTemp;
                        updateOldAccelMulti = currentAccelMulti;
                        accelTravel = intermediateTravel;
                        accelExtraDurationTime.restart();
                    } else if (extraAccelerationEnabled && isPartRealAxis() && (accelDuration > 0.0) &&
                               (currentAccelMulti > 0.0) &&
                               (fabs(getAccelerationDistance() - startingAccelerationDistance) < minstop))
                    {
                        qDebug() << "Keep Trying: " << fabs(getAccelerationDistance() - lastAccelerationDistance);
                        qDebug() << "MIN TRAVEL: " << mintravel;

                        updateStartingMouseDistance = true;
                        double magfactor = extraAccelerationMultiplier;
                        double minfactor = qMax((GlobalVariables::JoyButton::DEFAULTSTARTACCELMULTIPLIER * 0.001) + 1.0,
                                                magfactor * (startAccelMultiplier * 0.01));
                        double maxtravel = maxMouseDistanceAccelThreshold * 0.01;
                        double slope = (magfactor - minfactor) / (maxtravel - mintravel);
                        double intercept = minfactor - (slope * mintravel);

                        int elapsedElapsed = accelExtraDurationTime.elapsed();

                        double intermediateTravel = accelTravel;
                        if (((getAccelerationDistance() - startingAccelerationDistance) >= 0) !=
                            (getAccelerationDistance() >= 0))
                        {
                            // Travelling towards dead zone. Decrease acceleration and duration.
                            intermediateTravel =
                                qMax(intermediateTravel - fabs(getAccelerationDistance() - startingAccelerationDistance),
                                     mintravel);
                        }

                        // Linear case
                        double currentAccelMultiTemp = (slope * intermediateTravel + intercept);
                        double elapsedDuration = accelDuration * ((currentAccelMultiTemp - minfactor) /
                                                                  (extraAccelerationMultiplier - minfactor));

                        switch (extraAccelCurve)
                        {
                        case EaseOutSineCurve: {
                            double multiDiff =
                                ((currentAccelMultiTemp - minfactor) / (extraAccelerationMultiplier - minfactor));
                            double temp = sin(multiDiff * (GlobalVariables::JoyControlStick::PI / 2.0));
                            elapsedDuration = accelDuration * temp + 0;
                            currentAccelMultiTemp = (extraAccelerationMultiplier - minfactor) *
                                                        sin(multiDiff * (GlobalVariables::JoyControlStick::PI / 2.0)) +
                                                    minfactor;

                            break;
                        }
                        case EaseOutQuadAccelCurve: {
                            double getMultiDiff2 =
                                ((currentAccelMultiTemp - minfactor) / (extraAccelerationMultiplier - minfactor));
                            double temp = (getMultiDiff2 * (getMultiDiff2 - 2));
                            elapsedDuration = -accelDuration * temp + 0;
                            currentAccelMultiTemp = -(extraAccelerationMultiplier - minfactor) * temp + minfactor;

                            break;
                        }
                        case EaseOutCubicAccelCurve: {
                            double getMultiDiff =
                                ((currentAccelMultiTemp - minfactor) / (extraAccelerationMultiplier - minfactor)) - 1;
                            double temp = ((getMultiDiff) * (getMultiDiff) * (getMultiDiff) + 1);
                            elapsedDuration = accelDuration * temp + 0;
                            currentAccelMultiTemp = (extraAccelerationMultiplier - minfactor) * temp + minfactor;

                            break;
                        }
                        default: {
                            break;
                        }
                        }

                        double tempAccel = currentAccelMultiTemp;
                        double elapsedDiff;

                        if ((elapsedDuration > 0.0) && ((elapsedElapsed * 0.001) < elapsedDuration))
                        {
                            elapsedDiff = ((elapsedElapsed * 0.001) / elapsedDuration);
                            elapsedDiff = (1.0 - tempAccel) * (elapsedDiff * elapsedDiff * elapsedDiff) + tempAccel;
                            difference = elapsedDiff * difference;

                            // As acceleration is applied, do not update last
                            // distance values when not necessary.
                            updateStartingMouseDistance = false;
                            updateOldAccelMulti = currentAccelMulti;
                        } else
                        {
                            currentAccelMulti = 0.0;
                            updateOldAccelMulti = 0.0;
                            accelTravel = 0.0;
                        }
                    } else if (extraAccelerationEnabled && isPartRealAxis())
                    {
                        currentAccelMulti = 0.0;
                        updateStartingMouseDistance = true;
                        oldAccelMulti = updateOldAccelMulti = 0.0;
                        accelTravel = 0.0;
                    }

                    sumDist += difference * (mousespeed * GlobalVariables::JoyButtonSlot::JOYSPEED * timeElapsed) * 0.001;
                    distance = sumDist;

                    switch (mousedirection)
                    {
                    case 1:
                        mouse2 = -distance;
                        break;

                    case 2:
                        mouse2 = distance;
                        break;

                    case 3:
                        mouse1 = -distance;
                        break;

                    case 4:
                        mouse1 = distance;
                        break;
                    }

                    mouseCursorInfo infoX;
                    infoX.code = mouse1;
                    infoX.slot = buttonslot;
                    cursorXSpeeds.append(infoX);

                    mouseCursorInfo infoY;
                    infoY.code = mouse2;
                    infoY.slot = buttonslot;
                    cursorYSpeeds.append(infoY);
                    sumDist = 0;

                    buttonslot->setDistance(sumDist);
                } else if (mousemode == JoyButton::MouseSpring)
                {
                    double mouse1 = -2.0;
                    double mouse2 = -2.0;
                    double difference = getMouseDistanceFromDeadZone();

                    switch (mousedirection)
                    {
                    case 1:

                        setDistanceForSpring(mouseHelper, mouse1, mouse2, -difference);
                        break;

                    case 2:

                        setDistanceForSpring(mouseHelper, mouse1, mouse2, difference);
                        break;

                    case 3:

                        setDistanceForSpring(mouseHelper, mouse2, mouse1, -difference);
                        break;

                    case 4:

                        setDistanceForSpring(mouseHelper, mouse2, mouse1, difference);
                        break;
                    }

                    updateMouseProperties(mouse1, 0.0, springWidth, springHeight, relativeSpring,
                                          GlobalVariables::JoyButton::springModeScreen, springXSpeeds, 'X');
                    updateMouseProperties(mouse2, 0.0, springWidth, springHeight, relativeSpring,
                                          GlobalVariables::JoyButton::springModeScreen, springYSpeeds, 'Y');
                    mouseInterval->restart();
                }

                tempQueue.enqueue(buttonslot);
            }

            if (!mouseEventQueue.isEmpty() && !singleShot)
                buttonslot = mouseEventQueue.dequeue();
            else
                buttonslot = nullptr;
        }

        if (!tempQueue.isEmpty())
        {
            while (!tempQueue.isEmpty())
            {
                JoyButtonSlot *tempslot = tempQueue.dequeue();
                mouseEventQueue.enqueue(tempslot);
            }
        }
    }
}

void JoyButton::setDistanceForSpring(JoyButtonMouseHelper &mouseHelper, double &mouseFirstAx, double &mouseSecondAx,
                                     double distanceFromDeadZone)
{
    if (mouseHelper.getFirstSpringStatus())
    {
        mouseFirstAx = 0.0;
        mouseHelper.setFirstSpringStatus(false);
    }

    mouseSecondAx = distanceFromDeadZone;
}

void JoyButton::updateMouseProperties(double newAxisValue, double newSpringDead, int newSpringWidth, int newSpringHeight,
                                      bool relatived, int modeScreen, QList<PadderCommon::springModeInfo> &springSpeeds,
                                      QChar axis, double newAxisValueY, double newSpringDeadY)
{
    PadderCommon::springModeInfo axisInfo;

    if (axis == 'X')
    {
        axisInfo.displacementX = newAxisValue;
        axisInfo.springDeadX = newSpringDead;

    } else if (axis == 'Y')
    {
        axisInfo.displacementY = newAxisValue;
        axisInfo.springDeadY = newSpringDead;

    } else
    {
        axisInfo.displacementX = newAxisValue;
        axisInfo.springDeadX = newSpringDead;
        axisInfo.displacementY = newAxisValueY;
        axisInfo.springDeadY = newSpringDeadY;
    }

    axisInfo.width = newSpringWidth;
    axisInfo.height = newSpringHeight;
    axisInfo.relative = relatived;
    axisInfo.screen = modeScreen;
    springSpeeds.append(axisInfo);
}

void JoyButton::wheelEventVertical()
{
    JoyButtonSlot *buttonslot = nullptr;

    if (currentWheelVerticalEvent != nullptr)
        buttonslot = currentWheelVerticalEvent;

    if (buttonslot && (wheelSpeedY != 0))
    {
        bool isActive = getActiveSlots().contains(buttonslot);

        if (isActive)
        {
            sendevent(buttonslot, true);
            sendevent(buttonslot, false);
            mouseWheelVerticalEventQueue.enqueue(buttonslot);
            mouseWheelVerticalEventTimer.start(1000 / wheelSpeedY);
        } else
        {
            mouseWheelVerticalEventTimer.stop();
        }
    } else if (!mouseWheelVerticalEventQueue.isEmpty() && (wheelSpeedY != 0))
    {
        QQueue<JoyButtonSlot *> tempQueue;

        while (!mouseWheelVerticalEventQueue.isEmpty())
        {
            buttonslot = mouseWheelVerticalEventQueue.dequeue();
            bool isActive = getActiveSlots().contains(buttonslot);

            if (isActive)
            {
                sendevent(buttonslot, true);
                sendevent(buttonslot, false);
                tempQueue.enqueue(buttonslot);
            }
        }

        if (!tempQueue.isEmpty())
        {
            mouseWheelVerticalEventQueue = tempQueue;
            mouseWheelVerticalEventTimer.start(1000 / wheelSpeedY);
        } else
        {
            mouseWheelVerticalEventTimer.stop();
        }
    } else
    {
        mouseWheelVerticalEventTimer.stop();
    }
}

void JoyButton::wheelEventHorizontal()
{
    JoyButtonSlot *buttonslot = nullptr;

    if (currentWheelHorizontalEvent != nullptr)
        buttonslot = currentWheelHorizontalEvent;

    if (buttonslot && (wheelSpeedX != 0))
    {
        bool isActive = getActiveSlots().contains(buttonslot);

        if (isActive)
        {
            sendevent(buttonslot, true);
            sendevent(buttonslot, false);
            mouseWheelHorizontalEventQueue.enqueue(buttonslot);
            mouseWheelHorizontalEventTimer.start(1000 / wheelSpeedX);
        } else
        {
            mouseWheelHorizontalEventTimer.stop();
        }
    } else if (!mouseWheelHorizontalEventQueue.isEmpty() && (wheelSpeedX != 0))
    {
        QQueue<JoyButtonSlot *> tempQueue;

        while (!mouseWheelHorizontalEventQueue.isEmpty())
        {
            buttonslot = mouseWheelHorizontalEventQueue.dequeue();
            bool isActive = getActiveSlots().contains(buttonslot);

            if (isActive)
            {
                sendevent(buttonslot, true);
                sendevent(buttonslot, false);
                tempQueue.enqueue(buttonslot);
            }
        }

        if (!tempQueue.isEmpty())
        {
            mouseWheelHorizontalEventQueue = tempQueue;
            mouseWheelHorizontalEventTimer.start(1000 / wheelSpeedX);
        } else
        {
            mouseWheelHorizontalEventTimer.stop();
        }
    } else
    {
        mouseWheelHorizontalEventTimer.stop();
    }
}

void JoyButton::setUseTurbo(bool useTurbo)
{
    bool initialState = m_useTurbo;

    if (useTurbo != m_useTurbo)
    {
        if (useTurbo && this->containsSequence())
            m_useTurbo = false;
        else
            m_useTurbo = useTurbo;

        if (initialState != m_useTurbo)
        {
            emit turboChanged(m_useTurbo);
            emit propertyUpdated();

            if (m_useTurbo && (this->turboInterval == 0))
                this->setTurboInterval(GlobalVariables::JoyButton::ENABLEDTURBODEFAULT);
        }
    }
}

bool JoyButton::isUsingTurbo() { return m_useTurbo; }

QString JoyButton::getXmlName() { return GlobalVariables::JoyButton::xmlName; }

QString JoyButton::getName(bool forceFullFormat, bool displayNames)
{
    QString newlabel = getPartialName(forceFullFormat, displayNames);
    // qDebug() << "partial name is: " << newlabel;
    newlabel.append(": ");

    // qDebug() << "actionName is " << actionName;
    if (!actionName.isEmpty() && displayNames)
        newlabel.append(actionName);
    else
        newlabel.append(getCalculatedActiveZoneSummary());

    // qDebug() << "name in getName(bool forceFullFormat, bool displayNames) is now: " << newlabel;
    return newlabel;
}

QString JoyButton::getPartialName(bool forceFullFormat, bool displayNames) const
{
    QString temp = QString();

    if (!buttonName.isEmpty() && displayNames)
    {
        if (forceFullFormat)
            temp.append(tr("Button")).append(" ");

        temp.append(buttonName);
    } else if (!defaultButtonName.isEmpty())
    {
        if (forceFullFormat)
            temp.append(tr("Button")).append(" ");

        temp.append(defaultButtonName);
    } else
    {
        temp.append(tr("Button")).append(" ").append(QString::number(getRealJoyNumber()));
    }

    return temp;
}

/**
 * @brief Generate a string representing a summary of the slots currently
 *     assigned to a button
 * @return String of currently assigned slots
 */
QString JoyButton::getSlotsSummary()
{
    QString newlabel = QString();
    int slotCount = getAssignedSlots()->size();

    if (slotCount > 0)
    {
        QListIterator<JoyButtonSlot *> iter(*getAssignedSlots());
        QStringList stringlist = QStringList();
        int i = 0;

        while (iter.hasNext())
        {
            JoyButtonSlot *slot = iter.next();
            stringlist.append(slot->getSlotString());
            i++;

            if ((i > 4) && iter.hasNext())
            {
                stringlist.append(" ...");
                iter.toBack();
            }
        }

        newlabel = stringlist.join(", ");
    } else
    {
        newlabel = newlabel.append(tr("[NO KEY]"));
    }

    return newlabel;
}

/**
 * @brief Generate a string that represents slots that will be activated or
 *     slots that are currently active if a button is pressed
 * @return String of currently applicable slots for a button
 */
QString JoyButton::getActiveZoneSummary()
{
    QList<JoyButtonSlot *> tempList = getActiveZoneList();
    return buildActiveZoneSummary(tempList);
}

QString JoyButton::getCalculatedActiveZoneSummary()
{
    QString temp = QString();
    lockForWritedString(temp, activeZoneString);
    return temp;
}

/**
 * @brief Generate active zone string and notify other objects.
 */
void JoyButton::buildActiveZoneSummaryString()
{
    lockForWritedString(activeZoneString, getActiveZoneSummary());
    qDebug() << "activeZoneString after getActiveZoneSummary() is: " << activeZoneString;
    emit activeZoneChanged();
}

/**
 * @brief Generate active zone string but do not notify any other object.
 */
void JoyButton::localBuildActiveZoneSummaryString() { lockForWritedString(activeZoneString, getActiveZoneSummary()); }

QString JoyButton::buildActiveZoneSummary(QList<JoyButtonSlot *> &tempList)
{
    QString newlabel = QString();
    QListIterator<JoyButtonSlot *> iter(tempList);
    QListIterator<JoyButtonSlot *> *iterMain(&iter);
    QStringList stringlist = QStringList();
    QStringList stringListMix = QStringList();
    int i = 0;
    int j = 0;
    bool slotsActive = !getActiveSlots().isEmpty();

    if (setSelectionCondition == SetChangeOneWay)
    {
        newlabel.append(tr("[Set %1 1W]").arg(setSelection + 1));
        if (iterMain->hasNext())
            newlabel.append(" ");
    } else if (setSelectionCondition == SetChangeTwoWay)
    {
        newlabel = newlabel.append(tr("[Set %1 2W]").arg(setSelection + 1));
        if (iterMain->hasNext())
            newlabel.append(" ");
    }

    if (setSelectionCondition == SetChangeWhileHeld)
    {
        newlabel.append(tr("[Set %1 WH]").arg(setSelection + 1));
    } else if (iterMain->hasNext())
    {
        bool behindHold = false;

        while (iterMain->hasNext())
        {
            JoyButtonSlot *slot = iterMain->next();
            JoyButtonSlot::JoySlotInputAction mode = slot->getSlotMode();

            if (mode == JoyButtonSlot::JoySlotInputAction::JoyMix)
            {
                QListIterator<JoyButtonSlot *> iterMini(*slot->getMixSlots());
                QListIterator<JoyButtonSlot *> *iterM(&iterMini);

                while (iterM->hasNext())
                {
                    JoyButtonSlot *slotMini = iterM->next();
                    JoyButtonSlot::JoySlotInputAction modeMini = slotMini->getSlotMode();
                    qDebug() << "modeMini is " << modeMini;
                    qDebug() << "slotsActive are empty? " << slotsActive;
                    buildActiveZoneSummarySwitchSlots(modeMini, slotMini, behindHold, &stringListMix, j, iterM, slotsActive);

                    stringListMix.append("+");

                    qDebug() << "Create summary for JoyMix. Progress: " << stringListMix;
                }

                j = 0;
                i++;

                if (!stringListMix.isEmpty())
                {
                    if (stringListMix.last() == '+')
                        stringListMix.removeLast();

                    qDebug() << "Create summary for JoyMix. Progress: " << stringListMix;

                    QString res = "";

                    for (const QString &strListEl : stringListMix)
                        res += strListEl;

                    stringlist.append(res);
                    stringListMix.clear();
                } else
                {
                    stringlist.append(slot->getTextData());
                    stringListMix.clear();
                }

                behindHold = false;

            } else
            {
                buildActiveZoneSummarySwitchSlots(mode, slot, behindHold, &stringlist, i, iterMain, slotsActive);
            }

            if ((i > 4) && iterMain->hasNext())
            {
                stringlist.append(" ...");
                iterMain->toBack();
            }
        }

        newlabel.append(stringlist.join(", "));
    } else if (setSelectionCondition == SetChangeDisabled)
    {
        newlabel.append(tr("[NO KEY]"));
    }

    qDebug() << "NEW LABEL IS: " << newlabel;
    qDebug() << "i: " << i;
    qDebug() << "j: " << j;
    return newlabel;
}

void JoyButton::buildActiveZoneSummarySwitchSlots(JoyButtonSlot::JoySlotInputAction mode, JoyButtonSlot *slot,
                                                  bool &behindHold, QStringList *stringlist, int &i,
                                                  QListIterator<JoyButtonSlot *> *iter, bool slotsActive)
{
    switch (mode)
    {
    case JoyButtonSlot::JoyKeyboard:
    case JoyButtonSlot::JoyMouseButton:
    case JoyButtonSlot::JoyMouseMovement: {
        QString temp = slot->getSlotString();

        if (behindHold)
        {
            temp.prepend("[H] ");
            behindHold = false;
        }

        stringlist->append(temp);
        i++;
        break;
    }
    case JoyButtonSlot::JoyKeyPress: {
        // Skip slot if a press time slot was inserted.
        break;
    }
    case JoyButtonSlot::JoyHold: {
        if (!slotsActive && (i == 0))
        {
            // If button is not active and first slot is a hold,
            // keep processing slots but take note of the hold.
            behindHold = true;
        } else
        {
            // Move iter to back so loop will end.
            iter->toBack();
        }

        break;
    }
    case JoyButtonSlot::JoyLoadProfile:
    case JoyButtonSlot::JoySetChange:
    case JoyButtonSlot::JoyTextEntry:
    case JoyButtonSlot::JoyExecute: {
        QString temp = slot->getSlotString();

        if (behindHold)
        {
            temp.prepend("[H] ");
            behindHold = false;
        }

        stringlist->append(temp);
        i++;
        break;
    }
    case JoyButtonSlot::JoyRelease: {
        if (currentRelease == nullptr)
            findJoySlotsEnd(iter);

        break;
    }
    case JoyButtonSlot::JoyDistance: {
        iter->toBack();
        break;
    }
    case JoyButtonSlot::JoyDelay: {
        iter->toBack();
        break;
    }
    case JoyButtonSlot::JoyCycle: {
        iter->toBack();
        break;
    }

    default:

        if (mode > 15 || mode < 0)
        {
            QString temp = slot->getSlotString();

            if (behindHold)
            {
                temp.prepend("[H] ");
                behindHold = false;
            }

            stringlist->append(temp);
            i++;
            break;
        }

        break;
    }
}

QList<JoyButtonSlot *> JoyButton::getActiveZoneList()
{
    QListIterator<JoyButtonSlot *> activeSlotsIter(getActiveSlots());
    QListIterator<JoyButtonSlot *> assignmentsIter(*getAssignedSlots());
    QListIterator<JoyButtonSlot *> *iter = nullptr;
    QReadWriteLock *tempLock = nullptr;

    qDebug() << "Active slots are: ";

    int x, y;
    x = 0;
    y = 0;
    for (auto actSlot : getActiveSlots())
    {
        x++;
        qDebug() << x << ") " << actSlot->getSlotString();
    }

    qDebug() << "Assigned slots are: ";
    for (auto assignedSlot : *getAssignedSlots())
    {
        y++;
        qDebug() << y << ") " << assignedSlot->getSlotString();
    }

    activeZoneLock.lockForRead();
    int numActiveSlots = getActiveSlots().size();
    activeZoneLock.unlock();

    if (numActiveSlots > 0)
    {
        tempLock = &activeZoneLock;
        iter = &activeSlotsIter;
    } else
    {
        tempLock = &assignmentsLock;
        iter = &assignmentsIter;
    }

    QReadLocker tempLocker(tempLock);
    Q_UNUSED(tempLocker);

    if (tempLock == &assignmentsLock)
    {
        if (previousCycle != nullptr)
        {
            qDebug() << "if there exists previous Cycle, find it in activeSlots";

            iter->findNext(previousCycle);
        }
    }

    QList<JoyButtonSlot *> tempSlotList;

    if ((setSelectionCondition != SetChangeWhileHeld) && iter->hasNext())
    {
        while (iter->hasNext())
        {
            JoyButtonSlot *slot = iter->next();
            JoyButtonSlot::JoySlotInputAction mode = slot->getSlotMode();

            switch (mode)
            {
            case JoyButtonSlot::JoyKeyboard:
            case JoyButtonSlot::JoyMouseButton:
            case JoyButtonSlot::JoyMouseMovement:
            case JoyButtonSlot::JoyKeyPress:
            case JoyButtonSlot::JoyHold:
            case JoyButtonSlot::JoyLoadProfile:
            case JoyButtonSlot::JoySetChange:
            case JoyButtonSlot::JoyTextEntry:
            case JoyButtonSlot::JoyExecute:
            case JoyButtonSlot::JoyMix: {
                tempSlotList.append(slot);
                break;
            }
            case JoyButtonSlot::JoyRelease: {
                if (currentRelease == nullptr)
                    findJoySlotsEnd(iter);

                break;
            }
            case JoyButtonSlot::JoyDistance:
            case JoyButtonSlot::JoyCycle: {
                iter->toBack();
                break;
            }
            default: {
                break;
            }
            }
        }
    }

    return tempSlotList;
}

/**
 * @brief Generate a string representing all the currently assigned slots for
 *     a button
 * @return String representing all assigned slots for a button
 */
QString JoyButton::getSlotsString()
{
    QString label = QString();

    if (getAssignedSlots()->size() > 0)
    {
        qDebug() << "There is more assignments than 0 in getSlotsString(): " << getAssignedSlots()->count();

        QListIterator<JoyButtonSlot *> iter(*getAssignedSlots());
        QStringList stringlist = QStringList();

        while (iter.hasNext())
        {
            JoyButtonSlot *slot = iter.next();
            qDebug() << "deviceCode = " << slot->getSlotCode();
            qDebug() << "slotMode = " << slot->getSlotMode();
            QString slotString = slot->getSlotString();

            if (slotString == tr("[NO KEY]"))
            {
                qDebug() << "EMPTY ASSIGNED SLOT";
            }

            stringlist.append(slotString); // tu
        }

        label = stringlist.join(", ");
    } else
    {
        qDebug() << "There is no assignments for button in getSlotsString()";

        label = label.append(tr("[NO KEY]"));
    }

    return label;
}

void JoyButton::setCustomName(QString name) { customName = name; }

QString JoyButton::getCustomName() { return customName; }

/**
 * @brief Create new JoyButtonSlot with data provided and append the new slot
 *     an existing to the assignment list.
 * @param Native virtual code being used.
 * @param Mode of the slot.
 * @return Whether the new slot was successfully added to the assignment list.
 */
bool JoyButton::setAssignedSlot(int code, JoyButtonSlot::JoySlotInputAction mode)
{
    bool slotInserted = false;
    JoyButtonSlot *slot = new JoyButtonSlot(code, mode, this);

    if (slot->getSlotMode() == JoyButtonSlot::JoyDistance && (slot->getSlotCode() >= 1) && (slot->getSlotCode() <= 100) &&
        (getTotalSlotDistance(slot) <= 1.0))
    {
        assignmentsLock.lockForWrite();
        getAssignmentsLocal().append(slot);
        assignmentsLock.unlock();

        buildActiveZoneSummaryString();
        slotInserted = true;
    } else if (slot->getSlotCode() >= 0)
    {
        assignmentsLock.lockForWrite();
        getAssignmentsLocal().append(slot);
        assignmentsLock.unlock();

        buildActiveZoneSummaryString();
        slotInserted = true;
    }

    if (slotInserted)
    {
        checkTurboCondition(slot);
        emit slotsChanged();
    } else if (slot != nullptr)
    {
        slot->deleteLater();
        slot = nullptr;
    }

    return slotInserted;
}

/**
 * @brief Create new JoyButtonSlot with data provided and append the new slot
 *     an existing to the assignment list.
 * @param Native virtual code being used.
 * @param Qt key alias used for abstracting native virtual code.
 * @param Mode of the slot.
 * @return Whether the new slot was successfully added to the assignment list.
 */
bool JoyButton::setAssignedSlot(int code, int alias, JoyButtonSlot::JoySlotInputAction mode)
{
    bool slotInserted = false;
    JoyButtonSlot *slot = new JoyButtonSlot(code, alias, mode, this);

    if (slot->getSlotMode() == JoyButtonSlot::JoyDistance && (slot->getSlotCode() >= 1) && (slot->getSlotCode() <= 100) &&
        (getTotalSlotDistance(slot) <= 1.0))
    {
        assignmentsLock.lockForWrite();
        getAssignmentsLocal().append(slot);
        assignmentsLock.unlock();

        buildActiveZoneSummaryString();
        slotInserted = true;
    } else if (slot->getSlotCode() >= 0)
    {
        assignmentsLock.lockForWrite();
        getAssignmentsLocal().append(slot);
        assignmentsLock.unlock();

        buildActiveZoneSummaryString();
        slotInserted = true;
    }

    if (slotInserted)
    {
        checkTurboCondition(slot);
        emit slotsChanged();
    } else if (slot != nullptr)
    {
        slot->deleteLater();
        slot = nullptr;
    }

    return slotInserted;
}

/**
 * @brief Create new JoyButtonSlot with data provided and replace an existing
 *     slot in the assignment list if one exists.
 * @param Native virtual code being used.
 * @param Qt key alias used for abstracting native virtual code.
 * @param Index number in the list.
 * @param Mode of the slot.
 * @return Whether the new slot was successfully added to the assignment list.
 */
bool JoyButton::setAssignedSlot(int code, int alias, int index, JoyButtonSlot::JoySlotInputAction mode)
{
    bool permitSlot = true;
    JoyButtonSlot *slot = new JoyButtonSlot(code, alias, mode, this);

    if ((slot->getSlotMode() == JoyButtonSlot::JoyDistance) && (slot->getSlotCode() >= 1) && (slot->getSlotCode() <= 100))
    {
        if (getTotalSlotDistance(slot) > 1.0)
            permitSlot = false;
    } else if (slot->getSlotCode() < 0)
    {
        permitSlot = false;
    }

    if (permitSlot)
    {
        assignmentsLock.lockForWrite();

        if ((index >= 0) && (index < getAssignedSlots()->count()))
        {
            // Insert slot and move existing slots.
            JoyButtonSlot *temp = getAssignedSlots()->at(index);
            if (temp != nullptr)
            {
                temp->deleteLater();
                temp = nullptr;
            }

            getAssignmentsLocal().replace(index, slot);
        } else if (index >= getAssignmentsLocal().count())
        {
            // Append code into a new slot
            getAssignmentsLocal().append(slot);
        }

        checkTurboCondition(slot);
        assignmentsLock.unlock();
        buildActiveZoneSummaryString();
        emit slotsChanged();
    } else if (slot != nullptr)
    {
        slot->deleteLater();
        slot = nullptr;
    }

    return permitSlot;
}

/**
 * @brief Create new JoyButtonSlot with data provided and insert it into
 *     assignments list if it is valid.
 * @param Native virtual code being used.
 * @param Qt key alias used for abstracting native virtual code.
 * @param Index number in the list.
 * @param Mode of the slot.
 * @return Whether the new slot was successfully added to the assignment list.
 */
bool JoyButton::insertAssignedSlot(int code, int alias, int index, JoyButtonSlot::JoySlotInputAction mode)
{
    bool permitSlot = true;
    JoyButtonSlot *slot = new JoyButtonSlot(code, alias, mode, this);

    if (slot->getSlotMode() == JoyButtonSlot::JoyDistance && (slot->getSlotCode() >= 1) && (slot->getSlotCode() <= 100))
    {
        if (getTotalSlotDistance(slot) > 1.0)
            permitSlot = false;
    } else if (slot->getSlotCode() < 0)
    {
        permitSlot = false;
    }

    if (permitSlot)
    {
        assignmentsLock.lockForWrite();

        if ((index >= 0) && (index < getAssignedSlots()->count()))
        {
            // Insert new slot into list. Move old slots if needed.
            getAssignmentsLocal().insert(index, slot);
        } else if (index >= getAssignedSlots()->count())
        {
            // Append new slot into list.
            getAssignmentsLocal().append(slot);
        }

        qDebug() << "assignments variable in joybutton has now: " << getAssignedSlots()->count() << " input slots";

        checkTurboCondition(slot);
        assignmentsLock.unlock();
        buildActiveZoneSummaryString();
        emit slotsChanged();
    } else if (slot != nullptr)
    {
        slot->deleteLater();
        slot = nullptr;
    }

    return permitSlot;
}

bool JoyButton::insertAssignedSlot(JoyButtonSlot *newSlot, bool updateActiveString)
{
    bool permitSlot = false;

    if ((newSlot->getSlotMode() == JoyButtonSlot::JoyDistance) && (newSlot->getSlotCode() >= 1) &&
        (newSlot->getSlotCode() <= 100))
    {
        if (getTotalSlotDistance(newSlot) <= 1.0)
            permitSlot = true;
    } else if ((newSlot->getSlotMode() == JoyButtonSlot::JoyTextEntry ||
                newSlot->getSlotMode() == JoyButtonSlot::JoyExecute) &&
               !newSlot->getTextData().isEmpty())
    {
        permitSlot = true;
    } else if ((newSlot->getSlotMode() == JoyButtonSlot::JoyLoadProfile) || newSlot->getSlotCode() >= 0)
    {
        permitSlot = true;
    }

    if (permitSlot)
    {
        assignmentsLock.lockForWrite();
        checkTurboCondition(newSlot);
        getAssignmentsLocal().append(newSlot);
        assignmentsLock.unlock();

        if (updateActiveString)
            buildActiveZoneSummaryString();

        emit slotsChanged();
    }

    return permitSlot;
}

bool JoyButton::insertAssignedSlot(JoyButtonSlot *newSlot, int index, bool updateActiveString)
{
    bool permitSlot = true;
    JoyButtonSlot *slot = new JoyButtonSlot(newSlot, this);

    if (slot->getSlotMode() == JoyButtonSlot::JoyDistance && (slot->getSlotCode() >= 1) && (slot->getSlotCode() <= 100))
    {
        if (getTotalSlotDistance(slot) > 1.0)
            permitSlot = false;
    } else if (slot->getSlotCode() < 0)
    {
        permitSlot = false;
    }

    if (permitSlot)
    {
        assignmentsLock.lockForWrite();

        if ((index >= 0) && (index < getAssignedSlots()->count()))
        {
            // Insert new slot into list. Move old slots if needed.
            getAssignmentsLocal().insert(index, slot);
        } else if (index >= getAssignedSlots()->count())
        {
            // Append new slot into list.
            getAssignmentsLocal().append(slot);
        }

        qDebug() << "assignments variable in joybutton has now: " << getAssignedSlots()->count() << " input slots";

        checkTurboCondition(slot);
        assignmentsLock.unlock();
        buildActiveZoneSummaryString();
        emit slotsChanged();
    } else if (slot != nullptr)
    {
        /*if (slot->getSlotMode() == 15)
        {
            qDeleteAll(*slot->getMixSlots());
        }*/

        slot->deleteLater();
        slot = nullptr;
    }

    return permitSlot;
}

bool JoyButton::setAssignedSlot(JoyButtonSlot *otherSlot, int index)
{
    bool permitSlot = false;
    JoyButtonSlot *newslot = new JoyButtonSlot(otherSlot, this);

    if (newslot->getSlotMode() == JoyButtonSlot::JoyDistance && (newslot->getSlotCode() >= 1) &&
        (newslot->getSlotCode() <= 100))
    {
        if (getTotalSlotDistance(newslot) <= 1.0)
            permitSlot = true;
    } else if (newslot->getSlotMode() == JoyButtonSlot::JoyLoadProfile)
    {
        permitSlot = true;
    } else if (newslot->getSlotMode() == JoyButtonSlot::JoyMix && newslot->getMixSlots()->count() > 1)
    {
        permitSlot = true;
    } else if ((newslot->getSlotMode() == JoyButtonSlot::JoyExecute ||
                newslot->getSlotMode() == JoyButtonSlot::JoyTextEntry) &&
               !newslot->getTextData().isEmpty())
    {
        permitSlot = true;
    } else if (newslot->getSlotCode() >= 0)
    {
        permitSlot = true;
    }

    if (permitSlot)
    {
        assignmentsLock.lockForWrite();
        checkTurboCondition(newslot);

        if ((index >= 0) && (index < getAssignedSlots()->count()))
        {
            // Slot already exists. Override code and place into desired slot
            JoyButtonSlot *temp = getAssignedSlots()->at(index);

            if (temp->getSlotMode() == JoyButtonSlot::JoySlotInputAction::JoyMix)
            {
                for (auto minislot : *temp->getMixSlots())
                {
                    minislot->deleteLater();
                    minislot = nullptr;
                }

                delete temp->getMixSlots();
                temp->assignMixSlotsToNull();
            }

            if (temp != nullptr)
            {
                temp->deleteLater();
                temp = nullptr;
            }

            getAssignmentsLocal().replace(index, newslot);
        } else if (index >= getAssignmentsLocal().count())
        {
            // Append code into a new slot
            getAssignmentsLocal().append(newslot);
        }

        assignmentsLock.unlock();
        buildActiveZoneSummaryString();
        emit slotsChanged();
    } else if (newslot != nullptr)
    {
        newslot->deleteLater();
        newslot = nullptr;
    }

    return permitSlot;
}

QList<JoyButtonSlot *> *JoyButton::getAssignedSlots() { return &assignments; }

QList<JoyButtonSlot *> const &JoyButton::getActiveSlots() { return activeSlots; }

void JoyButton::setMouseSpeedX(int speed)
{
    if ((speed >= 1) && (speed <= GlobalVariables::JoyButton::MAXMOUSESPEED))
    {
        mouseSpeedX = speed;
        emit propertyUpdated();
    }
}

int JoyButton::getMouseSpeedX() { return mouseSpeedX; }

void JoyButton::setMouseSpeedY(int speed)
{
    if ((speed >= 1) && (speed <= GlobalVariables::JoyButton::MAXMOUSESPEED))
    {
        mouseSpeedY = speed;
        emit propertyUpdated();
    }
}

int JoyButton::getMouseSpeedY() { return mouseSpeedY; }

void JoyButton::setChangeSetSelection(int index, bool updateActiveString)
{
    if ((index >= -1) && (index <= 7))
    {
        setSelection = index;

        if (updateActiveString)
            buildActiveZoneSummaryString();

        emit propertyUpdated();
    }
}

int JoyButton::getSetSelection() { return setSelection; }

void JoyButton::setChangeSetCondition(SetChangeCondition condition, bool passive, bool updateActiveString)
{
    SetChangeCondition oldCondition = setSelectionCondition;

    if ((condition != setSelectionCondition) && !passive)
    {
        if ((condition == SetChangeWhileHeld) || (condition == SetChangeTwoWay))
        {
            // Set new condition
            emit setAssignmentChanged(m_index_sdl, setSelection, condition);
        } else if ((setSelectionCondition == SetChangeWhileHeld) || (setSelectionCondition == SetChangeTwoWay))
        {
            // Remove old condition
            emit setAssignmentChanged(m_index_sdl, setSelection, SetChangeDisabled);
        }

        setSelectionCondition = condition;
    } else if (passive)
    {
        setSelectionCondition = condition;
    }

    if (setSelectionCondition == SetChangeDisabled)
        setChangeSetSelection(-1);

    if (setSelectionCondition != oldCondition)
    {
        if (updateActiveString)
            buildActiveZoneSummaryString();

        emit propertyUpdated();
    }
}

JoyButton::SetChangeCondition JoyButton::getChangeSetCondition() { return setSelectionCondition; }

/**
 * @brief Checks if this button is currently active
 * @returns True if the button is pressed, false otherwise
 */
bool JoyButton::getButtonState() { return isButtonPressed; }

int JoyButton::getOriginSet() { return m_originset; }

void JoyButton::pauseWaitEvent()
{
    if (currentPause != nullptr)
    {
        if (!isButtonPressedQueue.isEmpty() && createDeskTimer.isActive())
        {
            if (slotiter != nullptr)
            {
                slotiter->toBack();

                bool lastIgnoreSetState = ignoreSetQueue.last();
                bool lastIsButtonPressed = isButtonPressedQueue.last();
                ignoreSetQueue.clear();
                isButtonPressedQueue.clear();

                ignoreSetQueue.enqueue(lastIgnoreSetState);
                isButtonPressedQueue.enqueue(lastIsButtonPressed);
                currentPause = nullptr;
                currentRelease = nullptr;
                releaseDeskTimer.stop();
                pauseWaitTimer.stop();

                slotiter->toFront();

                if (previousCycle != nullptr)
                    slotiter->findNext(previousCycle);

                quitEvent = true;
                keyPressHold.restart();
            }
        }
    }

    if (currentPause != nullptr)
    {
        // If release timer is active, temporarily
        // disable it
        if (releaseDeskTimer.isActive())
            releaseDeskTimer.stop();

        if (inpauseHold.elapsed() < currentPause->getSlotCode())
        {
            startTimerOverrun(currentPause->getSlotCode(), &inpauseHold, &pauseWaitTimer, 0);
        } else
        {
            pauseWaitTimer.stop();
            createDeskTimer.stop();
            currentPause = nullptr;
            createDeskEvent();

            // If release timer was disabled but if the button
            // is not pressed, restart the release timer.
            if (!releaseDeskTimer.isActive() && (isButtonPressedQueue.isEmpty() || !isButtonPressedQueue.last()))
                waitForReleaseDeskEvent();
        }
    } else
    {
        pauseWaitTimer.stop();
    }
}

void JoyButton::checkForSetChange()
{
    if (!ignoreSetQueue.isEmpty() && !isButtonPressedQueue.isEmpty())
    {
        bool tempFinalState = isButtonPressedQueue.last();
        bool tempFinalIgnoreSetsState = ignoreSetQueue.last();

        if (!tempFinalIgnoreSetsState)
        {
            if (!tempFinalState && (setSelectionCondition == SetChangeOneWay) && (setSelection > -1))
            {
                restartAllForSetChange();
            } else if (!tempFinalState && (setSelectionCondition == SetChangeTwoWay) && (setSelection > -1))
            {
                restartAllForSetChange();
            } else if ((setSelectionCondition == SetChangeWhileHeld) && (setSelection > -1))
            {
                if (tempFinalState)
                    whileHeldStatus = true;
                else
                    whileHeldStatus = false;

                restartAllForSetChange();
            }
        }

        // Clear queue except for a press if it is last in
        if (!isButtonPressedQueue.isEmpty())
        {
            isButtonPressedQueue.clear();

            if (tempFinalState)
                isButtonPressedQueue.enqueue(tempFinalState);
        }

        // Clear queue except for a press if it is last in
        if (!ignoreSetQueue.isEmpty())
        {
            bool tempFinalIgnoreSetsState_local = ignoreSetQueue.last();
            ignoreSetQueue.clear();

            if (tempFinalState)
                ignoreSetQueue.enqueue(tempFinalIgnoreSetsState_local);
        }
    }
}

void JoyButton::restartAllForSetChange()
{
    if (createDeskTimer.isActive())
        createDeskTimer.stop();

    if (releaseDeskTimer.isActive())
        releaseDeskTimer.stop();

    isButtonPressedQueue.clear();
    ignoreSetQueue.clear();

    emit released(m_index_sdl);
    emit setChangeActivated(setSelection);
}

void JoyButton::waitForDeskEvent()
{
    if (quitEvent && !isButtonPressedQueue.isEmpty() && isButtonPressedQueue.last())
    {
        if (createDeskTimer.isActive())
        {
            keyPressTimer.stop();
            createDeskTimer.stop();
            releaseDeskTimer.stop();
            createDeskEvent();
        } else
        {
            keyPressTimer.stop();
            releaseDeskTimer.stop();
            createDeskEvent();
        }
    } else if (!createDeskTimer.isActive())
    {
#ifdef Q_CC_MSVC
        createDeskTimer.start(5);
        releaseDeskTimer.stop();
#else
        createDeskTimer.start(0);
        releaseDeskTimer.stop();
#endif
    } else if (createDeskTimer.isActive())
    {
        // Decrease timer interval of active timer.
        createDeskTimer.start(0);
        releaseDeskTimer.stop();
    }
}

void JoyButton::waitForReleaseDeskEvent()
{
    if (quitEvent && !keyPressTimer.isActive())
    {
        if (releaseDeskTimer.isActive())
            releaseDeskTimer.stop();

        createDeskTimer.stop();
        keyPressTimer.stop();
        releaseDeskEvent();
    } else if (!releaseDeskTimer.isActive())
    {
#ifdef Q_CC_MSVC
        releaseDeskTimer.start(1);
        createDeskTimer.stop();
#else
        releaseDeskTimer.start(1);
        createDeskTimer.stop();
#endif
    } else if (releaseDeskTimer.isActive())
    {
        createDeskTimer.stop();
    }
}

bool JoyButton::containsSequence()
{
    bool result = false;

    assignmentsLock.lockForRead();
    QListIterator<JoyButtonSlot *> tempiter(*getAssignedSlots());

    while (tempiter.hasNext())
    {
        JoyButtonSlot *slot = tempiter.next();
        JoyButtonSlot::JoySlotInputAction mode = slot->getSlotMode();

        if ((mode == JoyButtonSlot::JoyPause) || (mode == JoyButtonSlot::JoyHold) || (mode == JoyButtonSlot::JoyDistance))
        {
            result = true;
            tempiter.toBack();
        }
    }

    assignmentsLock.unlock();

    return result;
}

void JoyButton::holdEvent()
{
    if (currentHold != nullptr)
    {
        bool currentlyPressed = false;

        if (!isButtonPressedQueue.isEmpty())
            currentlyPressed = isButtonPressedQueue.last();

        // Activate hold event
        if (currentlyPressed && (buttonHold.elapsed() > currentHold->getSlotCode()))
        {
            qDebug() << buttonHold.elapsed() << " > " << currentHold->getSlotCode();
            qDebug() << "Activate hold event";

            releaseActiveSlots();
            currentHold = nullptr;
            holdTimer.stop();
            buttonHold.restart();
            createDeskEvent();
        }
        // Elapsed time has not occurred
        else if (currentlyPressed)
        {
            qDebug() << "Elapsed time has not occurred, because buttonHold: " << buttonHold.elapsed()
                     << " is not greater than currentHoldCode: " << currentHold->getSlotCode();

            startTimerOverrun(currentHold->getSlotCode(), &buttonHold, &holdTimer);
        }
        // Pre-emptive release
        else
        {
            qDebug() << "Hold button is not pressed";

            currentHold = nullptr;
            holdTimer.stop();

            if (slotiter != nullptr)
            {
                qDebug() << "slotiter exists";

                findJoySlotsEnd(slotiter);
                createDeskEvent();
            }
        }
    } else
    {
        holdTimer.stop();
    }
}

void JoyButton::startTimerOverrun(int slotCode, QElapsedTimer *currSlotTime, QTimer *currSlotTimer, bool releasedDeskTimer)
{
    int proposedInterval = slotCode - currSlotTime->elapsed();
    proposedInterval = (proposedInterval > 0) ? proposedInterval : 0;
    int newTimerInterval = qMin(10, proposedInterval);
    currSlotTimer->start(newTimerInterval);

    if (releasedDeskTimer)
    {
        // If release timer is active, push next run until
        // after keyDelayTimer will timeout again. Helps
        // reduce CPU usage of an excessively repeating timer.
        if (releaseDeskTimer.isActive())
            releaseDeskTimer.start(proposedInterval);
    }
}

void JoyButton::delayEvent()
{
    if (currentDelay != nullptr)
    {
        bool currentlyPressed = false;

        if (!isButtonPressedQueue.isEmpty())
            currentlyPressed = isButtonPressedQueue.last();

        if ((currentDelay != nullptr) && (buttonDelay.elapsed() > currentDelay->getSlotCode()))
        {
            // Delay time has elapsed. Continue processing slots.
            currentDelay = nullptr;
            delayTimer.stop();
            buttonDelay.restart();
            createDeskEvent();
        } else if (currentlyPressed)
        {
            // Elapsed time has not occurred
            startTimerOverrun(currentDelay->getSlotCode(), &buttonDelay, &delayTimer);
        } else
        {
            // Pre-emptive release
            currentDelay = nullptr;
            delayTimer.stop();
        }
    } else
    {
        delayTimer.stop();
    }
}

void JoyButton::releaseDeskEvent(bool skipsetchange)
{
    quitEvent = false;

    pauseWaitTimer.stop();
    holdTimer.stop();
    createDeskTimer.stop();
    keyPressTimer.stop();
    delayTimer.stop();
    setChangeTimer.stop();
    releaseActiveSlots();

    if (!isButtonPressedQueue.isEmpty() && (currentRelease == nullptr))
        releaseSlotEvent();
    else
        currentRelease = nullptr;

    if (!skipsetchange && (setSelectionCondition != SetChangeDisabled) && !isButtonPressedQueue.isEmpty() &&
        (currentRelease == nullptr))
    {
        bool tempButtonPressed = isButtonPressedQueue.last();
        bool tempFinalIgnoreSetsState = ignoreSetQueue.last();

        if (!tempButtonPressed && !tempFinalIgnoreSetsState)
        {
            if ((setSelectionCondition == SetChangeWhileHeld) && whileHeldStatus)
                setChangeTimer.start(0);
            else if (setSelectionCondition != SetChangeWhileHeld)
                setChangeTimer.start();
        } else
        {
            changeStatesQueue(false);
        }
    } else
    {
        changeStatesQueue(true);
    }

    if (currentRelease == nullptr)
    {
        restartAccelParams(true, true, false);
        lastMouseDistance = 0.0;
        currentMouseDistance = 0.0;
        updateStartingMouseDistance = true;

        if ((slotiter != nullptr) && !slotiter->hasNext())
        {
            // At the end of the list of assignments.

            qDebug() << "There is end of slotiter. Set currentCycle and previousCycle as null pointers";

            currentCycle = nullptr;
            previousCycle = nullptr;
            slotiter->toFront();
        } else if ((slotiter != nullptr) && slotiter->hasNext() && (currentCycle != nullptr))
        {
            // Cycle at the end of a segment.
            qDebug() << "There exists next element in slotiter and exists currentCycle. Skip to currentCycle in slotiter "
                        "starting from beginning";

            slotiter->toFront();
            slotiter->findNext(currentCycle);
        } else if ((slotiter != nullptr) && slotiter->hasPrevious() && slotiter->hasNext() && (currentCycle == nullptr))
        {
            // Check if there is a cycle action slot after
            // current slot. Useful after dealing with pause
            // actions.

            qDebug() << "There exists next element and previous element in slotiter but doesn't exists currentCycle. From "
                        "current point in slotiter find JoyButtonSlot::JoyCycle as slotMode and assign to currentCycle";

            JoyButtonSlot *tempslot = nullptr;
            bool exit = false;

            while (slotiter->hasNext() && !exit)
            {
                tempslot = slotiter->next();

                if (tempslot->getSlotMode() == JoyButtonSlot::JoyCycle)
                {
                    currentCycle = tempslot;
                    exit = true;
                }
            }

            // Didn't find any cycle. Move iterator
            // to the front.
            if (currentCycle == nullptr)
            {
                qDebug() << "Didn't find any cycle. Back to start of slotiter";

                slotiter->toFront();
                previousCycle = nullptr;
            }
        }

        if (currentCycle != nullptr)
        {
            qDebug() << "currentCycle exists and previousCycle will be current but current will be null pointer";

            previousCycle = currentCycle;
            currentCycle = nullptr;
        } else if ((slotiter != nullptr) && slotiter->hasNext() && containsReleaseSlots())
        {
            qDebug() << "Slotiter has next element on the list. In assignments exists JoyButtonSlot::JoyRelease starting "
                        "from current point. CurrentCycle and previousCycle are set null pointers now";

            currentCycle = nullptr;
            previousCycle = nullptr;
            slotiter->toFront();
        }

        m_currentDistance = nullptr;
        this->currentKeyPress = nullptr;
        quitEvent = true;
    }
}

void JoyButton::changeStatesQueue(bool currentReleased)
{
    bool tempFinalState = false;

    if (!isButtonPressedQueue.isEmpty())
    {
        tempFinalState = isButtonPressedQueue.last();
        isButtonPressedQueue.clear();

        if (currentReleased)
        {
            if (tempFinalState || (currentRelease != nullptr))
                isButtonPressedQueue.enqueue(tempFinalState);
        } else
        {
            if (tempFinalState)
                isButtonPressedQueue.enqueue(tempFinalState);
        }
    }

    if (!ignoreSetQueue.isEmpty())
    {
        bool tempFinalIgnoreSetsState = ignoreSetQueue.last();
        ignoreSetQueue.clear();

        if (currentReleased)
        {
            if (tempFinalState || (currentRelease != nullptr))
                ignoreSetQueue.enqueue(tempFinalIgnoreSetsState);
        } else
        {
            if (tempFinalState)
                ignoreSetQueue.enqueue(tempFinalIgnoreSetsState);
        }
    }
}

/**
 * @brief Get the distance that an element is away from its assigned
 *     dead zone
 * @return Normalized distance away from dead zone
 */
double JoyButton::getDistanceFromDeadZone() { return isButtonPressed ? 1.0 : 0.0; }

double JoyButton::getAccelerationDistance() { return this->getDistanceFromDeadZone(); }

/**
 * @brief Get the distance factor that should be used for mouse movement
 * @return Distance factor that should be used for mouse movement
 */
double JoyButton::getMouseDistanceFromDeadZone() { return this->getDistanceFromDeadZone(); }

double JoyButton::getTotalSlotDistance(JoyButtonSlot *slot)
{
    double tempDistance = 0.0;

    QListIterator<JoyButtonSlot *> iter(*getAssignedSlots());

    while (iter.hasNext())
    {
        JoyButtonSlot *currentSlot = iter.next();
        int tempcode = currentSlot->getSlotCode();
        JoyButtonSlot::JoySlotInputAction mode = currentSlot->getSlotMode();

        if (mode == JoyButtonSlot::JoyDistance)
        {
            tempDistance += tempcode / 100.0;

            if (slot == currentSlot)
                iter.toBack();

        } else if (mode == JoyButtonSlot::JoyCycle) // Reset tempDistance
        {
            tempDistance = 0.0;
        }
    }

    return tempDistance;
}

bool JoyButton::containsDistanceSlots()
{
    bool result = false;
    QListIterator<JoyButtonSlot *> iter(*getAssignedSlots());

    while (iter.hasNext())
    {
        JoyButtonSlot *slot = iter.next();

        if (slot->getSlotMode() == JoyButtonSlot::JoyDistance)
        {
            result = true;
            iter.toBack();
        }
    }

    return result;
}

void JoyButton::clearAssignedSlots(bool signalEmit)
{
    QListIterator<JoyButtonSlot *> iter(assignments);
    while (iter.hasNext())
    {
        auto *slot = iter.next();

        qDebug() << "AssignedSLot mode: " << slot->getSlotMode();
        qDebug() << "cleared assigned slot's mode: " << slot->getSlotMode();
        qDebug() << "list of mix slots is a null pointer? " << ((slot->getMixSlots() == nullptr) ? "yes" : "no");

        if (slot != nullptr)
        {
            if (slot->getMixSlots() != nullptr && slot->getMixSlots() != NULL)
            {
                slot->cleanMixSlots();
            }

            slot->deleteLater();
            slot = nullptr;
        }
    }

    getAssignmentsLocal().clear();
    if (signalEmit)
        emit slotsChanged();
}

void JoyButton::removeAssignedSlot(int index)
{
    QWriteLocker tempAssignLocker(&assignmentsLock);

    int j = 0;
    qDebug() << "Assigned list slots after joining";
    for (auto el : *getAssignedSlots())
    {
        qDebug() << j << ")";
        qDebug() << "code: " << el->getSlotCode();
        qDebug() << "mode: " << el->getSlotMode();
        qDebug() << "string: " << el->getSlotString();
        j++;
    }

    if ((index >= 0) && (index < getAssignedSlots()->size()))
    {
        JoyButtonSlot *slot = getAssignedSlots()->takeAt(index);

        if (slot->getSlotMode() == JoyButtonSlot::JoyMix)
        {
            for (auto minislot : *slot->getMixSlots())
            {
                minislot->deleteLater();
                minislot = nullptr;
            }

            slot->getMixSlots()->clear();
            delete slot->getMixSlots();
            slot->assignMixSlotsToNull();

            getAssignedSlots()->removeAt(index);
        } else
        {
            slot->deleteLater();
            slot = nullptr;
        }

        tempAssignLocker.unlock();
        buildActiveZoneSummaryString();
        emit slotsChanged();
    }
}

void JoyButton::clearSlotsEventReset(bool clearSignalEmit)
{
    assignmentsLock.lockForWrite();
    resetSlotsProp();
    stopTimers(false);
    releaseActiveSlots();
    clearAssignedSlots(clearSignalEmit);
    clearQueues();
    assignmentsLock.unlock();
    buildActiveZoneSummaryString();
    DEBUG() << "all current slots and previous slots ale cleared";
}

void JoyButton::eventReset()
{
    QWriteLocker tempAssignLocker(&assignmentsLock);

    resetSlotsProp();
    stopTimers(false);
    clearQueues();

    qDebug() << "all current slots and previous slots ale cleared";

    releaseActiveSlots();
}

void JoyButton::releaseActiveSlots()
{
    if (!getActiveSlots().isEmpty())
    {
        QWriteLocker tempLocker(&activeZoneLock);

        bool changeRepeatState = false;

        QListIterator<JoyButtonSlot *> iter(getActiveSlots());
        iter.toBack();

        while (iter.hasPrevious())
        {
            JoyButtonSlot *slot = iter.previous();
            int tempcode = slot->getSlotCode();
            int references = 0;
            JoyButtonSlot::JoySlotInputAction mode = slot->getSlotMode();

            if (mode == JoyButtonSlot::JoySlotInputAction::JoyMix)
            {
                QListIterator<JoyButtonSlot *> iterMini(*slot->getMixSlots());
                iterMini.toBack();

                // go through all slots in JoyMix slot
                while (iterMini.hasPrevious())
                {
                    JoyButtonSlot *slotMini = iterMini.previous();
                    int tempcodeMini = slotMini->getSlotCode();
                    int referencesMini = 0;
                    JoyButtonSlot::JoySlotInputAction mode = slotMini->getSlotMode();

                    releaseEachSlot(changeRepeatState, referencesMini, tempcodeMini, mode, slotMini);
                }

                if (!slot->getMixSlots()->isEmpty())
                {
                    qDeleteAll(*slot->getMixSlots());
                    slot->getMixSlots()->clear();
                    delete slot->getMixSlots();
                    slot->assignMixSlotsToNull();
                }
            } else
            {
                releaseEachSlot(changeRepeatState, references, tempcode, mode, slot);
            }
        }

        getActiveSlotsLocal().clear();
        currentMouseEvent = nullptr;

        if (!mouseEventQueue.isEmpty())
            mouseEventQueue.clear();

        pendingMouseButtons.removeAll(this);
        currentWheelVerticalEvent = nullptr;
        currentWheelHorizontalEvent = nullptr;
        mouseWheelVerticalEventTimer.stop();
        mouseWheelHorizontalEventTimer.stop();

        if (!mouseWheelVerticalEventQueue.isEmpty())
        {
            mouseWheelVerticalEventQueue.clear();
            lastWheelVerticalDistance = getMouseDistanceFromDeadZone();
            wheelVerticalTime.restart();
        }

        if (!mouseWheelHorizontalEventQueue.isEmpty())
        {
            mouseWheelHorizontalEventQueue.clear();
            lastWheelHorizontalDistance = getMouseDistanceFromDeadZone();
            wheelHorizontalTime.restart();
        }

        // Check if mouse remainder should be zero.
        // Only need to check one list from cursor speeds and spring speeds
        // since the correspond Y lists will be the same size.
        if ((pendingMouseButtons.length() == 0) && (cursorXSpeeds.length() == 0) && (springXSpeeds.length() == 0))
        {
            GlobalVariables::JoyButton::cursorRemainderX = 0;
            GlobalVariables::JoyButton::cursorRemainderY = 0;
        }
    }
}

void JoyButton::releaseEachSlot(bool &changeRepeatState, int &references, int tempcode,
                                JoyButtonSlot::JoySlotInputAction mode, JoyButtonSlot *slot)
{
    if (mode == JoyButtonSlot::JoyKeyboard)
    {
        countActiveSlots(tempcode, references, slot, GlobalVariables::JoyButton::activeKeys, changeRepeatState, true);

        if ((lastActiveKey == slot) && (references <= 0))
            lastActiveKey = nullptr;
    } else if (mode == JoyButtonSlot::JoyMouseButton)
    {
        if ((tempcode != static_cast<int>(JoyButtonSlot::MouseWheelUp)) &&
            (tempcode != static_cast<int>(JoyButtonSlot::MouseWheelDown)) &&
            (tempcode != static_cast<int>(JoyButtonSlot::MouseWheelLeft)) &&
            (tempcode != static_cast<int>(JoyButtonSlot::MouseWheelRight)))
        {
            countActiveSlots(tempcode, references, slot, GlobalVariables::JoyButton::activeMouseButtons, changeRepeatState);
        } else if ((tempcode == static_cast<int>(JoyButtonSlot::MouseWheelUp)) ||
                   (tempcode == static_cast<int>(JoyButtonSlot::MouseWheelDown)))
        {
            mouseWheelVerticalEventQueue.removeAll(slot);
        } else if ((tempcode == static_cast<int>(JoyButtonSlot::MouseWheelLeft)) ||
                   (tempcode == static_cast<int>(JoyButtonSlot::MouseWheelRight)))
        {
            mouseWheelHorizontalEventQueue.removeAll(slot);
        }

        slot->setDistance(0.0);
        slot->getMouseInterval()->restart();
    } else if (mode == JoyButtonSlot::JoyMouseMovement)
    {
        JoyMouseMovementMode mousemode = getMouseMode();

        if (mousemode == MouseCursor)
        {
            QList<int> indexesToRemove;

            releaseMoveSlots(cursorXSpeeds, slot, indexesToRemove);
            releaseMoveSlots(cursorYSpeeds, slot, indexesToRemove);
            slot->getEasingTime()->restart();
            slot->setEasingStatus(false);
        } else if (mousemode == JoyButton::MouseSpring)
        {
            double mouse1 = (tempcode == static_cast<int>(JoyButtonSlot::MouseLeft) ||
                             tempcode == static_cast<int>(JoyButtonSlot::MouseRight))
                                ? 0.0
                                : -2.0;
            double mouse2 = (tempcode == static_cast<int>(JoyButtonSlot::MouseUp) ||
                             tempcode == static_cast<int>(JoyButtonSlot::MouseDown))
                                ? 0.0
                                : -2.0;

            double springDeadCircleX = 0.0;
            double springDeadCircleY = 0.0;

            checkSpringDeadCircle(tempcode, springDeadCircleX, JoyButtonSlot::MouseLeft, JoyButtonSlot::MouseRight);
            checkSpringDeadCircle(tempcode, springDeadCircleY, JoyButtonSlot::MouseUp, JoyButtonSlot::MouseDown);
            updateMouseProperties(mouse1, springDeadCircleX, springWidth, springHeight, relativeSpring,
                                  GlobalVariables::JoyButton::springModeScreen, springXSpeeds, 'n', -2.0, springDeadCircleY);
            updateMouseProperties(-2.0, springDeadCircleX, springWidth, springHeight, relativeSpring,
                                  GlobalVariables::JoyButton::springModeScreen, springYSpeeds, 'n', mouse2,
                                  springDeadCircleY);
        }

        mouseEventQueue.removeAll(slot);
        slot->setDistance(0.0);
        slot->getMouseInterval()->restart();
    } else if (mode == JoyButtonSlot::JoyMouseSpeedMod)
    {
        int queueLength = mouseSpeedModList.length();

        if (!mouseSpeedModList.isEmpty())
        {
            mouseSpeedModList.removeAll(slot);
            queueLength -= 1;
        }

        if (queueLength <= 0)
            GlobalVariables::JoyButton::mouseSpeedModifier = GlobalVariables::JoyButton::DEFAULTMOUSESPEEDMOD;
    } else if (mode == JoyButtonSlot::JoySetChange)
    {
        currentSetChangeSlot = slot;
        slotSetChangeTimer.start();
    }
}

void JoyButton::countActiveSlots(int tempcode, int &references, JoyButtonSlot *slot, QHash<int, int> &activeSlotsHash,
                                 bool &changeRepeatState, bool activeSlotHashWindows)
{
    changeRepeatState = false;
    references = activeSlotsHash.value(tempcode, 1) - 1;

    if (references <= 0)
    {
        sendevent(slot, false);
        activeSlotsHash.remove(tempcode);
    } else
    {
        activeSlotsHash.insert(tempcode, references);
    }
}

void JoyButton::setSpringDeadCircle(double &springDeadCircle, int mouseDirection)
{
    if (getCurrentSpringDeadCircle() > getLastMouseDistanceFromDeadZone())
        springDeadCircle = (mouseDirection == JoyButtonSlot::MouseLeft || mouseDirection == JoyButtonSlot::MouseUp)
                               ? -getLastMouseDistanceFromDeadZone()
                               : getLastMouseDistanceFromDeadZone();
    else
        springDeadCircle = (mouseDirection == JoyButtonSlot::MouseLeft || mouseDirection == JoyButtonSlot::MouseUp)
                               ? -getCurrentSpringDeadCircle()
                               : getCurrentSpringDeadCircle();
}

void JoyButton::checkSpringDeadCircle(int tempcode, double &springDeadCircle, int mouseSlot1, int mouseSlot2)
{
    if (getSpringDeadCircleMultiplier() > 0)
    {
        if (tempcode == mouseSlot1)
        {
            setSpringDeadCircle(springDeadCircle, mouseSlot1);
        } else if (tempcode == mouseSlot2)
        {
            setSpringDeadCircle(springDeadCircle, mouseSlot2);
        }
    }
}

void JoyButton::releaseMoveSlots(QList<JoyButton::mouseCursorInfo> &cursorSpeeds, JoyButtonSlot *slot,
                                 QList<int> &indexesToRemove)
{
    QListIterator<mouseCursorInfo> iter(cursorSpeeds);
    int i = cursorSpeeds.length();

    while (iter.hasNext())
    {
        mouseCursorInfo info = iter.next();
        if (info.slot == slot)
            indexesToRemove.append(i);
        i++;
    }

    QListIterator<int> removeIter(indexesToRemove);
    while (removeIter.hasPrevious())
    {
        int index = removeIter.previous();
        cursorSpeeds.removeAt(index);
    }

    indexesToRemove.clear();
}

bool JoyButton::containsReleaseSlots()
{
    bool result = false;
    QListIterator<JoyButtonSlot *> iter(*getAssignedSlots());

    while (iter.hasNext())
    {
        if (iter.next()->getSlotMode() == JoyButtonSlot::JoyRelease)
        {
            result = true;
            iter.toBack();
        }
    }

    return result;
}

bool JoyButton::containsJoyMixSlot()
{
    bool result = false;
    QListIterator<JoyButtonSlot *> iter(*getAssignedSlots());

    while (iter.hasNext())
    {
        if (iter.next()->getSlotMode() == JoyButtonSlot::JoyMix)
        {
            result = true;
            iter.toBack();
        }
    }

    return result;
}

void JoyButton::releaseSlotEvent()
{
    JoyButtonSlot *temp = nullptr;

    int timeElapsed = buttonHeldRelease.elapsed();

    if (containsReleaseSlots())
    {
        QListIterator<JoyButtonSlot *> iter(*getAssignedSlots());

        if (previousCycle != nullptr)
            iter.findNext(previousCycle);

        int tempElapsed = 0;

        while (iter.hasNext())
        {
            JoyButtonSlot *currentSlot = iter.next();
            int tempcode = currentSlot->getSlotCode();
            JoyButtonSlot::JoySlotInputAction mode = currentSlot->getSlotMode();

            if (mode == JoyButtonSlot::JoyRelease)
            {
                tempElapsed += tempcode;

                if (tempElapsed <= timeElapsed)
                    temp = currentSlot;
                else
                    iter.toBack();
            } else if (mode == JoyButtonSlot::JoyCycle)
            {
                tempElapsed = 0;
                iter.toBack();
            }
        }

        if ((temp != nullptr) && (slotiter != nullptr))
        {
            slotiter->toFront();
            slotiter->findNext(temp);
            currentRelease = temp;
            activateSlots();

            if (!keyPressTimer.isActive() && !pauseWaitTimer.isActive())
            {
                releaseActiveSlots();
                currentRelease = nullptr;
            }

            // Stop hold timer here to be sure that
            // a hold timer that could be activated
            // during a release event is stopped.
            holdTimer.stop();

            currentHold = nullptr;
        }
    }
}

void JoyButton::findJoySlotsEnd(QListIterator<JoyButtonSlot *> *slotiter)
{
    if (slotiter != nullptr)
    {
        bool found = false;
        while (!found && slotiter->hasNext())
        {
            qDebug() << "slotiter has next element";

            JoyButtonSlot::JoySlotInputAction mode = slotiter->next()->getSlotMode();

            switch (mode)
            {
            case JoyButtonSlot::JoyRelease:
            case JoyButtonSlot::JoyCycle:
            case JoyButtonSlot::JoyHold:

                found = true;
                break;

            default:
                break;
            }
        }

        if (found && slotiter->hasPrevious())
            slotiter->previous();
    }
}

void JoyButton::setVDPad(VDPad *vdpad)
{
    joyEvent(false, true);
    m_vdpad = vdpad;
    emit propertyUpdated();
}

bool JoyButton::isPartVDPad() { return (m_vdpad != nullptr); }

VDPad *JoyButton::getVDPad() { return m_vdpad; }

void JoyButton::removeVDPad()
{
    m_vdpad = nullptr;
    emit propertyUpdated();
}

/**
 * @brief Check if button properties are at their default values
 * @return Status of possible property edits
 */
bool JoyButton::isDefault()
{
    bool value = true;

    value = value && (m_toggle == GlobalVariables::JoyButton::DEFAULTTOGGLE);
    value = value && (turboInterval == GlobalVariables::JoyButton::DEFAULTTURBOINTERVAL);
    value = value && (currentTurboMode == NormalTurbo);
    value = value && (m_useTurbo == GlobalVariables::JoyButton::DEFAULTUSETURBO);
    value = value && (mouseSpeedX == GlobalVariables::JoyButton::DEFAULTMOUSESPEEDX);
    value = value && (mouseSpeedY == GlobalVariables::JoyButton::DEFAULTMOUSESPEEDY);
    value = value && (setSelection == GlobalVariables::JoyButton::DEFAULTSETSELECTION);
    value = value && (setSelectionCondition == DEFAULTSETCONDITION);
    value = value && (getAssignedSlots()->isEmpty());
    value = value && (mouseMode == DEFAULTMOUSEMODE);
    value = value && (mouseCurve == getDefaultMouseCurve());
    value = value && (springWidth == GlobalVariables::JoyButton::DEFAULTSPRINGWIDTH);
    value = value && (springHeight == GlobalVariables::JoyButton::DEFAULTSPRINGHEIGHT);
    value = value && qFuzzyCompare(sensitivity, GlobalVariables::JoyButton::DEFAULTSENSITIVITY);
    value = value && (actionName.isEmpty());
    value = value && (wheelSpeedX == GlobalVariables::JoyButton::DEFAULTWHEELX);
    value = value && (wheelSpeedY == GlobalVariables::JoyButton::DEFAULTWHEELY);
    value = value && (cycleResetActive == GlobalVariables::JoyButton::DEFAULTCYCLERESETACTIVE);
    value = value && (cycleResetInterval == GlobalVariables::JoyButton::DEFAULTCYCLERESET);
    value = value && (relativeSpring == GlobalVariables::JoyButton::DEFAULTRELATIVESPRING);
    value = value && qFuzzyCompare(m_easingDuration, GlobalVariables::JoyButton::DEFAULTEASINGDURATION);
    value = value && !extraAccelerationEnabled;
    value = value && qFuzzyCompare(extraAccelerationMultiplier, GlobalVariables::JoyButton::DEFAULTEXTRACCELVALUE);
    value = value && qFuzzyCompare(minMouseDistanceAccelThreshold, GlobalVariables::JoyButton::DEFAULTMINACCELTHRESHOLD);
    value = value && qFuzzyCompare(maxMouseDistanceAccelThreshold, GlobalVariables::JoyButton::DEFAULTMAXACCELTHRESHOLD);
    value = value && qFuzzyCompare(startAccelMultiplier, GlobalVariables::JoyButton::DEFAULTSTARTACCELMULTIPLIER);
    value = value && qFuzzyCompare(accelDuration, GlobalVariables::JoyButton::DEFAULTACCELEASINGDURATION);
    value = value && (springDeadCircleMultiplier == GlobalVariables::JoyButton::DEFAULTSPRINGRELEASERADIUS);
    value = value && (extraAccelCurve == DEFAULTEXTRAACCELCURVE);

    return value;
}

/**
 * @brief Returns the default mouse curve for this JoyButton type.
 *  Can be overwritten by subclasses.
 * @returns Default mouse curve
 */
JoyButton::JoyMouseCurve JoyButton::getDefaultMouseCurve() const { return DEFAULTMOUSECURVE; }

void JoyButton::setIgnoreEventState(bool ignore) { ignoreEvents = ignore; }

bool JoyButton::getIgnoreEventState() { return ignoreEvents; }

void JoyButton::setMouseMode(JoyMouseMovementMode mousemode)
{
    this->mouseMode = mousemode;
    emit propertyUpdated();
}

JoyButton::JoyMouseMovementMode JoyButton::getMouseMode() { return mouseMode; }

void JoyButton::setMouseCurve(JoyMouseCurve selectedCurve)
{
    mouseCurve = selectedCurve;
    emit propertyUpdated();
}

JoyButton::JoyMouseCurve JoyButton::getMouseCurve() { return mouseCurve; }

void JoyButton::setSpringWidth(int value)
{
    if (value >= 0)
    {
        springWidth = value;
        emit propertyUpdated();
    }
}

int JoyButton::getSpringWidth() { return springWidth; }

void JoyButton::setSpringHeight(int value)
{
    if (springHeight >= 0)
    {
        springHeight = value;
        emit propertyUpdated();
    }
}

int JoyButton::getSpringHeight() { return springHeight; }

void JoyButton::setSensitivity(double value)
{
    if ((value >= 0.001) && (value <= 1000))
    {
        sensitivity = value;
        emit propertyUpdated();
    }
}

double JoyButton::getSensitivity() { return sensitivity; }

bool JoyButton::getWhileHeldStatus() { return whileHeldStatus; }

void JoyButton::setWhileHeldStatus(bool status) { whileHeldStatus = status; }

void JoyButton::setActionName(QString tempName)
{
    if ((tempName.length() <= 50) && (tempName != actionName))
    {
        actionName = tempName;
        emit actionNameChanged();
        emit propertyUpdated();
    }
}

QString JoyButton::getActionName() { return actionName; }

void JoyButton::setButtonName(QString tempName)
{
    if ((tempName.length() <= 20) && (tempName != buttonName))
    {
        buttonName = tempName;
        emit buttonNameChanged();
        emit propertyUpdated();
    }
}

QString JoyButton::getButtonName() { return buttonName; }

void JoyButton::setWheelSpeed(int speed, QChar ax)
{
    if ((speed >= 1) && (speed <= 100))
    {
        if (ax == 'X')
            wheelSpeedX = speed;
        else if (ax == 'Y')
            wheelSpeedY = speed;
        emit propertyUpdated();
    }
}

int JoyButton::getWheelSpeedX() { return wheelSpeedX; }

int JoyButton::getWheelSpeedY() { return wheelSpeedY; }

void JoyButton::setDefaultButtonName(QString tempname) { defaultButtonName = tempname; }

QString JoyButton::getDefaultButtonName() { return defaultButtonName; }

/**
 * @brief Take cursor mouse information provided by all buttons and
 *     send a cursor mode mouse event to the display server.
 */
void JoyButton::moveMouseCursor(int &movedX, int &movedY, int &movedElapsed, QList<double> *mouseHistoryX,
                                QList<double> *mouseHistoryY, QElapsedTimer *testOldMouseTime, QTimer *staticMouseEventTimer,
                                int mouseRefreshRate, int mouseHistorySize, QList<JoyButton::mouseCursorInfo> *cursorXSpeeds,
                                QList<JoyButton::mouseCursorInfo> *cursorYSpeeds, double &cursorRemainderX,
                                double &cursorRemainderY, double weightModifier, int idleMouseRefrRate,
                                QList<JoyButton *> *pendingMouseButtons)
{
    movedX = 0;
    movedY = 0;
    int elapsedTime = testOldMouseTime->elapsed();
    movedElapsed = elapsedTime;

    if (staticMouseEventTimer->interval() < mouseRefreshRate)
        movedElapsed = mouseRefreshRate + (elapsedTime - staticMouseEventTimer->interval());

    if (mouseHistoryX->size() >= mouseHistorySize)
        mouseHistoryX->removeLast();

    if (mouseHistoryY->size() >= mouseHistorySize)
        mouseHistoryY->removeLast();

    /*
     * Combine all mouse events to find the distance to move the mouse
     * along the X and Y axis. If necessary, perform mouse smoothing.
     * The mouse smoothing technique used is an interpretation of the method
     * outlined at http://flipcode.net/archives/Smooth_Mouse_Filtering.shtml.
     */
    if ((cursorXSpeeds->length() == cursorYSpeeds->length()) && (cursorXSpeeds->length() > 0))
    {
        int queueLength = cursorXSpeeds->length();
        double finalx = 0.0;
        double finaly = 0.0;

        for (int i = 0; i < queueLength; i++)
        {
            mouseCursorInfo infoX = cursorXSpeeds->takeFirst();
            mouseCursorInfo infoY = cursorYSpeeds->takeFirst();

            distanceForMovingAx(finalx, infoX);
            distanceForMovingAx(finaly, infoY);

            infoX.slot->getMouseInterval()->restart();
            infoY.slot->getMouseInterval()->restart();
        }

        // Only apply remainder if both current displacement and remainder
        // follow the same direction.
        if ((cursorRemainderX >= 0) == (finalx >= 0))
            finalx += cursorRemainderX;

        // Cap maximum relative mouse movement.
        if (abs(finalx) > 127)
            finalx = (finalx < 0) ? -127 : 127;

        mouseHistoryX->prepend(finalx);

        // Only apply remainder if both current displacement and remainder
        // follow the same direction.
        if ((cursorRemainderY >= 0) == (finaly >= 0))
            finaly += cursorRemainderY;

        // Cap maximum relative mouse movement.
        if (abs(finaly) > 127)
            finaly = (finaly < 0) ? -127 : 127;

        mouseHistoryY->prepend(finaly);

        cursorRemainderX = 0;
        cursorRemainderY = 0;
        double adjustedX = 0;
        double adjustedY = 0;

        adjustAxForCursor(mouseHistoryX, adjustedX, cursorRemainderX, weightModifier);
        adjustAxForCursor(mouseHistoryY, adjustedY, cursorRemainderY, weightModifier);

        // This check is more of a precaution than anything. No need to cause
        // a sync to happen when not needed.
        if (!qFuzzyIsNull(adjustedX) || !qFuzzyIsNull(adjustedY))
            sendevent(adjustedX, adjustedY);

        movedX = adjustedX;
        movedY = adjustedY;
    } else
    {
        mouseHistoryX->prepend(0);
        mouseHistoryY->prepend(0);
    }

    // Check if mouse event timer should use idle time.
    if (pendingMouseButtons->length() == 0)
    {
        if (staticMouseEventTimer->interval() != idleMouseRefrRate)
        {
            staticMouseEventTimer->start(idleMouseRefrRate);

            // Clear current mouse history
            mouseHistoryX->clear();
            mouseHistoryY->clear();

            // Fill history with zeroes.
            for (int i = 0; i < mouseHistorySize; i++)
            {
                mouseHistoryX->append(0);
                mouseHistoryY->append(0);
            }
        }

        cursorRemainderX = 0;
        cursorRemainderY = 0;
    } else
    {
        if (staticMouseEventTimer->interval() != mouseRefreshRate)
            staticMouseEventTimer->start(mouseRefreshRate); // Restore intended QTimer interval.
    }

    cursorXSpeeds->clear();
    cursorYSpeeds->clear();
}

/**
 * @brief Combines mouse movement distances from multiple mouse mappings.
 * @param[in,out] finalAx Combined mouse distance from previous iteration. Updated by this function.
 * @param[in] infoAx Next mouse event to join into finalAx.
 */
void JoyButton::distanceForMovingAx(double &finalAx, mouseCursorInfo infoAx)
{
    if (!qFuzzyIsNull(infoAx.code))
        finalAx += infoAx.code;
}

void JoyButton::adjustAxForCursor(QList<double> *mouseHistoryList, double &adjustedAx, double &cursorRemainder,
                                  double weightModifier)
{
    double currentWeight = 1.0;
    double finalWeight = 0.0;

    QListIterator<double> mouseHist(*mouseHistoryList);
    while (mouseHist.hasNext())
    {
        adjustedAx += mouseHist.next() * currentWeight;
        finalWeight += currentWeight;
        currentWeight *= weightModifier;
    }

    if (fabs(adjustedAx) > 0)
    {
        adjustedAx = adjustedAx / finalWeight;
        double oldAx = adjustedAx;

        if (adjustedAx > 0)
            adjustedAx = floor(adjustedAx);
        else
            adjustedAx = ceil(adjustedAx);

        cursorRemainder = oldAx - adjustedAx;
    }
}

/**
 * @brief Take spring mouse information provided by all buttons and
 *     send a spring mode mouse event to the display server.
 */
void JoyButton::moveSpringMouse(int &movedX, int &movedY, bool &hasMoved, int springModeScreen,
                                QList<PadderCommon::springModeInfo> *springXSpeeds,
                                QList<PadderCommon::springModeInfo> *springYSpeeds, QList<JoyButton *> *pendingMouseButtons,
                                int mouseRefreshRate, int idleMouseRefrRate, QTimer *staticMouseEventTimer)
{
    PadderCommon::springModeInfo fullSpring = {-2.0, -2.0, 0, 0, false, springModeScreen, 0.0, 0.0};

    PadderCommon::springModeInfo relativeSpring = {-2.0, -2.0, 0, 0, false, springModeScreen, 0.0, 0.0};

    int realMouseX = movedX = 0;
    int realMouseY = movedY = 0;
    hasMoved = false;

    if ((springXSpeeds->length() == springYSpeeds->length()) && (springXSpeeds->length() > 0))
    {
        bool complete = false;

        for (int i = (springXSpeeds->length() - 1); (i >= 0) && !complete; i--)
        {
            double tempSpringDeadX = 0.0;
            double tempSpringDeadY = 0.0;

            PadderCommon::springModeInfo infoX;
            PadderCommon::springModeInfo infoY;

            infoX = springXSpeeds->takeLast();
            infoY = springYSpeeds->takeLast();

            double tempx = infoX.displacementX;
            double tempy = infoY.displacementY;
            tempSpringDeadX = infoX.springDeadX;
            tempSpringDeadY = infoY.springDeadY;

            if (infoX.relative)
            {
                if (qFuzzyCompare(relativeSpring.displacementX, -2.0))
                    relativeSpring.displacementX = tempx;

                relativeSpring.relative = true;

                // Use largest found width for spring
                // mode dimensions.
                relativeSpring.width = qMax(infoX.width, relativeSpring.width);
            } else
            {
                if (qFuzzyCompare(fullSpring.displacementX, -2.0))
                    fullSpring.displacementX = tempx;

                if (fullSpring.springDeadX == 0.0)
                    fullSpring.springDeadX = tempSpringDeadX;

                // Use largest found width for spring
                // mode dimensions.
                fullSpring.width = qMax(infoX.width, fullSpring.width);
            }

            if (infoY.relative)
            {
                if (qFuzzyCompare(relativeSpring.displacementY, -2.0))
                    relativeSpring.displacementY = tempy;

                relativeSpring.relative = true;

                // Use largest found height for spring
                // mode dimensions.
                relativeSpring.height = qMax(infoX.height, relativeSpring.height);
            } else
            {
                if (qFuzzyCompare(fullSpring.displacementY, -2.0))
                    fullSpring.displacementY = tempy;

                if (fullSpring.springDeadY == 0.0)
                    fullSpring.springDeadY = tempSpringDeadY;

                // Use largest found height for spring
                // mode dimensions.
                fullSpring.height = qMax(infoX.height, fullSpring.height);
            }

            if ((!qFuzzyCompare(relativeSpring.displacementX, -2.0) && !qFuzzyCompare(relativeSpring.displacementY, -2.0)) &&
                (!qFuzzyCompare(fullSpring.displacementX, -2.0) && !qFuzzyCompare(fullSpring.displacementY, -2.0)))
            {
                complete = true;
            } else if (((relativeSpring.springDeadX != 0.0) && (relativeSpring.springDeadY != 0.0)) &&
                       ((fullSpring.springDeadX != 0.0) && (fullSpring.springDeadY != 0.0)))
            {
                complete = true;
            }
        }

        fullSpring.screen = springModeScreen;
        relativeSpring.screen = springModeScreen;

        if (relativeSpring.relative)
        {
            sendSpringEvent(&fullSpring, &relativeSpring, &realMouseX, &realMouseY);
        } else
        {
            if (!hasFutureSpringEvents(JoyButton::getPendingMouseButtons()))
            {
                if (fullSpring.springDeadX != 0.0)
                    fullSpring.displacementX = fullSpring.springDeadX;

                if (fullSpring.springDeadY != 0.0)
                    fullSpring.displacementY = fullSpring.springDeadY;

                sendSpringEvent(&fullSpring, nullptr, &realMouseX, &realMouseY);
            } else
            {
                sendSpringEvent(&fullSpring, nullptr, &realMouseX, &realMouseY);
            }
        }

        movedX = realMouseX;
        movedY = realMouseY;
        hasMoved = true;
    }

    // Check if mouse event timer should use idle time.
    if (pendingMouseButtons->length() == 0)
    {
        staticMouseEventTimer->start(idleMouseRefrRate);
    } else
    {
        if (staticMouseEventTimer->interval() != mouseRefreshRate)
            // Restore intended QTimer interval.
            staticMouseEventTimer->start(mouseRefreshRate);
    }

    springXSpeeds->clear();
    springYSpeeds->clear();
}

void JoyButton::keyPressEvent()
{
    if (keyPressTimer.isActive() && (keyPressHold.elapsed() >= getPreferredKeyPressTime()))
    {
        currentKeyPress = nullptr;
        keyPressTimer.stop();
        keyPressHold.restart();
        releaseActiveSlots();
        createDeskTimer.stop();

        if (currentRelease != nullptr)
        {
            releaseDeskTimer.stop();
            createDeskEvent();
            waitForReleaseDeskEvent();
        } else
        {
            createDeskEvent();
        }
    } else
    {
        createDeskTimer.stop();
        startTimerOverrun(getPreferredKeyPressTime(), &keyPressHold, &keyPressTimer, true);
    }
}

/**
 * @brief TODO: CHECK IF METHOD WOULD BE USEFUL. CURRENTLY NOT USED.
 * @return Result
 */
bool JoyButton::checkForDelaySequence()
{
    bool result = false;
    QListIterator<JoyButtonSlot *> tempiter(*getAssignedSlots());

    // Move iterator to start of cycle.
    if (previousCycle != nullptr)
        tempiter.findNext(previousCycle);

    while (tempiter.hasNext())
    {
        JoyButtonSlot *slot = tempiter.next();

        if ((slot->getSlotMode() == JoyButtonSlot::JoyPause) || (slot->getSlotMode() == JoyButtonSlot::JoyRelease))
        {
            result = true;
            tempiter.toBack();
        } else if (slot->getSlotMode() == JoyButtonSlot::JoyCycle)
        {
            result = false;
            tempiter.toBack();
        }
    }

    return result;
}

SetJoystick *JoyButton::getParentSet() { return m_parentSet; }

void JoyButton::checkForPressedSetChange()
{
    if (!isButtonPressedQueue.isEmpty() && !whileHeldStatus)
    {
        bool tempButtonPressed = isButtonPressedQueue.last();
        bool tempFinalIgnoreSetsState = ignoreSetQueue.last();

        if (tempButtonPressed && !tempFinalIgnoreSetsState && (setSelectionCondition == SetChangeWhileHeld) &&
            (currentRelease == nullptr))
        {
            setChangeTimer.start(0);
            quitEvent = true;
        }
    }
}

/**
 * @brief Obtain the appropriate key press time for the current event.
 *     Order of preference: active key press time slot value ->
 *     profile value -> program default value.
 * @return Appropriate key press time for current event.
 */
int JoyButton::getPreferredKeyPressTime()
{
    if ((currentKeyPress != nullptr) && (currentKeyPress->getSlotCode() > 0))
    {
        return currentKeyPress->getSlotCode();
    } else if ((m_parentSet != nullptr) && (m_parentSet->getInputDevice()->getDeviceKeyPressTime() > 0))
    {
        return m_parentSet->getInputDevice()->getDeviceKeyPressTime();
    }

    return GlobalVariables::InputDevice::NUMBER_JOYSETS;
}

void JoyButton::setCycleResetTime(int interval)
{
    if (interval >= GlobalVariables::JoyButton::MINCYCLERESETTIME)
        cycleResetInterval =
            qBound(GlobalVariables::JoyButton::MINCYCLERESETTIME, interval, GlobalVariables::JoyButton::MAXCYCLERESETTIME);
    else
        cycleResetActive = false;

    emit propertyUpdated();
}

int JoyButton::getCycleResetTime() { return cycleResetInterval; }

void JoyButton::setCycleResetStatus(bool enabled)
{
    cycleResetActive = enabled;
    emit propertyUpdated();
}

bool JoyButton::isCycleResetActive() { return cycleResetActive; }

void JoyButton::establishPropertyUpdatedConnections()
{
    Q_ASSERT(!m_parentSet.isNull());
    connect(this, &JoyButton::slotsChanged, m_parentSet->getInputDevice(), &InputDevice::profileEdited);
    connect(this, &JoyButton::propertyUpdated, m_parentSet->getInputDevice(), &InputDevice::profileEdited);
}

void JoyButton::disconnectPropertyUpdatedConnections()
{
    disconnect(this, &JoyButton::slotsChanged, nullptr, nullptr);
    if (!m_parentSet.isNull())
        disconnect(this, &JoyButton::propertyUpdated, m_parentSet->getInputDevice(), &InputDevice::profileEdited);
}

/**
 * @brief Change initial settings used for mouse event timer being used by
 *     the application.
 */
void JoyButton::establishMouseTimerConnections()
{
    if (staticMouseEventTimer.timerType() != Qt::PreciseTimer)
        staticMouseEventTimer.setTimerType(Qt::PreciseTimer);

    // Only one connection will be made for each.
    connect(&staticMouseEventTimer, &QTimer::timeout, &mouseHelper, &JoyButtonMouseHelper::mouseEvent, Qt::UniqueConnection);

    if (staticMouseEventTimer.interval() != GlobalVariables::JoyButton::IDLEMOUSEREFRESHRATE)
        staticMouseEventTimer.setInterval(GlobalVariables::JoyButton::IDLEMOUSEREFRESHRATE);
}

void JoyButton::setSpringRelativeStatus(bool value)
{
    if (value != relativeSpring)
    {
        if (value)
            setSpringDeadCircleMultiplier(0);

        relativeSpring = value;
        emit propertyUpdated();
    }
}

bool JoyButton::isRelativeSpring() { return relativeSpring; }

/**
 * @brief Copy assignments and properties from one button to another.
 *     Used for set copying.
 * @param Button instance that should be modified.
 */
void JoyButton::copyAssignments(JoyButton *destButton)
{
    destButton->eventReset();
    destButton->assignmentsLock.lockForWrite();
    destButton->getAssignmentsLocal().clear();
    destButton->assignmentsLock.unlock();

    assignmentsLock.lockForWrite();
    QListIterator<JoyButtonSlot *> iter(*getAssignedSlots());

    while (iter.hasNext())
    {
        JoyButtonSlot *slot = iter.next();
        JoyButtonSlot *newslot = new JoyButtonSlot(slot, destButton);
        destButton->insertAssignedSlot(newslot, false);
    }

    assignmentsLock.unlock();

    destButton->m_toggle = m_toggle;
    destButton->turboInterval = turboInterval;
    destButton->m_useTurbo = m_useTurbo;
    destButton->mouseSpeedX = mouseSpeedX;
    destButton->mouseSpeedY = mouseSpeedY;
    destButton->wheelSpeedX = wheelSpeedX;
    destButton->wheelSpeedY = wheelSpeedY;
    destButton->mouseMode = mouseMode;
    destButton->mouseCurve = mouseCurve;
    destButton->springWidth = springWidth;
    destButton->springHeight = springHeight;
    destButton->sensitivity = sensitivity;
    destButton->buttonName = buttonName;
    destButton->actionName = actionName;
    destButton->cycleResetActive = cycleResetActive;
    destButton->cycleResetInterval = cycleResetInterval;
    destButton->relativeSpring = relativeSpring;
    destButton->currentTurboMode = currentTurboMode;
    destButton->m_easingDuration = m_easingDuration;
    destButton->extraAccelerationEnabled = extraAccelerationEnabled;
    destButton->extraAccelerationMultiplier = extraAccelerationMultiplier;
    destButton->minMouseDistanceAccelThreshold = minMouseDistanceAccelThreshold;
    destButton->maxMouseDistanceAccelThreshold = maxMouseDistanceAccelThreshold;
    destButton->startAccelMultiplier = startAccelMultiplier;
    destButton->springDeadCircleMultiplier = springDeadCircleMultiplier;
    destButton->extraAccelCurve = extraAccelCurve;

    destButton->buildActiveZoneSummaryString();

    if (!destButton->isDefault())
        emit propertyUpdated();
}

/**
 * @brief Set the turbo mode that the button should use
 * @param Mode that should be used
 */
void JoyButton::setTurboMode(TurboMode mode) { currentTurboMode = mode; }

/**
 * @brief Get currently assigned turbo mode
 * @return Currently assigned turbo mode
 */
JoyButton::TurboMode JoyButton::getTurboMode() { return currentTurboMode; }

/**
 * @brief Check if button should be considered a part of a real controller
 *     axis. Needed for some dialogs so the program won't have to resort to
 *     type checking.
 * @return Status of being part of a real controller axis
 */
bool JoyButton::isPartRealAxis() { return false; }

/**
 * @brief Calculate maximum mouse speed when using a given mouse curve.
 * @param Mouse curve
 * @param Mouse speed value
 * @return Final mouse speed
 */
int JoyButton::calculateFinalMouseSpeed(JoyMouseCurve curve, int value, const float joyspeed)
{
    int result = joyspeed * value;

    switch (curve)
    {
    case QuadraticExtremeCurve:
    case EasingQuadraticCurve:
    case EasingCubicCurve: {
        result *= 1.5;
        break;
    }
    default: {
        break;
    }
    }

    return result;
}

void JoyButton::setEasingDuration(double value)
{
    if ((value >= GlobalVariables::JoyButton::MINIMUMEASINGDURATION) &&
        (value <= GlobalVariables::JoyButton::MAXIMUMEASINGDURATION) && (!qFuzzyCompare(value, m_easingDuration)))
    {
        m_easingDuration = value;
        emit propertyUpdated();
    }
}

double JoyButton::getEasingDuration() { return m_easingDuration; }

JoyButtonMouseHelper *JoyButton::getMouseHelper() { return &mouseHelper; }

/**
 * @brief Get the list of buttons that have a pending mouse movement event.
 * @return QList<JoyButton*>*
 */
QList<JoyButton *> *JoyButton::getPendingMouseButtons() { return &pendingMouseButtons; }

QList<JoyButton::mouseCursorInfo> *JoyButton::getCursorXSpeeds() { return &cursorXSpeeds; }

QList<JoyButton::mouseCursorInfo> *JoyButton::getCursorYSpeeds() { return &cursorYSpeeds; }

QList<PadderCommon::springModeInfo> *JoyButton::getSpringXSpeeds() { return &springXSpeeds; }

QList<PadderCommon::springModeInfo> *JoyButton::getSpringYSpeeds() { return &springYSpeeds; }

QTimer *JoyButton::getStaticMouseEventTimer() { return &staticMouseEventTimer; }

QElapsedTimer *JoyButton::getTestOldMouseTime() { return &testOldMouseTime; }

bool JoyButton::hasCursorEvents(QList<JoyButton::mouseCursorInfo> *cursorXSpeedsList,
                                QList<JoyButton::mouseCursorInfo> *cursorYSpeedsList)
{
    //  qInstallMessageHandler(MessageHandler::myMessageOutput);

    return (cursorXSpeedsList->length() != 0) || (cursorYSpeedsList->length() != 0);
}

bool JoyButton::hasSpringEvents(QList<PadderCommon::springModeInfo> *springXSpeedsList,
                                QList<PadderCommon::springModeInfo> *springYSpeedsList)
{
    return (springXSpeedsList->length() != 0) || (springYSpeedsList->length() != 0);
}

/**
 * @brief Set the weight modifier to use for mouse smoothing.
 * @param Weight modifier in the range of 0.0 - 1.0.
 */
void JoyButton::setWeightModifier(double modifier, double maxWeightModifier, double &weightModifier)
{
    if ((modifier >= 0.0) && (modifier <= maxWeightModifier))
        weightModifier = modifier;
}

/**
 * @brief Set mouse history buffer size used for mouse smoothing.
 * @param Mouse history buffer size
 */
void JoyButton::setMouseHistorySize(int size, int maxMouseHistSize, int &mouseHistSize, QList<double> *mouseHistoryX,
                                    QList<double> *mouseHistoryY)
{
    if ((size >= 1) && (size <= maxMouseHistSize))
    {
        mouseHistoryX->clear();
        mouseHistoryY->clear();
        mouseHistSize = size;
    }
}

/**
 * @brief Set the mouse refresh rate when a mouse slot is active.
 * @param Refresh rate in ms.
 */
void JoyButton::setMouseRefreshRate(int refresh, int &mouseRefreshRate, int idleMouseRefrRate,
                                    JoyButtonMouseHelper *mouseHelper, QList<double> *mouseHistoryX,
                                    QList<double> *mouseHistoryY, QElapsedTimer *testOldMouseTime,
                                    QTimer *staticMouseEventTimer)
{
    if ((refresh >= 1) && (refresh <= 16))
    {
        mouseRefreshRate = refresh;

        if (staticMouseEventTimer->isActive())
        {
            testOldMouseTime->restart();

            if (staticMouseEventTimer->interval() != idleMouseRefrRate && staticMouseEventTimer->interval() != 0)
            {
                QMetaObject::invokeMethod(staticMouseEventTimer, "start", Q_ARG(int, mouseRefreshRate));
            } else
            {
                // Restart QTimer to keep QTimer in line with QTime
                QMetaObject::invokeMethod(staticMouseEventTimer, "start", Q_ARG(int, idleMouseRefrRate));
            }

            // Clear current mouse history
            mouseHistoryX->clear();
            mouseHistoryY->clear();
        } else
        {
            staticMouseEventTimer->setInterval(idleMouseRefrRate);
        }

        mouseHelper->carryMouseRefreshRateUpdate(mouseRefreshRate);
    }
}

/**
 * @brief Set the gamepad poll rate to be used in the application.
 * @param Poll rate in ms.
 */

void JoyButton::setGamepadRefreshRate(int refresh, int &gamepadRefreshRate, JoyButtonMouseHelper *mouseHelper)
{
    if ((refresh >= 1) && (refresh <= 16))
    {
        gamepadRefreshRate = refresh;
        mouseHelper->carryGamePollRateUpdate(gamepadRefreshRate);
    }
}

/**
 * @brief Check if turbo should be disabled for a slot
 * @param JoyButtonSlot to check
 */
void JoyButton::checkTurboCondition(JoyButtonSlot *slot)
{
    JoyButtonSlot::JoySlotInputAction mode = slot->getSlotMode();

    switch (mode)
    {
    case JoyButtonSlot::JoyPause:
    case JoyButtonSlot::JoyHold:
    case JoyButtonSlot::JoyDistance:
    case JoyButtonSlot::JoyRelease:
    case JoyButtonSlot::JoyLoadProfile:
    case JoyButtonSlot::JoySetChange: {
        setUseTurbo(false);
        break;
    }
    default: {
        break;
    }
    }
}

void JoyButton::resetProperties()
{
    qDebug() << "all current slots and previous slots ale cleared";

    resetAllProperties();
}

void JoyButton::resetAllProperties()
{
    resetSlotsProp(true);

    actionName.clear();
    m_toggle = GlobalVariables::JoyButton::DEFAULTTOGGLE;
    turboInterval = GlobalVariables::JoyButton::DEFAULTTURBOINTERVAL;
    currentTurboMode = NormalTurbo;
    m_useTurbo = GlobalVariables::JoyButton::DEFAULTUSETURBO;
    isDown = false;
    toggleActiveState = false;
    m_useTurbo = false;
    mouseSpeedX = GlobalVariables::JoyButton::DEFAULTMOUSESPEEDX;
    mouseSpeedY = GlobalVariables::JoyButton::DEFAULTMOUSESPEEDY;
    wheelSpeedX = GlobalVariables::JoyButton::DEFAULTWHEELX;
    wheelSpeedY = GlobalVariables::JoyButton::DEFAULTWHEELY;
    mouseMode = DEFAULTMOUSEMODE;
    mouseCurve = getDefaultMouseCurve();
    springWidth = GlobalVariables::JoyButton::DEFAULTSPRINGWIDTH;
    springHeight = GlobalVariables::JoyButton::DEFAULTSPRINGHEIGHT;
    sensitivity = GlobalVariables::JoyButton::DEFAULTSENSITIVITY;
    setSelection = GlobalVariables::JoyButton::DEFAULTSETSELECTION;
    setSelectionCondition = DEFAULTSETCONDITION;
    m_ignoresets = false;
    ignoreEvents = false;
    whileHeldStatus = false;
    buttonName.clear();
    actionName.clear();
    cycleResetActive = GlobalVariables::JoyButton::DEFAULTCYCLERESETACTIVE;
    cycleResetInterval = GlobalVariables::JoyButton::DEFAULTCYCLERESET;
    relativeSpring = GlobalVariables::JoyButton::DEFAULTRELATIVESPRING;
    lastDistance = 0.0;
    lastMouseDistance = 0.0;
    currentMouseDistance = 0.0;
    updateMouseParams(false, false, 0.0);
    restartAccelParams(false, false, true);
    lastWheelVerticalDistance = 0.0;
    lastWheelHorizontalDistance = 0.0;
    tempTurboInterval = 0;
    currentTurboMode = DEFAULTTURBOMODE;
    m_easingDuration = GlobalVariables::JoyButton::DEFAULTEASINGDURATION;
    springDeadCircleMultiplier = GlobalVariables::JoyButton::DEFAULTSPRINGRELEASERADIUS;

    updatePendingParams(false, false, false);
    lockForWritedString(activeZoneString, tr("[NO KEY]"));
}

bool JoyButton::isModifierButton() { return false; }

void JoyButton::resetActiveButtonMouseDistances(JoyButtonMouseHelper *mouseHelper)
{
    mouseHelper->resetButtonMouseDistances();
}

void JoyButton::resetAccelerationDistances()
{
    if (updateLastMouseDistance)
    {
        lastAccelerationDistance = currentAccelerationDistance;
        lastMouseDistance = currentMouseDistance;
        updateLastMouseDistance = false;
    }

    if (updateStartingMouseDistance)
    {
        startingAccelerationDistance = lastAccelerationDistance;
        updateStartingMouseDistance = false;
    }

    if (updateOldAccelMulti >= 0.0)
    {
        oldAccelMulti = updateOldAccelMulti;
        updateOldAccelMulti = 0.0;
    }

    currentAccelerationDistance = getAccelerationDistance();
    currentMouseDistance = getMouseDistanceFromDeadZone();
}

void JoyButton::initializeDistanceValues()
{
    lastAccelerationDistance = getLastAccelerationDistance();
    currentAccelerationDistance = getAccelerationDistance();
    startingAccelerationDistance = lastAccelerationDistance;

    lastMouseDistance = getLastMouseDistanceFromDeadZone();
    currentMouseDistance = getMouseDistanceFromDeadZone();
}

double JoyButton::getLastMouseDistanceFromDeadZone() { return lastMouseDistance; }

double JoyButton::getLastAccelerationDistance() { return lastAccelerationDistance; }

void JoyButton::copyLastMouseDistanceFromDeadZone(JoyButton *srcButton)
{
    this->lastMouseDistance = srcButton->lastMouseDistance;
}

void JoyButton::copyLastAccelerationDistance(JoyButton *srcButton)
{
    this->lastAccelerationDistance = srcButton->lastAccelerationDistance;
}

bool JoyButton::isExtraAccelerationEnabled() { return extraAccelerationEnabled; }

double JoyButton::getExtraAccelerationMultiplier() { return extraAccelerationMultiplier; }

void JoyButton::setExtraAccelerationStatus(bool status)
{
    if (isPartRealAxis())
    {
        extraAccelerationEnabled = status;
        emit propertyUpdated();
    } else
    {
        extraAccelerationEnabled = false;
    }
}

void JoyButton::setExtraAccelerationMultiplier(double value)
{
    if ((value >= 1.0) && (value <= 200.0))
    {
        extraAccelerationMultiplier = value;
        emit propertyUpdated();
    }
}

void JoyButton::setMinAccelThreshold(double value)
{
    if ((value >= 1.0) && (value <= 100.0) && (value <= maxMouseDistanceAccelThreshold))
    {
        minMouseDistanceAccelThreshold = value;
        // emit propertyUpdated();
    }
}

double JoyButton::getMinAccelThreshold() { return minMouseDistanceAccelThreshold; }

void JoyButton::setMaxAccelThreshold(double value)
{
    if ((value >= 1.0) && (value <= 100.0) && (value >= minMouseDistanceAccelThreshold))
    {
        maxMouseDistanceAccelThreshold = value;
        emit propertyUpdated();
    }
}

double JoyButton::getMaxAccelThreshold() { return maxMouseDistanceAccelThreshold; }

void JoyButton::setStartAccelMultiplier(double value)
{
    if ((value >= 0.0) && (value <= 100.0))
    {
        startAccelMultiplier = value;
        emit propertyUpdated();
    }
}

double JoyButton::getStartAccelMultiplier() { return startAccelMultiplier; }

void JoyButton::setSpringModeScreen(int screen, int &springModeScreen)
{
    if (screen >= -1)
        springModeScreen = screen;
}

void JoyButton::setAccelExtraDuration(double value)
{
    if ((value >= 0.0) && (value <= 5.0))
    {
        accelDuration = value;
        emit propertyUpdated();
    }
}

double JoyButton::getAccelExtraDuration() { return accelDuration; }

bool JoyButton::hasFutureSpringEvents(QList<JoyButton *> *pendingMouseButtons)
{
    bool result = false;
    QListIterator<JoyButton *> iter(*pendingMouseButtons);

    while (iter.hasNext())
    {
        JoyButton *temp = iter.next();

        if (temp->getMouseMode() == MouseSpring)
        {
            result = true;
            iter.toBack();
        }
    }

    return result;
}

void JoyButton::setSpringDeadCircleMultiplier(int value)
{
    if ((value >= 0) && (value <= 100))
    {
        springDeadCircleMultiplier = value;
        emit propertyUpdated();
    }
}

int JoyButton::getSpringDeadCircleMultiplier() { return springDeadCircleMultiplier; }

double JoyButton::getCurrentSpringDeadCircle() { return (springDeadCircleMultiplier * 0.01); }

void JoyButton::restartLastMouseTime(QElapsedTimer *testOldMouseTime) { testOldMouseTime->restart(); }

void JoyButton::setStaticMouseThread(QThread *thread, QTimer *staticMouseEventTimer, QElapsedTimer *testOldMouseTime,
                                     int idleMouseRefrRate, JoyButtonMouseHelper *mouseHelper)
{
    int oldInterval = staticMouseEventTimer->interval();

    if (oldInterval == 0)
        oldInterval = idleMouseRefrRate;

    staticMouseEventTimer->moveToThread(thread);
    mouseHelper->moveToThread(thread);

    QMetaObject::invokeMethod(staticMouseEventTimer, "start", Q_ARG(int, oldInterval));

    testOldMouseTime->start();
}

void JoyButton::indirectStaticMouseThread(QThread *thread, QTimer *staticMouseEventTimer, JoyButtonMouseHelper *mouseHelper)
{
    QMetaObject::invokeMethod(staticMouseEventTimer, "stop");
    QMetaObject::invokeMethod(mouseHelper, "changeThread", Q_ARG(QThread *, thread));
}

bool JoyButton::shouldInvokeMouseEvents(QList<JoyButton *> *pendingMouseButtons, QTimer *staticMouseEventTimer,
                                        QElapsedTimer *testOldMouseTime)
{
    bool result = false;

    if ((staticMouseEventTimer->interval() == 0) && (pendingMouseButtons->size() > 0) && staticMouseEventTimer->isActive())
        result = true;
    else if ((testOldMouseTime->elapsed() >= staticMouseEventTimer->interval()) && (pendingMouseButtons->size() > 0) &&
             staticMouseEventTimer->isActive())
        result = true;

    return result;
}

void JoyButton::invokeMouseEvents(JoyButtonMouseHelper *mouseHelper) { mouseHelper->mouseEvent(); }

bool JoyButton::hasActiveSlots() { return !getActiveSlots().isEmpty(); }

void JoyButton::setExtraAccelerationCurve(JoyExtraAccelerationCurve curve)
{
    extraAccelCurve = curve;
    emit propertyUpdated();
}

JoyButton::JoyExtraAccelerationCurve JoyButton::getExtraAccelerationCurve() { return extraAccelCurve; }

void JoyButton::setUpdateInitAccel(bool state) { this->updateInitAccelValues = state; }

QList<JoyButtonSlot *> &JoyButton::getAssignmentsLocal() { return assignments; }

QList<JoyButtonSlot *> &JoyButton::getActiveSlotsLocal() { return activeSlots; }
