require_relative "constants"
require_relative "null_section"

module Elf
  class ObjectFile
    include Constants

    def initialize(target)
      @target = target

      @sections = []
      add_section NullSection.new
    end

    def add_section(section)
      @sections << section
      section.index = @sections.length - 1
    end

    def write(io)
      write_preamble(io)

      sh_offset_pos = io.tell

      write_header(io)

      string_table = write_string_table(io)

      io.write_unsigned_int_16 @sections.length # section header count

      io.write_unsigned_int_16 @sections.length-1 # section name string table index

      section_data = write_sections(io)

      sh_offset = io.tell

      write_section_data(section_data, string_table , io)

      io.seek sh_offset_pos
      io.write_unsigned_int_32 sh_offset
    end

    def write_string_table(io)
      string_table = StringTableSection.new(".shstrtab")
      @sections << string_table
      @sections.each { |section|
        string_table.add_string section.name
      }
      string_table
    end

    def write_section_data(section_data, string_table,io)
      section_data.each { |data|
        section, offset, size = data[:section], data[:offset], data[:size]
        # write header first
        io.write_unsigned_int_32 string_table.index_for(section.name)
        io.write_unsigned_int_32 section.type
        io.write_unsigned_int_32 section.flags
        io.write_unsigned_int_32 section.addr
        if (section.type == SHT_NOBITS)
          raise 'SHT_NOBITS not handled yet'
        elsif (section.type == SHT_NULL)
          io.write_unsigned_int_32 0
          io.write_unsigned_int_32 0
        else
          io.write_unsigned_int_32 offset
          io.write_unsigned_int_32 size
        end
        io.write_unsigned_int_32 section.link
        io.write_unsigned_int_32 section.info
        io.write_unsigned_int_32 section.alignment
        io.write_unsigned_int_32 section.ent_size
      }
    end

    def write_sections(io)
      section_data = []
      @sections.each { |section|
        offset = io.tell
        section.write(io)
        size = io.tell - offset
        section_data << {:section => section, :offset => offset, :size => size}
      }
      section_data
    end

    def write_header(io)
      io.write_unsigned_int_32 0 # section header table offset
      io.write_unsigned_int_32 0 # no flags
      io.write_unsigned_int_16 52 # header length
      io.write_unsigned_int_16 0 # program header length
      io.write_unsigned_int_16 0 # program header count
      io.write_unsigned_int_16 40 # section header length
    end

    def write_preamble(io)
      io << "\x7fELF"
      io.write_unsigned_int_8 @target[0]
      io.write_unsigned_int_8 @target[1]
      io.write_unsigned_int_8 EV_CURRENT
      io.write_unsigned_int_8 @target[2]
      io << "\x00" * 8 # pad
      io.write_unsigned_int_16 ET_REL
      io.write_unsigned_int_16 @target[3]
      io.write_unsigned_int_32 EV_CURRENT
      io.write_unsigned_int_32 0 # entry point
      io.write_unsigned_int_32 0 # no program header table
    end
  end
end