require File.expand_path(File.join(File.dirname(__FILE__), "..", "namespace"))
require File.join(File.dirname(__FILE__), "tag")
require "cabin"

class RPM::File::Header
  include Cabin::Inspectable
  attr_reader :tags
  attr_reader :length

  attr_accessor :magic  # 8-byte string magic
  attr_accessor :index_count  # rpmlib calls this field 'il' unhelpfully
  attr_accessor :data_length  # rpmlib calls this field 'dl' unhelpfully

  HEADER_SIGNED_TYPE = 5
  HEADER_MAGIC = "\x8e\xad\xe8\x01\x00\x00\x00\x00".force_encoding("BINARY")

  # magic + index_count + data_length
  HEADER_HEADER_LENGTH = HEADER_MAGIC.length + 4 + 4
  TAG_ENTRY_SIZE = 16 # tag id, type, offset, count == 16 bytes

  def initialize(file)
    @file = file

    @inspectables = [:@length, :@index_count, :@data_length]
    @tags = []
  end

  def read
    # TODO(sissel): update the comments here to reflect learnings about rpm
    # internals
    # At this point assume we've read and consumed the lead and signature.
    #len = @rpm.signature.index_length + @rpm.signature
    #
    # header size is
    #     ( @rpm.signature.index_length * size of a header entry )
    #     + @rpm.signature.data_length
    #
    # header 'entries' are an
    #   int32 (tag id), int32 (tag type), int32  (offset), uint32 (count)
    #
    #       len = sizeof(il) + sizeof(dl) + (il * sizeof(struct entryInfo_s)) + dl;
    # See rpm's header.c, the headerLoad method function for reference.

    # Header always starts with HEADER_MAGIC + index_count(2bytes) +
    # data_length(2bytes)
    data = @file.read(HEADER_HEADER_LENGTH).unpack("a8NN")
    # TODO(sissel): @index_count is really a count, rename?
    @magic, @index_count, @data_length = data
    validate
    
    @index_size = @index_count * TAG_ENTRY_SIZE
    tag_data = @file.read(@index_size)
    data = @file.read(@data_length)

    (0 ... @index_count).each do |i|
      offset = i * TAG_ENTRY_SIZE
      entry_data = tag_data[i * TAG_ENTRY_SIZE, TAG_ENTRY_SIZE]
      entry = entry_data.unpack("NNNN")
      entry << data
      tag = ::RPM::File::Tag.new(*entry)
      @tags << tag
    end # each index

    @length = HEADER_HEADER_LENGTH + @index_size + @data_length
  end # def read

  def write
    raise "not implemented yet"
    # Sort tags by type (integer value)
    # emit all tags in order
    # then emit all data segments in same order
  end # def write

  def validate
    # TODO(sissel): raise real exceptions
    if @magic != ::RPM::File::Header::HEADER_MAGIC
      raise "Header magic did not match; got #{@magic.inspect}, " \
            "expected #{::RPM::File::Header::HEADER_MAGIC.inspect}"
    end

    #if !(0..32).include?(@index_count)
      #raise "Invalid 'index_count' value #{@index_count}, expected to be in range [0..32]"
    #end

    #if !(0..8192).include?(@data_length)
      #raise "Invalid 'data_length' value #{@data_length}, expected to be in range [0..8192]"
    #end
  end # def validate

end # class RPM::File::Header
