// This file is generated by TypeBuilder_cpp.template.

// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include {{format_domain_include(config.protocol.package, domain.domain)}}

#include {{format_include(config.protocol.package, "Protocol")}}

{% for namespace in config.protocol.namespace %}
namespace {{namespace}} {
{% endfor %}
namespace {{domain.domain}} {

// ------------- Enum values from types.

const char Metainfo::domainName[] = "{{domain.domain}}";
const char Metainfo::commandPrefix[] = "{{domain.domain}}.";
const char Metainfo::version[] = "{{domain.version}}";
  {% for type in domain.types %}
    {% if not protocol.generate_type(domain.domain, type.id) %}{% continue %} {% endif %}
    {% if "enum" in type %}

namespace {{type.id}}Enum {
      {% for literal in type.enum %}
const char {{ literal | dash_to_camelcase}}[] = "{{literal}}";
      {% endfor %}
} // namespace {{type.id}}Enum
      {% if protocol.is_exported(domain.domain, type.id) %}

namespace API {
namespace {{type.id}}Enum {
        {% for literal in type.enum %}
const char* {{ literal | dash_to_camelcase}} = "{{literal}}";
        {% endfor %}
} // namespace {{type.id}}Enum
} // namespace API
      {% endif %}
    {% endif %}
    {% for property in type.properties %}
      {% if "enum" in property %}

        {% for literal in property.enum %}
const char* {{type.id}}::{{property.name | to_title_case}}Enum::{{literal | dash_to_camelcase}} = "{{literal}}";
        {% endfor %}
      {% endif %}
    {% endfor %}
    {% if not (type.type == "object") or not ("properties" in type) %}{% continue %}{% endif %}

std::unique_ptr<{{type.id}}> {{type.id}}::fromValue(protocol::Value* value, ErrorSupport* errors)
{
    if (!value || value->type() != protocol::Value::TypeObject) {
        errors->addError("object expected");
        return nullptr;
    }

    std::unique_ptr<{{type.id}}> result(new {{type.id}}());
    protocol::DictionaryValue* object = DictionaryValue::cast(value);
    errors->push();
    {% for property in type.properties %}
    protocol::Value* {{property.name}}Value = object->get("{{property.name}}");
      {% if property.optional %}
    if ({{property.name}}Value) {
        errors->setName("{{property.name}}");
        result->m_{{property.name}} = ValueConversions<{{protocol.resolve_type(property).raw_type}}>::fromValue({{property.name}}Value, errors);
    }
      {% else %}
    errors->setName("{{property.name}}");
    result->m_{{property.name}} = ValueConversions<{{protocol.resolve_type(property).raw_type}}>::fromValue({{property.name}}Value, errors);
      {% endif %}
    {% endfor %}
    errors->pop();
    if (errors->hasErrors())
        return nullptr;
    return result;
}

std::unique_ptr<protocol::DictionaryValue> {{type.id}}::toValue() const
{
    std::unique_ptr<protocol::DictionaryValue> result = DictionaryValue::create();
    {% for property in type.properties %}
      {% set property_type = protocol.resolve_type(property) %}
      {% set property_field = "m_" + property.name %}
      {% if property.optional %}
    if ({{property_field}}.isJust())
        result->setValue("{{property.name}}", ValueConversions<{{property_type.raw_type}}>::toValue({{property_field}}.fromJust()));
      {% else %}
    result->setValue("{{property.name}}", ValueConversions<{{property_type.raw_type}}>::toValue({{property_type.to_raw_type % property_field}}));
      {% endif %}
    {% endfor %}
    return result;
}

std::unique_ptr<{{type.id}}> {{type.id}}::clone() const
{
    ErrorSupport errors;
    return fromValue(toValue().get(), &errors);
}
    {% if protocol.is_exported(domain.domain, type.id) %}

{{config.exported.string_out}} {{type.id}}::toJSONString() const
{
    String json = toValue()->serializeToJSON();
    return {{config.exported.to_string_out % "json"}};
}

void {{type.id}}::writeBinary(std::vector<uint8_t>* out) const
{
    toValue()->writeBinary(out);
}

// static
std::unique_ptr<API::{{type.id}}> API::{{type.id}}::fromJSONString(const {{config.exported.string_in}}& json)
{
    ErrorSupport errors;
    std::unique_ptr<Value> value = StringUtil::parseJSON(json);
    if (!value)
        return nullptr;
    return protocol::{{domain.domain}}::{{type.id}}::fromValue(value.get(), &errors);
}

// static
std::unique_ptr<API::{{type.id}}> API::{{type.id}}::fromBinary(const uint8_t* data, size_t length)
{
    ErrorSupport errors;
    std::unique_ptr<Value> value = Value::parseBinary(data, length);
    if (!value)
        return nullptr;
    return protocol::{{domain.domain}}::{{type.id}}::fromValue(value.get(), &errors);
}

    {% endif %}
  {% endfor %}

// ------------- Enum values from params.

  {% for command in join_arrays(domain, ["commands", "events"]) %}
    {% for param in join_arrays(command, ["parameters", "returns"]) %}
      {% if "enum" in param %}

namespace {{command.name | to_title_case}} {
namespace {{param.name | to_title_case}}Enum {
        {% for literal in param.enum %}
const char* {{ literal | to_title_case}} = "{{literal}}";
        {% endfor %}
} // namespace {{param.name | to_title_case}}Enum
} // namespace {{command.name | to_title_case }}
        {% if protocol.is_exported(domain.domain, command.name + "." + param.name) %}

namespace API {
namespace {{command.name | to_title_case}} {
namespace {{param.name | to_title_case}}Enum {
        {% for literal in param.enum %}
const char* {{ literal | to_title_case}} = "{{literal}}";
        {% endfor %}
} // namespace {{param.name | to_title_case}}Enum
} // namespace {{command.name | to_title_case }}
} // namespace API
        {% endif %}
      {% endif %}
    {% endfor %}
  {% endfor %}

// ------------- Frontend notifications.
  {% for event in domain.events %}
    {% if not protocol.generate_event(domain.domain, event.name) %}{% continue %}{% endif %}

void Frontend::{{event.name | to_method_case}}(
    {%- for parameter in event.parameters %}
      {% if "optional" in parameter -%}
        Maybe<{{protocol.resolve_type(parameter).raw_type}}>
      {%- else -%}
        {{protocol.resolve_type(parameter).pass_type}}
      {%- endif %} {{parameter.name}}{%- if not loop.last -%}, {% endif -%}
    {% endfor -%})
{
    if (!m_frontendChannel)
        return;
      {% if event.parameters %}
    std::unique_ptr<{{event.name | to_title_case}}Notification> messageData = {{event.name | to_title_case}}Notification::{{"create" | to_method_case}}()
        {% for parameter in event.parameters %}
          {% if not "optional" in parameter %}
        .{{"set" | to_method_case}}{{parameter.name | to_title_case}}({{protocol.resolve_type(parameter).to_pass_type % parameter.name}})
          {% endif %}
        {% endfor %}
        .{{ "build" | to_method_case }}();
        {% for parameter in event.parameters %}
          {% if "optional" in parameter %}
    if ({{parameter.name}}.isJust())
        messageData->{{"set" | to_method_case}}{{parameter.name | to_title_case}}(std::move({{parameter.name}}).takeJust());
          {% endif %}
        {% endfor %}
    m_frontendChannel->sendProtocolNotification(InternalResponse::createNotification("{{domain.domain}}.{{event.name}}", std::move(messageData)));
      {% else %}
    m_frontendChannel->sendProtocolNotification(InternalResponse::createNotification("{{domain.domain}}.{{event.name}}"));
      {% endif %}
}
  {% endfor %}

void Frontend::flush()
{
    m_frontendChannel->flushProtocolNotifications();
}

void Frontend::sendRawNotification(String notification)
{
    m_frontendChannel->sendProtocolNotification(InternalRawNotification::fromJSON(std::move(notification)));
}

void Frontend::sendRawNotification(std::vector<uint8_t> notification)
{
    m_frontendChannel->sendProtocolNotification(InternalRawNotification::fromBinary(std::move(notification)));
}

// --------------------- Dispatcher.

class DispatcherImpl : public protocol::DispatcherBase {
public:
    DispatcherImpl(FrontendChannel* frontendChannel, Backend* backend)
        : DispatcherBase(frontendChannel)
        , m_backend(backend) {
  {% for command in domain.commands %}
    {% if "redirect" in command %}
      m_redirects["{{domain.domain}}.{{command.name}}"] = "{{command.redirect}}.{{command.name}}";
      {% continue %}
    {% endif %}
    {% if not protocol.generate_command(domain.domain, command.name) %}{% continue %}{% endif %}
        m_dispatchMap["{{domain.domain}}.{{command.name}}"] = &DispatcherImpl::{{command.name}};
  {% endfor %}
    }
    ~DispatcherImpl() override { }
    bool canDispatch(const String& method) override;
    void dispatch(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr<protocol::DictionaryValue> messageObject) override;
    std::unordered_map<String, String>& redirects() { return m_redirects; }

protected:
    using CallHandler = void (DispatcherImpl::*)(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr<DictionaryValue> messageObject, ErrorSupport* errors);
    using DispatchMap = std::unordered_map<String, CallHandler>;
    DispatchMap m_dispatchMap;
    std::unordered_map<String, String> m_redirects;

  {% for command in domain.commands %}
    {% if "redirect" in command %}{% continue %}{% endif %}
    {% if not protocol.generate_command(domain.domain, command.name) %}{% continue %}{% endif %}
    void {{command.name}}(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr<DictionaryValue> requestMessageObject, ErrorSupport*);
  {% endfor %}

    Backend* m_backend;
};

bool DispatcherImpl::canDispatch(const String& method) {
    return m_dispatchMap.find(method) != m_dispatchMap.end();
}

void DispatcherImpl::dispatch(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr<protocol::DictionaryValue> messageObject)
{
    std::unordered_map<String, CallHandler>::iterator it = m_dispatchMap.find(method);
    DCHECK(it != m_dispatchMap.end());
    protocol::ErrorSupport errors;
    (this->*(it->second))(callId, method, message, std::move(messageObject), &errors);
}

  {% for command in domain.commands %}
    {% set command_name_title = command.name | to_title_case %}
    {% if "redirect" in command %}{% continue %}{% endif %}
    {% if not protocol.generate_command(domain.domain, command.name) %}{% continue %}{% endif %}
    {% if protocol.is_async_command(domain.domain, command.name) %}

class {{command_name_title}}CallbackImpl : public Backend::{{command_name_title}}Callback, public DispatcherBase::Callback {
public:
    {{command_name_title}}CallbackImpl(std::unique_ptr<DispatcherBase::WeakPtr> backendImpl, int callId, const String& method, const ProtocolMessage& message)
        : DispatcherBase::Callback(std::move(backendImpl), callId, method, message) { }

    void sendSuccess(
      {%- for parameter in command.returns -%}
        {%- if "optional" in parameter -%}
        Maybe<{{protocol.resolve_type(parameter).raw_type}}> {{parameter.name}}
        {%- else -%}
        {{protocol.resolve_type(parameter).pass_type}} {{parameter.name}}
        {%- endif -%}
        {%- if not loop.last -%}, {% endif -%}
      {%- endfor -%}) override
    {
        std::unique_ptr<protocol::DictionaryValue> resultObject = DictionaryValue::create();
          {% for parameter in command.returns %}
            {% if "optional" in parameter %}
        if ({{parameter.name}}.isJust())
            resultObject->setValue("{{parameter.name}}", ValueConversions<{{protocol.resolve_type(parameter).raw_type}}>::toValue({{parameter.name}}.fromJust()));
           {% else %}
        resultObject->setValue("{{parameter.name}}", ValueConversions<{{protocol.resolve_type(parameter).raw_type}}>::toValue({{protocol.resolve_type(parameter).to_raw_type % parameter.name}}));
            {% endif %}
          {% endfor %}
        sendIfActive(std::move(resultObject), DispatchResponse::OK());
    }

    void fallThrough() override
    {
        fallThroughIfActive();
    }

    void sendFailure(const DispatchResponse& response) override
    {
        DCHECK(response.status() == DispatchResponse::kError);
        sendIfActive(nullptr, response);
    }
};
    {% endif %}

void DispatcherImpl::{{command.name}}(int callId, const String& method, const ProtocolMessage& message, std::unique_ptr<DictionaryValue> requestMessageObject, ErrorSupport* errors)
{
    {% if "parameters" in command %}
    // Prepare input parameters.
    protocol::DictionaryValue* object = DictionaryValue::cast(requestMessageObject->get("params"));
    errors->push();
      {% for parameter in command.parameters %}
        {% set parameter_type = protocol.resolve_type(parameter) %}
    protocol::Value* {{parameter.name}}Value = object ? object->get("{{parameter.name}}") : nullptr;
        {% if parameter.optional %}
    Maybe<{{parameter_type.raw_type}}> in_{{parameter.name}};
    if ({{parameter.name}}Value) {
        errors->setName("{{parameter.name}}");
        in_{{parameter.name}} = ValueConversions<{{parameter_type.raw_type}}>::fromValue({{parameter.name}}Value, errors);
    }
        {% else %}
    errors->setName("{{parameter.name}}");
    {{parameter_type.type}} in_{{parameter.name}} = ValueConversions<{{parameter_type.raw_type}}>::fromValue({{parameter.name}}Value, errors);
        {% endif %}
      {% endfor %}
    errors->pop();
    if (errors->hasErrors()) {
        reportProtocolError(callId, DispatchResponse::kInvalidParams, kInvalidParamsString, errors);
        return;
    }
    {% endif %}
    {% if "returns" in command and not protocol.is_async_command(domain.domain, command.name) %}
    // Declare output parameters.
      {% for parameter in command.returns %}
        {% if "optional" in parameter %}
    Maybe<{{protocol.resolve_type(parameter).raw_type}}> out_{{parameter.name}};
        {% else %}
    {{protocol.resolve_type(parameter).type}} out_{{parameter.name}};
        {% endif %}
      {% endfor %}
    {% endif %}

    {% if not protocol.is_async_command(domain.domain, command.name) %}
    std::unique_ptr<DispatcherBase::WeakPtr> weak = weakPtr();
    DispatchResponse response = m_backend->{{command.name | to_method_case}}(
      {%- for parameter in command.parameters -%}
        {%- if not loop.first -%}, {% endif -%}
        {%- if "optional" in parameter -%}
        std::move(in_{{parameter.name}})
        {%- else -%}
        {{protocol.resolve_type(parameter).to_pass_type % ("in_" + parameter.name)}}
        {%- endif -%}
      {%- endfor %}
      {%- if "returns" in command %}
        {%- for parameter in command.returns -%}
          {%- if not loop.first or command.parameters -%}, {% endif -%}
          &out_{{parameter.name}}
        {%- endfor %}
      {% endif %});
    if (response.status() == DispatchResponse::kFallThrough) {
        channel()->fallThrough(callId, method, message);
        return;
    }
      {% if "returns" in command %}
    std::unique_ptr<protocol::DictionaryValue> result = DictionaryValue::create();
    if (response.status() == DispatchResponse::kSuccess) {
        {% for parameter in command.returns %}
          {% if "optional" in parameter %}
        if (out_{{parameter.name}}.isJust())
            result->setValue("{{parameter.name}}", ValueConversions<{{protocol.resolve_type(parameter).raw_type}}>::toValue(out_{{parameter.name}}.fromJust()));
          {% else %}
        result->setValue("{{parameter.name}}", ValueConversions<{{protocol.resolve_type(parameter).raw_type}}>::toValue({{protocol.resolve_type(parameter).to_raw_type % ("out_" + parameter.name)}}));
          {% endif %}
        {% endfor %}
    }
    if (weak->get())
        weak->get()->sendResponse(callId, response, std::move(result));
      {% else %}
    if (weak->get())
        weak->get()->sendResponse(callId, response);
      {% endif %}
    return;
    {% else %}
    std::unique_ptr<DispatcherBase::WeakPtr> weak = weakPtr();
    std::unique_ptr<{{command_name_title}}CallbackImpl> callback(new {{command.name | to_title_case}}CallbackImpl(weakPtr(), callId, method, message));
    m_backend->{{command.name | to_method_case}}(
      {%- for property in command.parameters -%}
        {%- if not loop.first -%}, {% endif -%}
        {%- if "optional" in property -%}
        std::move(in_{{property.name}})
        {%- else -%}
        {{protocol.resolve_type(property).to_pass_type % ("in_" + property.name)}}
        {%- endif -%}
      {%- endfor -%}
        {%- if command.parameters -%}, {% endif -%}
        std::move(callback));
    return;
    {% endif %}
}
  {% endfor %}

// static
void Dispatcher::wire(UberDispatcher* uber, Backend* backend)
{
    std::unique_ptr<DispatcherImpl> dispatcher(new DispatcherImpl(uber->channel(), backend));
    uber->setupRedirects(dispatcher->redirects());
    uber->registerBackend("{{domain.domain}}", std::move(dispatcher));
}

} // {{domain.domain}}
{% for namespace in config.protocol.namespace %}
} // namespace {{namespace}}
{% endfor %}
