abstract type ELFRel{H <: ELFHandle} end
abstract type ELFRela{H <: ELFHandle} end

@io struct ELFRel32{H <: ELFHandle} <: ELFRel{H}
    r_offset::UInt32
    r_info::UInt32
end

@io struct ELFRela32{H <: ELFHandle} <: ELFRela{H}
    r_offset::UInt32
    r_info::UInt32
    r_addend::Int32
end

@io struct ELFRel64{H <: ELFHandle} <: ELFRel{H}
    r_offset::UInt64
    r_info::UInt64
end

@io struct ELFRela64{H <: ELFHandle} <: ELFRela{H}
    r_offset::UInt64
    r_info::UInt64
    r_addend::Int64
end


# Access to relocations
struct Relocations{T <: ELFRel, S <: SectionRef}
    sec::S
end
function Relocations(sec::SectionRef)
    is64 = isa(sec.handle.file,ELF64.File)
    isRela = sec.header.sh_type == SHT_RELA
    Relocations{is64 ? (isRela ? ELF64.Rela : ELF64.Rel) : (isRela ? ELF32.Rela : ELF32.Rel),typeof(sec)}(sec)
end

struct RelocationRef{T <: ELFRel} <: ObjFileBase.RelocationRef{ELFHandle}
    h::ELFHandle
    reloc::T
end

deref(x::RelocationRef) = x.reloc

entrysize{T}(s::Relocations{T}) = sizeof(T)
lastindex{T}(s::Relocations{T}) = div(s.sec.header.sh_size,entrysize(s))
length(r::Relocations) = lastindex(r)
function getindex{T}(s::Relocations{T},n)
    if n < 1 || n > length(s)
        throw(BoundsError())
    end
    offset = sectionoffset(s.sec) + (n-1)*entrysize(s)
    seek(s.sec.handle,offset)
    RelocationRef{T}(s.sec.handle,unpack(s.sec.handle, T))
end

keys(s::Relocations) = 1:length(s)
iterate(s::Relocations, idx=1) = idx > length(s) ? nothing : (s[idx], idx+1)

# 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
struct RelocToApply
    value::UInt64
    size::UInt8
end

function compute_value(h, symbols, reloc, secs)
    # Here one could to external symbol lookups, etc, but I'm not interested
    UInt(symbolvalue(symbols[r_sym(reloc) + 1], secs)), 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, STValue = compute_value(h, symbols, reloc, sects)
            if LOI === nothing
                SectionLoadAddress = deref(relocated_sec).sh_addr
            else
                SectionLoadAddress = getSectionLoadAddress(LOI,relocated_sec)
            end
            rta = compute_relocation(h, reloc, SectionLoadAddress, Value, STValue)
            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, STValue)
    if h.file.header.e_machine == EM_X86_64
        return compute_X86_64(reloc, SectionLoadAddress, Value, STValue)
    else
        error("Unknown Machine!")
    end
end

function compute_X86_64(reloc, SectionLoadAddress, Value, STValue)
    # The distinction between Value and STValue deserves some explanation.
    # The added difficulty stems from the fact that we're also relocating
    # relocatable object whose sections need not be contiguous in memory,
    # which is an assumption that is implicitly made in the ELF relocation
    # rules. Instead, we need to confront the possibility that the location
    # at which we're applying the relocation and `Value` are contained in
    # sections with different slides. To counteract this, `Value` is the full
    # load address of the target. It is to be used in computations that demand
    # comparison to other absolute addresses. `STValue` on the other hand is the
    # symbol's `st_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
    Addend = addend(reloc)
    Value += Addend
    STValue += Addend
    if kind == R_X86_64_64
        return RelocToApply(Value, 8)
    elseif kind == R_X86_64_32
        return RelocToApply(convert(UInt32,STValue), 4)
    elseif kind == R_X86_64_32S
        return RelocToApply(convert(Int32,reinterpret(Int,STValue)), 4)
    else
        PCOffset = Value - UInt(SectionLoadAddress + Offset)
        if kind == R_X86_64_PC64
            return RelocToApply(PCOffset, 8)
        elseif kind == R_X86_64_PC32
            return RelocToApply(convert(Int32,PCOffset), 4)
        elseif kind == R_X86_64_GLOB_DAT || kind == R_X86_64_JUMP_SLOT
            return RelocToApply(Value, 8)
        elseif kind == R_X86_64_RELATIVE
            return RelocToApply(Addend #= + LoadAddress =#, 8) 
        else
            error("Unknown relocation ($(R_X86_64[kind]))")
        end
    end
end
