/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// presto_protocol.prolog.cpp
//

{{#.}}
{{#comment}}
{{comment}}
{{/comment}}
{{/.}}

#include <folly/Format.h>
#include <iostream>

#include "presto_cpp/presto_protocol/ConnectorProtocol.h"
#include "presto_cpp/presto_protocol/presto_protocol.h"

using namespace std::string_literals;

namespace nlohmann {
std::string json_map_key(std::string p) {
	return p;
}
} // namespace nlohmann

namespace facebook::presto::protocol {

const char* const PRESTO_PAGES_MIME_TYPE = "application/x-presto-pages";

const char* const PRESTO_CURRENT_STATE_HTTP_HEADER = "X-Presto-Current-State";
const char* const PRESTO_MAX_WAIT_HTTP_HEADER = "X-Presto-Max-Wait";
const char* const PRESTO_MAX_SIZE_HTTP_HEADER = "X-Presto-Max-Size";
const char* const PRESTO_TASK_INSTANCE_ID_HEADER = "X-Presto-Task-Instance-Id";
const char* const PRESTO_PAGE_TOKEN_HEADER = "X-Presto-Page-Sequence-Id";
const char* const PRESTO_PAGE_NEXT_TOKEN_HEADER = "X-Presto-Page-End-Sequence-Id";
const char* const PRESTO_BUFFER_COMPLETE_HEADER = "X-Presto-Buffer-Complete";
const char* const PRESTO_GET_DATA_SIZE_HEADER = "X-Presto-Get-Data-Size";
const char* const PRESTO_BUFFER_REMAINING_BYTES_HEADER = "X-Presto-Buffer-Remaining-Bytes";

const char* const PRESTO_MAX_WAIT_DEFAULT = "2s";
const char* const PRESTO_MAX_SIZE_DEFAULT = "4096 B";

const char* const PRESTO_ABORT_TASK_URL_PARAM = "abort";

std::string json_map_key(const VariableReferenceExpression& p) {
	return fmt::format("{}<{}>", p.name, p.type);
}
} // namespace facebook::presto::protocol

{{#.}}
{{#cinc}}
{{&cinc}}
{{/cinc}}
{{^cinc}}
{{#struct}}
namespace facebook::presto::protocol {
    {{#super_class}}
    {{&class_name}}::{{&class_name}}() noexcept {
        _type = "{{json_key}}";
    }
    {{/super_class}}

    void to_json(json& j, const {{&class_name}}& p) {
        j = json::object();
        {{#super_class}}
        j["@type"] = "{{&json_key}}";
        {{/super_class}}
        {{#fields}}
        to_json_key(j, "{{&field_name}}", p.{{field_name}}, "{{&class_name}}", "{{&field_text}}", "{{&field_name}}");
        {{/fields}}
    }

    void from_json(const json& j, {{&class_name}}& p) {
        {{#super_class}}
        p._type = j["@type"];
        {{/super_class}}
        {{#fields}}
        from_json_key(j, "{{&field_name}}", p.{{field_name}}, "{{&class_name}}", "{{&field_text}}", "{{&field_name}}");
        {{/fields}}
    }
}
{{/struct}}
{{#enum}}
namespace facebook::presto::protocol {
    //Loosly copied this here from NLOHMANN_JSON_SERIALIZE_ENUM()

    // NOLINTNEXTLINE: cppcoreguidelines-avoid-c-arrays
    static const std::pair<{{&class_name}}, json>
    {{&class_name}}_enum_table[] = {                // NOLINT: cert-err58-cpp
        {{#elements}}
        { {{&class_name}}::{{&element}}, "{{&element}}" }{{^_last}},{{/_last}}
        {{/elements}}
    };
    void to_json(json& j, const {{&class_name}}& e)
    {
        static_assert(std::is_enum<{{&class_name}}>::value, "{{&class_name}} must be an enum!");
        const auto* it = std::find_if(std::begin({{&class_name}}_enum_table), std::end({{&class_name}}_enum_table),
                               [e](const std::pair<{{&class_name}}, json>& ej_pair) -> bool
        {
            return ej_pair.first == e;
        });
        j = ((it != std::end({{&class_name}}_enum_table)) ? it : std::begin({{&class_name}}_enum_table))->second;
    }
    void from_json(const json& j, {{&class_name}}& e)
    {
        static_assert(std::is_enum<{{&class_name}}>::value, "{{&class_name}} must be an enum!");
        const auto* it = std::find_if(std::begin({{&class_name}}_enum_table), std::end({{&class_name}}_enum_table),
                               [&j](const std::pair<{{&class_name}}, json>& ej_pair) -> bool
        {
            return ej_pair.second == j;
        });
        e = ((it != std::end({{&class_name}}_enum_table)) ? it : std::begin({{&class_name}}_enum_table))->first;
    }
}
{{/enum}}
{{#abstract}}
namespace facebook::presto::protocol {
    void to_json(json& j, const std::shared_ptr<{{&class_name}}>& p) {
        if ( p == nullptr ) {
            return;
        }
        String type = p->_type;

        {{#subclasses}}
        if ( type == "{{&key}}" ) {
            j = *std::static_pointer_cast<{{&type}}>(p);
            return;
        }
        {{/subclasses}}

        throw TypeError(type + " no abstract type {{&class_name}} {{&key}}");
    }

    void from_json(const json& j, std::shared_ptr<{{&class_name}}>& p) {
        String type;
        try {
          type = p->getSubclassKey(j);
        } catch (json::parse_error &e) {
            throw ParseError(std::string(e.what()) + " {{&class_name}} {{&key}} {{&class_name}}");
        }

        {{#subclasses}}
        if ( type == "{{&key}}" ) {
            std::shared_ptr<{{&type}}> k = std::make_shared<{{&type}}>();
            j.get_to(*k);
            p = std::static_pointer_cast<{{&class_name}}>(k);
            return;
        }
        {{/subclasses}}

        throw TypeError(type +  " no abstract type {{&class_name}} {{&key}}");
    }
}
{{/abstract}}
{{/cinc}}
{{/.}}
