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

   This file is part of the JUCE framework.
   Copyright (c) Raw Material Software Limited

   JUCE is an open source framework subject to commercial or open source
   licensing.

   By downloading, installing, or using the JUCE framework, or combining the
   JUCE framework with any other source code, object code, content or any other
   copyrightable work, you agree to the terms of the JUCE End User Licence
   Agreement, and all incorporated terms including the JUCE Privacy Policy and
   the JUCE Website Terms of Service, as applicable, which will bind you. If you
   do not agree to the terms of these agreements, we will not license the JUCE
   framework to you, and you must discontinue the installation or download
   process and cease use of the JUCE framework.

   JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
   JUCE Privacy Policy: https://juce.com/juce-privacy-policy
   JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/

   Or:

   You may also use this code under the terms of the AGPLv3:
   https://www.gnu.org/licenses/agpl-3.0.en.html

   THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
   WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.

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

/*
    Namespace containing metadata about MIDI-CI message types, such as
    replies corresponding to inquiries, and serialization functions.

    @tags{Audio}
*/
namespace juce::midi_ci::detail::MessageMeta
{

//==============================================================================
/* The maximum CI version that can be parsed and generated by this implementation. */
static constexpr std::byte implementationVersion { 0x02 };

/* Wraps a pointer to a Span. Used to indicate to CI readers/writers that a particular field is
   of variable length, starting with a 16-bit or 32-bit byte count.
*/
template <uint8_t NumBytes, typename T, bool isJson = false>
struct SpanWithSizeBytes
{
    T& span;
};

/* Creates a SpanWithSizeBytes with an appropriate template argument. */
template <uint8_t NumBytes, typename T>
static constexpr auto makeSpanWithSizeBytes (      Span<T>& span) { return SpanWithSizeBytes<NumBytes,       Span<T>> { span }; }

template <uint8_t NumBytes, typename T>
static constexpr auto makeSpanWithSizeBytes (const Span<T>& span) { return SpanWithSizeBytes<NumBytes, const Span<T>> { span }; }

template <uint8_t NumBytes, typename T>
static constexpr auto makeJsonWithSizeBytes (      Span<T>& span) { return SpanWithSizeBytes<NumBytes,       Span<T>, true> { span }; }

template <uint8_t NumBytes, typename T>
static constexpr auto makeJsonWithSizeBytes (const Span<T>& span) { return SpanWithSizeBytes<NumBytes, const Span<T>, true> { span }; }

template <uint8_t SubID2, typename R = void>
struct Metadata
{
    static constexpr std::byte subID2 { SubID2 };
    using Reply = R;
};

template <typename T>
struct Meta;

template <>
struct Meta<Message::DiscoveryResponse> : Metadata<0x71> {};

template <>
struct Meta<Message::Discovery> : Metadata<0x70, Message::DiscoveryResponse> {};

template <>
struct Meta<Message::EndpointInquiryResponse> : Metadata<0x73> {};

template <>
struct Meta<Message::EndpointInquiry> : Metadata<0x72, Message::EndpointInquiryResponse> {};

template <>
struct Meta<Message::InvalidateMUID> : Metadata<0x7e> {};

template <>
struct Meta<Message::ACK> : Metadata<0x7d> {};

template <>
struct Meta<Message::NAK> : Metadata<0x7f> {};

template <>
struct Meta<Message::ProfileInquiryResponse> : Metadata<0x21> {};

template <>
struct Meta<Message::ProfileInquiry> : Metadata<0x20, Message::ProfileInquiryResponse> {};

template <>
struct Meta<Message::ProfileAdded> : Metadata<0x26> {};

template <>
struct Meta<Message::ProfileRemoved> : Metadata<0x27> {};

template <>
struct Meta<Message::ProfileDetailsResponse> : Metadata<0x29> {};

template <>
struct Meta<Message::ProfileDetails> : Metadata<0x28, Message::ProfileDetailsResponse> {};

template <>
struct Meta<Message::ProfileOn> : Metadata<0x22> {};

template <>
struct Meta<Message::ProfileOff> : Metadata<0x23> {};

template <>
struct Meta<Message::ProfileEnabledReport> : Metadata<0x24> {};

template <>
struct Meta<Message::ProfileDisabledReport> : Metadata<0x25> {};

template <>
struct Meta<Message::ProfileSpecificData> : Metadata<0x2f> {};

template <>
struct Meta<Message::PropertyExchangeCapabilitiesResponse> : Metadata<0x31> {};

template <>
struct Meta<Message::PropertyExchangeCapabilities> : Metadata<0x30, Message::PropertyExchangeCapabilitiesResponse> {};

template <>
struct Meta<Message::StaticSizePropertyExchange> {};

template <>
struct Meta<Message::DynamicSizePropertyExchange> {};

template <>
struct Meta<Message::PropertyGetDataResponse> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x35> {};

template <>
struct Meta<Message::PropertyGetData> : Meta<Message::StaticSizePropertyExchange>, Metadata<0x34, Message::PropertyGetDataResponse> {};

template <>
struct Meta<Message::PropertySetDataResponse> : Meta<Message::StaticSizePropertyExchange>, Metadata<0x37> {};

template <>
struct Meta<Message::PropertySetData> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x36, Message::PropertySetDataResponse> {};

template <>
struct Meta<Message::PropertySubscribeResponse> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x39> {};

template <>
struct Meta<Message::PropertySubscribe> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x38, Message::PropertySubscribeResponse> {};

template <>
struct Meta<Message::PropertyNotify> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x3f> {};

template <>
struct Meta<Message::ProcessInquiryResponse> : Metadata<0x41> {};

template <>
struct Meta<Message::ProcessInquiry> : Metadata<0x40, Message::ProcessInquiryResponse> {};

template <>
struct Meta<Message::ProcessMidiMessageReportResponse> : Metadata<0x43> {};

template <>
struct Meta<Message::ProcessMidiMessageReport> : Metadata<0x42, Message::ProcessMidiMessageReportResponse> {};

template <>
struct Meta<Message::ProcessEndMidiMessageReport> : Metadata<0x44> {};

} // namespace juce::midi_ci::detail::MessageMeta

/** @cond */
namespace juce
{

struct VersionBase
{
    static constexpr auto marshallingVersion = (int) juce::midi_ci::detail::MessageMeta::implementationVersion;
};

template <uint8_t NumBytes, typename T, bool isJson>
struct SerialisationTraits<midi_ci::detail::MessageMeta::SpanWithSizeBytes<NumBytes, T, isJson>>
{
    static constexpr auto marshallingVersion = std::nullopt;

    template <typename This>
    static auto getSize (This& t)
    {
        if constexpr (NumBytes == 1)
            return (uint8_t) t.size();
        else if constexpr (NumBytes == 2)
            return (uint16_t) t.size();
        else if constexpr (NumBytes == 4)
            return (uint32_t) t.size();
        else if constexpr (NumBytes == 8)
            return (uint64_t) t.size();
        else
            static_assert (detail::delayStaticAssert<T>, "NumBytes is not a power of two");
    }

    template <typename Archive, typename This>
    static auto load (Archive&, This&)
    {
    }

    template <typename Archive, typename This>
    static auto save (Archive& archive, const This& t)
    {
        auto size = getSize (t.span);
        archive (serialisationSize (size));

        for (const auto& element : t.span)
            archive (element);
    }
};

template <>
struct SerialisationTraits<ci::MUID> : VersionBase
{
    template <typename Archive>
    static auto load (Archive& archive, ci::MUID& t)
    {
        uint32_t muid{};
        auto result = archive (muid);
        t = ci::MUID::makeUnchecked (muid);
        return result;
    }

    template <typename Archive>
    static auto save (Archive& archive, const ci::MUID& t)
    {
        return archive (t.get());
    }
};

template <>
struct SerialisationTraits<ci::Message::Header> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        const std::byte universalSystemExclusive { 0x7e }, subID { 0x0d };
        return archive (universalSystemExclusive,
                        t.deviceID,
                        subID,
                        t.category,
                        t.version,
                        t.source,
                        t.destination);
    }
};

template <>
struct SerialisationTraits<ci::Message::Generic> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        return archive (t.header, t.data);
    }
};

template <>
struct SerialisationTraits<ci::Message::DiscoveryResponse> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("device", t.device),
                 named ("capabilities", t.capabilities),
                 named ("maximumSysexSize", t.maximumSysexSize));

        if (0x02 <= archive.getVersion())
            archive (named ("outputPathID", t.outputPathID), named ("functionBlock", t.functionBlock));
    }
};

template <>
struct SerialisationTraits<ci::Message::Discovery> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("device", t.device),
                 named ("capabilities", t.capabilities),
                 named ("maximumSysexSize", t.maximumSysexSize));

        if (0x02 <= archive.getVersion())
            archive (named ("outputPathID", t.outputPathID));
    }
};

template <>
struct SerialisationTraits<ci::Message::EndpointInquiryResponse> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("status", t.status),
                 named ("data", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.data)));
    }
};

template <>
struct SerialisationTraits<ci::Message::EndpointInquiry> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("status", t.status));
    }
};

template <>
struct SerialisationTraits<ci::Message::InvalidateMUID> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("target", t.target));
    }
};

template <>
struct SerialisationTraits<ci::Message::ACK> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("originalCategory", t.originalCategory),
                 named ("statusCode", t.statusCode),
                 named ("statusData", t.statusData),
                 named ("details", t.details),
                 named ("messageText", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.messageText)));
    }
};

template <>
struct SerialisationTraits<ci::Message::NAK> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        if (0x02 <= archive.getVersion())
        {
            archive (named ("originalCategory", t.originalCategory),
                     named ("statusCode", t.statusCode),
                     named ("statusData", t.statusData),
                     named ("details", t.details),
                     named ("messageText", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.messageText)));
        }
    }
};

template <>
struct SerialisationTraits<ci::Message::ProfileInquiryResponse> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("enabledProfiles", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.enabledProfiles)),
                 named ("disabledProfiles", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.disabledProfiles)));
    }
};

template <>
struct SerialisationTraits<ci::Message::ProfileInquiry> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive&, This&)
    {
    }
};

template <>
struct SerialisationTraits<ci::Message::ProfileAdded> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("profile", t.profile));
    }
};

template <>
struct SerialisationTraits<ci::Message::ProfileRemoved> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("profile", t.profile));
    }
};

template <>
struct SerialisationTraits<ci::Message::ProfileDetailsResponse> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("profile", t.profile),
                 named ("target", t.target),
                 named ("data", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.data)));
    }
};

template <>
struct SerialisationTraits<ci::Message::ProfileDetails> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("profile", t.profile),
                 named ("target", t.target));
    }
};

template <>
struct SerialisationTraits<ci::Message::ProfileOn> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("profile", t.profile));

        if (0x02 <= archive.getVersion())
            archive (named ("numChannels", t.numChannels));
    }
};

template <>
struct SerialisationTraits<ci::Message::ProfileOff> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("profile", t.profile));

        if (0x02 <= archive.getVersion())
        {
            uint16_t reserved{};
            archive (named ("reserved", reserved));
        }
    }
};

template <>
struct SerialisationTraits<ci::Message::ProfileEnabledReport> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("profile", t.profile));

        if (0x02 <= archive.getVersion())
            archive (named ("numChannels", t.numChannels));
    }
};

template <>
struct SerialisationTraits<ci::Message::ProfileDisabledReport> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("profile", t.profile));

        if (0x02 <= archive.getVersion())
            archive (named ("numChannels", t.numChannels));
    }
};

template <>
struct SerialisationTraits<ci::Message::ProfileSpecificData> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("profile", t.profile), named ("data", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<4> (t.data)));
    }
};

template <>
struct SerialisationTraits<ci::Message::PropertyExchangeCapabilitiesResponse> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("numRequests", t.numSimultaneousRequestsSupported));

        if (0x02 <= archive.getVersion())
            archive (named ("major", t.majorVersion), named ("minor", t.minorVersion));
    }
};

template <>
struct SerialisationTraits<ci::Message::PropertyExchangeCapabilities> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("numRequests", t.numSimultaneousRequestsSupported));

        if (0x02 <= archive.getVersion())
            archive (named ("major", t.majorVersion), named ("minor", t.minorVersion));
    }
};

template <>
struct SerialisationTraits<ci::Message::StaticSizePropertyExchange> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        const uint16_t chunkNum = 1, dataLength = 0;
        archive (named ("requestID", t.requestID),
                 named ("header", midi_ci::detail::MessageMeta::makeJsonWithSizeBytes<2> (t.header)),
                 named ("numChunks", chunkNum),
                 named ("thisChunk", chunkNum),
                 named ("length", dataLength));
    }
};

template <>
struct SerialisationTraits<ci::Message::DynamicSizePropertyExchange> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (named ("requestID", t.requestID),
                 named ("header", midi_ci::detail::MessageMeta::makeJsonWithSizeBytes<2> (t.header)),
                 named ("numChunks", t.totalNumChunks),
                 named ("thisChunk", t.thisChunkNum),
                 named ("data", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.data)));
    }
};

template <>
struct SerialisationTraits<ci::Message::PropertyGetDataResponse> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};

template <>
struct SerialisationTraits<ci::Message::PropertyGetData> : SerialisationTraits<ci::Message::StaticSizePropertyExchange> {};

template <>
struct SerialisationTraits<ci::Message::PropertySetDataResponse> : SerialisationTraits<ci::Message::StaticSizePropertyExchange> {};

template <>
struct SerialisationTraits<ci::Message::PropertySetData> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};

template <>
struct SerialisationTraits<ci::Message::PropertySubscribeResponse> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};

template <>
struct SerialisationTraits<ci::Message::PropertySubscribe> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};

template <>
struct SerialisationTraits<ci::Message::PropertyNotify> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};

template <>
struct SerialisationTraits<ci::Message::ProcessInquiryResponse> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        archive (t.supportedFeatures);
    }
};

template <>
struct SerialisationTraits<ci::Message::ProcessInquiry> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive&, This&)
    {
    }
};

template <>
struct SerialisationTraits<ci::Message::ProcessMidiMessageReportResponse> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        std::byte reserved{};
        archive (named ("messageDataControl", t.messageDataControl),
                 named ("requestedMessages", t.requestedMessages),
                 named ("reserved", reserved),
                 named ("channelControllerMessages", t.channelControllerMessages),
                 named ("noteDataMessages", t.noteDataMessages));
    }
};

template <>
struct SerialisationTraits<ci::Message::ProcessMidiMessageReport> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive& archive, This& t)
    {
        std::byte reserved{};
        archive (named ("messageDataControl", t.messageDataControl),
                 named ("requestedMessages", t.requestedMessages),
                 named ("reserved", reserved),
                 named ("channelControllerMessages", t.channelControllerMessages),
                 named ("noteDataMessages", t.noteDataMessages));
    }
};

template <>
struct SerialisationTraits<ci::Message::ProcessEndMidiMessageReport> : VersionBase
{
    template <typename Archive, typename This>
    static auto serialise (Archive&, This&)
    {
    }
};

} // namespace juce
/** @endcond */
