#!/usr/bin/env python
# Copyright 2014 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.

import sys
import string
import json

blink_protocol_path = sys.argv[1]
output_cc_path = sys.argv[2]
output_h_path = sys.argv[3]

header = """\
// Copyright 2014 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.

// THIS FILE IS AUTOGENERATED. DO NOT EDIT.
// Generated by
//  content/browser/devtools/protocol/devtools_protocol_handler_generator.py
//  from gen/blink/core/inspector/protocol.json
"""

template_h = string.Template(header + """\

#ifndef CONTENT_BROWSER_DEVTOOLS_PROTOCOL_DEVTOOLS_PROTOCOL_DISPATCHER_H_
#define CONTENT_BROWSER_DEVTOOLS_PROTOCOL_DEVTOOLS_PROTOCOL_DISPATCHER_H_

#include <memory>
#include <utility>

#include "base/memory/ptr_util.h"
#include "content/browser/devtools/protocol/devtools_protocol_client.h"

namespace content {

class DevToolsProtocolDispatcher;

namespace devtools {

extern const char kProtocolVersion[];

bool IsSupportedProtocolVersion(const std::string& version);

template<typename T>
std::unique_ptr<base::Value> CreateValue(const T& param) {
  return base::MakeUnique<base::FundamentalValue>(param);
}

template<typename T>
std::unique_ptr<base::Value> CreateValue(std::unique_ptr<T>& param) {
  return std::move(param);
}

template<class T>
std::unique_ptr<base::Value> CreateValue(const scoped_refptr<T>& param) {
  return param->ToValue();
}

template<typename T>
std::unique_ptr<base::Value> CreateValue(const std::vector<T>& param) {
  std::unique_ptr<base::ListValue> result(new base::ListValue());
  for (auto& item : param) {
    result->Append(CreateValue(item));
  }
  return std::move(result);
}

template<>
std::unique_ptr<base::Value> CreateValue(const std::string& param);

${types}\

}  // namespace devtools

class DevToolsProtocolDispatcher {
 public:
  using CommandHandler =
      base::Callback<bool(DevToolsCommandId,
                          std::unique_ptr<base::DictionaryValue>)>;

  explicit DevToolsProtocolDispatcher(DevToolsProtocolDelegate* notifier);
  ~DevToolsProtocolDispatcher();

  CommandHandler FindCommandHandler(const std::string& method);

${setters}\

 private:
  using Response = DevToolsProtocolClient::Response;
  using CommandHandlers = std::map<std::string, CommandHandler>;

${methods}\

  DevToolsProtocolDelegate* notifier_;
  DevToolsProtocolClient client_;
  CommandHandlers command_handlers_;
${fields}\
};

}  // namespace content

#endif  // CONTENT_BROWSER_DEVTOOLS_PROTOCOL_DEVTOOLS_PROTOCOL_DISPATCHER_H_
""")

tmpl_typedef = string.Template("""\
namespace ${domain} {
typedef ${param_type} ${declared_name};
}  // namespace ${domain}
""")

tmpl_struct = string.Template("""\
namespace ${domain} {
template<int MASK>
struct ${declared_name}Builder
    : base::RefCounted<${declared_name}Builder<MASK>> {
 public:
  enum {
    kAllSet = 0,
${fields_enum}\
  };

${methods}\

  static scoped_refptr<${declared_name}Builder<kNoneSet>> Create() {
    return new ${declared_name}Builder<kNoneSet>();
  }

  std::unique_ptr<base::DictionaryValue> ToValue() {
    static_assert(MASK == kAllSet, "required properties missing");
    return base::WrapUnique(dict_->DeepCopy());
  }

 private:
  friend struct ${declared_name}Builder<0>;
  friend class base::RefCounted<${declared_name}Builder<MASK>>;
  ~${declared_name}Builder() {}

  ${declared_name}Builder() : dict_(new base::DictionaryValue()) {
  }

  template<class T> T* ThisAs() {
    static_assert(sizeof(*this) == sizeof(T), "cannot cast");
    return reinterpret_cast<T*>(this);
  }

  std::unique_ptr<base::DictionaryValue> dict_;
};

typedef ${declared_name}Builder<0> ${declared_name};

}  // namespace ${domain}
""")

tmpl_builder_setter_req = string.Template("""\
  scoped_refptr<${declared_name}Builder<MASK & ~k${Param}>>
  set_${param}(${pass_type} ${param}) {
    static_assert(MASK & k${Param}, "already set");
    dict_->Set("${proto_param}", CreateValue(${param}));
    return ThisAs<${declared_name}Builder<MASK & ~k${Param}>>();
  }
""")

tmpl_builder_setter_opt = string.Template("""\
  scoped_refptr<${declared_name}Builder<MASK>>
  set_${param}(${pass_type} ${param}) {
    dict_->Set("${proto_param}", CreateValue(${param}));
    return this;
  }
""")

tmpl_builder_enum = string.Template("""\
    k${Param} = 1 << ${ordinal},
""")

tmpl_builder_none_set = string.Template("""\
    kNoneSet = ${all_fields}
""")

tmpl_named_enum = string.Template("""\
namespace ${domain} {
${values}\
}  // namespace ${domain}
""")

tmpl_inline_enum = string.Template("""\
namespace ${domain} {
namespace ${subdomain} {
${values}\
}  // namespace ${subdomain}
}  // namespace ${domain}
""")

tmpl_enum_value = string.Template("""\
extern const char k${Enum}${Value}[];
""")

tmpl_enum_value_def = string.Template("""\
const char k${Enum}${Value}[] = "${value}";
""")

tmpl_handler = string.Template("""\
namespace ${domain} {
class ${Domain}Handler;
}  // namespace domain
""")

tmpl_client = string.Template("""\
namespace ${domain} {
class Client : public DevToolsProtocolClient {
 public:
  explicit Client(DevToolsProtocolDelegate* notifier);
  ~Client() override;

${methods}\
};
}  // namespace ${domain}
""")

tmpl_event = string.Template("""\
  void ${Command}(
      scoped_refptr<${Command}Params> params);
""")

tmpl_response = string.Template("""\
  void Send${Command}Response(
      DevToolsCommandId command_id,
      scoped_refptr<${Command}Response> params);
""")

tmpl_setter = string.Template("""\
  void Set${Domain}Handler(
      devtools::${domain}::${Domain}Handler* ${domain}_handler);
""")

tmpl_callback = string.Template("""\
  bool On${Domain}${Command}(
      DevToolsCommandId command_id,
      std::unique_ptr<base::DictionaryValue> params);
""")

tmpl_field = string.Template("""\
  devtools::${domain}::${Domain}Handler* ${domain}_handler_;
""")

template_cc = string.Template(header + """\

#include "base/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
${includes}\

namespace content {

DevToolsProtocolDispatcher::DevToolsProtocolDispatcher(
    DevToolsProtocolDelegate* notifier)
    : notifier_(notifier),
      client_(notifier),
      ${fields_init} {
}

DevToolsProtocolDispatcher::~DevToolsProtocolDispatcher() {
}

DevToolsProtocolDispatcher::CommandHandler
DevToolsProtocolDispatcher::FindCommandHandler(const std::string& method) {
  CommandHandlers::iterator it = command_handlers_.find(method);
  return it == command_handlers_.end() ? CommandHandler() : it->second;
}

${methods}\

namespace devtools {

const char kProtocolVersion[] = "${major}.${minor}";

bool IsSupportedProtocolVersion(const std::string& version) {
  std::vector<base::StringPiece> tokens = base::SplitStringPiece(
      version, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  int major, minor;
  return tokens.size() == 2 &&
      base::StringToInt(tokens[0], &major) && major == ${major} &&
      base::StringToInt(tokens[1], &minor) && minor <= ${minor};
}

template<>
std::unique_ptr<base::Value> CreateValue(const std::string& param) {
  return base::MakeUnique<base::StringValue>(param);
}

${types}\

}  // namespace devtools

}  // namespace content
""")

tmpl_include = string.Template("""\
#include "content/browser/devtools/protocol/${domain}_handler.h"
""")

tmpl_field_init = string.Template("${domain}_handler_(nullptr)")

tmpl_setter_impl = string.Template("""\
void DevToolsProtocolDispatcher::Set${Domain}Handler(
    devtools::${domain}::${Domain}Handler* ${domain}_handler) {
  DCHECK(!${domain}_handler_);
  ${domain}_handler_ = ${domain}_handler;
${initializations}\
}
""")

tmpl_register = string.Template("""\
  command_handlers_["${Domain}.${command}"] =
      base::Bind(
          &DevToolsProtocolDispatcher::On${TargetDomain}${Command},
          base::Unretained(this));
""")

tmpl_init_client = string.Template("""\
  ${domain}_handler_->SetClient(base::WrapUnique(
      new devtools::${domain}::Client(notifier_)));
""")

tmpl_callback_impl = string.Template("""\
bool DevToolsProtocolDispatcher::On${Domain}${Command}(
    DevToolsCommandId command_id,
    std::unique_ptr<base::DictionaryValue> params) {
${prep}\
  Response response = ${domain}_handler_->${Command}(${args});
  std::unique_ptr<base::DictionaryValue> protocol_response;
  if (client_.SendError(command_id, response))
    return true;
  if (response.IsFallThrough())
    return false;
  std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
${wrap}\
  client_.SendSuccess(command_id, std::move(result));
  return true;
}
""")

tmpl_wrap = string.Template("""\
  result->Set("${proto_param}", devtools::CreateValue(out_${param}));
""")

tmpl_callback_async_impl = string.Template("""\
bool DevToolsProtocolDispatcher::On${Domain}${Command}(
    DevToolsCommandId command_id,
    std::unique_ptr<base::DictionaryValue> params) {
${prep}\
  Response response = ${domain}_handler_->${Command}(${args});
  if (client_.SendError(command_id, response))
    return true;
  return !response.IsFallThrough();
}
""")

tmpl_prep_req = string.Template("""\
  ${raw_type} in_${param}${init};
  if (!params || !params->Get${Type}("${proto_param}", &in_${param})) {
    client_.SendError(command_id, Response::InvalidParams("${proto_param}"));
    return true;
  }
""")

tmpl_prep_req_list = string.Template("""\
  base::ListValue* list_${param} = nullptr;
  if (!params || !params->GetList("${proto_param}", &list_${param})) {
    client_.SendError(command_id, Response::InvalidParams("${proto_param}"));
    return true;
  }
  std::vector<${item_type}> in_${param};
  for (base::ListValue::const_iterator it =
      list_${param}->begin(); it != list_${param}->end(); ++it) {
    ${item_raw_type} item;
    if (!(*it)->GetAs${ItemType}(&item)) {
      client_.SendError(command_id, Response::InvalidParams("${proto_param}"));
      return true;
    }
    in_${param}.push_back(${item_pass});
  }
""")

tmpl_prep_opt = string.Template("""\
  ${raw_type} in_${param}${init};
  bool ${param}_found = params && params->Get${Type}(
      "${proto_param}",
      &in_${param});
""")

tmpl_prep_output = string.Template("""\
  ${param_type} out_${param}${init};
""")

tmpl_arg_name = string.Template("in_${param}")

tmpl_arg_req = string.Template("${param_pass}")

tmpl_arg_opt = string.Template(
    "${param}_found ? ${param_pass} : nullptr")

tmpl_object_pass = string.Template(
    "base::WrapUnique<base::DictionaryValue>(${name}->DeepCopy())")

tmpl_client_impl = string.Template("""\
namespace ${domain} {

Client::Client(DevToolsProtocolDelegate* notifier)
    : DevToolsProtocolClient(notifier) {
}

Client::~Client() {
}

${methods}\

}  // namespace ${domain}
""")

tmpl_event_impl = string.Template("""\
void Client::${Command}(
    scoped_refptr<${Command}Params> params) {
  SendNotification("${Domain}.${command}",
                   params->ToValue());
}
""")

tmpl_response_impl = string.Template("""\
void Client::Send${Command}Response(
    DevToolsCommandId command_id,
    scoped_refptr<${Command}Response> params) {
  SendSuccess(command_id, params->ToValue());
}
""")

tmpl_typename = string.Template("devtools::${domain}::${declared_name}")

def Capitalize(s):
  return s[:1].upper() + s[1:]

def Uncamelcase(s):
  result = ""
  for i, c in enumerate(s):
    if c.isupper():
      if (i > 0) and ((i < len(s)-1) and s[i+1].islower() or s[i-1].islower()):
        result += "_"
      result += c.lower()
    else:
      result += c
  return result

types = {}
blink_protocol = json.loads(open(blink_protocol_path, "r").read())
type_decls = []
type_impls = []
handler_methods = []
handler_method_impls = []
domain_maps = []
redirects = {}

all_domains = blink_protocol["domains"]

for json_domain in all_domains:
  if "types" in json_domain:
    for json_type in json_domain["types"]:
      types["%s.%s" % (json_domain["domain"], json_type["id"])] = json_type

def DeclareStruct(json_properties, mapping):
  methods = []
  fields_enum = []
  enum_items = []
  req_fields_num = 0
  for json_prop in json_properties:
    prop_map = mapping.copy()
    prop_map["proto_param"] = json_prop["name"]
    prop_map["param"] = Uncamelcase(json_prop["name"])
    prop_map["Param"] = Capitalize(json_prop["name"])
    prop_map["subdomain"] = Uncamelcase(prop_map["declared_name"])
    del prop_map["declared_name"]
    ResolveType(json_prop, prop_map)
    prop_map["declared_name"] = mapping["declared_name"]
    if prop_map["pass_type"].find(mapping["declared_name"]) != -1:
      prop_map["pass_type"] = prop_map["pass_type"].replace(
          mapping["declared_name"], mapping["declared_name"] + "Builder<0>")
    if json_prop.get("optional"):
      methods.append(tmpl_builder_setter_opt.substitute(prop_map))
    else:
      methods.append(tmpl_builder_setter_req.substitute(prop_map))
      enum_items.append("k%s" % prop_map["Param"]);
      fields_enum.append(tmpl_builder_enum.substitute(prop_map,
        ordinal = req_fields_num))
      req_fields_num += 1

  all_fields = "kAllSet"
  if len(enum_items) > 0:
    all_fields = " | ".join(enum_items)
  fields_enum.append(tmpl_builder_none_set.substitute(mapping,
      all_fields = all_fields))
  type_decls.append(tmpl_struct.substitute(mapping,
      methods = "\n".join(methods),
      fields_enum = "".join(fields_enum)))

def DeclareEnum(json, mapping):
  values = []
  value_defs = []
  tmpl_enum = tmpl_inline_enum
  if "declared_name" in mapping:
    mapping["Enum"] = mapping["declared_name"]
    tmpl_enum = tmpl_named_enum
  else:
    mapping["Enum"] = Capitalize(mapping["proto_param"])

  for enum_value in json["enum"]:
    values.append(tmpl_enum_value.substitute(mapping,
        Value = Capitalize(enum_value)))
    value_defs.append(tmpl_enum_value_def.substitute(mapping,
        value = enum_value,
        Value = Capitalize(enum_value)))
  type_decls.append(tmpl_enum.substitute(mapping,
      values = "".join(values)))
  type_impls.append(tmpl_enum.substitute(mapping,
      values = "".join(value_defs)))

def ResolveRef(json, mapping):
  dot_pos = json["$ref"].find(".")
  if dot_pos == -1:
    domain_name = mapping["Domain"]
    type_name = json["$ref"]
  else:
    domain_name = json["$ref"][:dot_pos]
    type_name = json["$ref"][dot_pos + 1:]
  json_type = types["%s.%s" % (domain_name, type_name)]
  mapping["declared_name"] = Capitalize(type_name)
  mapping["Domain"] = domain_name
  mapping["domain"] = Uncamelcase(domain_name)
  mapping["param_type"] = tmpl_typename.substitute(mapping)
  ResolveType(json_type, mapping)
  if not "___type_declared" in json_type:
    json_type["___type_declared"] = True;
    if (json_type.get("type") == "object") and ("properties" in json_type):
      DeclareStruct(json_type["properties"], mapping)
    else:
      if ("enum" in json_type):
        DeclareEnum(json_type, mapping)
      type_decls.append(tmpl_typedef.substitute(mapping))

def ResolveArray(json, mapping):
  items_map = mapping.copy()
  ResolveType(json["items"], items_map)
  if items_map["Type"] == "List":
    # TODO(dgozman) Implement this.
    raise Exception("Nested arrays are not implemented")
  mapping["param_type"] = "std::vector<%s>" % items_map["param_type"]
  mapping["Type"] = "List"
  mapping["pass_type"] = "const %s&" % mapping["param_type"]
  mapping["storage_type"] = "std::vector<%s>" % items_map["storage_type"]
  mapping["raw_type"] = mapping["storage_type"]
  mapping["prep_req"] = tmpl_prep_req_list.substitute(mapping,
      item_type = items_map["storage_type"],
      item_init = items_map["init"],
      item_raw_type = items_map["raw_type"],
      item_pass = items_map["pass_template"].substitute(name="item", opt=""),
      ItemType = items_map["Type"])
  mapping["arg_out"] = "&out_%s" % mapping["param"]

def ResolveObject(json, mapping):
  mapping["Type"] = "Dictionary"
  mapping["storage_type"] = "std::unique_ptr<base::DictionaryValue>"
  mapping["raw_type"] = "base::DictionaryValue*"
  mapping["pass_template"] = tmpl_object_pass
  mapping["init"] = " = nullptr"
  if "properties" in json:
    if not "declared_name" in mapping:
      mapping["declared_name"] = ("%s%s" %
          (mapping["Command"], Capitalize(mapping["proto_param"])))
      mapping["param_type"] = ("scoped_refptr<%s>" %
          tmpl_typename.substitute(mapping))
      DeclareStruct(json["properties"], mapping)
    else:
      mapping["param_type"] = ("scoped_refptr<%s>" %
          tmpl_typename.substitute(mapping))
    mapping["pass_type"] = mapping["param_type"]
    mapping["arg_out"] = "&out_%s" % mapping["param"]
  else:
    mapping["param_type"] = "base::DictionaryValue"
    mapping["pass_type"] = "std::unique_ptr<base::DictionaryValue>"
    mapping["arg_out"] = "out_%s.get()" % mapping["param"]
  mapping["prep_req"] = tmpl_prep_req.substitute(mapping)

def ResolvePrimitive(json, mapping):
  jsonrpc_type = json["type"]
  if jsonrpc_type == "boolean":
    mapping["param_type"] = "bool"
    mapping["Type"] = "Boolean"
    mapping["init"] = " = false"
  elif jsonrpc_type == "integer":
    mapping["param_type"] = "int"
    mapping["Type"] = "Integer"
    mapping["init"] = " = 0"
  elif jsonrpc_type == "number":
    mapping["param_type"] = "double"
    mapping["Type"] = "Double"
    mapping["init"] = " = 0.0"
  elif jsonrpc_type == "string":
    mapping["param_type"] = "std::string"
    mapping["pass_type"] = "const std::string&"
    mapping["Type"] = "String"
    if "enum" in json and not "declared_name" in mapping:
      if not "subdomain" in mapping:
        mapping["subdomain"] = Uncamelcase(mapping["command"])
      DeclareEnum(json, mapping)
  else:
    raise Exception("Unknown type: %s" % jsonrpc_type)
  mapping["storage_type"] = mapping["param_type"]
  mapping["raw_type"] = mapping["param_type"]
  mapping["prep_req"] = tmpl_prep_req.substitute(mapping)
  if jsonrpc_type != "string":
    mapping["pass_type"] = mapping["param_type"]
  mapping["arg_out"] = "&out_%s" % mapping["param"]

def ResolveType(json, mapping):
  mapping["init"] = ""
  mapping["pass_template"] = string.Template("${opt}${name}")
  if "$ref" in json:
    ResolveRef(json, mapping)
  elif "type" in json:
    jsonrpc_type = json["type"]
    if jsonrpc_type == "array":
      ResolveArray(json, mapping)
    elif jsonrpc_type == "object":
      ResolveObject(json, mapping)
    else:
      ResolvePrimitive(json, mapping)
  else:
    raise Exception("Unknown type at %s.%s %s" %
        (mapping["Domain"], mapping["command"], mapping["proto_param"]))

setters = []
fields = []

includes = []
fields_init = []

browser_domains_list = ["Inspector", "Memory", "Page", "Emulation", "Security",
    "IO", "Target", "ServiceWorker", "Input", "Tracing", "Storage",
    "SystemInfo", "Tethering", "Schema"]
browser_commands_list = [
    "DOM.setFileInputFiles",
    "Network.enable",
    "Network.disable",
    "Network.clearBrowserCache",
    "Network.clearBrowserCookies",
    "Network.getCookies",
    "Network.deleteCookie",
    "Network.setCookie",
    "Network.canEmulateNetworkConditions",
    "Network.emulateNetworkConditions"]
async_commands_list = [
    "Page.getResourceContent",
    "Page.searchInResource",
    "Page.captureScreenshot",
    "Network.getCookies",
    "Network.deleteCookie",
    "Network.setCookie",
    "IO.read",
    "Input.synthesizePinchGesture",
    "Input.synthesizeScrollGesture",
    "Input.synthesizeTapGesture",
    "Tracing.start",
    "Tracing.end",
    "Tracing.getCategories",
    "Tracing.requestMemoryDump",
    "SystemInfo.getInfo",
    "Tethering.bind",
    "Tethering.unbind"]

for json_domain in all_domains:
  domain_map = {}
  domain_map["Domain"] = json_domain["domain"]
  domain_map["domain"] = Uncamelcase(json_domain["domain"])

  initializations = []
  client_methods = []
  client_method_impls = []
  domain_empty = True
  domain_needs_client = False

  if "commands" in json_domain:
    for json_command in json_domain["commands"]:
      full_command_name = json_domain["domain"] + "." + json_command["name"]
      if (json_domain["domain"] not in browser_domains_list and
          full_command_name not in browser_commands_list):
        continue
      domain_empty = False

      command_map = domain_map.copy()
      command_map["command"] = json_command["name"]
      command_map["Command"] = Capitalize(json_command["name"])

      if "redirect" in json_command:
        redirect_domain = json_command["redirect"]
        if not (redirect_domain in redirects):
          redirects[redirect_domain] = []
        command_map["TargetDomain"] = redirect_domain
        redirects[redirect_domain].append(tmpl_register.substitute(command_map))
        continue

      command_map["TargetDomain"] = command_map["Domain"]
      prep = []
      args = []

      if "parameters" in json_command:
        for json_param in json_command["parameters"]:
          param_map = command_map.copy()
          param_map["proto_param"] = json_param["name"]
          param_map["param"] = Uncamelcase(json_param["name"])
          ResolveType(json_param, param_map)
          if json_param.get("optional"):
            if param_map["Type"] in ["List"]:
              # TODO(vkuzkokov) Implement transformation of base::ListValue
              # to std::vector and base::DictonaryValue to struct.
              raise Exception(
                  "Optional array parameters are not implemented")
            prep.append(tmpl_prep_opt.substitute(param_map))
            param_pass = param_map["pass_template"].substitute(
                name=tmpl_arg_name.substitute(param_map),
                opt="&")
            args.append(
                tmpl_arg_opt.substitute(param_map, param_pass=param_pass))
          else:
            prep.append(param_map["prep_req"])
            param_pass = param_map["pass_template"].substitute(
                name=tmpl_arg_name.substitute(param_map),
                opt="")
            args.append(
                tmpl_arg_req.substitute(param_map, param_pass=param_pass))

      if full_command_name in async_commands_list:
        domain_needs_client = True
        json_returns = []
        if "returns" in json_command:
          json_returns = json_command["returns"]
        command_map["declared_name"] = "%sResponse" % command_map["Command"]
        DeclareStruct(json_returns, command_map)
        # TODO(vkuzkokov) Pass async callback instance similar to how
        # InspectorBackendDispatcher does it. This, however, can work
        # only if Blink and Chrome are in the same repo.
        args.insert(0, "command_id")
        handler_method_impls.append(
            tmpl_callback_async_impl.substitute(command_map,
                prep = "".join(prep),
                args = "\n      " + ",\n      ".join(args)))
        client_methods.append(tmpl_response.substitute(command_map))
        client_method_impls.append(tmpl_response_impl.substitute(command_map))
      else:
        wrap = []
        if "returns" in json_command:
          for json_param in json_command["returns"]:
            param_map = command_map.copy()
            param_map["proto_param"] = json_param["name"]
            param_map["param"] = Uncamelcase(json_param["name"])
            is_string = "type" in json_param and json_param["type"] == "string"
            if json_param.get("optional") and not is_string:
              # TODO(vkuzkokov) Implement Optional<T> for value types.
              raise Exception(
                  "Optional return values are not implemented (%s.%s.%s)" %
                  (param_map["Domain"], param_map["command"],
                   param_map["proto_param"]))
            ResolveType(json_param, param_map)
            prep.append(tmpl_prep_output.substitute(param_map))
            args.append(param_map["arg_out"])
            wrap.append(tmpl_wrap.substitute(param_map))
        args_str = ""
        if len(args) > 0:
          args_str = "\n      " + ",\n      ".join(args)
        handler_method_impls.append(tmpl_callback_impl.substitute(command_map,
            prep = "".join(prep),
            args = args_str,
            wrap = "".join(wrap)))

      initializations.append(tmpl_register.substitute(command_map))
      handler_methods.append(tmpl_callback.substitute(command_map))

  if "events" in json_domain:
    for json_event in json_domain["events"]:
      if json_domain["domain"] not in browser_domains_list:
        continue
      domain_empty = False
      domain_needs_client = True

      event_map = domain_map.copy()
      event_map["command"] = json_event["name"]
      event_map["Command"] = Capitalize(json_event["name"])

      json_parameters = []
      if "parameters" in json_event:
        json_parameters = json_event["parameters"]
      event_map["declared_name"] = "%sParams" % event_map["Command"]
      DeclareStruct(json_parameters, event_map);

      client_methods.append(tmpl_event.substitute(event_map))
      client_method_impls.append(tmpl_event_impl.substitute(event_map))

  if domain_empty:
    continue
  type_decls.append(tmpl_handler.substitute(domain_map))
  setters.append(tmpl_setter.substitute(domain_map))
  fields.append(tmpl_field.substitute(domain_map))
  includes.append(tmpl_include.substitute(domain_map))
  fields_init.append(tmpl_field_init.substitute(domain_map))
  if domain_needs_client:
    type_decls.append(tmpl_client.substitute(domain_map,
        methods = "".join(client_methods)))
    initializations.append(tmpl_init_client.substitute(domain_map))
    type_impls.append(tmpl_client_impl.substitute(domain_map,
        methods = "\n".join(client_method_impls)))
  domain_map["initializations"] = "".join(initializations)
  domain_maps.append(domain_map)

for domain_map in domain_maps:
  domain = domain_map["Domain"]
  if domain in redirects:
    domain_map["initializations"] += "".join(redirects[domain])
  handler_method_impls.append(tmpl_setter_impl.substitute(domain_map))

output_h_file = open(output_h_path, "w")
output_cc_file = open(output_cc_path, "w")

output_h_file.write(template_h.substitute({},
    types = "\n".join(type_decls),
    setters = "".join(setters),
    methods = "".join(handler_methods),
    fields = "".join(fields)))
output_h_file.close()

output_cc_file.write(template_cc.substitute({},
    major = blink_protocol["version"]["major"],
    minor = blink_protocol["version"]["minor"],
    includes = "".join(sorted(includes)),
    fields_init = ",\n      ".join(fields_init),
    methods = "\n".join(handler_method_impls),
    types = "\n".join(type_impls)))
output_cc_file.close()
