require "protobuf_descriptor/version"
require "protobuf_descriptor/file_descriptor"
require "protobuf_descriptor/message_descriptor"
require "protobuf_descriptor/named_collection"

require "stringio"
require "protobuf"
require "protobuf/descriptors/google/protobuf/descriptor.pb.rb"

# A wrapper for the
# {+FileDescriptorSet+}[https://code.google.com/p/protobuf/source/browse/trunk/src/google/protobuf/descriptor.proto#49]
# proto. This acts as the root from which name resolution occurs.
class ProtobufDescriptor
  # Decode a ProtobufDescriptor from bytes
  #
  #   ProtobufDescriptor.decode(File.read("descriptor.desc"))
  def self.decode(bytes)
    return self.decode_from(::StringIO.new(bytes))
  end

  # Decode a ProtobufDescriptor from a readable stream
  #
  #   ProtobufDescriptor.decode_from(File.open("descriptor.desc"))
  def self.decode_from(stream)
    return self.new(stream)
  end

  # Loads a ProtobufDescriptor from a file
  #
  #   ProtobufDescriptor.load("descriptor.desc")
  def self.load(path)
    return self.decode_from(File.open(path))
  end

  # Raw FileDescriptorSet protocol buffer
  attr_reader :descriptor_set
  # Set of .proto files that are contained within the descriptor set, as a NamedCollection of FileDescriptors[link:ProtobufDescriptor/FileDescriptor.html]
  attr_reader :file

  def initialize(stream)
    @descriptor_set = Google::Protobuf::FileDescriptorSet.new.decode_from(stream)
    @file = ProtobufDescriptor::NamedCollection.new(@descriptor_set.file.map { |f| ProtobufDescriptor::FileDescriptor.new(self, f) }) do |name, member|
      member.name == name || member.name == "#{name}.proto"
    end
  end

  alias_method :files, :file

  # Returns all the named descendants of this descriptor set, basically every
  # defined MessageDescriptor[link:ProtobufDescriptor/MessageDescriptor.html],
  # EnumDescriptor[link:ProtobufDescriptor/EnumDescriptor.html], and
  # ServiceDescriptor[link:ProtobufDescriptor/ServiceDescriptor] referenced
  # in this set of proto files.
  def all_descendants
    seeds = files.to_a.dup
    children = Set.new
    while !seeds.empty?
      seeds.pop.children.each do |child|
        children << child

        seeds << child if child.respond_to?(:children)
      end
    end
    children
  end

  # Finds the descriptor corresponding to a given type name. +type_name+ can
  # either be a fully qualified name (with a leading "."), or a relative name,
  # in which case +relative_to+ must either be a descriptor or a fully qualified
  # name that the relative name is resolved relative to.
  def resolve_type_name(type_name, relative_to=nil)
    if type_name.start_with?('.')
      all_descendants.find { |descendant|
        descendant.fully_qualified_name == type_name
      }
    else
      raise "Must provide a relative path!" unless relative_to

      relative_to = relative_to.fully_qualified_name if relative_to.respond_to? :fully_qualified_name
      parents = relative_to.split('.')

      # The first element is the empty string, which is the root.
      while parents.size > 1
        type = resolve_type_name("#{parents.join('.')}.#{type_name}")
        return type if type
        parents.pop
      end
    end
  end

  # Shorthand for accessing files
  def [](index)
    return files[index]
  end
end