# -*- coding: utf-8 -*-
module GitObjectBrowser

  module Models

    # v2
    #   signature  4bytes
    #   version    4bytes
    #   fanout     4bytes * 256
    #   sha1      20bytes * fanout[255]
    #   crc        4bytes * fanout[255]
    #   offset     4bytes * fanout[255]
    class PackIndex < Bindata

      def initialize(input)
        super(input)
      end

      def parse
        parse_fanout

        @entries = []
        @fanout[255].times do |i|
          entry = {}
          entry[:sha1] = hex(20)
          @entries << entry
        end

        @fanout[255].times do |i|
          @entries[i][:crc32] = hex(4)
        end

        @fanout[255].times do |i|
          @entries[i][:offset] = int
        end

        # XXX Doesn't support packfile >= 2 GiB
        # x.times do |i|
        #   puts "offset[#{ i }] = #{ hex(8) }"
        # end

        @packfile_sha1 = hex(20)
        @index_sha1 = hex(20)

        self
      end

      def parse_fanout
        return if @fanout

        seek(0)
        signature = raw(4)
        signature_v2 = [255, 'tOc'].pack('Ca*')

        raise "FIXME" if signature != signature_v2
        @version = int
        raise "FIXME" if @version != 2

        @fanout = []
        256.times do |i|
          @fanout << int
        end
      end

      def find(sha1_hex)
        parse_fanout
        sha1 = [sha1_hex].pack("H*")
        fanout_idx = sha1.unpack("C").first

        lo = fanout_idx == 0 ? 0 : @fanout[fanout_idx - 1]
        hi = @fanout[fanout_idx]

        while lo < hi
          mid = (lo + hi) / 2
          mid_sha1 = get_sha1(mid)
          if mid_sha1 == sha1
            return {
              :sha1   => sha1_hex,
              :crc    => get_crc_hex(mid),
              :offset => get_offset(mid)
            }
          elsif sha1 < mid_sha1
            hi = mid
          else
            lo = mid + 1
          end
        end
        nil
      end

      def get_sha1(pos)
        if @version == 2
          seek(4 + 4 + 4 * 256 + 20 * pos)
        else
          raise "FIXME version 1"
        end
        raw(20)
      end

      def get_crc_hex(pos)
        if @version == 2
          seek(4 + 4 + 4 * 256 + 20 * @fanout[255] + 4 * pos)
        else
          raise "FIXME version 1"
        end
        hex(4)
      end

      def get_offset(pos)
        if @version == 2
          seek(4 + 4 + 4 * 256 + 20 * @fanout[255] + 4 * @fanout[255] + 4 * pos)
        else
          raise "FIXME version 1"
        end
        int
      end

      def self.path?(relpath)
        return relpath =~ %r{\Aobjects/pack/pack-[0-9a-f]{40}.idx\z}
      end

      def to_hash
        return {
          :fanout        => @fanout,
          :entries       => @entries,
          :packfile_sha1 => @packfile_sha1,
          :index_sha1    => @index_sha1,
        }
      end

      def load_object_types(input)
        obj = PackedObject.new(self, input)
        @entries.each do |entry|
          header = obj.parse_header(entry[:offset])
          if header[:type] == 'ofs_delta'
            obj.parse_ofs_delta_header(entry[:offset], header)
          elsif header[:type] == 'ref_delta'
            obj.parse_ref_delta_header(header)
          end
          entry.merge!(header)
        end
      end

    end
  end
end