// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.

// mcpack2pb - Make protobuf be front-end of mcpack/compack

// Date: Mon Oct 19 17:17:36 CST 2015

#include <set>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/io/printer.h>
#include <google/protobuf/compiler/code_generator.h>
#include <google/protobuf/compiler/plugin.h>

#include "butil/file_util.h"
#include "butil/string_printf.h"
#include "butil/strings/string_util.h"
#include "idl_options.pb.h"
#include "mcpack2pb/mcpack2pb.h"

namespace mcpack2pb {

const std::string get_idl_name(const google::protobuf::FieldDescriptor* f) {
    const std::string real_name = butil::EnsureString(f->options().GetExtension(idl_name));
    return real_name.empty() ? butil::EnsureString(f->name()) : real_name;
}

bool is_integral_type(ConvertibleIdlType type) {
    switch (type) {
    case IDL_INT8:
    case IDL_INT16:
    case IDL_INT32:
    case IDL_INT64:
    case IDL_UINT8:
    case IDL_UINT16:
    case IDL_UINT32:
    case IDL_UINT64:
        return true;
    default:
        return false;
    }
}

const std::string field_to_string(const google::protobuf::FieldDescriptor* f) {
    switch (f->type()) {
    case google::protobuf::FieldDescriptor::TYPE_DOUBLE:   return "double";
    case google::protobuf::FieldDescriptor::TYPE_FLOAT:    return "float";
    case google::protobuf::FieldDescriptor::TYPE_INT64:    return "int64";
    case google::protobuf::FieldDescriptor::TYPE_UINT64:   return "uint64";
    case google::protobuf::FieldDescriptor::TYPE_INT32:    return "int32";
    case google::protobuf::FieldDescriptor::TYPE_FIXED64:  return "fixed64";
    case google::protobuf::FieldDescriptor::TYPE_FIXED32:  return "fixed32";
    case google::protobuf::FieldDescriptor::TYPE_BOOL:     return "bool";
    case google::protobuf::FieldDescriptor::TYPE_STRING:   return "string";
    case google::protobuf::FieldDescriptor::TYPE_GROUP:
    case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
        return butil::EnsureString(f->message_type()->name());
    case google::protobuf::FieldDescriptor::TYPE_BYTES:    return "bytes";
    case google::protobuf::FieldDescriptor::TYPE_UINT32:   return "uint32";
    case google::protobuf::FieldDescriptor::TYPE_ENUM:
        return butil::EnsureString(f->enum_type()->name());
    case google::protobuf::FieldDescriptor::TYPE_SFIXED32: return "sfixed32";
    case google::protobuf::FieldDescriptor::TYPE_SFIXED64: return "sfixed64";
    case google::protobuf::FieldDescriptor::TYPE_SINT32:   return "sint32";
    case google::protobuf::FieldDescriptor::TYPE_SINT64:   return "sint64";
    }
    return "unknown_protobuf_type";
}

const char* to_mcpack_typestr(const google::protobuf::FieldDescriptor* f) {
    switch (f->type()) {
    case google::protobuf::FieldDescriptor::TYPE_DOUBLE:   return "double";
    case google::protobuf::FieldDescriptor::TYPE_FLOAT:    return "float";
    case google::protobuf::FieldDescriptor::TYPE_INT64:    return "int64";
    case google::protobuf::FieldDescriptor::TYPE_UINT64:   return "uint64";
    case google::protobuf::FieldDescriptor::TYPE_INT32:    return "int32";
    case google::protobuf::FieldDescriptor::TYPE_FIXED64:  return "uint64";
    case google::protobuf::FieldDescriptor::TYPE_FIXED32:  return "uint32";
    case google::protobuf::FieldDescriptor::TYPE_BOOL:     return "bool";
    case google::protobuf::FieldDescriptor::TYPE_STRING:   return "string";
    case google::protobuf::FieldDescriptor::TYPE_GROUP:    return "object";
    case google::protobuf::FieldDescriptor::TYPE_MESSAGE:  return "object";
    case google::protobuf::FieldDescriptor::TYPE_BYTES:    return "binary";
    case google::protobuf::FieldDescriptor::TYPE_UINT32:   return "uint32";
    case google::protobuf::FieldDescriptor::TYPE_ENUM:     return "int32";
    case google::protobuf::FieldDescriptor::TYPE_SFIXED32: return "int32";
    case google::protobuf::FieldDescriptor::TYPE_SFIXED64: return "int64";
    case google::protobuf::FieldDescriptor::TYPE_SINT32:   return "int32";
    case google::protobuf::FieldDescriptor::TYPE_SINT64:   return "int64";
    }
    return "unknown_protobuf_type";
}

const char* to_mcpack_typestr(ConvertibleIdlType type,
                              const google::protobuf::FieldDescriptor* f) {
    switch (type) {
    case IDL_AUTO:   return to_mcpack_typestr(f);
    case IDL_INT8:   return "int8";
    case IDL_INT16:  return "int16";
    case IDL_INT32:  return "int32";
    case IDL_INT64:  return "int64";
    case IDL_UINT8:  return "uint8";
    case IDL_UINT16: return "uint16";
    case IDL_UINT32: return "uint32";
    case IDL_UINT64: return "uint64";
    case IDL_BOOL:   return "bool";
    case IDL_FLOAT:  return "float";
    case IDL_DOUBLE: return "double";
    case IDL_BINARY: return "binary";
    case IDL_STRING: return "string";
    }
    return "unknown";
}

const char* to_mcpack_typestr_uppercase(
    ConvertibleIdlType type,
    const google::protobuf::FieldDescriptor* f) {
    const char* s = to_mcpack_typestr(type, f);
    static char tempbuf[32];
    char* p = tempbuf;
    for (; *s; ++s, ++p) {
        *p = ::toupper(*s);
    }
    *p = 0;
    return tempbuf;
}

const char* describe_idl_type(ConvertibleIdlType type) {
    switch (type) {
    case IDL_AUTO:   return "IDL_AUTO";
    case IDL_INT8:   return "IDL_INT8";
    case IDL_INT16:  return "IDL_INT16";
    case IDL_INT32:  return "IDL_INT32";
    case IDL_INT64:  return "IDL_INT64";
    case IDL_UINT8:  return "IDL_UINT8";
    case IDL_UINT16: return "IDL_UINT16";
    case IDL_UINT32: return "IDL_UINT32";
    case IDL_UINT64: return "IDL_UINT64";
    case IDL_BOOL:   return "IDL_BOOL";
    case IDL_FLOAT:  return "IDL_FLOAT";
    case IDL_DOUBLE: return "IDL_DOUBLE";
    case IDL_BINARY: return "IDL_BINARY";
    case IDL_STRING: return "IDL_STRING";
    }
    return "Bad ConvertibleIdlType";
}

static std::string to_var_name(const std::string& name) {
    std::string result = name;
    for (size_t i = 0; i < result.size(); ++i) {
        if (result[i] == '.') {
            result[i] = '_';
        }
    }
    return result;
}

static std::string to_cpp_name(const std::string& full_name) {
    std::string cname;
    cname.reserve(full_name.size() + 8);
    for (size_t i = 0; i < full_name.size(); ++i) {
        if (full_name[i] == '.') {
            cname.append("::", 2);
        } else {
            cname.push_back(full_name[i]);
        }
    }
    return cname;
}

bool generate_declarations(const std::set<std::string>& ref_msgs,
                           const std::set<std::string>& ref_maps,
                           google::protobuf::io::Printer& decl) {
    for (std::set<std::string>::const_iterator
             it = ref_msgs.begin(); it != ref_msgs.end(); ++it) {
        decl.Print(
            "extern bool parse_$vmsg$_body_internal(\n"
            "    ::google::protobuf::Message* msg,\n"
            "    ::mcpack2pb::UnparsedValue& value);\n"
            "extern void serialize_$vmsg$_body(\n"
            "    const ::google::protobuf::Message& msg,\n"
            "    ::mcpack2pb::Serializer& serializer,\n"
            "    ::mcpack2pb::SerializationFormat format);\n"
            "extern ::mcpack2pb::FieldMap* g_$vmsg$_fields;\n"
            , "vmsg", *it);
    }
    for (std::set<std::string>::const_iterator
             it = ref_maps.begin(); it != ref_maps.end(); ++it) {
        decl.Print(
            "extern bool set_$vmsg$_value(::google::protobuf::Message* msg,\n"
            "                                 ::mcpack2pb::UnparsedValue& value);\n"
            , "vmsg", *it);
    }

    return !decl.failed();
}

#define TEMPLATE_OF_SET_FUNC_SIGNATURE()                                \
    "bool set_$vmsg$_$lcfield$(::google::protobuf::Message* msg_base,\n" \
    "                                ::mcpack2pb::UnparsedValue& value) "

#define TEMPLATE_OF_SET_FUNC_BODY(fntype)                               \
    "{\n"                                                               \
    "  static_cast<$msg$*>(msg_base)->set_$lcfield$(value.as_"#fntype"(\"$field$\"));\n" \
    "  return value.stream()->good();\n"                                \
    "}\n"                                                               

#define TEMPLATE_OF_ADD_FUNC_SIGNATURE()                                \
    "bool set_$vmsg$_$lcfield$(::google::protobuf::Message* msg_base,\n"     \
    "                                ::mcpack2pb::UnparsedValue& value) "  \

#define TEMPLATE_OF_ADD_FUNC_BODY(fntype, pbtype)                       \
    "{\n"                                                               \
    "  $msg$* const msg = static_cast<$msg$*>(msg_base);\n"             \
    "  if (value.type() == ::mcpack2pb::FIELD_ISOARRAY) {\n"               \
    "    ::mcpack2pb::ISOArrayIterator it(value);\n"                       \
    "    msg->mutable_$lcfield$()->Reserve(it.item_count());\n"         \
    "    for (; it != NULL; ++it) {\n"                                  \
    "      msg->add_$lcfield$(it.as_"#fntype "());\n"                   \
    "    }\n"                                                           \
    "    return value.stream()->good();\n"                              \
    "  } else if (value.type() == ::mcpack2pb::FIELD_ARRAY) {\n"           \
    "    ::mcpack2pb::ArrayIterator it(value);\n"                          \
    "    msg->mutable_$lcfield$()->Reserve(it.item_count());\n"         \
    "    for (; it != NULL; ++it) {\n"                                  \
    "      msg->add_$lcfield$(it->as_"#fntype "(\"$field$\"));\n" \
    "    }\n"                                                           \
    "    return value.stream()->good();\n"                              \
    "  }\n"                                                             \
    "  LOG(ERROR) << \"Can't set \" << value << \" to $field$ (repeated " #pbtype ")\";\n" \
    "  return false;\n"                                                 \
    "}\n"

static bool is_map_entry(const google::protobuf::Descriptor* d) {
    if (d->field_count() != 2 ||
        d->field(0)->name() != "key" ||
        d->field(0)->number() != 1 ||
        d->field(1)->name() != "value" ||
        d->field(1)->number() != 2) {
        return false;
    }
    if (d->field(0)->is_repeated()) {
        LOG(ERROR) << d->field(0)->full_name() << " must be required or optional";
        return false;
    }
    if (d->field(1)->is_repeated()) {
        LOG(ERROR) << d->field(1)->full_name() << " must be required or optional";
        return false;
    }
    if (d->field(0)->cpp_type() != google::protobuf::FieldDescriptor::CPPTYPE_STRING) {
        LOG(ERROR) << "key of idl map must be string";
        return false;
    }
    return true;
}

static bool generate_parsing(const google::protobuf::Descriptor* d,
                             std::set<std::string> & ref_msgs,
                             std::set<std::string> & ref_maps,
                             google::protobuf::io::Printer& impl) {
    std::string var_name = mcpack2pb::to_var_name(butil::EnsureString(d->full_name()));
    std::string cpp_name = mcpack2pb::to_cpp_name(butil::EnsureString(d->full_name()));
    ref_msgs.insert(var_name);

    impl.Print("\n// $msg$ from mcpack\n", "msg", d->full_name());
    for (int i = 0; i < d->field_count(); ++i) {
        const google::protobuf::FieldDescriptor* f = d->field(i);
        if (f->is_repeated()) {
            impl.Print("// repeated $type$ $name$ = $number$;\n"
                       , "type", field_to_string(f)
                       , "name", f->name()
                       , "number", butil::string_printf("%d", f->number()));
            impl.Print(TEMPLATE_OF_ADD_FUNC_SIGNATURE()
                       , "vmsg", var_name
                       , "lcfield", f->lowercase_name());
            switch (f->cpp_type()) {
            case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
                impl.Print(TEMPLATE_OF_ADD_FUNC_BODY(int32, int32)
                           , "msg", cpp_name
                           , "field", f->full_name()
                           , "lcfield", f->lowercase_name());
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
                impl.Print(TEMPLATE_OF_ADD_FUNC_BODY(int64, int64)
                           , "msg", cpp_name
                           , "field", f->full_name()
                           , "lcfield", f->lowercase_name());
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
                impl.Print(TEMPLATE_OF_ADD_FUNC_BODY(uint32, uint32)
                           , "msg", cpp_name
                           , "field", f->full_name()
                           , "lcfield", f->lowercase_name());
                break;                
            case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
                impl.Print(TEMPLATE_OF_ADD_FUNC_BODY(uint64, uint64)
                           , "msg", cpp_name
                           , "field", f->full_name()
                           , "lcfield", f->lowercase_name());
                break;                
            case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
                impl.Print(TEMPLATE_OF_ADD_FUNC_BODY(bool, bool)
                           , "msg", cpp_name
                           , "field", f->full_name()
                           , "lcfield", f->lowercase_name());
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
                impl.Print(
                    "{\n"
                    "  $msg$* const msg = static_cast<$msg$*>(msg_base);\n"
                    "  if (value.type() == ::mcpack2pb::FIELD_ISOARRAY) {\n"
                    "    ::mcpack2pb::ISOArrayIterator it(value);\n"
                    "    msg->mutable_$lcfield$()->Reserve(it.item_count());\n" 
                    "    for (; it != NULL; ++it) {\n"
                    "      msg->add_$lcfield$(($enum$)it.as_int32());\n"
                    "    }\n"
                    "    return value.stream()->good();\n"
                    "  } else if (value.type() == ::mcpack2pb::FIELD_ARRAY) {\n"            
                    "    ::mcpack2pb::ArrayIterator it(value);\n"
                    "    msg->mutable_$lcfield$()->Reserve(it.item_count());\n"
                    "    for (; it != NULL; ++it) {\n"
                    "      msg->add_$lcfield$(($enum$)it->as_int32(\"$enum$\"));\n"
                    "    }\n"
                    "    return value.stream()->good();\n"
                    "  }\n"                                                             
                    "  LOG(ERROR) << \"Can't set \" << value << \" to repeated $enum$\";\n"
                    "  return false;\n"                                                 
                    "}\n"
                    , "msg", cpp_name
                    , "enum", to_cpp_name(butil::EnsureString(f->enum_type()->full_name()))
                    , "lcfield", f->lowercase_name());
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
                impl.Print(TEMPLATE_OF_ADD_FUNC_BODY(float, float)
                           , "msg", cpp_name
                           , "field", f->full_name()
                           , "lcfield", f->lowercase_name());
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
                impl.Print(TEMPLATE_OF_ADD_FUNC_BODY(double, double)
                           , "msg", cpp_name
                           , "field", f->full_name()
                           , "lcfield", f->lowercase_name());
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
                impl.Print(
                    "{\n"
                    "  $msg$* const msg = static_cast<$msg$*>(msg_base);\n"
                    "  if (value.type() == ::mcpack2pb::FIELD_ARRAY) {\n"
                    "    ::mcpack2pb::ArrayIterator it(value);\n"
                    "    msg->mutable_$lcfield$()->Reserve(it.item_count());\n"
                    "    for (; it != NULL; ++it) {\n"
                    "      if (it->type() == ::mcpack2pb::FIELD_STRING) {\n"
                    "        it->as_string(msg->add_$lcfield$(), \"$field$\");\n"
                    "      } else if (it->type() == ::mcpack2pb::FIELD_BINARY) {\n"
                    "        it->as_binary(msg->add_$lcfield$(), \"$field$\");\n"
                    "      } else {\n"
                    "        LOG(ERROR) << \"Can't add \" << *it << \" to $field$ (repeated string)\";\n"
                    "        return false;\n"    
                    "      }\n"
                    "    }\n"
                    "    return value.stream()->good();\n"
                    "  }\n"                                                             
                    "  LOG(ERROR) << \"Can't set \" << value << \" to $field$ (repeated string)\";\n"
                    "  return false;\n"    
                    "}\n"
                    , "msg", cpp_name
                    , "field", f->full_name()
                    , "lcfield", f->lowercase_name());
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
                std::string var_name2 = mcpack2pb::to_var_name(butil::EnsureString(f->message_type()->full_name()));
                std::string cpp_name2 = mcpack2pb::to_cpp_name(butil::EnsureString(f->message_type()->full_name()));
                if (is_map_entry(f->message_type())) {
                    ref_maps.insert(var_name2);
                    impl.Print(
                        "{\n"
                        "  $msg$* const msg = static_cast<$msg$*>(msg_base);\n"
                        "  if (value.type() == ::mcpack2pb::FIELD_OBJECT) {\n"
                        "    ::mcpack2pb::ObjectIterator it(value);\n"
                        "    for (; it != NULL; ++it) {\n"
                        "      $msg2$* sub = msg->add_$lcfield$();\n"
                        "      sub->set_key(it->name.data(), it->name.size());\n"
                        , "msg", cpp_name
                        , "msg2", cpp_name2
                        , "lcfield", f->lowercase_name());
                    impl.Print(
                        "      if (!set_$vmsg2$_value(sub, it->value)) {\n"
                        "        return false;\n"
                        "      }\n"
                        "    }\n"
                        "    return value.stream()->good();\n"
                        "  }\n"
                        "  LOG(ERROR) << \"Can't set \" << value << \" to $field$ (repeated $msg2$)\";\n"
                        "  return false;\n"    
                        "}\n"
                        , "vmsg2", var_name2
                        , "msg2", f->message_type()->full_name()
                        , "field", f->full_name());
                    break;
                }
                ref_msgs.insert(var_name2);
                impl.Print(
                    "{\n"
                    "  $msg$* const msg = static_cast<$msg$*>(msg_base);\n"
                    "  if (value.type() == ::mcpack2pb::FIELD_OBJECTISOARRAY) {\n"
                    "    ::mcpack2pb::ObjectIterator it(value);\n"
                    "    for (; it != NULL; ++it) {\n"
                    "      ::mcpack2pb::SetFieldFn* fn = g_$vmsg2$_fields->seek(it->name);\n"
                    "      if (!fn) {\n"
                    "        if (!FLAGS_mcpack2pb_absent_field_is_error) {\n"
                    "          continue;\n"
                    "        } else {\n"
                    "          LOG(ERROR) << \"No field=\" << it->name << \" (\"\n"
                    "                     << value << \") in $msg$\";\n"
                    "          return false;\n"
                    "        }\n"
                    "      }\n"
                    "      if (it->value.type() == ::mcpack2pb::FIELD_ARRAY) {\n"
                    "        ::mcpack2pb::ArrayIterator it2(it->value);\n"
                    "        int i = 0;\n"
                    "        for (; it2 != NULL; ++it2, ++i) {\n"
                    "          ::google::protobuf::Message* sub_msg = NULL;\n"
                    "          if (i < msg->$lcfield$_size()) {\n"
                    "            sub_msg = msg->mutable_$lcfield$(i);\n"
                    "          } else {\n"
                    "            sub_msg = msg->add_$lcfield$();\n"
                    "          }\n"
                    "          if (it2->type() != ::mcpack2pb::FIELD_NULL) {\n"
                    "            if (!(*fn)(sub_msg, *it2)) {\n"
                    "              LOG(ERROR) << \"Fail to set item of \" << it->name;\n"
                    "              return false;\n"
                    "            }\n"
                    "          }\n"
                    "        }\n"
                    "      } else if (it->value.type() == ::mcpack2pb::FIELD_ISOARRAY) {\n"
                    "         LOG(ERROR) << \"Shouldn't be a iso array, name=\" << it->name;\n"
                    "         return false;\n"
                    "      } else {\n"
                    "         LOG(ERROR) << \"Can't add \" << value << \" to repeated $msg$\";\n"
                    "         return false;\n"
                    "      }\n"
                    "    }\n"
                    "    return value.stream()->good();\n"
                    "  } else if (value.type() == ::mcpack2pb::FIELD_ARRAY) {\n"
                    "    ::mcpack2pb::ArrayIterator it(value);\n"
                    "    msg->mutable_$lcfield$()->Reserve(it.item_count());\n"
                    "    for (; it != NULL; ++it) {\n"
                    "      if (it->type() == ::mcpack2pb::FIELD_OBJECT) {\n"
                    "        if (!parse_$vmsg2$_body_internal(msg->add_$lcfield$(), *it)) {\n"
                    "          return false;\n"
                    "        }\n"
                    "      } else {\n"
                    "        LOG(ERROR) << \"Can't add \" << *it << \" to repeated $msg$\";\n"
                    "        return false;\n"    
                    "      }\n"
                    "    }\n"
                    "    return value.stream()->good();\n"
                    "  }\n"
                    , "vmsg2", var_name2
                    , "msg", cpp_name
                    , "lcfield", f->lowercase_name());
                impl.Print(
                    "  LOG(ERROR) << \"Can't set \" << value << \" to $field$ (repeated $msg2$)\";\n"
                    "  return false;\n"    
                    "}\n"
                    , "msg2", f->message_type()->full_name()
                    , "field", f->full_name());
            } break;
            } // switch
        } else {
            if (!f->is_required() && !f->is_repeated()) {
                impl.Print("// optional $type$ $name$ = $number$;\n"
                           , "type", field_to_string(f)
                           , "name", f->name()
                           , "number", butil::string_printf("%d", f->number()));
            } else {
                impl.Print("// required $type$ $name$ = $number$;\n"
                           , "type", field_to_string(f)
                           , "name", f->name()
                           , "number", butil::string_printf("%d", f->number()));
            }
            impl.Print(TEMPLATE_OF_SET_FUNC_SIGNATURE()
                       , "vmsg", var_name
                       , "lcfield", f->lowercase_name());
            switch (f->cpp_type()) {
            case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
                impl.Print(TEMPLATE_OF_SET_FUNC_BODY(int32)
                           , "msg", cpp_name
                           , "field", f->full_name()
                           , "lcfield", f->lowercase_name());
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
                impl.Print(TEMPLATE_OF_SET_FUNC_BODY(int64)
                           , "msg", cpp_name
                           , "field", f->full_name()
                           , "lcfield", f->lowercase_name());
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
                impl.Print(TEMPLATE_OF_SET_FUNC_BODY(uint32)
                           , "msg", cpp_name
                           , "field", f->full_name()
                           , "lcfield", f->lowercase_name());
                break;                
            case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
                impl.Print(TEMPLATE_OF_SET_FUNC_BODY(uint64)
                           , "msg", cpp_name
                           , "field", f->full_name()
                           , "lcfield", f->lowercase_name());
                break;                
            case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
                impl.Print(TEMPLATE_OF_SET_FUNC_BODY(bool)
                           , "msg", cpp_name
                           , "field", f->full_name()
                           , "lcfield", f->lowercase_name());
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
                impl.Print(TEMPLATE_OF_SET_FUNC_BODY(float)
                           , "msg", cpp_name
                           , "field", f->full_name()
                           , "lcfield", f->lowercase_name());
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
                impl.Print(TEMPLATE_OF_SET_FUNC_BODY(double)
                           , "msg", cpp_name
                           , "field", f->full_name()
                           , "lcfield", f->lowercase_name());
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
                impl.Print(
                    "{\n"
                    "  static_cast<$msg$*>(msg_base)->set_$lcfield$(static_cast<$enum$>(value.as_int32(\"$enum$\")));\n"
                    "  return value.stream()->good();\n"
                    "}\n"
                    , "msg", cpp_name
                    , "enum", to_cpp_name(butil::EnsureString(f->enum_type()->full_name()))
                    , "lcfield", f->lowercase_name());
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
                // TODO: Encoding checking/conversion for pb strings(utf8).
                impl.Print(
                    "{\n"
                    "  if (value.type() == ::mcpack2pb::FIELD_STRING) {\n"
                    "    value.as_string(static_cast<$msg$*>(msg_base)->mutable_$lcfield$(), \"$field$\");\n"
                    "    return value.stream()->good();\n"
                    "  } else if (value.type() == ::mcpack2pb::FIELD_BINARY) {\n"
                    "    value.as_binary(static_cast<$msg$*>(msg_base)->mutable_$lcfield$(), \"$field$\");\n"
                    "    return value.stream()->good();\n"
                    "  }\n"
                    "  LOG(ERROR) << \"Can't set \" << value << \" to $field$ (string)\";\n"
                    "  return false;\n"
                    "}\n"
                    , "msg", cpp_name
                    , "field", f->full_name()
                    , "lcfield", f->lowercase_name());
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
                std::string var_name2 = mcpack2pb::to_var_name(butil::EnsureString(f->message_type()->full_name()));
                ref_msgs.insert(var_name2);
                impl.Print(
                    "{\n"
                    "  if (value.type() == ::mcpack2pb::FIELD_OBJECT) {\n"
                    "    return parse_$vmsg2$_body_internal(static_cast<$msg$*>(msg_base)->mutable_$lcfield$(), value);\n"
                    "  }\n"
                    , "vmsg2", var_name2
                    , "msg", cpp_name
                    , "lcfield", f->lowercase_name());
                impl.Print(
                    // FIXME: describe type.
                    "  LOG(ERROR) << \"Can't set \" << value << \" to $field$\";\n"
                    "  return false;\n"
                    "}\n"
                    , "field", f->full_name());
            } break;
            } // switch
        } // else
    }

    impl.Print(
        "bool parse_$vmsg$_body_internal(\n"
        "    ::google::protobuf::Message* msg,\n"
        "    ::mcpack2pb::UnparsedValue& value) {\n"
        "  ::mcpack2pb::ObjectIterator it(value);\n"
        "  for (; it != NULL; ++it) {\n"
        "    ::mcpack2pb::SetFieldFn* fn = g_$vmsg$_fields->seek(it->name);\n"
        "    if (!fn) {\n"
        "      if (!FLAGS_mcpack2pb_absent_field_is_error) {\n"
        "        continue;\n"
        "      } else {\n"
        "        LOG(ERROR) << \"No field=\" << it->name << \" (\"\n"
        "                   << it->value << \") in $msg$\";\n"
        "        return false;\n"
        "      }\n"
        "    }\n"
        "    if (!(*fn)(msg, it->value)) {\n"
        "      return false;\n"
        "    }\n"
        "  }\n"
        "  return value.stream()->good();\n"
        "}\n"
        "bool parse_$vmsg$_body(\n"
        "    ::google::protobuf::Message* msg,\n"
        "    ::google::protobuf::io::ZeroCopyInputStream* input,\n"
        "     size_t size) {\n"
        "  ::mcpack2pb::InputStream mc_stream(input);\n"
        "  ::mcpack2pb::UnparsedValue value(::mcpack2pb::FIELD_OBJECT, &mc_stream, size);\n"
        "  if (!parse_$vmsg$_body_internal(msg, value)) {\n"
        "    return false;\n"
        "  }\n"
        "  if (!msg->IsInitialized()) {\n"
        "    LOG(ERROR) << \"Missing required fields: \" << msg->InitializationErrorString();\n"
        "    return false;\n"
        "  }\n"
        "  return true;\n"
        "}\n"
        "size_t parse_$vmsg$(\n"
        "    ::google::protobuf::Message* msg,\n"
        "    ::google::protobuf::io::ZeroCopyInputStream* input) {\n"
        "  ::mcpack2pb::InputStream mc_stream(input);\n"
        "  const size_t value_size = ::mcpack2pb::unbox(&mc_stream);\n"
        "  if (!value_size) {\n"
        "    LOG(ERROR) << \"Fail to unbox\";\n"
        "    return 0;\n"
        "  }\n"
        "  ::mcpack2pb::UnparsedValue value(::mcpack2pb::FIELD_OBJECT, &mc_stream, value_size);\n"
        "  if (!parse_$vmsg$_body_internal(msg, value)) {\n"
        "    return 0;\n"
        "  }\n"
        "  if (!msg->IsInitialized()) {\n"
        "    LOG(ERROR) << \"Missing required fields: \" << msg->InitializationErrorString();\n"
        "    return 0;\n"
        "  }\n"
        "  return 6/*sizeof(FieldLongHead)*/ + value_size;\n"
        "}\n"
        , "vmsg", var_name
        , "msg", d->full_name());
    return !impl.failed();
}

#define TEMPLATE_SERIALIZE_REPEATED(cit, printer, field, strict_cond, looser_cond) \
    if (strict_cond) {                                                  \
        (printer).Print(                                                \
            "if (msg.$lcfield$_size()) {\n"                             \
            "  if (format == ::mcpack2pb::FORMAT_COMPACK) {\n"             \
            "    serializer.begin_compack_array(\"$field$\", ::mcpack2pb::FIELD_$TYPE$);\n" \
            "  } else {\n"                                              \
            "    serializer.begin_mcpack_array(\"$field$\", ::mcpack2pb::FIELD_$TYPE$);\n" \
            "  }\n"                                                     \
            , "lcfield", (field)->lowercase_name()                      \
            , "field", get_idl_name(field)                              \
            , "TYPE", to_mcpack_typestr_uppercase(cit, (field)));       \
        (printer).Print(                                                \
            "  serializer.add_multiple_$type$(msg.$lcfield$().data(), msg.$lcfield$_size());\n" \
            "  serializer.end_array();\n"                               \
            "}"                                                         \
            , "type", to_mcpack_typestr(cit, (field))                   \
            , "field", get_idl_name(field)                              \
            , "lcfield", (field)->lowercase_name());                    \
        if ((field)->options().GetExtension(idl_on)) {                  \
          (printer).Print(                                              \
            " else {\n"                                                 \
            "  serializer.add_empty_array(\"$field$\");\n"              \
            "}\n"                                                       \
            , "field", get_idl_name(field));                            \
        } else {                                                        \
          (printer).Print("\n");                                        \
        }                                                               \
    } else if (looser_cond) {                                           \
        (printer).Print(                                                \
            "if (msg.$lcfield$_size()) {\n"                             \
            "  if (format == ::mcpack2pb::FORMAT_COMPACK) {\n"             \
            "    serializer.begin_compack_array(\"$field$\", ::mcpack2pb::FIELD_$TYPE$);\n" \
            "  } else {\n"                                              \
            "    serializer.begin_mcpack_array(\"$field$\", ::mcpack2pb::FIELD_$TYPE$);\n" \
            "  }\n"                                                     \
            , "lcfield", (field)->lowercase_name()                      \
            , "field", get_idl_name(field)                              \
            , "TYPE", to_mcpack_typestr_uppercase(cit, (field)));       \
        (printer).Print(                                                \
            "  for (int i = 0; i < msg.$lcfield$_size(); ++i) {\n"      \
            "    serializer.add_$type$(msg.$lcfield$(i));\n"            \
            "  }\n"                                                     \
            "  serializer.end_array();\n"                               \
            "}"                                                         \
            , "type", to_mcpack_typestr(cit, (field))                   \
            , "field", get_idl_name(field)                              \
            , "lcfield", (field)->lowercase_name());                    \
        if ((field)->options().GetExtension(idl_on)) {                  \
          (printer).Print(                                              \
            " else {\n"                                                 \
            "  serializer.add_empty_array(\"$field$\");\n"              \
            "}\n"                                                       \
            , "field", get_idl_name(field));                            \
        } else {                                                        \
          (printer).Print("\n");                                        \
        }                                                               \
    } else {                                                            \
        if ((field)->type() == google::protobuf::FieldDescriptor::TYPE_ENUM) { \
            LOG(ERROR) << "Disallow converting " << (field)->full_name() \
                       << " (" << (field)->enum_type()->full_name() << ") to " \
                       << to_mcpack_typestr(cit, (field)) << " (idl)";  \
        } else {                                                        \
            LOG(ERROR) << "Disallow converting " << (field)->full_name() \
                       << " (" << field_to_string(field) << ") to "     \
                       << to_mcpack_typestr(cit, (field)) << " (idl)";  \
        }                                                               \
        return false;                                                   \
    }                                                                   \

#define TEMPLATE_SERIALIZE_REPEATED_INTEGRAL(cit, printer, field, strict_cond) \
    TEMPLATE_SERIALIZE_REPEATED(cit, printer, field, ((cit) == IDL_AUTO || (strict_cond)), \
                                is_integral_type(cit))

    
#define TEMPLATE_SERIALIZE(cit, printer, field, cond)                   \
    if (!(cond)) {                                                      \
        if ((field)->type() == google::protobuf::FieldDescriptor::TYPE_ENUM) { \
            LOG(ERROR) << "Disallow converting " << (field)->full_name() \
                       << " (" << (field)->enum_type()->full_name() << ") to " \
                       << to_mcpack_typestr(cit, (field)) << " (idl)";  \
        } else {                                                        \
            LOG(ERROR) << "Disallow converting " << (field)->full_name() \
                       << " (" << field_to_string(field) << ") to "     \
                       << to_mcpack_typestr(cit, (field)) << " (idl)";  \
        }                                                               \
        return false;                                                   \
    }                                                                   \
    (printer).Print(                                                    \
        "if (msg.has_$lcfield$()) {\n"                                  \
        "  serializer.add_$type$(\"$field$\", msg.$lcfield$());\n"      \
        "}"                                                             \
        , "type", to_mcpack_typestr(cit, (field))                       \
        , "field", get_idl_name(field)                                  \
        , "lcfield", (field)->lowercase_name());                        \
        if ((field)->options().GetExtension(idl_on)) {                  \
          (printer).Print(                                              \
            " else {\n"                                                 \
            "  serializer.add_empty_array(\"$field$\");\n"              \
            "}\n"                                                       \
            , "field", get_idl_name(field));                            \
        } 

#define TEMPLATE_SERIALIZE_INTEGRAL(cit, printer, field)                \
    TEMPLATE_SERIALIZE(cit, printer, field,                             \
                       ((cit) == IDL_AUTO || is_integral_type(cit)))


#define TEMPLATE_INNER_SERIALIZE_REPEATED(msg, cit, printer, field,     \
                                          strict_cond, looser_cond)     \
    if ((strict_cond)) {                                                \
        (printer).Print(                                                \
            "if ($msg$.$lcfield$_size()) {\n"                           \
            "  serializer.begin_compack_array(::mcpack2pb::FIELD_$TYPE$);\n" \
            , "msg", msg                                                \
            , "TYPE", to_mcpack_typestr_uppercase(cit, (field))         \
            , "lcfield", (field)->lowercase_name());                    \
        (printer).Print(                                                \
            "  serializer.add_multiple_$type$($msg$.$lcfield$().data(), $msg$.$lcfield$_size());\n" \
            "  serializer.end_array();\n"                               \
            "}"                                                         \
            , "msg", msg                                                \
            , "type", to_mcpack_typestr(cit, (field))                   \
            , "lcfield", (field)->lowercase_name());                    \
        if ((field)->options().GetExtension(idl_on)) {                  \
          (printer).Print(                                              \
            " else {\n"                                                 \
            "  serializer.add_empty_array();\n"                         \
            "}\n");                                                     \
        } else {                                                        \
          (printer).Print(                                              \
            " else {\n"                                                 \
            "  serializer.add_null();\n"                                \
            "}\n");                                                     \
        }                                                               \
    } else if (looser_cond) {                                           \
        (printer).Print(                                                \
            "if ($msg$.$lcfield$_size()) {\n"                           \
            "  serializer.begin_compack_array(::mcpack2pb::FIELD_$TYPE$);\n" \
            , "msg", msg                                                \
            , "TYPE", to_mcpack_typestr_uppercase(cit, (field))         \
            , "lcfield", (field)->lowercase_name());                    \
        (printer).Print(                                                \
            "  for (int j = 0; j < $msg$.$lcfield$_size(); ++j) {\n"    \
            "    serializer.add_$type$($msg$.$lcfield$(j));\n"          \
            "  }\n"                                                     \
            "  serializer.end_array();\n"                               \
            "}"                                                         \
            , "msg", msg                                                \
            , "type", to_mcpack_typestr(cit, (field))                   \
            , "lcfield", (field)->lowercase_name());                    \
        if ((field)->options().GetExtension(idl_on)) {                  \
          (printer).Print(                                              \
            " else {\n"                                                 \
            "  serializer.add_empty_array();\n"                         \
            "}\n");                                                     \
        } else {                                                        \
          (printer).Print(                                              \
            " else {\n"                                                 \
            "  serializer.add_null();\n"                                \
            "}\n");                                                     \
        }                                                               \
    } else {                                                            \
        if ((field)->type() == google::protobuf::FieldDescriptor::TYPE_ENUM) { \
            LOG(ERROR) << "Disallow converting " << (field)->full_name() \
                       << " (" << (field)->enum_type()->full_name() << ") to " \
                       << to_mcpack_typestr(cit, (field)) << " (idl)";  \
        } else {                                                        \
            LOG(ERROR) << "Disallow converting " << (field)->full_name() \
                       << " (" << field_to_string(field) << ") to "     \
                       << to_mcpack_typestr(cit, (field)) << " (idl)";  \
        }                                                               \
        return false;                                                   \
    }                                                                   \

#define TEMPLATE_INNER_SERIALIZE_REPEATED_INTEGRAL(msg, cit, printer, field, strict_cond) \
    TEMPLATE_INNER_SERIALIZE_REPEATED(msg, cit, printer, field, ((cit) == IDL_AUTO || (strict_cond)), \
                                      is_integral_type(cit))

    
#define TEMPLATE_INNER_SERIALIZE(msg, cit, printer, field, cond)        \
    if (!(cond)) {                                                      \
        if ((field)->type() == google::protobuf::FieldDescriptor::TYPE_ENUM) { \
            LOG(ERROR) << "Disallow converting " << (field)->full_name() \
                       << " (" << (field)->enum_type()->full_name() << ") to " \
                       << to_mcpack_typestr(cit, (field)) << " (idl)";  \
        } else {                                                        \
            LOG(ERROR) << "Disallow converting " << (field)->full_name() \
                       << " (" << field_to_string(field) << ") to "     \
                       << to_mcpack_typestr(cit, (field)) << " (idl)";  \
        }                                                               \
        return false;                                                   \
    }                                                                   \
    (printer).Print("if ($msg$.has_$lcfield$()) {\n"                    \
                    "  serializer.add_$type$($msg$.$lcfield$());\n"     \
                    "} else {\n"                                        \
                    "  serializer.add_null();\n"                        \
                    "}\n"                                               \
                    , "msg", msg                                        \
                    , "type", to_mcpack_typestr(cit, (field))           \
                    , "lcfield", (field)->lowercase_name())            

#define TEMPLATE_INNER_SERIALIZE_INTEGRAL(msg, cit, printer, field)     \
    TEMPLATE_INNER_SERIALIZE(msg, cit, printer, field,                  \
                             ((cit) == IDL_AUTO || is_integral_type(cit)))

static bool generate_serializing(const google::protobuf::Descriptor* d,
                                 std::set<std::string> & ref_msgs,
                                 std::set<std::string> & ref_maps,
                                 google::protobuf::io::Printer & impl) {
    std::string var_name = mcpack2pb::to_var_name(butil::EnsureString(d->full_name()));
    std::string cpp_name = mcpack2pb::to_cpp_name(butil::EnsureString(d->full_name()));
    ref_msgs.insert(var_name);
    impl.Print(
        "void serialize_$vmsg$_body(\n"
        "    const ::google::protobuf::Message& msg_base,\n"
        "    ::mcpack2pb::Serializer& serializer,\n"
        "    ::mcpack2pb::SerializationFormat format) {\n"
        "  (void)format;     // suppress compiler warning when it's not used\n"
        , "vmsg", var_name);
    if (d->field_count()) {
        impl.Print(
            "  const $msg$& msg = static_cast<const $msg$&>(msg_base);\n"
            , "msg", cpp_name);
    } else {
        impl.Print("  (void)msg_base;   // ^\n"
                   "  (void)serializer; // ^\n");
    }
    impl.Indent();
    for (int i = 0; i < d->field_count(); ++i) {
        const google::protobuf::FieldDescriptor* f = d->field(i);
        ConvertibleIdlType cit = f->options().GetExtension(idl_type);
        // Print the field as comment.
        std::string comment_template;
        if (cit == IDL_AUTO) {
            butil::string_printf(&comment_template,
                                "// %s $type$ $name$ = $number$;\n",
                                (f->is_repeated() ? "repeated" :
                                 (f->is_required() ? "required" : "optional")));
        } else {
            butil::string_printf(&comment_template,
                                "// %s $type$ $name$ = $number$ [(idl_type)=%s];\n",
                                (f->is_repeated() ? "repeated" :
                                 (f->is_required() ? "required" : "optional")),
                                describe_idl_type(cit));
        }
        impl.Print(comment_template.c_str()
                   , "type", field_to_string(f)
                   , "name", f->name()
                   , "number", butil::string_printf("%d", f->number()));
        if (f->is_repeated()) {
            switch (f->cpp_type()) {
            case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
            case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
            case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
                TEMPLATE_SERIALIZE_REPEATED_INTEGRAL(
                    cit, impl, f, (cit == IDL_INT32 || cit == IDL_UINT32));
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
            case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
                TEMPLATE_SERIALIZE_REPEATED_INTEGRAL(
                    cit, impl, f, (cit == IDL_INT64 || cit == IDL_UINT64));
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
                TEMPLATE_SERIALIZE_REPEATED(
                    cit, impl, f, (cit == IDL_AUTO || cit == IDL_BOOL), false);
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
                TEMPLATE_SERIALIZE_REPEATED(
                    cit, impl, f,
                    (cit == IDL_AUTO || cit == IDL_FLOAT), (cit == IDL_DOUBLE));
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
                TEMPLATE_SERIALIZE_REPEATED(
                    cit, impl, f,
                    (cit == IDL_AUTO || cit == IDL_DOUBLE), (cit == IDL_FLOAT));
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
                if (f->type() == google::protobuf::FieldDescriptor::TYPE_STRING) {
                    TEMPLATE_SERIALIZE_REPEATED(
                        cit, impl, f, false, (cit == IDL_AUTO || cit == IDL_STRING));
                } else if (f->type() == google::protobuf::FieldDescriptor::TYPE_BYTES) {
                    TEMPLATE_SERIALIZE_REPEATED(
                        cit, impl, f, false,
                        (cit == IDL_AUTO || cit == IDL_BINARY || cit == IDL_STRING));
                } else {
                    LOG(ERROR) << "Unknown pb type=" << f->type();
                    return false;
                }
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
                if (cit != IDL_AUTO) {
                    LOG(ERROR) << "Disallow converting " << f->full_name()
                               << " (" << f->message_type()->full_name() << ") to "
                               << to_mcpack_typestr(cit, f) << " (idl)";
                    return false;
                }
                const google::protobuf::Descriptor* msg2 = f->message_type();
                std::string var_name2 = mcpack2pb::to_var_name(butil::EnsureString(msg2->full_name()));
                std::string cpp_name2 = mcpack2pb::to_cpp_name(butil::EnsureString(msg2->full_name()));
                if (is_map_entry(msg2)) {
                    ref_maps.insert(var_name2);
                    impl.Print(
                        "serializer.begin_object(\"$field$\");\n"
                        "for (int i = 0; i < msg.$lcfield$_size(); ++i) {\n"
                        "  const $msg2$& pair = msg.$lcfield$(i);\n"
                        , "field", get_idl_name(f)
                        , "lcfield", f->lowercase_name()
                        , "msg2", cpp_name2);
                    const google::protobuf::FieldDescriptor* value_desc = msg2->field(1);
                    switch (value_desc->cpp_type()) {
                    case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
                        impl.Print("  serializer.add_int32(pair.key(), pair.value());\n");
                        break;
                    case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
                        impl.Print("  serializer.add_uint32(pair.key(), pair.value());\n");
                        break;
                    case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
                        impl.Print("  serializer.add_int32(pair.key(), (int32_t)pair.value());\n");
                        break;
                    case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
                        impl.Print("  serializer.add_int64(pair.key(), pair.value());\n");
                        break;
                    case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
                        impl.Print("  serializer.add_uint64(pair.key(), pair.value());\n");
                        break;
                    case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
                        impl.Print("  serializer.add_bool(pair.key(), pair.value());\n");
                        break;
                    case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
                        impl.Print("  serializer.add_float(pair.key(), pair.value());\n");
                        break;
                    case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
                        impl.Print("  serializer.add_double(pair.key(), pair.value());\n");
                        break;
                    case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
                        impl.Print("  serializer.add_string(pair.key(), pair.value());\n");
                        break;
                    case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
                        std::string var_name3 = mcpack2pb::to_var_name(
                            butil::EnsureString(value_desc->message_type()->full_name()));
                        ref_msgs.insert(var_name3);
                        impl.Print(
                            "  serializer.begin_object(pair.key());\n"
                            "  serialize_$vmsg3$_body(pair.value(), serializer, format);\n"
                            "  serializer.end_object();\n"
                            , "vmsg3", var_name3);
                    } break;
                    } // switch
                    impl.Print(
                        "}\n"
                        "serializer.end_object();\n");
                    break;
                }

                ref_msgs.insert(var_name2);
                impl.Print(
                    "if (format == ::mcpack2pb::FORMAT_MCPACK_V2) {\n"
                    "  if (msg.$lcfield$_size()) {\n"
                    "    serializer.begin_mcpack_array(\"$field$\", ::mcpack2pb::FIELD_OBJECT);\n"
                    "    for (int i = 0; i < msg.$lcfield$_size(); ++i) {\n"
                    "      serializer.begin_object();\n"
                    "      serialize_$vmsg2$_body(msg.$lcfield$(i), serializer, format);\n"
                    "      serializer.end_object();\n"
                    "    }\n"
                    "    serializer.end_array();\n"
                    "  }"
                    , "field", get_idl_name(f)
                    , "lcfield", f->lowercase_name()
                    , "vmsg2", var_name2);
                if (f->options().GetExtension(idl_on)) {
                    impl.Print(
                       " else {\n"
                       "    serializer.add_empty_array(\"$field$\");\n"
                       "  }\n", "field", get_idl_name(f));
                } else {
                    impl.Print("\n");
                }
                impl.Print("} else if (msg.$lcfield$_size()) {\n"
                    , "lcfield", f->lowercase_name());
                impl.Indent();
                impl.Print("serializer.begin_object(\"$field$\");\n"
                           , "field", get_idl_name(f));
                for (int j = 0; j < msg2->field_count(); ++j) {
                    const google::protobuf::FieldDescriptor* f2 = msg2->field(j);
                    ConvertibleIdlType cit2 = f2->options().GetExtension(idl_type);
                    impl.Print(
                        "serializer.begin_mcpack_array(\"$f2$\", ::mcpack2pb::FIELD_$TYPE$);\n"
                        "for (int i = 0; i < msg.$lcfield$_size(); ++i) {\n"
                        , "f2", get_idl_name(f2)
                        , "TYPE", (f2->is_repeated() ? "ARRAY" : to_mcpack_typestr_uppercase(cit2, (f2)))
                        , "lcfield", f->lowercase_name());
                    impl.Indent();
                    if (f2->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) {
                        if (cit2 != IDL_AUTO) {
                            LOG(ERROR) << "Disallow converting " << f2->full_name()
                                       << " (" << f2->message_type()->full_name() << ") to "
                                       << to_mcpack_typestr(cit2, f2) << " (idl)";
                            return false;
                        }
                        std::string var_name3 = mcpack2pb::to_var_name(butil::EnsureString(f2->message_type()->full_name()));
                        ref_msgs.insert(var_name3);
                        if (f2->is_repeated()) {
                            impl.Print(
                                "const int $lcfield2$_size = msg.$lcfield$(i).$lcfield2$_size();\n"
                                "if ($lcfield2$_size) {\n"
                                "  serializer.begin_mcpack_array(::mcpack2pb::FIELD_OBJECT);\n"
                                "  for (int j = 0; j < $lcfield2$_size; ++j) {\n"
                                "    serializer.begin_object();\n"
                                "    serialize_$vmsg3$_body(msg.$lcfield$(i).$lcfield2$(j), serializer, format);\n"
                                "    serializer.end_object();\n"
                                "  }\n"
                                "  serializer.end_array();\n"
                                "}"
                                , "vmsg3", var_name3
                                , "lcfield", f->lowercase_name()
                                , "lcfield2", f2->lowercase_name());
                            if (f2->options().GetExtension(idl_on)) {
                                impl.Print(
                                " else {\n"
                                "  serializer.add_empty_array();\n"
                                "}\n");
                            } else {
                                impl.Print(
                                " else {\n"
                                "  serializer.add_null();\n"
                                "}\n");
                            }
                        } else {
                            impl.Print(
                                "if (msg.$lcfield$(i).has_$lcfield2$()) {\n"
                                "  serializer.begin_object();\n"
                                "  serialize_$vmsg3$_body(msg.$lcfield$(i).$lcfield2$(), serializer, format);\n"
                                "  serializer.end_object();\n"
                                "} else {\n"
                                "  serializer.add_null();\n"
                                "}\n"
                                , "vmsg3", var_name3
                                , "lcfield", f->lowercase_name()
                                , "lcfield2", f2->lowercase_name());
                        }
                    } else if (f2->is_repeated()) {
                        const std::string msgstr = butil::string_printf(
                            "msg.%s(i)", butil::EnsureString(f->lowercase_name()).c_str());
                        switch (f2->cpp_type()) {
                        case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
                        case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
                        case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
                            TEMPLATE_INNER_SERIALIZE_REPEATED_INTEGRAL(
                                msgstr.c_str(), cit2, impl, f2,
                                (cit2 == IDL_INT32 || cit2 == IDL_UINT32));
                            break;
                        case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
                        case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
                            TEMPLATE_INNER_SERIALIZE_REPEATED_INTEGRAL(
                                msgstr.c_str(), cit2, impl, f2,
                                (cit2 == IDL_INT64 || cit2 == IDL_UINT64));
                            break;
                        case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
                            TEMPLATE_INNER_SERIALIZE_REPEATED(
                                msgstr.c_str(), cit2, impl, f2,
                                (cit2 == IDL_AUTO || cit2 == IDL_BOOL), false);
                            break;
                        case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
                            TEMPLATE_INNER_SERIALIZE_REPEATED(
                                msgstr.c_str(), cit2, impl, f2,
                                (cit2 == IDL_AUTO || cit2 == IDL_FLOAT), (cit2 == IDL_DOUBLE));
                            break;
                        case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
                            TEMPLATE_INNER_SERIALIZE_REPEATED(
                                msgstr.c_str(), cit2, impl, f2,
                                (cit2 == IDL_AUTO || cit2 == IDL_DOUBLE), (cit2 == IDL_FLOAT));
                            break;
                        case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
                            if (f2->type() == google::protobuf::FieldDescriptor::TYPE_STRING) {
                                TEMPLATE_INNER_SERIALIZE_REPEATED(
                                    msgstr.c_str(), cit2, impl, f2, false,
                                    (cit2 == IDL_AUTO || cit2 == IDL_STRING));
                            } else if (f2->type() == google::protobuf::FieldDescriptor::TYPE_BYTES) {
                                TEMPLATE_INNER_SERIALIZE_REPEATED(
                                    msgstr.c_str(), cit2, impl, f2, false,
                                    (cit2 == IDL_AUTO || cit2 == IDL_BINARY || cit2 == IDL_STRING));
                            } else {
                                LOG(ERROR) << "Unknown pb type=" << f2->type();
                                return false;
                            }
                            break;
                        case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
                            CHECK(false) << "Impossible";
                            return false;
                        }
                    } else {
                        const std::string msgstr = butil::string_printf(
                            "msg.%s(i)", butil::EnsureString(f->lowercase_name()).c_str());
                        switch (f2->cpp_type()) {
                        case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
                        case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
                        case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
                        case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
                        case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
                            TEMPLATE_INNER_SERIALIZE_INTEGRAL(msgstr.c_str(), cit2, impl, f2);
                            break;
                        case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
                            TEMPLATE_INNER_SERIALIZE(
                                msgstr.c_str(), cit2, impl, f2,
                                (cit2 == IDL_AUTO || cit2 == IDL_BOOL));
                            break;
                        case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
                            TEMPLATE_INNER_SERIALIZE(
                                msgstr.c_str(), cit2, impl, f2,
                                (cit2 == IDL_AUTO || cit2 == IDL_FLOAT));
                            break;
                        case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
                            TEMPLATE_INNER_SERIALIZE(
                                msgstr.c_str(), cit2, impl, f2,
                                (cit2 == IDL_AUTO || cit2 == IDL_FLOAT || cit2 == IDL_DOUBLE));
                            break;
                        case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
                            if (f2->type() == google::protobuf::FieldDescriptor::TYPE_STRING) {
                                TEMPLATE_INNER_SERIALIZE(
                                    msgstr.c_str(), cit2, impl, f2,
                                    (cit2 == IDL_AUTO || cit2 == IDL_STRING));
                            } else if (f2->type() == google::protobuf::FieldDescriptor::TYPE_BYTES) {
                                TEMPLATE_INNER_SERIALIZE(
                                    msgstr.c_str(), cit2, impl, f2,
                                    (cit2 == IDL_AUTO || cit2 == IDL_BINARY || cit2 == IDL_STRING));
                            } else {
                                LOG(ERROR) << "Unknown pb type=" << f2->type();
                                return false;
                            }
                            break;
                        case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
                            LOG(ERROR) << "Impossible";
                            return false;
                        }
                    }
                    impl.Outdent();
                    impl.Print("}\n"
                               "serializer.end_array();\n");
                }
                impl.Print("serializer.end_object_iso();\n");
                impl.Outdent();
                impl.Print("} else {\n"
                           "  serializer.begin_object(\"$field$\");\n"
                           "  serializer.end_object_iso();\n"
                           "}\n"
                           , "field", get_idl_name(f));
            } break;
            } // switch
        } else {
            switch (f->cpp_type()) {
            case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
            case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
            case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
            case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
            case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
                TEMPLATE_SERIALIZE_INTEGRAL(cit, impl, f);
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
                TEMPLATE_SERIALIZE(cit, impl, f,
                                   (cit == IDL_AUTO || cit == IDL_BOOL));
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
                TEMPLATE_SERIALIZE(cit, impl, f,
                                   (cit == IDL_AUTO || cit == IDL_FLOAT));
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
                TEMPLATE_SERIALIZE(
                    cit, impl, f,
                    (cit == IDL_AUTO || cit == IDL_FLOAT || cit == IDL_DOUBLE));
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
                if (f->type() == google::protobuf::FieldDescriptor::TYPE_STRING) {
                    TEMPLATE_SERIALIZE(
                        cit, impl, f, (cit == IDL_AUTO || cit == IDL_STRING));
                } else if (f->type() == google::protobuf::FieldDescriptor::TYPE_BYTES) {
                    TEMPLATE_SERIALIZE(
                        cit, impl, f,
                        (cit == IDL_AUTO || cit == IDL_BINARY || cit == IDL_STRING));
                } else {
                    LOG(ERROR) << "Unknown pb type=" << f->type();
                    return false;
                }
                break;
            case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
                if (cit != IDL_AUTO) {
                    LOG(ERROR) << "Disallow converting " << f->full_name()
                               << " (" << f->message_type()->full_name() << ") to "
                               << to_mcpack_typestr(cit, f) << " (idl)";
                    return false;
                }
                std::string var_name2 = mcpack2pb::to_var_name(butil::EnsureString(f->message_type()->full_name()));
                ref_msgs.insert(var_name2);
                impl.Print("if (msg.has_$lcfield$()) {\n"
                           "  serializer.begin_object(\"$field$\");\n"
                           "  serialize_$vmsg$_body(msg.$lcfield$(), serializer, format);\n"
                           "  serializer.end_object();\n"
                           "}"
                           , "field", get_idl_name(f)
                           , "lcfield", f->lowercase_name()
                           , "vmsg", var_name2);
            } break;
            } // switch
            if (f->is_required()) {
                impl.Print(" else {\n"
                           "  LOG(ERROR) << \"Missing required $field$\";\n"
                           "  return serializer.set_bad();\n"
                           "}\n", "field", f->full_name());
            } else {
                impl.Print("\n");
            }
        } // else
    }
    impl.Outdent();
    impl.Print(
        "}\n"
        "bool serialize_$vmsg$(\n"
        "    const ::google::protobuf::Message& msg_base,\n"
        "    ::google::protobuf::io::ZeroCopyOutputStream* output,\n"
        "    ::mcpack2pb::SerializationFormat format) {\n"
        "  ::mcpack2pb::OutputStream ostream(output);\n"
        "  ::mcpack2pb::Serializer serializer(&ostream);\n"
        "  serializer.begin_object();\n"
        "  serialize_$vmsg$_body(msg_base, serializer, format);\n"
        "  serializer.end_object();\n"
        "  ostream.done();\n"
        "  return serializer.good();\n"
        "}\n"
        , "vmsg", var_name);
    return !impl.failed();
}

static std::string protobuf_style_normalize_filename(const std::string & fname) {
    std::string norm_fname;
    norm_fname.reserve(fname.size() + 10);
    for (size_t i = 0; i < fname.size(); ++i) {
        if (fname[i] == '_' || isdigit(fname[i]) || isalpha(fname[i])) {
            norm_fname.push_back(fname[i]);
        } else {
            char symbol[4];
            snprintf(symbol, sizeof(symbol), "_%02x", (int)fname[i]);
            norm_fname.append(symbol, 3);
        }
    }
    return norm_fname;
}

static bool generate_registration(
    const google::protobuf::FileDescriptor* file,
    google::protobuf::io::Printer & impl) {
    const std::string cpp_ns = to_cpp_name(butil::EnsureString(file->package()));
    std::string norm_fname = protobuf_style_normalize_filename(butil::EnsureString(file->name()));
    impl.Print(
        "\n// register all message handlers\n"
        "struct RegisterMcpackFunctions_$norm_fname$ {\n"
        "  RegisterMcpackFunctions_$norm_fname$() {\n"
        , "norm_fname", norm_fname);
    impl.Indent();
    impl.Indent();
    for (int i = 0; i < file->message_type_count(); ++i) {
        const google::protobuf::Descriptor* d = file->message_type(i);
        std::string var_name = mcpack2pb::to_var_name(butil::EnsureString(d->full_name()));

        impl.Print(
            "\n"
            "g_$vmsg$_fields = new ::mcpack2pb::FieldMap;\n"
            "CHECK_EQ(0, g_$vmsg$_fields->init(std::max($field_count$, 1), 30));\n"
            , "vmsg", var_name
            , "field_count", ::butil::string_printf("%d", d->field_count())); 
        for (int i = 0; i < d->field_count(); ++i) {
            const google::protobuf::FieldDescriptor* f = d->field(i);
            impl.Print("(*g_$vmsg$_fields)[\"$field$\"] = ::set_$vmsg$_$lcfield$;\n"
                       , "vmsg", var_name
                       , "field", get_idl_name(f)
                       , "lcfield", f->lowercase_name());
        }
        impl.Print(
            "::mcpack2pb::MessageHandler $vmsg$_handler = {\n"
            "  parse_$vmsg$,\n"
            "  parse_$vmsg$_body,\n"
            "  serialize_$vmsg$,\n"
            "  serialize_$vmsg$_body\n"
            "};\n"
            "::mcpack2pb::register_message_handler_or_die(\"$fmsg$\", $vmsg$_handler);\n"
            , "fmsg", d->full_name()
            , "vmsg", var_name);
    }
    impl.Outdent();
    impl.Outdent();
    impl.Print("  }\n"
               "} static_init_mcpack_$suffix$;\n"
               , "suffix", norm_fname);
    return !impl.failed();
}

class McpackToProtobuf : public google::protobuf::compiler::CodeGenerator {
public:
    bool Generate(const google::protobuf::FileDescriptor* file,
                  const std::string& parameter,
                  google::protobuf::compiler::GeneratorContext*,
                  std::string* error) const override;
};

bool McpackToProtobuf::Generate(const google::protobuf::FileDescriptor* file,
                                const std::string& /*parameter*/,
                                google::protobuf::compiler::GeneratorContext* ctx,
                                std::string* error) const {
    if (!file->options().GetExtension(idl_support)) {
        // skip the file.
        return true;
    }

    std::string cpp_name = butil::EnsureString(file->name());
    const size_t pos = cpp_name.find_last_of('.');
    if (pos == std::string::npos) {
        ::butil::string_printf(error, "Bad filename=%s", cpp_name.c_str());
        return false;
    }
    cpp_name.resize(pos);
    cpp_name.append(".pb.cc");
    
    google::protobuf::io::Printer inc_printer(
        ctx->OpenForInsert(cpp_name, "includes"), '$');
    inc_printer.Print("#include <butil/logging.h>\n"
                      "#include <mcpack2pb/mcpack2pb.h>\n"
                      "#include <gflags/gflags.h>\n");

    google::protobuf::io::Printer gdecl_printer(
        ctx->OpenForInsert(cpp_name, "includes"), '$');
    google::protobuf::io::Printer gimpl_printer(
        ctx->OpenForInsert(cpp_name, "global_scope"), '$');

    gdecl_printer.Print(
        "\n// ==== declarations generated by brpc/mcpack2pb/protoc-gen-mcpack ====\n"
        "DECLARE_bool(mcpack2pb_absent_field_is_error);\n");

    std::set<std::string> ref_msgs;
    std::set<std::string> ref_maps;
    for (int i = 0; i < file->message_type_count(); ++i) {
        const google::protobuf::Descriptor* d = file->message_type(i);
        if (!generate_parsing(d, ref_msgs, ref_maps, gimpl_printer)) {
            ::butil::string_printf(
                error, "Fail to generate parsing code for %s",
                butil::EnsureString(d->full_name()).c_str());
            return false;
        }
        if (!generate_serializing(d, ref_msgs, ref_maps, gimpl_printer)) {
            ::butil::string_printf(
                error, "Fail to generate serializing code for %s",
                butil::EnsureString(d->full_name()).c_str());
            return false;
        }
        std::string var_name = mcpack2pb::to_var_name(butil::EnsureString(d->full_name()));
        gdecl_printer.Print(
            "::mcpack2pb::FieldMap* g_$vmsg$_fields = NULL;\n"
            , "vmsg", var_name);
    }
    if (!generate_declarations(ref_msgs, ref_maps, gdecl_printer)) {
        ::butil::string_printf(
            error, "Fail to generate declarations for %s",
            cpp_name.c_str());
        return false;        
    }
    if (!generate_registration(file, gimpl_printer)) {
        ::butil::string_printf(
            error, "Fail to generate registration code for %s",
            cpp_name.c_str());
        return false;
    }
    return true;
}
} // namespace mcpack2pb

int main(int argc, char* argv[]) {
    ::mcpack2pb::McpackToProtobuf generator;
    return google::protobuf::compiler::PluginMain(argc, argv, &generator);
}
