/* 
   Mathieu Stefani, 14 mai 2016
   
   Swagger serializer for RapidJSON
*/

#pragma once

#include <rapidjson/prettywriter.h>

#include <pistache/description.h>
#include <pistache/http_defs.h>
#include <pistache/mime.h>

namespace Pistache {
namespace Rest {
namespace Serializer {

template<typename Writer>
void serializeInfo(Writer& writer, const Schema::Info& info) {
    writer.String("swagger");
    writer.String("2.0");
    writer.String("info");
    writer.StartObject();
    {
        writer.String("title");
        writer.String(info.title.c_str());
        writer.String("version");
        writer.String(info.version.c_str());
        if (!info.description.empty()) {
            writer.String("description");
            writer.String(info.description.c_str());
        }
        if (!info.termsOfService.empty()) {
            writer.String("termsOfService");
            writer.String(info.termsOfService.c_str());
        }
    }
    writer.EndObject();
}

template<typename Writer>
void serializePC(Writer& writer, const Schema::ProduceConsume& pc) {
    auto serializeMimes = [&](const char* name, const std::vector<Http::Mime::MediaType>& mimes) {
        if (!mimes.empty()) {
            writer.String(name);
            writer.StartArray();
            {
                for (const auto& mime: mimes) {
                    auto str = mime.toString();
                    writer.String(str.c_str());
                }
            }
            writer.EndArray();
        }
    };

        serializeMimes("consumes", pc.consume);
        serializeMimes("produces", pc.produce);
}

template<typename Writer>
void serializeParameter(Writer& writer, const Schema::Parameter& parameter) {
    writer.StartObject();
    {
        writer.String("name");
        writer.String(parameter.name.c_str());
        writer.String("in");
        // @Feature: support other types of parameters
        writer.String("path");
        writer.String("description");
        writer.String(parameter.description.c_str());
        writer.String("required");
        writer.Bool(parameter.required);
        writer.String("type");
        writer.String(parameter.type->typeName());
    }
    writer.EndObject();
}

template<typename Writer>
void serializeResponse(Writer& writer, const Schema::Response& response) {
    auto code = std::to_string(static_cast<uint32_t>(response.statusCode));
    writer.String(code.c_str());
    writer.StartObject();
    {
        writer.String("description");
        writer.String(response.description.c_str());
    }
    writer.EndObject();
}

template<typename Writer>
void serializePath(Writer& writer, const Schema::Path& path) {
    std::string methodStr(methodString(path.method));
    // So it looks like Swagger requires method to be in lowercase
    std::transform(std::begin(methodStr), std::end(methodStr), std::begin(methodStr), ::tolower);

    writer.String(methodStr.c_str());
    writer.StartObject();
    {
        writer.String("description");
        writer.String(path.description.c_str());
        serializePC(writer, path.pc);

        const auto& parameters = path.parameters;
        if (!parameters.empty()) {
            writer.String("parameters");
            writer.StartArray();
            {
                for (const auto& parameter: parameters) {
                    serializeParameter(writer, parameter);
                }
            }
            writer.EndArray();
        }

        const auto& responses = path.responses;
        if (!responses.empty()) {
            writer.String("responses");
            writer.StartObject();
            {
                for (const auto& response: responses) {
                    serializeResponse(writer, response);
                }
            }
            writer.EndObject();
        }
    }
    writer.EndObject();
}

template<typename Writer>
void serializePathGroups(
        Writer& writer, const std::string& prefix,
        const Schema::PathGroup& paths, Schema::PathGroup::Format format) {
    writer.String("paths");
    writer.StartObject();
    {
        auto groups = paths.groups();
        for (const auto& group: groups) {
            if (group.second.isHidden()) continue;

            std::string name(group.first);
            if (!prefix.empty()) {
                if (!name.compare(0, prefix.size(), prefix)) {
                    name = name.substr(prefix.size());
                }
            }

            if (format == Schema::PathGroup::Format::Default) {
                writer.String(name.c_str());
            } else {
                auto swaggerPath = Schema::Path::swaggerFormat(name);
                writer.String(swaggerPath.c_str());
            }
            writer.StartObject();
            {
                for (const auto& path: group.second) {
                    if (!path.hidden)
                        serializePath(writer, path);
                }
            }
            writer.EndObject();
        }
    }
    writer.EndObject();
}

template<typename Writer>
void serializeDescription(Writer& writer, const Description& desc) {
    writer.StartObject();
    {
        serializeInfo(writer, desc.rawInfo());
        auto host = desc.rawHost();
        auto basePath = desc.rawBasePath();
        auto schemes = desc.rawSchemes();
        auto pc = desc.rawPC();
        auto paths = desc.rawPaths();

        if (!host.empty()) {
            writer.String("host");
            writer.String(host.c_str());
        }
        if (!basePath.empty()) {
            writer.String("basePath");
            writer.String(basePath.c_str());
        }
        if (!schemes.empty()) {
            writer.String("schemes");
            writer.StartArray();
            {
                for (const auto& scheme: schemes) {
                    writer.String(schemeString(scheme));
                }
            }
            writer.EndArray();
        }
        serializePC(writer, pc);
        serializePathGroups(writer, basePath, paths, Schema::PathGroup::Format::Swagger);
    }
    writer.EndObject();
}

inline std::string rapidJson(const Description& desc) {
    rapidjson::StringBuffer sb;
    rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(sb);
    serializeDescription(writer, desc);

    return sb.GetString();
}

} // namespace Serializer
} // namespace Rest
} // namespace Pistache
