// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// Author: tball@google.com (Tom Ball)
//  Based on original Protocol Buffers design by
//  Sanjay Ghemawat, Jeff Dean, Cyrus Najmabadi, and others.

#include <google/protobuf/compiler/j2objc/j2objc_field.h>

#include <google/protobuf/compiler/j2objc/j2objc_helpers.h>

namespace google {
namespace protobuf {
namespace compiler {
namespace j2objc {

namespace {

std::string GetParameterType(const FieldDescriptor* descriptor) {
  switch (GetJavaType(descriptor)) {
    case JAVATYPE_INT:
      return "Int";
    case JAVATYPE_LONG:
      return "Long";
    case JAVATYPE_FLOAT:
      return "Float";
    case JAVATYPE_DOUBLE:
      return "Double";
    case JAVATYPE_BOOLEAN:
      return "Boolean";
    case JAVATYPE_STRING:
      return "NSString";
    case JAVATYPE_BYTES:
      return "ComGoogleProtobufByteString";
    case JAVATYPE_ENUM:
      return ClassName(descriptor->enum_type());
    case JAVATYPE_MESSAGE:
      return ClassName(descriptor->message_type());
  }
}

std::string GetStorageType(const FieldDescriptor* descriptor) {
  switch (GetJavaType(descriptor)) {
    case JAVATYPE_INT:
      return "jint";
    case JAVATYPE_LONG:
      return "jlong";
    case JAVATYPE_FLOAT:
      return "jfloat";
    case JAVATYPE_DOUBLE:
      return "jdouble";
    case JAVATYPE_BOOLEAN:
      return "jboolean";
    case JAVATYPE_STRING:
      return "NSString *";
    case JAVATYPE_BYTES:
      return "ComGoogleProtobufByteString *";
    case JAVATYPE_ENUM:
      return ClassName(descriptor->enum_type()) + " *";
    case JAVATYPE_MESSAGE:
      return ClassName(descriptor->message_type()) + " *";
  }
}

std::string GetNonNullType(const FieldDescriptor* descriptor) {
  switch (GetJavaType(descriptor)) {
    case JAVATYPE_INT:
    case JAVATYPE_LONG:
    case JAVATYPE_FLOAT:
    case JAVATYPE_DOUBLE:
    case JAVATYPE_BOOLEAN:
      return GetStorageType(descriptor);
    case JAVATYPE_STRING:
    case JAVATYPE_BYTES:
    case JAVATYPE_ENUM:
    case JAVATYPE_MESSAGE:
      return GetStorageType(descriptor) + " _Nonnull";
  }
}

std::string GetFieldName(const FieldDescriptor* descriptor) {
  if (descriptor->type() == FieldDescriptor::TYPE_GROUP) {
    return descriptor->message_type()->name();
  } else {
    return descriptor->name();
  }
}

std::string GetListType(const FieldDescriptor* descriptor) {
  if (GetJavaType(descriptor) == JAVATYPE_STRING) {
    return "ComGoogleProtobufProtocolStringList";
  }
  return "JavaUtilList";
}

void SetCommonFieldVariables(const FieldDescriptor* descriptor,
                             std::map<std::string, std::string>* variables) {
  (*variables)["classname"] = ClassName(descriptor->containing_type());
  (*variables)["camelcase_name"] = UnderscoresToCamelCase(descriptor);
  (*variables)["capitalized_name"] =
      UnderscoresToCapitalizedCamelCase(descriptor);
  (*variables)["field_number"] = SimpleItoa(descriptor->number());
  (*variables)["constant_name"] = FieldConstantName(descriptor);
  (*variables)["parameter_type"] = GetParameterType(descriptor);
  (*variables)["storage_type"] = GetStorageType(descriptor);
  (*variables)["nonnull_type"] = GetNonNullType(descriptor);
  (*variables)["field_name"] = GetFieldName(descriptor);
  (*variables)["flags"] = GetFieldFlags(descriptor);
  (*variables)["field_type"] = GetFieldTypeEnumValue(descriptor);
  (*variables)["default_value_type"] = GetDefaultValueTypeName(descriptor);
  (*variables)["default_value"] = DefaultValue(descriptor);
  (*variables)["has_bit_index"] = "0";
  (*variables)["options_data"] = GetFieldOptionsData(descriptor);
  (*variables)["list_type"] = GetListType(descriptor);
}

void CollectForwardDeclarationsForFieldType(std::set<std::string>* declarations,
                                            const FieldDescriptor* descriptor,
                                            bool includeBuilder) {
  JavaType type = GetJavaType(descriptor);
  if (type == JAVATYPE_BYTES) {
    declarations->insert("@class ComGoogleProtobufByteString");
  } else if (type == JAVATYPE_ENUM) {
    declarations->insert("@class " + ClassName(descriptor->enum_type()));
    declarations->insert("@class ComGoogleProtobufDescriptors_EnumDescriptor");
    declarations->insert("J2OBJC_CLASS_DECLARATION(" +
                         ClassName(descriptor->enum_type()) + ")");
    declarations->insert(
        "FOUNDATION_EXPORT "
        "ComGoogleProtobufDescriptors_EnumDescriptor * _Nonnull " +
        ClassName(descriptor->enum_type()) + "_descriptor_");
  } else if (type == JAVATYPE_MESSAGE) {
    std::string classname = ClassName(descriptor->message_type());
    declarations->insert("@class " + classname);
    declarations->insert("J2OBJC_CLASS_DECLARATION(" + classname + ")");
    if (includeBuilder) {
      declarations->insert("@class " + classname + "_Builder");
      declarations->insert("J2OBJC_CLASS_DECLARATION(" + classname +
                           "_Builder)");
    }
  }
}
}  // namespace

void CollectSourceImportsForField(std::set<std::string>* imports,
                                  const FieldDescriptor* descriptor) {
  // Enums and messages have their Class referenced in the field metadata.
  switch (GetJavaType(descriptor)) {
    case JAVATYPE_ENUM:
      imports->insert(GetHeader(descriptor->enum_type()));
      break;
    case JAVATYPE_MESSAGE:
      imports->insert(GetHeader(descriptor->message_type()));
      break;
    default:
      // add nothing.
      break;
  }
}

void GenerateObjcClassRef(
    io::Printer *printer, const FieldDescriptor *descriptor) {
  JavaType type = GetJavaType(descriptor);
  std::string classref;
  if (type == JAVATYPE_ENUM) {
    classref =
        "J2OBJC_CLASS_REFERENCE(" + ClassName(descriptor->enum_type()) + ")";
  } else if (type == JAVATYPE_MESSAGE) {
    classref =
        "J2OBJC_CLASS_REFERENCE(" + ClassName(descriptor->message_type()) + ")";
  } else {
    classref = "NULL";
  }
  printer->Print("  .objcType = $classref$,\n", "classref", classref);
}

FieldGenerator::FieldGenerator(const FieldDescriptor *descriptor)
    : descriptor_(descriptor) {
  SetCommonFieldVariables(descriptor, &variables_);
}

FieldGenerator::~FieldGenerator() {
}

void FieldGenerator::CollectForwardDeclarations(
    std::set<std::string>* declarations) const {
  CollectForwardDeclarationsForFieldType(declarations, descriptor_, true);
}

void FieldGenerator::CollectMessageOrBuilderForwardDeclarations(
    std::set<std::string>* declarations) const {
  CollectForwardDeclarationsForFieldType(declarations, descriptor_, false);
}

void FieldGenerator::CollectSourceImports(
    std::set<std::string>* imports) const {
  // Imports needed for generated metadata of enum and message fields.
  CollectSourceImportsForField(imports, descriptor_);
}

void FieldGenerator::CollectMessageOrBuilderImports(
    std::set<std::string>* imports) const {}

void FieldGenerator::GenerateFieldHeader(io::Printer *printer) const {
  printer->Print(variables_,
      "#define $classname$_$constant_name$ $field_number$\n");
}

void FieldGenerator::GenerateMapEntryFieldData(io::Printer *printer) const {
}

void FieldGenerator::GenerateFieldData(io::Printer *printer) const {
  printer->Print(variables_,
      "{\n"
      "  .name = \"$field_name$\",\n"
      "  .number = $field_number$,\n"
      "  .flags = $flags$,\n"
      "  .type = ComGoogleProtobufDescriptors_FieldDescriptor_Type_Enum_"
          "$field_type$,\n"
      "  .defaultValue.value$default_value_type$ = $default_value$,\n"
      "  .hasBitIndex = $has_bit_index$,\n"
  );
  GenerateFieldDataOffset(printer);
  GenerateClassNameOrMapData(printer);
  GenerateStaticRefs(printer);
  printer->Print(variables_,
      "  .containingType = NULL,\n"  // Used by extensions.
      "  .optionsData = $options_data$,\n"
      "},\n");
}

void FieldGenerator::GenerateFieldDataOffset(io::Printer *printer) const {
  printer->Print(variables_,
      "  .offset = offsetof($classname$_Storage, $camelcase_name$_),\n");
}

void FieldGenerator::GenerateClassNameOrMapData(io::Printer *printer) const {
  GenerateObjcClassRef(printer, descriptor_);
}

void FieldGenerator::GenerateStaticRefs(io::Printer *printer) const {
  JavaType type = GetJavaType(descriptor_);
  std::string staticref;
  if (type == JAVATYPE_MESSAGE) {
    staticref = "&" +
        GetParameterType(descriptor_) + "_descriptor_";
  } else if (type == JAVATYPE_ENUM) {
    staticref = "&" +
        GetParameterType(descriptor_) + "_descriptor_";
  } else {
    staticref = "NULL";
  }
  printer->Print("  .descriptorRef = $staticref$,\n", "staticref", staticref);
}

SingleFieldGenerator::SingleFieldGenerator(
    const FieldDescriptor *descriptor, uint32_t *numHasBits)
  : FieldGenerator(descriptor) {
  if (descriptor->real_containing_oneof() == nullptr) {
    variables_["has_bit_index"] = SimpleItoa((*numHasBits)++);
  }
}

void SingleFieldGenerator::GenerateFieldBuilderHeader(io::Printer* printer)
    const {
  printer->Print(variables_, "\n"
      "- (nonnull $classname$_Builder *)set$capitalized_name$With$parameter_type$:\n"
      "    ($storage_type$)value;\n"
      "- (nonnull $classname$_Builder *)clear$capitalized_name$;\n");
  if (GetJavaType(descriptor_) == JAVATYPE_MESSAGE) {
    printer->Print(variables_,
        "- (nonnull $classname$_Builder*)\n"
        "    set$capitalized_name$With$parameter_type$_Builder:\n"
        "    ($parameter_type$_Builder *)value;\n");
  }
}

void SingleFieldGenerator::GenerateMessageOrBuilderProtocol(io::Printer* printer)
    const {
  printer->Print(variables_,
                 "\n"
                 "- (BOOL)has$capitalized_name$;\n"
                 "- ($nonnull_type$)get$capitalized_name$;\n");
}

void SingleFieldGenerator::GenerateDeclaration(io::Printer* printer) const {
  printer->Print(variables_, "$nonnull_type$ $camelcase_name$_;\n");
}

void RepeatedFieldGenerator::CollectForwardDeclarations(
    std::set<std::string>* declarations) const {
  FieldGenerator::CollectForwardDeclarations(declarations);
  declarations->insert("@protocol JavaLangIterable");
}

void RepeatedFieldGenerator::CollectMessageOrBuilderForwardDeclarations(
    std::set<std::string>* declarations) const {
  FieldGenerator::CollectMessageOrBuilderForwardDeclarations(declarations);
  declarations->insert("@protocol " + GetListType(descriptor_));
}

void RepeatedFieldGenerator::CollectMessageOrBuilderImports(
    std::set<std::string>* imports) const {
  if (GetJavaType(descriptor_) == JAVATYPE_STRING) {
    // When translated against an older Java protobuf runtime, the caller
    // will need the full type info for ProtocolStringList.
    imports->insert("com/google/protobuf/ProtocolStringList.h");
  }
}

void RepeatedFieldGenerator::GenerateFieldBuilderHeader(io::Printer* printer)
    const {
  printer->Print(variables_, "\n"
      "- (nonnull $classname$_Builder *)set$capitalized_name$WithInt:(int)index\n"
      "    with$parameter_type$:($storage_type$)value;\n"
      "- (nonnull $classname$_Builder *)add$capitalized_name$With$parameter_type$:\n"
      "    ($storage_type$)value;\n"
      "- (nonnull $classname$_Builder *)addAll$capitalized_name$WithJavaLangIterable:\n"
      "    (id<JavaLangIterable>)values;\n"
      "- (nonnull $classname$_Builder *)clear$capitalized_name$;\n"
  );
  if (GetJavaType(descriptor_) == JAVATYPE_MESSAGE) {
    printer->Print(variables_,
        "- (nonnull $classname$_Builder*)\n"
        "    add$capitalized_name$With$parameter_type$_Builder:\n"
        "    ($parameter_type$_Builder *)value;\n"
        "- (nonnull $classname$_Builder *)remove$capitalized_name$WithInt:(int)index;\n"
    );
  }
}

void RepeatedFieldGenerator::GenerateMessageOrBuilderProtocol(
    io::Printer* printer) const {
  printer->Print(
      variables_,
      "\n"
      "- (jint)get$capitalized_name$Count;\n"
      "- (id<$list_type$>)get$capitalized_name$List;\n"
      "- ($nonnull_type$)get$capitalized_name$WithInt:(int)index;\n");
}

void RepeatedFieldGenerator::GenerateDeclaration(io::Printer* printer) const {
  printer->Print(variables_, "CGPRepeatedField $camelcase_name$_;\n");
}

MapFieldGenerator::MapFieldGenerator(
    const FieldDescriptor *descriptor, uint32_t map_fields_idx)
    : FieldGenerator(descriptor),
    entry_fields_idx_(map_fields_idx * 2) {
  GOOGLE_CHECK_EQ(FieldDescriptor::TYPE_MESSAGE, descriptor->type());
  const Descriptor* entry_message = descriptor->message_type();
  GOOGLE_CHECK(entry_message->options().map_entry());
  key_field_ = entry_message->FindFieldByName("key");
  value_field_ = entry_message->FindFieldByName("value");

  variables_["key_storage_type"] = GetStorageType(key_field_);
  variables_["key_parameter_type"] = GetParameterType(key_field_);
  variables_["key_descriptor_type"] = GetFieldTypeEnumValue(key_field_);
  variables_["value_storage_type"] = GetStorageType(value_field_);
  variables_["value_nonnull_type"] = GetNonNullType(value_field_);
  variables_["value_parameter_type"] = GetParameterType(value_field_);
  variables_["value_descriptor_type"] = GetFieldTypeEnumValue(value_field_);
  variables_["map_entry_fields_idx"] = SimpleItoa(entry_fields_idx_);
}

void MapFieldGenerator::CollectForwardDeclarations(
    std::set<std::string>* declarations) const {}

void MapFieldGenerator::CollectMessageOrBuilderForwardDeclarations(
    std::set<std::string>* declarations) const {
  CollectForwardDeclarationsForFieldType(declarations, value_field_, false);
  declarations->insert("@protocol JavaUtilMap");
}

void MapFieldGenerator::CollectSourceImports(
    std::set<std::string>* imports) const {
  // Don't call super. Map fields are a special case.
  imports->insert("com/google/protobuf/MapField.h");
  CollectSourceImportsForField(imports, key_field_);
  CollectSourceImportsForField(imports, value_field_);
}

void MapFieldGenerator::GenerateFieldBuilderHeader(io::Printer* printer) const {
  printer->Print(variables_,
                 "\n"
                 "- (nonnull $classname$_Builder *)clear$capitalized_name$;\n"
                 "- (nonnull $classname$_Builder *)remove$capitalized_name$With"
                 "$key_parameter_type$:($key_storage_type$)key;\n"
                 "- (nonnull $classname$_Builder "
                 "*)put$capitalized_name$With$key_parameter_type$:"
                 "($key_storage_type$)key with$value_parameter_type$:"
                 "($value_storage_type$)value;\n");
}

void MapFieldGenerator::GenerateMessageOrBuilderProtocol(
    io::Printer* printer) const {
  printer->Print(
      variables_,
      "\n"
      "- (jint)get$capitalized_name$Count;\n"
      "- (jboolean)contains$capitalized_name$With$key_parameter_type$:"
      "($key_storage_type$)key;\n"
      "- (nonnull id<JavaUtilMap>)get$capitalized_name$Map;\n"
      "- ($value_nonnull_type$)get$capitalized_name$OrDefaultWith"
      "$key_parameter_type$:($key_storage_type$)key "
      "with$value_parameter_type$:($value_storage_type$)defaultValue;\n"
      "- ($value_nonnull_type$)get$capitalized_name$OrThrowWith"
      "$key_parameter_type$:($key_storage_type$)key;\n");
}

void MapFieldGenerator::GenerateDeclaration(io::Printer* printer) const {
  printer->Print(variables_, "CGPMapField $camelcase_name$_;\n");
}

void MapFieldGenerator::GenerateMapEntryFieldData(io::Printer *printer) const {
  MapEntryFieldGenerator(key_field_).GenerateFieldData(printer);
  MapEntryFieldGenerator(value_field_).GenerateFieldData(printer);
}

void MapFieldGenerator::GenerateClassNameOrMapData(io::Printer *printer) const {
  printer->Print(variables_,
      "  .mapEntryFields = &mapEntryFields[$map_entry_fields_idx$],\n");
}

void MapEntryFieldGenerator::GenerateFieldDataOffset(io::Printer *printer)
    const {
  printer->Print(variables_, "  .offset = 0,\n");
}

void MapFieldGenerator::GenerateStaticRefs(io::Printer *printer) const {
  printer->Print(variables_, "  .descriptorRef = NULL,\n");
}

void MapEntryFieldGenerator::GenerateFieldBuilderHeader(io::Printer* printer)
    const {
}

void MapEntryFieldGenerator::GenerateMessageOrBuilderProtocol(
    io::Printer* printer) const {
}

void MapEntryFieldGenerator::GenerateDeclaration(io::Printer* printer) const {
}

void MapEntryFieldGenerator::GenerateStaticRefs(io::Printer *printer) const {
  printer->Print(variables_, "  .descriptorRef = NULL,\n");
}

FieldGeneratorMap::FieldGeneratorMap(const Descriptor* descriptor)
  : descriptor_(descriptor),
    field_generators_(
        new std::unique_ptr<FieldGenerator>[descriptor->field_count()]),
    numHasBits_(0),
    numMapFields_(0) {

  // Construct all the FieldGenerators.
  for (int i = 0; i < descriptor->field_count(); i++) {
    field_generators_[i].reset(MakeGenerator(descriptor->field(i)));
  }
}

FieldGenerator* FieldGeneratorMap::MakeGenerator(
    const FieldDescriptor* field) {
  // is_repeated() is also true for map fields so test for maps first.
  if (field->is_map()) {
    return new MapFieldGenerator(field, numMapFields_++);
  } else if (field->is_repeated()) {
    return new RepeatedFieldGenerator(field);
  } else {
    return new SingleFieldGenerator(field, &numHasBits_);
  }
}

FieldGeneratorMap::~FieldGeneratorMap() {
}

const FieldGenerator& FieldGeneratorMap::get(
  const FieldDescriptor* field) const {
    GOOGLE_CHECK_EQ(field->containing_type(), descriptor_);
    return *field_generators_[field->index()];
}

}  // namespace j2objc
}  // namespace compiler
}  // namespace protobuf
}  // namespace google
