module DataTemplates

import ..CrystFEL: libcrystfel
export DataTemplate, InternalDataTemplate, loaddatatemplate
export wavelength, cameralength
export translategroup!, rotategroup!
export writedatatemplate!
export resetpaneltotals!, printpaneltotals

# Represents the real C-side (opaque) structure.
mutable struct InternalDataTemplate end

# The Julia-side structure, needed to house the pointer to the C structure
# Without this, we would only ever have a Ptr{DataTemplate}, not a DataTemplate.
mutable struct DataTemplate
    internalptr::Ptr{InternalDataTemplate}
end

"""
    loaddatatemplate(filename)

Creates a CrystFEL DataTemplate by loading a geometry file.

Corresponds to CrystFEL C API function `data_template_new_from_file()`.
"""
function loaddatatemplate(filename::AbstractString)

    out = ccall((:data_template_new_from_file, libcrystfel),
                Ptr{InternalDataTemplate}, (Cstring,), filename)
    if out == C_NULL
        throw(ArgumentError("Failed to load geometry file"))
    end

    dt = DataTemplate(out)

    finalizer(dt) do x
        ccall((:data_template_free, libcrystfel),
              Cvoid, (Ptr{InternalDataTemplate},), x.internalptr)
    end

    return dt
end


"""
    writedatatemplate!(filename)

Writes a CrystFEL DataTemplate to a geometry file.

Corresponds to CrystFEL C API function `data_template_write_to_file()`.
"""
function writedatatemplate!(dtempl::DataTemplate, filename::AbstractString)

    out = ccall((:data_template_write_to_file, libcrystfel),
                Cint, (Ptr{InternalDataTemplate},Cstring),
                dtempl.internalptr, filename)
    if out != 0
        throw(ArgumentError("Failed to write geometry file"))
    end
end

"""
    wavelength(datatemplate)

Retrieves the radiation wavelength from a `DataTemplate`, if possible.

It's not always possible to do this.  Some geometry files declare the
wavelength as a parameter to be retrieved fom each image's metadata.  A
program using this routine should take this possibility into account.

Corresponds to CrystFEL C API function `data_template_get_wavelength_if_possible`.
"""
function wavelength(dtempl::DataTemplate)
    wl = ccall((:data_template_get_wavelength_if_possible, libcrystfel),
               Cdouble, (Ptr{InternalDataTemplate},), dtempl.internalptr)
    if isnan(wl)
        return nothing
    else
        return wl
    end
end


"""
    cameralength(datatemplate)

Retrieves the camera length from a `DataTemplate`, if possible.

It's not always possible to do this.  Some geometry files declare the
detector position(s) as a parameter to be retrieved fom each image's metadata.  A
program using this routine should take this possibility into account.

Corresponds to CrystFEL C API function `data_template_get_clen_if_possible`.
"""
function cameralength(dtempl::DataTemplate)
    clen = ccall((:data_template_get_clen_if_possible, libcrystfel),
                 Cdouble, (Ptr{InternalDataTemplate},), dtempl.internalptr)
    if isnan(clen)
        return nothing
    else
        return clen
    end
end


"""
    translategroup!(datatemplate, groupname, xshift, yshift, zshift)

Modifies `DataTemplate` by moving the specified panel group by the specified
amount (in metres).

Corresponds to CrystFEL C API function `data_template_translate_group_m`.
"""
function translategroup!(dtempl::DataTemplate, groupname, xshift, yshift, zshift)
    r = @ccall libcrystfel.data_template_translate_group_m(dtempl.internalptr::Ptr{InternalDataTemplate},
                                                             groupname::Cstring,
                                                             xshift::Cdouble,
                                                             yshift::Cdouble,
                                                             zshift::Cdouble)::Cint
    if r != 0
        throw(ErrorException("Failed to shift DataTemplate"))
    end

end


"""
    rotategroup!(datatemplate, groupname, angle, axis)

Modifies `DataTemplate` by rotating the specified panel group by the specified
amount (in degrees) about the specified xaxis (:x, :y or :z).

Corresponds to CrystFEL C API function `data_template_rotate_group`.
"""
function rotategroup!(dtempl::DataTemplate, groupname, angle, axis)
    r = @ccall libcrystfel.data_template_rotate_group(dtempl.internalptr::Ptr{InternalDataTemplate},
                                                      groupname::Cstring,
                                                      deg2rad(angle)::Cdouble,
                                                      String(axis)[1]::Cchar)::Cint
    if r != 0
        throw(ErrorException("Failed to rotate DataTemplate"))
    end

end


"""
    resetpaneltotals!(datatemplate)

Corresponds to CrystFEL C API function `data_template_reset_total_movements`.
"""
function resetpaneltotals!(dtempl::DataTemplate)
    @ccall libcrystfel.data_template_reset_total_movements(dtempl.internalptr::Ptr{InternalDataTemplate})::Cvoid
end

"""
    printpaneltotals(datatemplate)

Corresponds to CrystFEL C API function `data_template_print_total_movements`.
"""
function printpaneltotals(dtempl::DataTemplate)
    @ccall libcrystfel.data_template_print_total_movements(dtempl.internalptr::Ptr{InternalDataTemplate})::Cvoid
end

end  # of module
