class ProtobufDescriptor
  # Describes a complete .proto file.
  #
  # See {+FileDescriptorProto+}[https://code.google.com/p/protobuf/source/browse/trunk/src/google/protobuf/descriptor.proto#56]
  class FileDescriptor
    include ProtobufDescriptor::HasChildren

    # The parent {ProtobufDescriptor}
    attr_reader :file_descriptor_set

    # The +FileDescriptorProto+ this +FileDescriptor+ is wrapping.
    attr_reader :file_descriptor_proto

    # List of the message types that are defined at the top level of this file,
    # as a NamedCollection of {ProtobufDescriptor::MessageDescriptor}
    attr_reader :message_type

    # List of the enum types that are defined at the top level of this file,
    # as a NamedCollection of {ProtobufDescriptor::EnumDescriptor}
    attr_reader :enum_type

    # List of the services that are defined at the top level of this file, as a
    # NamedCollection of {ProtobufDescriptor::ServiceDescriptor}
    attr_reader :service

    # Field index is hard-coded since these are a bit annoying to grab
    # consistently with the different protocol buffer implementations.
    self.register_children(:message_type, 4)
    self.register_children(:enum_type, 5)
    self.register_children(:service, 6)

    def initialize(file_descriptor_set, file_descriptor_proto) #:nodoc:
      # This is basically a parent pointer.
      @file_descriptor_set = file_descriptor_set
      @file_descriptor_proto = file_descriptor_proto

      @message_type = ProtobufDescriptor::NamedCollection.new(
          file_descriptor_proto.message_type.map { |m|
            ProtobufDescriptor::MessageDescriptor.new(self, m)
          })
      @enum_type = ProtobufDescriptor::NamedCollection.new(
          file_descriptor_proto.enum_type.map { |m|
            ProtobufDescriptor::EnumDescriptor.new(self, m)
          })
      @service = ProtobufDescriptor::NamedCollection.new(
          file_descriptor_proto.service.map { |m|
            ProtobufDescriptor::ServiceDescriptor.new(self, m)
          })
    end

    alias_method :parent, :file_descriptor_set
    alias_method :message_types, :message_type
    alias_method :messages, :message_type
    alias_method :enum_types, :enum_type
    alias_method :enums, :enum_types
    alias_method :services, :service

    # Whether source code info is associated with this descriptor
    def has_source_code_info?
      file_descriptor_proto.has_field?(:source_code_info)
    end

    def source_code_info
      file_descriptor_proto.source_code_info
    end

    # Filename relative to root of source tree.
    def name
      file_descriptor_proto.name
    end

    # The package name defined by the .proto file. E.G. "foo", "foo.bar", etc.
    def package
      file_descriptor_proto.package
    end

    # The Java package where classes generated from this .proto will be
    # placed. By default, the proto package is used, but this is often
    # inappropriate because proto packages do not normally start with backwards
    # domain names.
    def java_package
      if file_descriptor_proto.has_field?(:options) && present?(file_descriptor_proto.options.java_package)
        return file_descriptor_proto.options.java_package
      else
        return file_descriptor_proto.package
      end
    end

    # If set, all the classes from the .proto file are wrapped in a single
    # outer class with the given name. This applies to both Proto1
    # (equivalent to the old "--one_java_file" option) and Proto2 (where
    # a .proto always translates to a single class, but you may want to
    # explicitly choose the class name).
    def java_outer_classname
      if file_descriptor_proto.has_field?(:options) && present?(file_descriptor_proto.options.java_multiple_files)
        return nil
      elsif file_descriptor_proto.has_field?(:options) && present?(file_descriptor_proto.options.java_outer_classname)
        return file_descriptor_proto.options.java_outer_classname
      else
        basename = name.split('/').last
        basename = basename.gsub('.proto', '')
        return camelize(basename)
      end
    end

    # Returns the fully qualified name, as used by the +resolve_type+ methods.
    def fully_qualified_name
      return ".#{self.package}"
    end

    # Returns the fully qualified Java class name.
    def fully_qualified_java_name
      return [java_package, java_outer_classname].compact.join('.')
    end

    # Returns the fully qualified Java name as Wire would generate it. Wire
    # never wraps the definitions in a class named after the .proto file
    # (essentially behaving as if +java_outer_classname+ were always true)
    def fully_qualified_wire_name
      return java_package
    end

    # Returns the fully qualified Ruby class name as generated by various
    # protobuf gems.
    def fully_qualified_ruby_name
      return "::#{self.package.gsub('.', '::')}"
    end

    private
    # Copy over a bunch of methods that are normally part of active_support:
    def present?(string)
      return !blank?(string)
    end

    def blank?(string)
      string.respond_to?(:empty?) ? !!string.empty? : !string
    end

    def camelize(string)
      if string.respond_to?(:camelize)
        return string.camelize
      else
        return string.split("_").map { |s| s.capitalize }.join("")
      end
    end
  end
end