# Utilities for extracting information from relocation entries
r_sym(rel::Union(ELF32.Rel,ELF32.Rela)) = rel.r_info >> 8
r_type(rel::Union(ELF32.Rel,ELF32.Rela)) = rel.r_info % UInt8
r_sym(rel::Union(ELF64.Rel,ELF64.Rela)) = rel.r_info >> 32
r_type(rel::Union(ELF64.Rel,ELF64.Rela)) = rel.r_info % Uint32
addend(rel::Union(ELF32.Rela,ELF64.Rela)) = rel.r_addend
addend(rel::Union(ELF32.Rel,ELF64.Rel)) = 0

r_sym(x) = r_sym(deref(x))
r_type(x) = r_type(deref(x))
addend(x) = addend(deref(x))

# The relocation to apply
immutable RelocToApply
    value::UInt64
    size::Uint8
end

function compute_value(h, symbols, reloc)
    # Here one could to external symbol lookups, etc, but I'm not interested
    deref(symbols[r_sym(reloc) + 1]).st_value
end

# Apply relocations in `buffer`. `h` should be the buffer being relocated
function relocate!(buffer, h; LOI = nothing)
    sects = Sections(h)
    rel_sects = filter(sects) do sec
        sht = deref(sec).sh_type
        sht == SHT_RELA || sht == SHT_REL
    end
    for sec in rel_sects
        symbols = Symbols(link_sec(sec))
        relocated_sec = info_sec(sec)
        for reloc in Relocations(sec)
            if r_type(reloc) == 0
                continue
            end
            Value = compute_value(h, symbols, reloc)
            if LOI === nothing
                SectionLoadAddress = deref(relocated_sec).sh_addr
            else
                SectionLoadAddress = getSectionLoadAddress(LOI,relocated_sec)
            end
            rta = compute_relocation(h, reloc, SectionLoadAddress, Value)
            seek(buffer, deref(relocated_sec).sh_offset + deref(reloc).r_offset)
            write(buffer, rta.size == 8 ? rta.value :
                rta.size == 4 ? convert(UInt32,rta.value) :
                rta.size == 2 ? convert(UInt16,rta.value) :
                rta.size == 1 ? convert(UInt8,rta.value) :
                error("Unsupported Relocation Size"))
        end
    end
end

function compute_relocation(h, reloc, SectionLoadAddress, Value)
    if h.file.header.e_machine == EM_X86_64
        return compute_X86_64(reloc, SectionLoadAddress, Value)
    else
        error("Unknown Machine!")
    end
end

function compute_X86_64(reloc, SectionLoadAddress, Value)
    # Any error generated by the conversions below indicates a bad
    # value (the conversions are checked)
    kind = r_type(reloc)
    Offset = deref(reloc).r_offset
    Value += addend(reloc)
    if kind == R_X86_64_64
        return RelocToApply(Value, 8)
    elseif kind == R_X86_64_32
        return RelocToApply(convert(Uint32,Value), 4)
    elseif kind == R_X86_64_32S
        return RelocToApply(convert(Int32,reinterpret(Int,Value)), 4)
    else
        PCOffset = Value - (SectionLoadAddress + Offset)
        if kind == R_X86_64_PC64
            return RelocToApply(PCOffset, 8)
        elseif kind == R_X86_64_PC32
            return RelocToApply(convert(Int32,PCOffset), 4)
        else
            error("Unknown relocation ($(R_X86_64[kind])")
        end
    end
end
