/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2015 - ROLI Ltd.

   Permission is granted to use this software under the terms of either:
   a) the GPL v2 (or any later version)
   b) the Affero GPL v3

   Details of these licenses can be found at: www.gnu.org/licenses

   JUCE 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.

   ------------------------------------------------------------------------------

   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.juce.com for more information.

  ==============================================================================
*/

MidiRPNDetector::MidiRPNDetector() noexcept
{
}

MidiRPNDetector::~MidiRPNDetector() noexcept
{
}

bool MidiRPNDetector::parseControllerMessage (int midiChannel,
                                              int controllerNumber,
                                              int controllerValue,
                                              MidiRPNMessage& result) noexcept
{
    jassert (midiChannel >= 1 && midiChannel <= 16);
    jassert (controllerNumber >= 0 && controllerNumber < 128);
    jassert (controllerValue >= 0 && controllerValue < 128);

    return states[midiChannel - 1].handleController (midiChannel, controllerNumber, controllerValue, result);
}

void MidiRPNDetector::reset() noexcept
{
    for (int i = 0; i < 16; ++i)
    {
        states[i].parameterMSB = 0xff;
        states[i].parameterLSB = 0xff;
        states[i].resetValue();
        states[i].isNRPN = false;
    }
}

//==============================================================================
MidiRPNDetector::ChannelState::ChannelState() noexcept
    : parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false)
{
}

bool MidiRPNDetector::ChannelState::handleController (int channel,
                                                      int controllerNumber,
                                                      int value,
                                                      MidiRPNMessage& result) noexcept
{
    switch (controllerNumber)
    {
        case 0x62:  parameterLSB = uint8 (value); resetValue(); isNRPN = true;  break;
        case 0x63:  parameterMSB = uint8 (value); resetValue(); isNRPN = true;  break;

        case 0x64:  parameterLSB = uint8 (value); resetValue(); isNRPN = false; break;
        case 0x65:  parameterMSB = uint8 (value); resetValue(); isNRPN = false; break;

        case 0x06:  valueMSB = uint8 (value); return sendIfReady (channel, result);
        case 0x26:  valueLSB = uint8 (value); break;

        default:  break;
    }

    return false;
}

void MidiRPNDetector::ChannelState::resetValue() noexcept
{
    valueMSB = 0xff;
    valueLSB = 0xff;
}

//==============================================================================
bool MidiRPNDetector::ChannelState::sendIfReady (int channel, MidiRPNMessage& result) noexcept
{
    if (parameterMSB < 0x80 && parameterLSB < 0x80)
    {
        if (valueMSB < 0x80)
        {
            result.channel = channel;
            result.parameterNumber = (parameterMSB << 7) + parameterLSB;
            result.isNRPN = isNRPN;

            if (valueLSB < 0x80)
            {
                result.value = (valueMSB << 7) + valueLSB;
                result.is14BitValue = true;
            }
            else
            {
                result.value = valueMSB;
                result.is14BitValue = false;
            }

            return true;
        }
    }

    return false;
}

//==============================================================================
MidiBuffer MidiRPNGenerator::generate (MidiRPNMessage message)
{
    return generate (message.channel,
                     message.parameterNumber,
                     message.value,
                     message.isNRPN,
                     message.is14BitValue);
}

MidiBuffer MidiRPNGenerator::generate (int midiChannel,
                                       int parameterNumber,
                                       int value,
                                       bool isNRPN,
                                       bool use14BitValue)
{
    jassert (midiChannel > 0 && midiChannel <= 16);
    jassert (parameterNumber >= 0 && parameterNumber < 16384);
    jassert (value >= 0 && value < (use14BitValue ? 16384 : 128));

    uint8 parameterLSB = uint8 (parameterNumber & 0x0000007f);
    uint8 parameterMSB = uint8 (parameterNumber >> 7);

    uint8 valueLSB = use14BitValue ? uint8 (value & 0x0000007f) : 0x00;
    uint8 valueMSB = use14BitValue ? uint8 (value >> 7) : uint8 (value);

    uint8 channelByte = uint8 (0xb0 + midiChannel - 1);

    MidiBuffer buffer;

    buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x62 : 0x64, parameterLSB),  0);
    buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x63 : 0x65, parameterMSB),  0);

    // sending the value LSB is optional, but must come before sending the value MSB:
    if (use14BitValue)
        buffer.addEvent (MidiMessage (channelByte, 0x26, valueLSB), 0);

    buffer.addEvent (MidiMessage (channelByte, 0x06, valueMSB), 0);

    return buffer;
}

//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS

class MidiRPNDetectorTests   : public UnitTest
{
public:
    MidiRPNDetectorTests()  : UnitTest ("MidiRPNDetector class") {}

    void runTest() override
    {
        beginTest ("7-bit RPN");
        {
            MidiRPNDetector detector;
            MidiRPNMessage rpn;
            expect (! detector.parseControllerMessage (2, 101, 0,  rpn));
            expect (! detector.parseControllerMessage (2, 100, 7,  rpn));
            expect (detector.parseControllerMessage   (2, 6,   42, rpn));

            expectEquals (rpn.channel, 2);
            expectEquals (rpn.parameterNumber, 7);
            expectEquals (rpn.value, 42);
            expect (! rpn.isNRPN);
            expect (! rpn.is14BitValue);
        }

        beginTest ("14-bit RPN");
        {
            MidiRPNDetector detector;
            MidiRPNMessage rpn;
            expect (! detector.parseControllerMessage (1, 100, 44, rpn));
            expect (! detector.parseControllerMessage (1, 101, 2,  rpn));
            expect (! detector.parseControllerMessage (1, 38,  94, rpn));
            expect (detector.parseControllerMessage   (1, 6,   1,  rpn));

            expectEquals (rpn.channel, 1);
            expectEquals (rpn.parameterNumber, 300);
            expectEquals (rpn.value, 222);
            expect (! rpn.isNRPN);
            expect (rpn.is14BitValue);
        }

        beginTest ("RPNs on multiple channels simultaneously");
        {
            MidiRPNDetector detector;
            MidiRPNMessage rpn;
            expect (! detector.parseControllerMessage (1, 100, 44, rpn));
            expect (! detector.parseControllerMessage (2, 101, 0,  rpn));
            expect (! detector.parseControllerMessage (1, 101, 2,  rpn));
            expect (! detector.parseControllerMessage (2, 100, 7,  rpn));
            expect (! detector.parseControllerMessage (1, 38,  94, rpn));
            expect (detector.parseControllerMessage   (2, 6,   42, rpn));

            expectEquals (rpn.channel, 2);
            expectEquals (rpn.parameterNumber, 7);
            expectEquals (rpn.value, 42);
            expect (! rpn.isNRPN);
            expect (! rpn.is14BitValue);

            expect (detector.parseControllerMessage   (1, 6,   1,  rpn));

            expectEquals (rpn.channel, 1);
            expectEquals (rpn.parameterNumber, 300);
            expectEquals (rpn.value, 222);
            expect (! rpn.isNRPN);
            expect (rpn.is14BitValue);
        }

        beginTest ("14-bit RPN with value within 7-bit range");
        {
            MidiRPNDetector detector;
            MidiRPNMessage rpn;
            expect (! detector.parseControllerMessage (16, 100, 0 , rpn));
            expect (! detector.parseControllerMessage (16, 101, 0,  rpn));
            expect (! detector.parseControllerMessage (16, 38,  3,  rpn));
            expect (detector.parseControllerMessage   (16, 6,   0,  rpn));

            expectEquals (rpn.channel, 16);
            expectEquals (rpn.parameterNumber, 0);
            expectEquals (rpn.value, 3);
            expect (! rpn.isNRPN);
            expect (rpn.is14BitValue);
        }

        beginTest ("invalid RPN (wrong order)");
        {
            MidiRPNDetector detector;
            MidiRPNMessage rpn;
            expect (! detector.parseControllerMessage (2, 6,   42, rpn));
            expect (! detector.parseControllerMessage (2, 101, 0,  rpn));
            expect (! detector.parseControllerMessage (2, 100, 7,  rpn));
        }

        beginTest ("14-bit RPN interspersed with unrelated CC messages");
        {
            MidiRPNDetector detector;
            MidiRPNMessage rpn;
            expect (! detector.parseControllerMessage (16, 3,   80, rpn));
            expect (! detector.parseControllerMessage (16, 100, 0 , rpn));
            expect (! detector.parseControllerMessage (16, 4,   81, rpn));
            expect (! detector.parseControllerMessage (16, 101, 0,  rpn));
            expect (! detector.parseControllerMessage (16, 5,   82, rpn));
            expect (! detector.parseControllerMessage (16, 5,   83, rpn));
            expect (! detector.parseControllerMessage (16, 38,  3,  rpn));
            expect (! detector.parseControllerMessage (16, 4,   84, rpn));
            expect (! detector.parseControllerMessage (16, 3,   85, rpn));
            expect (detector.parseControllerMessage   (16, 6,   0,  rpn));

            expectEquals (rpn.channel, 16);
            expectEquals (rpn.parameterNumber, 0);
            expectEquals (rpn.value, 3);
            expect (! rpn.isNRPN);
            expect (rpn.is14BitValue);
        }

        beginTest ("14-bit NRPN");
        {
            MidiRPNDetector detector;
            MidiRPNMessage rpn;
            expect (! detector.parseControllerMessage (1, 98,  44, rpn));
            expect (! detector.parseControllerMessage (1, 99 , 2,  rpn));
            expect (! detector.parseControllerMessage (1, 38,  94, rpn));
            expect (detector.parseControllerMessage   (1, 6,   1,  rpn));

            expectEquals (rpn.channel, 1);
            expectEquals (rpn.parameterNumber, 300);
            expectEquals (rpn.value, 222);
            expect (rpn.isNRPN);
            expect (rpn.is14BitValue);
        }

        beginTest ("reset");
        {
            MidiRPNDetector detector;
            MidiRPNMessage rpn;
            expect (! detector.parseControllerMessage (2, 101, 0,  rpn));
            detector.reset();
            expect (! detector.parseControllerMessage (2, 100, 7,  rpn));
            expect (! detector.parseControllerMessage (2, 6,   42, rpn));
        }
    }
};

static MidiRPNDetectorTests MidiRPNDetectorUnitTests;

//==============================================================================
class MidiRPNGeneratorTests   : public UnitTest
{
public:
    MidiRPNGeneratorTests()  : UnitTest ("MidiRPNGenerator class") {}

    void runTest() override
    {
        beginTest ("generating RPN/NRPN");
        {
            {
                MidiBuffer buffer = MidiRPNGenerator::generate (1, 23, 1337, true, true);
                expectContainsRPN (buffer, 1, 23, 1337, true, true);
            }
            {
                MidiBuffer buffer = MidiRPNGenerator::generate (16, 101, 34, false, false);
                expectContainsRPN (buffer, 16, 101, 34, false, false);
            }
            {
                MidiRPNMessage message = { 16, 101, 34, false, false };
                MidiBuffer buffer = MidiRPNGenerator::generate (message);
                expectContainsRPN (buffer, message);
            }
        }
    }

private:
    //==============================================================================
    void expectContainsRPN (const MidiBuffer& midiBuffer,
                            int channel,
                            int parameterNumber,
                            int value,
                            bool isNRPN,
                            bool is14BitValue)
    {
        MidiRPNMessage expected = { channel, parameterNumber, value, isNRPN, is14BitValue };
        expectContainsRPN (midiBuffer, expected);
    }

    //==============================================================================
    void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected)
    {
        MidiBuffer::Iterator iter (midiBuffer);
        MidiMessage midiMessage;
        MidiRPNMessage result = MidiRPNMessage();
        MidiRPNDetector detector;
        int samplePosition; // not actually used, so no need to initialise.

        while (iter.getNextEvent (midiMessage, samplePosition))
        {
            if (detector.parseControllerMessage (midiMessage.getChannel(),
                                                 midiMessage.getControllerNumber(),
                                                 midiMessage.getControllerValue(),
                                                 result))
                break;
        }

        expectEquals (result.channel, expected.channel);
        expectEquals (result.parameterNumber, expected.parameterNumber);
        expectEquals (result.value, expected.value);
        expect (result.isNRPN == expected.isNRPN),
        expect (result.is14BitValue == expected.is14BitValue);
    }
};

static MidiRPNGeneratorTests MidiRPNGeneratorUnitTests;

#endif // JUCE_UNIT_TESTS
