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

   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.

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

MidiBuffer MPEMessages::addZone (MPEZone zone)
{
    MidiBuffer buffer (MidiRPNGenerator::generate (zone.getFirstNoteChannel(),
                                                   zoneLayoutMessagesRpnNumber,
                                                   zone.getNumNoteChannels(),
                                                   false, false));

    buffer.addEvents (perNotePitchbendRange (zone), 0, -1, 0);
    buffer.addEvents (masterPitchbendRange (zone), 0, -1, 0);

    return buffer;
}

MidiBuffer MPEMessages::perNotePitchbendRange (MPEZone zone)
{
    return MidiRPNGenerator::generate (zone.getFirstNoteChannel(), 0,
                                       zone.getPerNotePitchbendRange(),
                                       false, false);
}

MidiBuffer MPEMessages::masterPitchbendRange (MPEZone zone)
{
    return MidiRPNGenerator::generate (zone.getMasterChannel(), 0,
                                       zone.getMasterPitchbendRange(),
                                       false, false);
}

MidiBuffer MPEMessages::clearAllZones()
{
    return MidiRPNGenerator::generate (1, zoneLayoutMessagesRpnNumber, 16, false, false);
}

MidiBuffer MPEMessages::setZoneLayout (const MPEZoneLayout& layout)
{
    MidiBuffer buffer;

    buffer.addEvents (clearAllZones(), 0, -1, 0);

    for (int i = 0; i < layout.getNumZones(); ++i)
        buffer.addEvents (addZone (*layout.getZoneByIndex (i)), 0, -1, 0);

    return buffer;
}

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

class MPEMessagesTests  : public UnitTest
{
public:
    MPEMessagesTests() : UnitTest ("MPEMessages class") {}

    void runTest() override
    {
        beginTest ("add zone");
        {
            {
                MidiBuffer buffer = MPEMessages::addZone (MPEZone (1, 7));

                const uint8 expectedBytes[] =
                {
                    0xb1, 0x64, 0x06, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x07, // set up zone
                    0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x30, // per-note pbrange (default = 48)
                    0xb0, 0x64, 0x00, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x02  // master pbrange (default = 2)
                };

                testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
            }
            {
                MidiBuffer buffer = MPEMessages::addZone (MPEZone (11, 5, 96, 0));

                const uint8 expectedBytes[] =
                {
                    0xbb, 0x64, 0x06, 0xbb, 0x65, 0x00, 0xbb, 0x06, 0x05, // set up zone
                    0xbb, 0x64, 0x00, 0xbb, 0x65, 0x00, 0xbb, 0x06, 0x60, // per-note pbrange (custom)
                    0xba, 0x64, 0x00, 0xba, 0x65, 0x00, 0xba, 0x06, 0x00  // master pbrange (custom)
                };

                testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
            }
        }

        beginTest ("set per-note pitchbend range");
        {
            MPEZone zone (3, 7, 96);
            MidiBuffer buffer = MPEMessages::perNotePitchbendRange (zone);

            const uint8 expectedBytes[] = { 0xb3, 0x64, 0x00, 0xb3, 0x65, 0x00, 0xb3, 0x06, 0x60 };

            testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
        }


        beginTest ("set master pitchbend range");
        {
            MPEZone zone (3, 7, 48, 60);
            MidiBuffer buffer = MPEMessages::masterPitchbendRange (zone);

            const uint8 expectedBytes[] = { 0xb2, 0x64, 0x00, 0xb2, 0x65, 0x00, 0xb2, 0x06, 0x3c };

            testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
        }

        beginTest ("clear all zones");
        {
            MidiBuffer buffer = MPEMessages::clearAllZones();

            const uint8 expectedBytes[] = { 0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x10 };

            testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
        }

        beginTest ("set complete state");
        {
            MPEZoneLayout layout;
            layout.addZone (MPEZone (1, 7, 96, 0));
            layout.addZone (MPEZone (9, 7));
            layout.addZone (MPEZone (5, 3));
            layout.addZone (MPEZone (5, 4));
            layout.addZone (MPEZone (6, 4));

            MidiBuffer buffer = MPEMessages::setZoneLayout (layout);

            const uint8 expectedBytes[] = {
                0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x10,  // clear all zones
                0xb1, 0x64, 0x06, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x03,  // set zone 1 (1, 3)
                0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x60,  // per-note pbrange (custom)
                0xb0, 0x64, 0x00, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00,  // master pbrange (custom)
                0xb6, 0x64, 0x06, 0xb6, 0x65, 0x00, 0xb6, 0x06, 0x04,  // set zone 2 (6, 4)
                0xb6, 0x64, 0x00, 0xb6, 0x65, 0x00, 0xb6, 0x06, 0x30,  // per-note pbrange (default = 48)
                0xb5, 0x64, 0x00, 0xb5, 0x65, 0x00, 0xb5, 0x06, 0x02   // master pbrange (default = 2)
            };

            testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
        }
    }

private:
    //==============================================================================
    void testMidiBuffer (MidiBuffer& buffer, const uint8* expectedBytes, int expectedBytesSize)
    {
        uint8 actualBytes[128] = { 0 };
        extractRawBinaryData (buffer, actualBytes, sizeof (actualBytes));

        expectEquals (std::memcmp (actualBytes, expectedBytes, (std::size_t) expectedBytesSize), 0);
    }

    //==============================================================================
    void extractRawBinaryData (const MidiBuffer& midiBuffer, const uint8* bufferToCopyTo, std::size_t maxBytes)
    {
        std::size_t pos = 0;
        MidiBuffer::Iterator iter (midiBuffer);
        MidiMessage midiMessage;
        int samplePosition; // Note: not actually used, so no need to initialise.

        while (iter.getNextEvent (midiMessage, samplePosition))
        {
            const uint8* data = midiMessage.getRawData();
            std::size_t dataSize = (std::size_t) midiMessage.getRawDataSize();

            if (pos + dataSize > maxBytes)
                return;

            std::memcpy ((void*) (bufferToCopyTo + pos), data, dataSize);
            pos += dataSize;
        }
    }
};

static MPEMessagesTests MPEMessagesUnitTests;

#endif // JUCE_UNIT_TESTS
