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

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

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 7 End-User License
   Agreement and JUCE Privacy Policy.

   End User License Agreement: www.juce.com/juce-7-licence
   Privacy Policy: www.juce.com/juce-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

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

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

namespace juce::midi_ci
{

struct PropertyDelegateDetail
{
    /*
        Note: We don't use ToVar and FromVar here, because we want to omit fields that are using
        their default values.
    */

    template <typename Target>
    static Target parseTargetHeader (const var& v,
                                     const std::map<Identifier, void (*) (Target&, const var&)>& parsers)
    {
        Target target;

        if (auto* obj = v.getDynamicObject())
        {
            for (const auto& pair : obj->getProperties())
            {
                const auto parserIter = parsers.find (pair.name);

                if (parserIter != parsers.end())
                    parserIter->second (target, pair.value);
                else
                    target.extended[pair.name] = pair.value;
            }
        }

        return target;
    }

    static auto getParsersForPropertyReplyHeader()
    {
        using Target = PropertyReplyHeader;
        std::map<Identifier, void (*) (Target& header, const var& v)> map;

        map.emplace ("status",         [] (Target& header, const var& v) { header.status      = v; });
        map.emplace ("message",        [] (Target& header, const var& v) { header.message     = v; });
        map.emplace ("cacheTime",      [] (Target& header, const var& v) { header.cacheTime   = v; });
        map.emplace ("mediaType",      [] (Target& header, const var& v) { header.mediaType   = v; });
        map.emplace ("mutualEncoding", [] (Target& header, const var& v)
        {
            header.mutualEncoding = EncodingUtils::toEncoding (v.toString().toRawUTF8()).value_or (Encoding::ascii);
        });

        return map;
    }

    template <typename Target>
    static auto getParsersForGenericPropertyRequestHeader()
    {
        std::map<Identifier, void (*) (Target& header, const var& v)> map;

        map.emplace ("resource",       [] (Target& header, const var& v) { header.resource    = v; });
        map.emplace ("resId",          [] (Target& header, const var& v) { header.resId       = v; });
        map.emplace ("mediaType",      [] (Target& header, const var& v) { header.mediaType   = v; });
        map.emplace ("mutualEncoding", [] (Target& header, const var& v)
        {
            header.mutualEncoding = EncodingUtils::toEncoding (v.toString().toRawUTF8()).value_or (Encoding::ascii);
        });

        return map;
    }

    static auto getParsersForPropertyRequestHeader()
    {
        auto map = getParsersForGenericPropertyRequestHeader<PropertyRequestHeader>();
        map.emplace ("setPartial", [] (PropertyRequestHeader& header, const var& v) { header.setPartial = v; });
        map.emplace ("offset",     [] (PropertyRequestHeader& header, const var& v)
        {
            if (! header.pagination.has_value())
                header.pagination = Pagination{};

            header.pagination->offset = v;
        });
        map.emplace ("limit",      [] (PropertyRequestHeader& header, const var& v)
        {
            if (! header.pagination.has_value())
                header.pagination = Pagination{};

            header.pagination->limit = v;
        });
        return map;
    }

    static auto getParsersForPropertySubscriptionHeader()
    {
        auto map = getParsersForGenericPropertyRequestHeader<PropertySubscriptionHeader>();

        map.emplace ("subscribeId", [] (PropertySubscriptionHeader& header, const var& v) { header.subscribeId = v; });
        map.emplace ("command",     [] (PropertySubscriptionHeader& header, const var& v)
        {
            header.command = [&]
            {
                if (v == "start")
                    return PropertySubscriptionCommand::start;

                if (v == "partial")
                    return PropertySubscriptionCommand::partial;

                if (v == "full")
                    return PropertySubscriptionCommand::full;

                if (v == "notify")
                    return PropertySubscriptionCommand::notify;

                if (v == "end")
                    return PropertySubscriptionCommand::end;

                return PropertySubscriptionCommand::notify;
            }();
        });

        return map;
    }

    static auto getSetPartial (const PropertySubscriptionHeader&) { return false; }
    static auto getSetPartial (const PropertyRequestHeader& h) { return h.setPartial; }
    static auto getSetPartial (const PropertyReplyHeader&) { return false; }

    static auto getPagination (const PropertySubscriptionHeader&) { return std::optional<Pagination>{}; }
    static auto getPagination (const PropertyRequestHeader& h) { return h.pagination; }
    static auto getPagination (const PropertyReplyHeader&) { return std::optional<Pagination>{}; }

    static auto getCacheTime (const PropertySubscriptionHeader&) { return 0; }
    static auto getCacheTime (const PropertyRequestHeader&) { return 0; }
    static auto getCacheTime (const PropertyReplyHeader& h) { return h.cacheTime; }

    static auto getMessage (const PropertySubscriptionHeader&) { return String{}; }
    static auto getMessage (const PropertyRequestHeader&) { return String{}; }
    static auto getMessage (const PropertyReplyHeader& h) { return h.message; }

    static auto getResource (const PropertySubscriptionHeader& h) { return h.resource; }
    static auto getResource (const PropertyRequestHeader& h) { return h.resource; }
    static auto getResource (const PropertyReplyHeader&) { return String{}; }

    static auto getResId (const PropertySubscriptionHeader& h) { return h.resId; }
    static auto getResId (const PropertyRequestHeader& h) { return h.resId; }
    static auto getResId (const PropertyReplyHeader&) { return String{}; }

    static auto getCommand (const PropertySubscriptionHeader& h) { return h.command; }
    static auto getCommand (const PropertyRequestHeader&) { return PropertySubscriptionCommand{}; }
    static auto getCommand (const PropertyReplyHeader&) { return PropertySubscriptionCommand{}; }

    static auto getSubscribeId (const PropertySubscriptionHeader& h) { return h.subscribeId; }
    static auto getSubscribeId (const PropertyRequestHeader&) { return String{}; }
    static auto getSubscribeId (const PropertyReplyHeader&) { return String{}; }

    static std::optional<int> getStatus (const PropertySubscriptionHeader&) { return {}; }
    static std::optional<int> getStatus (const PropertyRequestHeader&) { return {}; }
    static std::optional<int> getStatus (const PropertyReplyHeader& h) { return h.status; }

    template <typename T>
    static auto toFieldsFromHeader (const T& t)
    {
        auto fields = t.extended;

        // Status shall always be included if it is present in the header
        if (const auto status = getStatus (t))
            fields["status"] = *status;

        if (getResource (t) != getResource (T()))
            fields["resource"] = getResource (t);

        if (getCommand (t) != getCommand (T()))
            fields["command"] = PropertySubscriptionCommandUtils::toString (getCommand (t));

        if (getSubscribeId (t) != getSubscribeId (T()))
            fields["subscribeId"] = getSubscribeId (t);

        if (getResId (t) != getResId (T()))
            fields["resId"] = getResId (t);

        if (t.mutualEncoding != T().mutualEncoding)
            fields["mutualEncoding"] = EncodingUtils::toString (t.mutualEncoding);

        if (t.mediaType != T().mediaType)
            fields["mediaType"] = t.mediaType;

        if (getSetPartial (t))
            fields["setPartial"] = true;

        if (getCacheTime (t) != getCacheTime (T()))
            fields["cacheTime"] = getCacheTime (t);

        if (getMessage (t) != getMessage (T()))
            fields["message"] = getMessage (t);

        if (const auto pagination = getPagination (t))
        {
            fields["offset"] = pagination->offset;
            fields["limit"]  = pagination->limit;
        }

        return fields;
    }
};

//==============================================================================
PropertySubscriptionHeader PropertySubscriptionHeader::parseCondensed (const var& v)
{
    return PropertyDelegateDetail::parseTargetHeader (v, PropertyDelegateDetail::getParsersForPropertySubscriptionHeader());
}

var PropertySubscriptionHeader::toVarCondensed() const
{
    return JSONUtils::makeObjectWithKeyFirst (PropertyDelegateDetail::toFieldsFromHeader (*this), "command");
}

PropertyRequestHeader PropertyRequestHeader::parseCondensed (const var& v)
{
    return PropertyDelegateDetail::parseTargetHeader (v, PropertyDelegateDetail::getParsersForPropertyRequestHeader());
}

var PropertyRequestHeader::toVarCondensed() const
{
    return JSONUtils::makeObjectWithKeyFirst (PropertyDelegateDetail::toFieldsFromHeader (*this), "resource");
}

PropertyReplyHeader PropertyReplyHeader::parseCondensed (const var& v)
{
    return PropertyDelegateDetail::parseTargetHeader (v, PropertyDelegateDetail::getParsersForPropertyReplyHeader());
}

var PropertyReplyHeader::toVarCondensed() const
{
    return JSONUtils::makeObjectWithKeyFirst (PropertyDelegateDetail::toFieldsFromHeader (*this), "status");
}

} // namespace juce::midi_ci
