text:
"Return the layer name."
getname(layer::FeatureLayer) = GDAL.getname(layer.ptr)

"Return the layer geometry type."
getgeomtype(layer::FeatureLayer) =
    OGRwkbGeometryType(GDAL.getgeomtype(layer.ptr))

"Returns the current spatial filter for this layer."
getspatialfilter(layer::FeatureLayer) =
    Geometry(GDAL.C.OGR_L_GetSpatialFilter(Ptr{Void}(layer.ptr)))

"""
Fetch the spatial reference system for this layer.

The returned object is owned by the OGRLayer and should not be modified or
freed by the application.
"""
getspatialref(layer::FeatureLayer) = SpatialRef(GDAL.getspatialref(layer.ptr))

"""
Set a new spatial filter for the layer, using the geom.

This method set the geometry to be used as a spatial filter when fetching
features via the GetNextFeature() method. Only features that geometrically
intersect the filter geometry will be returned.

Currently this test may be inaccurately implemented, but it is guaranteed
that all features who's envelope (as returned by OGRGeometry::getEnvelope())
overlaps the envelope of the spatial filter will be returned. This can result
in more shapes being returned that should strictly be the case.

This method makes an internal copy of the passed geometry. The passed geometry
remains the responsibility of the caller, and may be safely destroyed.

### Parameters
* `layer`  handle to the layer on which to set the spatial filter.
* `geom`   handle to the geometry to use as a filtering region. NULL may be
           passed indicating that the current spatial filter should be cleared,
           but no new one instituted.

### Additional Remarks
For the time being the passed filter geometry should be in the same SRS as the
geometry field definition it corresponds to (as returned by
`GetLayerDefn()->OGRFeatureDefn::GetGeomFieldDefn(i)->GetSpatialRef()`).
In the future this may be generalized.

Note that only the last spatial filter set is applied, even if several
successive calls are done with different iGeomField values.
"""
setspatialfilter!(layer::FeatureLayer, geom::Geometry) =
    (GDAL.setspatialfilter(layer.ptr, geom.ptr); layer)

clearspatialfilter!(layer::FeatureLayer) =
    (GDAL.setspatialfilter(layer.ptr, GDALGeometry(C_NULL)); layer)

"""
Set a new rectangular spatial filter for the layer.

This method set rectangle to be used as a spatial filter when fetching features
via the GetNextFeature() method. Only features that geometrically intersect the
given rectangle will be returned.

The x/y values should be in the same coordinate system as the layer as a whole
(as returned by OGRLayer::GetSpatialRef()). Internally this method is normally
implemented as creating a 5 vertex closed rectangular polygon and passing it to
OGRLayer::SetSpatialFilter(). It exists as a convenience.

The only way to clear a spatial filter set with this method is to call
`OGRLayer::SetSpatialFilter(NULL)`.
"""
function setspatialfilter!(
        layer::FeatureLayer,
        xmin::Real,
        ymin::Real,
        xmax::Real,
        ymax::Real
    )
    GDAL.setspatialfilterrect(layer.ptr, xmin, ymin, xmax, ymax)
    layer
end

"""
Set a new spatial filter.

This method set the geometry to be used as a spatial filter when fetching
features via the GetNextFeature() method. Only features that geometrically
intersect the filter geometry will be returned.

Currently this test is may be inaccurately implemented, but it is guaranteed
that all features who's envelope (as returned by OGRGeometry::getEnvelope())
overlaps the envelope of the spatial filter will be returned. This can result
in more shapes being returned that should strictly be the case.

This method makes an internal copy of the passed geometry. The passed geometry
remains the responsibility of the caller, and may be safely destroyed.

For the time being the passed filter geometry should be in the same SRS as the
layer (as returned by OGRLayer::GetSpatialRef()). In the future this may be
generalized.

### Parameters
* `layer`: the layer on which to set the spatial filter.
* `i`: index of the geometry field on which the spatial filter operates.
* `geom`: the geometry to use as a filtering region. NULL may be passed
    indicating that the current spatial filter should be cleared, but
    no new one instituted.
"""
setspatialfilter!(layer::FeatureLayer, i::Integer, geom::Geometry) =
    (GDAL.setspatialfilterex(layer.ptr, i, geom.ptr); layer)

clearspatialfilter!(layer::FeatureLayer, i::Integer) =
    (GDAL.setspatialfilterex(layer.ptr, i, GDALGeometry(C_NULL)); layer)

"""
Set a new rectangular spatial filter.
### Parameters
* `layer`: handle to the layer on which to set the spatial filter.
* `i`: index of the geometry field on which the spatial filter operates.
* `xmin`: the minimum X coordinate for the rectangular region.
* `ymin`: the minimum Y coordinate for the rectangular region.
* `xmax`: the maximum X coordinate for the rectangular region.
* `ymax`: the maximum Y coordinate for the rectangular region.
"""
function setspatialfilter!(
        layer::FeatureLayer,
        i::Integer,
        xmin::Real,
        ymin::Real,
        xmax::Real,
        ymax::Real
    )
    GDAL.setspatialfilterrectex(layer.ptr, i, xmin, ymin, xmax, ymax)
    layer
end

"""
Set a new attribute query.

This method sets the attribute query string to be used when fetching features
via the GetNextFeature() method. Only features for which the query evaluates as
true will be returned.

The query string should be in the format of an SQL WHERE clause. For instance
`"population > 1000000 and population < 5000000"` where population is an
attribute in the layer. The query format is normally a restricted form of
SQL WHERE clause as described in the "WHERE" section of the OGR SQL tutorial.
In some cases (RDBMS backed drivers) the native capabilities of the database may
be used to interpret the WHERE clause in which case the capabilities will be
broader than those of OGR SQL.

Note that installing a query string will generally result in resetting the
current reading position (ala ResetReading()).

### Parameters
* `layer`: handle to the layer on which attribute query will be executed.
* `query`: query in restricted SQL WHERE format, or NULL to clear the
    current query.
"""
function setattributefilter!(layer::FeatureLayer, query::AbstractString)
    result = GDAL.setattributefilter(layer.ptr, query)
    @ogrerr result """Failed to set a new attribute query. The query expression
    might be in error."""
    layer
end

function clearattributefilter!(layer::FeatureLayer)
    result = GDAL.setattributefilter(layer.ptr, Ptr{UInt8}(C_NULL))
    @ogrerr result """OGRErr $result: Failed to clear attribute query."""
    layer
end

"""
Reset feature reading to start on the first feature.

This affects `GetNextFeature()`.
"""
resetreading!(layer::FeatureLayer) = (GDAL.resetreading(layer.ptr); layer)

"""
Fetch the next available feature from this layer.

The returned feature becomes the responsibility of the caller to delete with
`DestroyFeature()`. It is critical that all features associated with an OGRLayer
(more specifically an OGRFeatureDefn) be deleted before that layer/datasource is
deleted.

Only features matching the current spatial filter (set with SetSpatialFilter())
will be returned.

This method implements sequential access to the features of a layer. The
`ResetReading()` method can be used to start at the beginning again.

Features returned by `GetNextFeature()` may or may not be affected by concurrent
modifications depending on drivers. A guaranteed way of seeing modifications in
effect is to call ResetReading() on layers where `GetNextFeature()` has been
called, before reading again. Structural changes in layers (field addition,
deletion, ...) when a read is in progress may or may not be possible depending
on drivers. If a transaction is committed/aborted, the current sequential
reading may or may not be valid after that operation and a call to
`ResetReading()` might be needed.
"""
unsafe_nextfeature(layer::FeatureLayer) =
    Feature(GDAL.getnextfeature(layer.ptr))

"""
Move read cursor to the `i`-th feature in the current resultset.

This method allows positioning of a layer such that the GetNextFeature() call
will read the requested feature, where `i` is an absolute index into the
current result set. So, setting it to 3 would mean the next feature read with
`GetNextFeature()` would have been the 4th feature to have been read if
sequential reading took place from the beginning of the layer, including
accounting for spatial and attribute filters.

Only in rare circumstances is `SetNextByIndex()` efficiently implemented. In all
other cases the default implementation which calls `ResetReading()` and then
calls `GetNextFeature()` `i` times is used. To determine if fast seeking is
available on the current layer use the `TestCapability()` method with a value of
`OLCFastSetNextByIndex`.

### Parameters
* `layer`: handle to the layer
* `i`: the index indicating how many steps into the result set to seek.
"""
function setnextbyindex!(layer::FeatureLayer, i::Integer)
    result = @gdal(OGR_L_SetNextByIndex::GDAL.OGRErr,
        layer.ptr::GDALFeatureLayer,
        i::GDAL.GIntBig
    )
    @ogrerr result "Failed to move the cursor to index $i"
    layer
end

"""
Return a feature (now owned by the caller) by its identifier or NULL on failure.

This function will attempt to read the identified feature. The nFID value cannot
be OGRNullFID. Success or failure of this operation is unaffected by the spatial
or attribute filters (and specialized implementations in drivers should make
sure that they do not take into account spatial or attribute filters).

If this function returns a non-NULL feature, it is guaranteed that its feature
id (OGR_F_GetFID()) will be the same as nFID.

Use OGR_L_TestCapability(OLCRandomRead) to establish if this layer supports
efficient random access reading via OGR_L_GetFeature(); however, the call should
always work if the feature exists as a fallback implementation just scans all
the features in the layer looking for the desired feature.

Sequential reads (with OGR_L_GetNextFeature()) are generally considered
interrupted by a OGR_L_GetFeature() call.

The returned feature should be free with OGR_F_Destroy().
"""
unsafe_getfeature(layer::FeatureLayer, i::Integer) =
    Feature(GDAL.checknull(@gdal(OGR_L_GetFeature::GDALFeature,
        layer.ptr::GDALFeatureLayer,
        i::GDAL.GIntBig
    )))

"""
Rewrite an existing feature.

This function will write a feature to the layer, based on the feature id within
the OGRFeature.

Use OGR_L_TestCapability(OLCRandomWrite) to establish if this layer supports
random access writing via OGR_L_SetFeature().

### Returns
OGRERR_NONE if the operation works, otherwise an appropriate error code
(e.g OGRERR_NON_EXISTING_FEATURE if the feature does not exist).
"""
function setfeature!(layer::FeatureLayer, feature::Feature)
    result = GDAL.setfeature(layer.ptr, feature.ptr)
    @ogrerr result "Failed to set feature."
    layer
end

"""
Create and write a new feature within a layer.

The passed feature is written to the layer as a new feature, rather than
overwriting an existing one. If the feature has a feature id other than
OGRNullFID, then the native implementation may use that as the feature id of
the new feature, but not necessarily. Upon successful return the passed feature
will have been updated with the new feature id.
"""
function createfeature!(layer::FeatureLayer, feature::Feature)
    result = GDAL.createfeature(layer.ptr, feature.ptr)
    @ogrerr result "Failed to create and write feature in layer."
    layer
end

unsafe_createfeature(layer::FeatureLayer) =
    unsafe_createfeature(getlayerdefn(layer))

"""
Delete feature with fid `i` from layer.

The feature with the indicated feature id is deleted from the layer if supported
by the driver. Most drivers do not support feature deletion, and will return
OGRERR_UNSUPPORTED_OPERATION. The OGR_L_TestCapability() function may be called
with OLCDeleteFeature to check if the driver supports feature deletion.

### Returns
OGRERR_NONE if the operation works, otherwise an appropriate error code
(e.g OGRERR_NON_EXISTING_FEATURE if the feature does not exist).
"""
function deletefeature!(layer::FeatureLayer, i::Integer)
    result = GDAL.deletefeature(layer.ptr, i)
    @ogrerr result "OGRErr $result: Failed to delete feature $i"
    layer
end

"""
Fetch the schema information for this layer.

The returned handle to the OGRFeatureDefn is owned by the OGRLayer, and should
not be modified or freed by the application. It encapsulates the attribute
schema of the features of the layer.
"""
getlayerdefn(layer::FeatureLayer) = FeatureDefn(GDAL.getlayerdefn(layer.ptr))

"""
Find the index of field in a layer.

If `exactMatch` is set to `false` and the field doesn't exists in the given form
the driver might apply some changes to make it match, like those it might do if
the layer was created (eg. like `LAUNDER` in the OCI driver).

### Returns
field index, or -1 if the field doesn't exist
"""
findfieldindex(layer::FeatureLayer, field::AbstractString, exactmatch::Bool) =
    GDAL.findfieldindex(layer.ptr, field, exactmatch)

"""
Fetch the feature count in this layer.

### Parameters
* `layer`: handle to the layer that owned the features.
* `force`: Flag indicating whether the count should be computed even if it
    is expensive.

### Returns
feature count, -1 if count not known.
"""
nfeature(layer::FeatureLayer, force::Bool = false) =
    GDAL.getfeaturecount(layer.ptr, force)

"""
Fetch the extent of this layer.

Returns the extent (MBR) of the data in the layer. If `force` is `false`, and it
would be expensive to establish the extent then OGRERR_FAILURE will be returned
indicating that the extent isn't know. If `force` is `true` then some
implementations will actually scan the entire layer once to compute the MBR of
all the features in the layer.

### Parameters
* `layer`: handle to the layer from which to get extent.
* `i`:     (optional) the index of the geometry field to compute the extent.
* `force`: Flag indicating whether the extent should be computed even if it is
            expensive.

### Additional Remarks
Depending on the drivers, the returned extent may or may not take the spatial
filter into account. So it is safer to call GetExtent() without setting a
spatial filter.

Layers without any geometry may return OGRERR_FAILURE just indicating that no
meaningful extents could be collected.

Note that some implementations of this method may alter the read cursor of the
layer.
"""
function getextent(layer::FeatureLayer, i::Integer, force::Bool = false)
    envelope = Ref{GDAL.OGREnvelope}(GDAL.OGREnvelope(0, 0, 0, 0))
    result = GDAL.getextentex(layer.ptr, i, envelope, force)
    @ogrerr result "Extent not known"
    envelope[]
end

function getextent(layer::FeatureLayer, force::Bool = false)
    envelope = Ref{GDAL.OGREnvelope}(GDAL.OGREnvelope(0, 0, 0, 0))
    result = GDAL.getextent(layer.ptr, envelope, force)
    @ogrerr result "Extent not known"
    envelope[]
end

"""
Test if this layer supported the named capability.

### Parameters
* `capability`  the name of the capability to test.

### Returns
TRUE if the layer has the requested capability, or FALSE otherwise.
OGRLayers will return FALSE for any unrecognized capabilities.

### Additional Remarks
The capability codes that can be tested are represented as strings, but
`#defined` constants exists to ensure correct spelling. Specific layer types may
implement class specific capabilities, but this can't generally be discovered by
the caller.

* `OLCRandomRead` / \"RandomRead\": TRUE if the GetFeature() method is
    implemented in an optimized way for this layer, as opposed to the default
    implementation using ResetReading() and GetNextFeature() to find the
    requested feature id.

* `OLCSequentialWrite` / \"SequentialWrite\": TRUE if the CreateFeature() method
    works for this layer. Note this means that this particular layer is
    writable. The same OGRLayer class may returned FALSE for other layer
    instances that are effectively read-only.

* `OLCRandomWrite` / \"RandomWrite\": TRUE if the SetFeature() method is
    operational on this layer. Note this means that this particular layer is
    writable. The same OGRLayer class may returned FALSE for other layer
    instances that are effectively read-only.

* `OLCFastSpatialFilter` / \"FastSpatialFilter\": TRUE if this layer implements
    spatial filtering efficiently. Layers that effectively read all features,
    and test them with the OGRFeature intersection methods should return FALSE.
    This can be used as a clue by the application whether it should build and
    maintain its own spatial index for features in this layer.

* `OLCFastFeatureCount` / \"FastFeatureCount\": TRUE if this layer can return a
    feature count (via GetFeatureCount()) efficiently. i.e. without counting the
    features. In some cases this will return TRUE until a spatial filter is
    installed after which it will return FALSE.

* `OLCFastGetExtent` / \"FastGetExtent\": TRUE if this layer can return its data
    extent (via GetExtent()) efficiently, i.e. without scanning all the
    features. In some cases this will return TRUE until a spatial filter is
    installed after which it will return FALSE.

* `OLCFastSetNextByIndex` / \"FastSetNextByIndex\": TRUE if this layer can
    perform the SetNextByIndex() call efficiently, otherwise FALSE.

* `OLCCreateField` / \"CreateField\": TRUE if this layer can create new fields
    on the current layer using CreateField(), otherwise FALSE.

* `OLCCreateGeomField` / \"CreateGeomField\": (GDAL >= 1.11) TRUE if this layer
    can create new geometry fields on the current layer using CreateGeomField(),
    otherwise FALSE.

* `OLCDeleteField` / \"DeleteField\": TRUE if this layer can delete existing
    fields on the current layer using DeleteField(), otherwise FALSE.

* `OLCReorderFields` / \"ReorderFields\": TRUE if this layer can reorder
    existing fields on the current layer using ReorderField() or
    ReorderFields(), otherwise FALSE.

* `OLCAlterFieldDefn` / \"AlterFieldDefn\": TRUE if this layer can alter the
    definition of an existing field on the current layer using AlterFieldDefn(),
    otherwise FALSE.

* `OLCDeleteFeature` / \"DeleteFeature\": TRUE if the DeleteFeature() method is
    supported on this layer, otherwise FALSE.

* `OLCStringsAsUTF8` / \"StringsAsUTF8\": TRUE if values of OFTString fields are
    assured to be in UTF-8 format. If FALSE the encoding of fields is uncertain,
    though it might still be UTF-8.

* `OLCTransactions` / \"Transactions\": TRUE if the StartTransaction(),
    CommitTransaction() and RollbackTransaction() methods work in a meaningful
    way, otherwise FALSE.

* `OLCIgnoreFields` / \"IgnoreFields\": TRUE if fields, geometry and style will
    be omitted when fetching features as set by SetIgnoredFields() method.

* `OLCCurveGeometries` / \"CurveGeometries\": TRUE if this layer supports
    writing curve geometries or may return such geometries. (GDAL 2.0).
"""
testcapability(layer::FeatureLayer, capability::AbstractString) =
    Bool(GDAL.testcapability(layer.ptr, capability))

function listcapability(
        layer::FeatureLayer,
        capabilities = (GDAL.OLCRandomRead,
                        GDAL.OLCSequentialWrite,
                        GDAL.OLCRandomWrite,
                        GDAL.OLCFastSpatialFilter,
                        GDAL.OLCFastFeatureCount,
                        GDAL.OLCFastGetExtent,
                        GDAL.OLCCreateField,
                        GDAL.OLCDeleteField,
                        GDAL.OLCReorderFields,
                        GDAL.OLCAlterFieldDefn,
                        GDAL.OLCTransactions,
                        GDAL.OLCDeleteFeature,
                        GDAL.OLCFastSetNextByIndex,
                        GDAL.OLCStringsAsUTF8,
                        GDAL.OLCIgnoreFields,
                        GDAL.OLCCreateGeomField,
                        GDAL.OLCCurveGeometries,
                        GDAL.OLCMeasuredGeometries)
    )
    Dict{String, Bool}([
        c => testcapability(layer,c) for c in capabilities
    ])
end

# TODO use syntax below once v0.4 support is dropped (not in Compat.jl)
# listcapability(layer::FeatureLayer) = Dict(
#     c => testcapability(layer,c) for c in
#     (GDAL.OLCRandomRead,        GDAL.OLCSequentialWrite, GDAL.OLCRandomWrite,
#      GDAL.OLCFastSpatialFilter, GDAL.OLCFastFeatureCount,GDAL.OLCFastGetExtent,
#      GDAL.OLCCreateField,       GDAL.OLCDeleteField,     GDAL.OLCReorderFields,
#      GDAL.OLCAlterFieldDefn,    GDAL.OLCTransactions,    GDAL.OLCDeleteFeature,
#      GDAL.OLCFastSetNextByIndex,GDAL.OLCStringsAsUTF8,   GDAL.OLCIgnoreFields,
#      GDAL.OLCCreateGeomField,   GDAL.OLCCurveGeometries,
#      GDAL.OLCMeasuredGeometries)
# )

"""
Create a new field on a layer.

You must use this to create new fields on a real layer. Internally the
OGRFeatureDefn for the layer will be updated to reflect the new field.
Applications should never modify the OGRFeatureDefn used by a layer directly.

This function should not be called while there are feature objects in existence
that were obtained or created with the previous layer definition.

Not all drivers support this function. You can query a layer to check if it
supports it with the OLCCreateField capability. Some drivers may only support
this method while there are still no features in the layer. When it is
supported, the existing features of the backing file/database should be updated
accordingly.

Drivers may or may not support not-null constraints. If they support creating
fields with not-null constraints, this is generally before creating any feature
to the layer.

### Parameters
* `layer`:  the layer to write the field definition.
* `field`:  the field definition to write to disk.
* `approx`: If TRUE, the field may be created in a slightly different form
            depending on the limitations of the format driver.
"""
function createfield!(
        layer::FeatureLayer,
        field::FieldDefn,
        approx::Bool = false
    )
    result = GDAL.createfield(layer.ptr, field.ptr, approx)
    @ogrerr result "Failed to create new field"
    layer
end

"""
Create a new geometry field on a layer.

You must use this to create new geometry fields on a real layer. Internally the
OGRFeatureDefn for the layer will be updated to reflect the new field.
Applications should never modify the OGRFeatureDefn used by a layer directly.

This function should not be called while there are feature objects in existence
that were obtained or created with the previous layer definition.

Not all drivers support this function. You can query a layer to check if it
supports it with the OLCCreateField capability. Some drivers may only support
this method while there are still no features in the layer. When it is
supported, the existing features of the backing file/database should be updated
accordingly.

Drivers may or may not support not-null constraints. If they support creating
fields with not-null constraints, this is generally before creating any feature
to the layer.

### Parameters
* `layer`:  the layer to write the field definition.
* `field`:  the geometry field definition to write to disk.
* `approx`: If TRUE, the field may be created in a slightly different form
            depending on the limitations of the format driver.

### Returns
OGRERR_NONE on success.
"""
function creategeomfield!(
        layer::FeatureLayer,
        field::GeomFieldDefn,
        approx::Bool = false
    )
    result = GDAL.creategeomfield(layer.ptr, field.ptr, approx)
    @ogrerr result "Failed to create new field"
    layer
end

"""
Delete the field at index `i` on a layer.

You must use this to delete existing fields on a real layer. Internally the
OGRFeatureDefn for the layer will be updated to reflect the deleted field.
Applications should never modify the OGRFeatureDefn used by a layer directly.

This function should not be called while there are feature objects in existence
that were obtained or created with the previous layer definition.

Not all drivers support this function. You can query a layer to check if it
supports it with the OLCDeleteField capability. Some drivers may only support
this method while there are still no features in the layer. When it is
supported, the existing features of the backing file/database should be updated
accordingly.

### Parameters
* `layer`: handle to the layer.
* `i`: index of the field to delete.
"""
function deletefield!(layer::FeatureLayer, i::Integer)
    result = GDAL.deletefield(layer.ptr, i)
    @ogrerr result "Failed to delete field $i"
    layer
end

"""
Reorder all the fields of a layer.

You must use this to reorder existing fields on a real layer. Internally the
OGRFeatureDefn for the layer will be updated to reflect the reordering of the
fields. Applications should never modify the OGRFeatureDefn used by a layer
directly.

This method should not be called while there are feature objects in existence
that were obtained or created with the previous layer definition.

panMap is such that,for each field definition at position i after reordering,
its position before reordering was panMap[i].

For example, let suppose the fields were "0","1","2","3","4" initially.
ReorderFields([0,2,3,1,4]) will reorder them as "0","2","3","1","4".

Not all drivers support this method. You can query a layer to check if it
supports it with the OLCReorderFields capability. Some drivers may only support
this method while there are still no features in the layer. When it is
supported, the existing features of the backing file/database should be updated
accordingly.

### Parameters
* `layer`: handle to the layer.
* `indices`: an array of GetLayerDefn()->OGRFeatureDefn::GetFieldCount()
            elements which is a permutation of
                `[0, GetLayerDefn()->OGRFeatureDefn::GetFieldCount()-1]`.
"""
function reorderfields!(layer::FeatureLayer, indices::Vector{Cint})
    result = GDAL.reorderfields(layer.ptr, indices)
    @ogrerr result "Failed to reorder the fields of layer according to $indices"
    layer
end

"""
Reorder an existing field on a layer.

This method is a convenience wrapper of ReorderFields() dedicated to move a
single field. It is a non-virtual method, so drivers should implement
ReorderFields() instead.

You must use this to reorder existing fields on a real layer. Internally the
OGRFeatureDefn for the layer will be updated to reflect the reordering of the
fields. Applications should never modify the OGRFeatureDefn used by a layer
directly.

This method should not be called while there are feature objects in existence
that were obtained or created with the previous layer definition.

The field definition that was at initial position iOldFieldPos will be moved at
position iNewFieldPos, and elements between will be shuffled accordingly.

For example, let suppose the fields were "0","1","2","3","4" initially.
ReorderField(1, 3) will reorder them as "0","2","3","1","4".

Not all drivers support this method. You can query a layer to check if it
supports it with the OLCReorderFields capability. Some drivers may only support
this method while there are still no features in the layer. When it is
supported, the existing features of the backing file/database should be updated
accordingly.

### Parameters
* `layer`: handle to the layer.
* `oldpos`: previous position of the field to move. Must be in the range
            [0,GetFieldCount()-1].
* `newpos`: new position of the field to move. Must be in the range
            [0,GetFieldCount()-1].
"""
function reorderfield!(layer::FeatureLayer, oldpos::Integer, newpos::Integer)
    result = GDAL.reorderfield(layer.ptr, oldpos, newpos)
    @ogrerr result "Failed to reorder field from $oldpos to $newpos."
    layer
end

"""
Alter the definition of an existing field on a layer.

You must use this to alter the definition of an existing field of a real layer.
Internally the OGRFeatureDefn for the layer will be updated to reflect the
altered field. Applications should never modify the OGRFeatureDefn used by a
layer directly.

This method should not be called while there are feature objects in existence
that were obtained or created with the previous layer definition.

Not all drivers support this method. You can query a layer to check if it
supports it with the OLCAlterFieldDefn capability. Some drivers may only
support this method while there are still no features in the layer. When it is
supported, the existing features of the backing file/database should be updated
accordingly. Some drivers might also not support all update flags.

### Parameters
* `layer`:        handle to the layer.
* `i`:            index of the field whose definition must be altered.
* `newfielddefn`: new field definition
* `flags`: combination of ALTER_NAME_FLAG, ALTER_TYPE_FLAG,
            ALTER_WIDTH_PRECISION_FLAG, ALTER_NULLABLE_FLAG and
            ALTER_DEFAULT_FLAG to indicate which of the name and/or type and/or
            width and precision fields and/or nullability from the new field
            definition must be taken into account.
"""
function alterfielddefn!(
        layer::FeatureLayer,
        i::Integer,
        newfielddefn::FieldDefn,
        flags::UInt8
    )
    result = OGR.alterfielddefn(layer.ptr, i, newfielddefn.ptr, flags)
    @ogrerr result "Failed to alter fielddefn of field $i."
    layer
end

"For datasources which support transactions, creates a transaction."
function starttransaction(layer::FeatureLayer)
    result = GDAL.starttransaction(layer.ptr)
    @ogrerr result "Failed to start transaction."
    layer
end

"For datasources which support transactions, commits a transaction."
function committransaction(layer::FeatureLayer)
    result = GDAL.committransaction(layer.ptr)
    @ogrerr result "Failed to commit transaction."
    layer
end

"""
For datasources which support transactions, RollbackTransaction will roll back
a datasource to its state before the start of the current transaction.
"""
function rollbacktransaction(layer::FeatureLayer)
    result = GDAL.rollbacktransaction(layer.ptr)
    @ogrerr result "Failed to rollback transaction."
    layer
end

"""
Increment layer reference count.

### Returns
the reference count after incrementing.
"""
reference(layer::FeatureLayer) = GDAL.reference(layer.ptr)

"""
Decrement layer reference count.

### Returns
the reference count after decrementing.
"""
dereference(layer::FeatureLayer) = GDAL.dereference(layer.ptr)

"the current reference count for the layer object itself."
nreference(layer::FeatureLayer) = GDAL.getrefcount(layer.ptr)

"""
Flush pending changes to disk.

This call is intended to force the layer to flush any pending writes to disk,
and leave the disk file in a consistent state. It would not normally have any
effect on read-only datasources.

Some layers do not implement this method, and will still return OGRERR_NONE.
The default implementation just returns OGRERR_NONE. An error is only returned
if an error occurs while attempting to flush to disk.

In any event, you should always close any opened datasource with
DestroyDataSource() that will ensure all data is correctly flushed.

### Returns
OGRERR_NONE if no error occurs (even if nothing is done) or an error code.
"""
function synctodisk!(layer::FeatureLayer)
    result = GDAL.synctodisk(layer.ptr)
    @ogrerr result "Failed to flush pending changes to disk"
    layer
end

"""
Return the total number of features read.

Warning: not all drivers seem to update this count properly.
"""
getfeaturesread(layer::FeatureLayer) = GDAL.getfeaturesread(layer.ptr)

"""This method returns the name of the underlying database column being used as
the FID column, or \"\" if not supported.
"""
getfidcolname(layer::FeatureLayer) = GDAL.getfidcolumn(layer.ptr)

"""
This method returns the name of the underlying database column being used as
the geometry column, or \"\" if not supported.
"""
getgeomcolname(layer::FeatureLayer) = GDAL.getgeometrycolumn(layer.ptr)

"""
Set which fields can be omitted when retrieving features from the layer.

If the driver supports this functionality (testable using `OLCIgnoreFields`
capability), it will not fetch the specified fields in subsequent calls to
`GetFeature()`/`GetNextFeature()` and thus save some processing time and/or
bandwidth.

Besides field names of the layers, the following special fields can be passed:
`"OGR_GEOMETRY"` to ignore geometry and `"OGR_STYLE"` to ignore layer style.

By default, no fields are ignored.

### Parameters
* `fieldnames`: an array of field names terminated by NULL item. If NULL is
passed, the ignored list is cleared.

### Returns
OGRERR_NONE if all field names have been resolved (even if the driver does not
support this method)
"""
function setignoredfields!(layer::FeatureLayer, fieldnames)
    result = @gdal(OGR_L_SetIgnoredFields::GDAL.OGRErr,
        layer.ptr::GDALFeatureLayer,
        fieldnames::StringList
    )
    @ogrerr result "Failed to set ignored fields $fieldnames."
    layer
end

"""
Intersection of two layers.

The result layer contains features whose geometries represent areas that are
common between features in the input layer and in the method layer. The features
in the result layer have attributes from both input and method layers. The
schema of the result layer can be set by the user or, if it is empty, is
initialized to contain all fields in the input and method layers.

### Parameters
* `input`: the input layer. Should not be NULL.
* `method`: the method layer. Should not be NULL.
* `result`: the layer where the features resulting from the operation
    are inserted. Should not be NULL. See the note about the schema.

### Keyword Arguments
* `options`: NULL terminated list of options (may be NULL).
* `progressfunc`: a GDALProgressFunc() compatible callback function for
    reporting progress or NULL.
* `progressdata`: argument to be passed to pfnProgress. May be NULL.

### Returns
an error code if there was an error or the execution was interrupted,
OGRERR_NONE otherwise.

### Additional Remarks
If the schema of the result is set by user and contains fields that have the
same name as a field in input and in method layer, then the attribute in the
result feature will get the value from the feature of the method layer.

For best performance use the minimum amount of features in the method layer and
copy it into a memory layer. This method relies on GEOS support. Do not use
unless the GEOS support is compiled in. The recognized list of options is:

* `SKIP_FAILURES=YES/NO`. Set it to YES to go on, even when a feature could not
    be inserted or a GEOS call failed.
* `PROMOTE_TO_MULTI=YES/NO`. Set it to YES to convert Polygons into
    MultiPolygons, or LineStrings to MultiLineStrings.
* `INPUT_PREFIX=string`. Set a prefix for the field names that will be created
    from the fields of the input layer.
* `METHOD_PREFIX=string`. Set a prefix for the field names that will be created
    from the fields of the method layer.
* `USE_PREPARED_GEOMETRIES=YES/NO`. Set to NO to not use prepared geometries to
    pretest intersection of features of method layer with features of this layer
* `PRETEST_CONTAINMENT=YES/NO`. Set to YES to pretest the containment of
    features of method layer within the features of this layer. This will speed
    up the method significantly in some cases. Requires that the prepared
    geometries are in effect.
* `KEEP_LOWER_DIMENSION_GEOMETRIES=YES/NO`. Set to NO to skip result features
    with lower dimension geometry that would otherwise be added to the result
    layer. The default is to add but only if the result layer has an unknown
    geometry type.
"""
function intersection(
        input::FeatureLayer,
        method::FeatureLayer,
        output::FeatureLayer;
        options                 = StringList(C_NULL),
        progressfunc::Function  = GDAL.C.GDALDummyProgress,
        progressdata            = C_NULL
    )
    result = GDAL.intersection(
        input.ptr,
        method.ptr,
        output.ptr,
        options,
        @cplprogress(progressfunc),
        progressdata
    )
    @ogrerr result "Failed to compute the intersection of the two layers"
    output
end

"""
Union of two layers.

### Parameters
* `input`: the input layer. Should not be NULL.
* `method`: the method layer. Should not be NULL.
* `result`: the layer where the features resulting from the operation
    are inserted. Should not be NULL. See the note about the schema.
* `papszOptions`: NULL terminated list of options (may be NULL).
* `pfnProgress`: a GDALProgressFunc() compatible callback function for
    reporting progress or NULL.
* `pProgressArg`: argument to be passed to pfnProgress. May be NULL.

### Returns
an error code if there was an error or the execution was interrupted,
OGRERR_NONE otherwise.

### Additional Remarks
If the schema of the result is set by user and contains fields that have the
same name as a field in input and in method layer, then the attribute in the
result feature will get the value from the feature of the method layer.

For best performance use the minimum amount of features in the method layer and
copy it into a memory layer. This method relies on GEOS support. Do not use
unless the GEOS support is compiled in. The recognized list of options is:

* `SKIP_FAILURES=YES/NO. Set it to YES to go on, even when a feature could not
    be inserted or a GEOS call failed.
* `PROMOTE_TO_MULTI=YES/NO. Set it to YES to convert Polygons into
    MultiPolygons, or LineStrings to MultiLineStrings.
* `INPUT_PREFIX=string. Set a prefix for the field names that will be created
    from the fields of the input layer.
* `METHOD_PREFIX=string. Set a prefix for the field names that will be created
    from the fields of the method layer.
* `USE_PREPARED_GEOMETRIES=YES/NO. Set to NO to not use prepared geometries to
    pretest intersection of features of method layer with features of this layer
* `KEEP_LOWER_DIMENSION_GEOMETRIES=YES/NO. Set to NO to skip result features
    with lower dimension geometry that would otherwise be added to the result
    layer. The default is to add but only if the result layer has an unknown
    geometry type.
"""
function union(
        input::FeatureLayer,
        method::FeatureLayer,
        output::FeatureLayer;
        options                 = StringList(C_NULL),
        progressfunc::Function  = GDAL.C.GDALDummyProgress,
        progressdata            = C_NULL
    )
    result = GDAL.union(
        input.ptr,
        method.ptr,
        output.ptr,
        options,
        @cplprogress(progressfunc),
        progressdata
    )
    @ogrerr result "Failed to compute the union of the two layers"
    output
end

"""
Symmetrical difference of two layers.

### Parameters
* `input`: the input layer. Should not be NULL.
* `method`: the method layer. Should not be NULL.
* `result`: the layer where the features resulting from the operation
    are inserted. Should not be NULL. See the note about the schema.
* `papszOptions`: NULL terminated list of options (may be NULL).
* `pfnProgress`: a GDALProgressFunc() compatible callback function for
    reporting progress or NULL.
* `pProgressArg`: argument to be passed to pfnProgress. May be NULL.

### Returns
an error code if there was an error or the execution was interrupted,
OGRERR_NONE otherwise.

### Additional Remarks
If the schema of the result is set by user and contains fields that have the
same name as a field in input and in method layer, then the attribute in the
result feature will get the value from the feature of the method layer.

For best performance use the minimum amount of features in the method layer and
copy it into a memory layer. This method relies on GEOS support. Do not use
unless the GEOS support is compiled in. The recognized list of options is :

* `SKIP_FAILURES=YES/NO`. Set it to YES to go on, even when a feature could not
    be inserted or a GEOS call failed.
* `PROMOTE_TO_MULTI=YES/NO`. Set it to YES to convert Polygons into
    MultiPolygons, or LineStrings to MultiLineStrings.
* `INPUT_PREFIX=string`. Set a prefix for the field names that will be created
    from the fields of the input layer.
* `METHOD_PREFIX=string`. Set a prefix for the field names that will be created
    from the fields of the method layer.
"""
function symdifference(
        input::FeatureLayer,
        method::FeatureLayer,
        output::FeatureLayer;
        options                 = StringList(C_NULL),
        progressfunc::Function  = GDAL.C.GDALDummyProgress,
        progressdata            = C_NULL
    )
    result = GDAL.symdifference(
        input.ptr,
        method.ptr,
        output.ptr,
        options,
        @cplprogress(progressfunc),
        progressdata
    )
    @ogrerr result "Failed to compute the sym difference of the two layers"
    output
end

"""
Identify the features of this layer with the ones from the identity layer.

### Parameters
* `input`: the input layer. Should not be NULL.
* `method`: the method layer. Should not be NULL.
* `result`: the layer where the features resulting from the operation
    are inserted. Should not be NULL. See the note about the schema.
* `papszOptions`: NULL terminated list of options (may be NULL).
* `pfnProgress`: a GDALProgressFunc() compatible callback function for
    reporting progress or NULL.
* `pProgressArg`: argument to be passed to pfnProgress. May be NULL.

### Returns
an error code if there was an error or the execution was interrupted,
OGRERR_NONE otherwise.

### Additional Remarks
If the schema of the result is set by user and contains fields that have the
same name as a field in input and in method layer, then the attribute in the
result feature will get the value from the feature of the method layer.

For best performance use the minimum amount of features in the method layer and
copy it into a memory layer. This method relies on GEOS support. Do not use
unless the GEOS support is compiled in. The recognized list of options is :

* `SKIP_FAILURES=YES/NO`. Set it to YES to go on, even when a feature could not
    be inserted or a GEOS call failed.
* `PROMOTE_TO_MULTI=YES/NO`. Set it to YES to convert Polygons into
    MultiPolygons, or LineStrings to MultiLineStrings.
* `INPUT_PREFIX=string`. Set a prefix for the field names that will be created
    from the fields of the input layer.
* `METHOD_PREFIX=string`. Set a prefix for the field names that will be created
    from the fields of the method layer.
* `USE_PREPARED_GEOMETRIES=YES/NO`. Set to NO to not use prepared geometries to
    pretest intersection of features of method layer with features of this layer
* `KEEP_LOWER_DIMENSION_GEOMETRIES=YES/NO`. Set to NO to skip result features
    with lower dimension geometry that would otherwise be added to the result
    layer. The default is to add but only if the result layer has an unknown
    geometry type.
"""
function identity(
        input::FeatureLayer,
        method::FeatureLayer,
        output::FeatureLayer;
        options                 = StringList(C_NULL),
        progressfunc::Function  = GDAL.C.GDALDummyProgress,
        progressdata            = C_NULL
    )
    result = GDAL.identity(
        input.ptr,
        method.ptr,
        output.ptr,
        options,
        @cplprogress(progressfunc),
        progressdata
    )
    @ogrerr result "Failed to compute the identity of the two layers"
    output
end

"""
Update this layer with features from the update layer.

### Parameters
* `input`: the input layer. Should not be NULL.
* `method`: the method layer. Should not be NULL.
* `result`: the layer where the features resulting from the operation
    are inserted. Should not be NULL. See the note about the schema.
* `papszOptions`: NULL terminated list of options (may be NULL).
* `pfnProgress`: a GDALProgressFunc() compatible callback function for
    reporting progress or NULL.
* `pProgressArg`: argument to be passed to pfnProgress. May be NULL.

### Returns
an error code if there was an error or the execution was interrupted,
OGRERR_NONE otherwise.

### Additional Remarks
If the schema of the result is set by user and contains fields that have the
same name as a field in input and in method layer, then the attribute in the
result feature will get the value from the feature of the method layer.

For best performance use the minimum amount of features in the method layer and
copy it into a memory layer. This method relies on GEOS support. Do not use
unless the GEOS support is compiled in. The recognized list of options is :

* `SKIP_FAILURES=YES/NO`. Set it to YES to go on, even when a feature could not
    be inserted or a GEOS call failed.
* `PROMOTE_TO_MULTI=YES/NO`. Set it to YES to convert Polygons into
    MultiPolygons, or LineStrings to MultiLineStrings.
* `INPUT_PREFIX=string`. Set a prefix for the field names that will be created
    from the fields of the input layer.
* `METHOD_PREFIX=string`. Set a prefix for the field names that will be created
    from the fields of the method layer.
"""
function update(
        input::FeatureLayer,
        method::FeatureLayer,
        output::FeatureLayer;
        options                 = StringList(C_NULL),
        progressfunc::Function  = GDAL.C.GDALDummyProgress,
        progressdata            = C_NULL
    )
    result = GDAL.update(
        input.ptr,
        method.ptr,
        output.ptr,
        options,
        @cplprogress(progressfunc),
        progressdata
    )
    @ogrerr result "Failed to update the layer"
    output
end

"""
Clip off areas that are not covered by the method layer.

### Parameters
* `input`: the input layer. Should not be NULL.
* `method`: the method layer. Should not be NULL.
* `result`: the layer where the features resulting from the operation
    are inserted. Should not be NULL. See the note about the schema.
* `papszOptions`: NULL terminated list of options (may be NULL).
* `pfnProgress`: a GDALProgressFunc() compatible callback function for
    reporting progress or NULL.
* `pProgressArg`: argument to be passed to pfnProgress. May be NULL.

### Returns
an error code if there was an error or the execution was interrupted,
OGRERR_NONE otherwise.

### Additional Remarks
If the schema of the result is set by user and contains fields that have the
same name as a field in input and in method layer, then the attribute in the
result feature will get the value from the feature of the method layer.

For best performance use the minimum amount of features in the method layer and
copy it into a memory layer. This method relies on GEOS support. Do not use
unless the GEOS support is compiled in. The recognized list of options is :

* `SKIP_FAILURES=YES/NO`. Set it to YES to go on, even when a feature could not
    be inserted or a GEOS call failed.
* `PROMOTE_TO_MULTI=YES/NO`. Set it to YES to convert Polygons into
    MultiPolygons, or LineStrings to MultiLineStrings.
* `INPUT_PREFIX=string`. Set a prefix for the field names that will be created
    from the fields of the input layer.
* `METHOD_PREFIX=string`. Set a prefix for the field names that will be created
    from the fields of the method layer.
"""
function clip(
        input::FeatureLayer,
        method::FeatureLayer,
        output::FeatureLayer;
        options                 = StringList(C_NULL),
        progressfunc::Function  = GDAL.C.GDALDummyProgress,
        progressdata            = C_NULL
    )
    result = GDAL.clip(
        input.ptr,
        method.ptr,
        output.ptr,
        options,
        @cplprogress(progressfunc),
        progressdata
    )
    @ogrerr result "Failed to clip the input layer"
    output
end

"""
Remove areas that are covered by the method layer.

### Parameters
* `input`: the input layer. Should not be NULL.
* `method`: the method layer. Should not be NULL.
* `result`: the layer where the features resulting from the operation
    are inserted. Should not be NULL. See the note about the schema.
* `papszOptions`: NULL terminated list of options (may be NULL).
* `pfnProgress`: a GDALProgressFunc() compatible callback function for
    reporting progress or NULL.
* `pProgressArg`: argument to be passed to pfnProgress. May be NULL.

### Returns
an error code if there was an error or the execution was interrupted,
OGRERR_NONE otherwise.

### Additional Remarks
If the schema of the result is set by user and contains fields that have the
same name as a field in input and in method layer, then the attribute in the
result feature will get the value from the feature of the method layer.

For best performance use the minimum amount of features in the method layer and
copy it into a memory layer. This method relies on GEOS support. Do not use
unless the GEOS support is compiled in. The recognized list of options is :

* `SKIP_FAILURES=YES/NO`. Set it to YES to go on, even when a feature could not
    be inserted or a GEOS call failed.
* `PROMOTE_TO_MULTI=YES/NO`. Set it to YES to convert Polygons into
    MultiPolygons, or LineStrings to MultiLineStrings.
* `INPUT_PREFIX=string`. Set a prefix for the field names that will be created
    from the fields of the input layer.
* `METHOD_PREFIX=string`. Set a prefix for the field names that will be created
    from the fields of the method layer.
"""
function erase(
        input::FeatureLayer,
        method::FeatureLayer,
        output::FeatureLayer;
        options = StringList(C_NULL),
        progressfunc::Function = GDAL.C.GDALDummyProgress,
        progressdata = C_NULL
    )
    result = GDAL.erase(
        input.ptr,
        method.ptr,
        output.ptr,
        options,
        @cplprogress(progressfunc),
        progressdata
    )
    @ogrerr result "Failed to remove areas covered by the method layer."
    output
end


text:
mutable struct Fmpz <: Signed
    d::Int

    function Fmpz()
        z = new(0)
        finalizer(fmpz_clear, z)
        return z
    end

    function Fmpz(x::Int)
        z = new(x)
        finalizer(fmpz_clear, z)
        return z
    end
end

Fmpz(x::Fmpz) = x

fmpz_clear(a::Fmpz) = ccall(@libflint(fmpz_clear), Cvoid, (Ref{Fmpz},), a)

ext: jl


text:
function wrap_type!(ctx::AbstractContext, type::CLType)
    decl_cursor::Union{CLCursor,Nothing} = nothing

    if typeof(type) <: Union{CLVector,CLConstantArray,CLIncompleteArray,CLVariableArray,CLDependentSizedArray,CLComplex}
        decl_cursor = clang_getCursorDefinition(typedecl(element_type(type)))
    else
        decl_cursor = clang_getCursorDefinition(typedecl((type)))
    end

    if !(decl_cursor in ctx.visited)
        push!(ctx.visited, decl_cursor)
        enqueue!(ctx.queue, decl_cursor)
    end
end

"""
    wrap!(ctx::AbstractContext, cursor::CLFunctionDecl)
Subroutine for handling function declarations. Note that VarArg functions are not supported.
"""
function wrap!(ctx::AbstractContext, cursor::CLFunctionDecl)
    func_type = type(cursor)
    if kind(func_type) == CXType_FunctionNoProto
        @warn "No Prototype for $cursor - assuming no arguments"
    end

    func_name = isempty(ctx.force_name) ? Symbol(spelling(cursor)) : ctx.force_name
    ret_type = clang2julia(return_type(cursor))
    args = function_args(cursor)
    arg_types = [argtype(func_type, i) for i in 0:length(args)-1]
    arg_reps = clang2julia.(arg_types)
    for (i, arg) in enumerate(arg_reps)
        # constant array argument should be converted to Ptr
        # e.g. double f[3] => Ptr{Cdouble} instead of NTuple{3, Cdouble}
        if Meta.isexpr(arg, :curly) && first(arg.args) == :NTuple
            arg_reps[i] = Expr(:curly, :Ptr, last(arg.args))
        end
    end

    # Wrap return type and argument types
    for arg_type in arg_types
        wrap_type!(ctx, arg_type)
    end
    wrap_type!(ctx, return_type(cursor))

    # handle unnamed args and convert names to symbols
    arg_count = 0
    arg_names = map(args) do x
                    n = name_safe(name(x))
                    s = !isempty(n) ? n : "arg"*string(arg_count+=1)
                    Symbol(s)
    end

    signature = efunsig(func_name, arg_names, arg_reps)
    if isvariadic(func_type)
        push!(signature.args, Expr(:(...), :var_arg))
    end

    ctx.libname == "libxxx" && @warn "default libname: libxxx are being used, did you forget to specify `context.libname`?"

    block = Expr(:block)
    push!(ctx.api_buffer, Expr(:macrocall, Symbol("@cbindings"), nothing, symbol_safe(ctx.libname), block))
    push!(block.args, Expr(:macrocall, Symbol("@cextern"), nothing, Expr(:(::), signature, ret_type)))

    return ctx
end

function is_ptr_type_expr(@nospecialize t)
    (t === :Cstring || t === :Cwstring) && return true
    isa(t, Expr) || return false
    t = t::Expr
    t.head === :curly && t.args[1] === :Ptr
end

function efunsig(name::Symbol, args::Vector{Symbol}, types)
    x = [Expr(:(::), a, t) for (a,t) in zip(args,types)]
    Expr(:call, name, x...)
end

function eccall(func_name::Symbol, libname::Symbol, rtype, args, types)
  :(ccall(($(QuoteNode(func_name)), $libname),
            $rtype,
            $(Expr(:tuple, types...)),
            $(args...))
    )
end

"""
    wrap!(ctx::AbstractContext, cursor::CLEnumDecl)
Subroutine for handling enum declarations.
"""
function wrap!(ctx::AbstractContext, cursor::CLEnumDecl)
    cursor_name = name(cursor)
    # handle typedef anonymous enum
    next_cursor = get(ctx.next_cursor, cursor, nothing)
    if next_cursor != nothing && is_typedef_anon(cursor, next_cursor)
        cursor_name = name(next_cursor)
    end

    !isempty(ctx.force_name) && (cursor_name = ctx.force_name;)
    cursor_name == "" && (@warn("Skipping unnamed EnumDecl: $cursor"); return ctx)

    enum_sym = symbol_safe(cursor_name)
    enum_type = INT_CONVERSION[clang2julia(cursor)]
    name2value = Tuple{Symbol,Int}[]
    # extract values and names
    for item_cursor in children(cursor)
        kind(item_cursor) == CXCursor_PackedAttr && (@warn("this is a `__attribute__((packed))` enum, the underlying alignment of generated structure may not be compatible with the original one in C!"); continue)
        item_name = spelling(item_cursor)
        isempty(item_name) && continue
        item_sym = symbol_safe(item_name)
        push!(name2value, (item_sym, value(item_cursor)))
    end

    expr = Expr(:macrocall, Symbol("@cenum"), nothing, Expr(:(::), enum_sym, enum_type))
    enum_pairs = Expr(:block)
    ctx.common_buffer[enum_sym] = ExprUnit(expr)
    for (name,value) in name2value
        ctx.common_buffer[name] = ctx.common_buffer[enum_sym]  ##???
        push!(enum_pairs.args, :($name = $value))
    end
    push!(expr.args, enum_pairs)

    return ctx
end

"""
    wrap!(ctx::AbstractContext, cursor::CLStructDecl)
Subroutine for handling struct declarations.
"""
function wrap!(ctx::AbstractContext, cursor::CLStructDecl)
    # make sure a empty struct is indeed an opaque struct typedef/typealias
    cursor = type(cursor) |> canonical |> typedecl
    cursor_name = name(cursor)
    # handle typedef anonymous struct
    next_cursor = get(ctx.next_cursor,cursor,nothing)
    if next_cursor != nothing && is_typedef_anon(cursor, next_cursor)
        cursor_name = name(next_cursor)
    end
    !isempty(ctx.force_name) && (cursor_name = ctx.force_name;)
    cursor_name == "" && (@warn("Skipping unnamed StructDecl: $cursor"); return ctx)

    struct_sym = symbol_safe(cursor_name)
    buffer = ctx.common_buffer

    # generate struct declaration
    block = Expr(:bracescat)
    expr = Expr(:macrocall, Symbol("@cstruct"), nothing, struct_sym, block)
    deps = OrderedSet{Symbol}()
    struct_fields = children(cursor)
    for (field_idx, field_cursor) in enumerate(struct_fields)
        field_name = name(field_cursor)
        field_kind = kind(field_cursor)

        alignment = get(ctx.fields_align, (struct_sym, symbol_safe(field_name)), nothing)
        if alignment != nothing
            push!(block.args, Expr(:macrocall, Symbol("@calign"), nothing, alignment))
        end

        if field_kind == CXCursor_StructDecl || field_kind == CXCursor_UnionDecl || field_kind == CXCursor_EnumDecl
            continue
        elseif field_kind == CXCursor_FirstAttr
            continue
        elseif field_kind != CXCursor_FieldDecl || field_kind == CXCursor_TypeRef
            buffer[struct_sym] = ExprUnit(Poisoned())
            @warn "Skipping struct: \"$cursor\" due to unsupported field: $field_cursor"
            return ctx
        elseif isempty(field_name)
            error("Unnamed struct member in: $cursor ... cursor: $field_cursor")
        end

        # anonymous field
        if occursin("anonymous", string(clang2julia(field_cursor)))
            children_cursors = children(field_cursor)

            # union field
            if kind(children_cursors[1]) == CXCursor_UnionDecl
                union_block = Expr(:bracescat)
                union_expr = Expr(:macrocall, Symbol("@cunion"), nothing, union_block)

                for union_field in children(children_cursors[1])
                    repr = clang2julia(union_field)
                    union_field_name = name(union_field)
                    push!(union_block.args, Expr(:(::), symbol_safe(union_field_name), repr))
                    push!(deps, target_type(repr))
                    wrap_type!(ctx, type(union_field))
                end
                push!(block.args, union_expr)
                continue
            end

            idx = field_idx-1
            anonymous_record = struct_fields[idx]
            while idx != 0 && kind(anonymous_record) == CXCursor_FieldDecl
                idx -= 1
                anonymous_record = struct_fields[idx]
            end
            if idx == field_idx-1
                ctx.anonymous_counter += 1
                anon_name = "ANONYMOUS$(ctx.anonymous_counter)_"*spelling(field_cursor)
                ctx.force_name = anon_name
                wrap!(ctx, anonymous_record)
                ctx.force_name = ""
                repr = symbol_safe(anon_name)
            else
                anon_name = "ANONYMOUS$(ctx.anonymous_counter)_"*spelling(struct_fields[idx+1])
                repr = symbol_safe(anon_name)
            end

            push!(block.args, Expr(:(::), symbol_safe(field_name), repr))
            push!(deps, target_type(repr))
            continue
        end

        # bitfield
        if clang_Cursor_isBitField(field_cursor) == 1
            repr = clang2julia(field_cursor)
            bit_width = clang_getFieldDeclBitWidth(field_cursor)
            symbol = symbol_safe(field_name)
            push!(block.args, Expr(:(::), :($symbol:$bit_width), repr))
            push!(deps, target_type(repr))
            continue
        end

        repr = clang2julia(field_cursor)
        wrap_type!(ctx, type(field_cursor))
        push!(block.args, Expr(:(::), symbol_safe(field_name), repr))
        push!(deps, target_type(repr))
    end

    # check for a previous forward ordering
    if !(struct_sym in keys(buffer)) || buffer[struct_sym].state == :empty
        if !isempty(struct_fields)
            buffer[struct_sym] = ExprUnit(expr, deps)
        else
            # opaque struct typedef/typealias
            buffer[struct_sym] = ExprUnit(:(const $struct_sym = Cvoid), deps, state=:empty)
        end
    end

    return ctx
end

"""
    wrap!(ctx::AbstractContext, cursor::CLUnionDecl)
Subroutine for handling union declarations.
"""
function wrap!(ctx::AbstractContext, cursor::CLUnionDecl)
    # make sure a empty union is indeed an opaque union typedef/typealias
    # cursor = canonical(cursor)  # this won't work
    cursor = type(cursor) |> canonical |> typedecl
    cursor_name = name(cursor)
    # handle typedef anonymous union
    next_cursor = ctx.next_cursor[cursor]
    if next_cursor != nothing && is_typedef_anon(cursor, next_cursor)
        cursor_name = name(next_cursor)
    end
    !isempty(ctx.force_name) && (cursor_name = ctx.force_name;)
    cursor_name == "" && (@warn("Skipping unnamed UnionDecl: $cursor"); return ctx)

    union_sym = symbol_safe(cursor_name)
    buffer = ctx.common_buffer

    # generate union declaration
    block = Expr(:bracescat)
    expr = Expr(:macrocall, Symbol("@cunion"), nothing, union_sym, block)
    deps = OrderedSet{Symbol}()
    union_fields = children(cursor)
    for (field_idx, field_cursor) in enumerate(union_fields)
        field_name = name(field_cursor)
        field_kind = kind(field_cursor)

        if field_kind == CXCursor_StructDecl || field_kind == CXCursor_UnionDecl || field_kind == CXCursor_EnumDecl
            continue
        elseif field_kind == CXCursor_FirstAttr
            continue
        elseif field_kind != CXCursor_FieldDecl || field_kind == CXCursor_TypeRef
            buffer[union_sym] = ExprUnit(Poisoned())
            @warn "Skipping union: \"$cursor\" due to unsupported field: $field_cursor"
            return ctx
        elseif isempty(field_name)
            error("Unnamed union member in: $cursor ... cursor: $field_cursor")
        end

        repr = clang2julia(field_cursor)
        push!(block.args, Expr(:(::), symbol_safe(field_name), repr))
        push!(deps, target_type(repr))
        wrap_type!(ctx, type(field_cursor))
    end

    # check for a previous forward ordering
    if !(union_sym in keys(buffer)) || buffer[union_sym].state == :empty
        if !isempty(union_fields)
            buffer[union_sym] = ExprUnit(expr, deps)
        else
            # opaque union typedef/typealias
            buffer[union_sym] = ExprUnit(:(const $union_sym = Cvoid), deps, state=:empty)
        end
    end

    return ctx
end

"""
    wrap!(ctx::AbstractContext, cursor::CLTypedefDecl)
Subroutine for handling typedef declarations.
"""
function wrap!(ctx::AbstractContext, cursor::CLTypedefDecl)
    td_type = underlying_type(cursor)
    td_sym = isempty(ctx.force_name) ? Symbol(spelling(cursor)) : ctx.force_name
    buffer = ctx.common_buffer
    if kind(td_type) == CXType_Unexposed
        # TODO: which corner case will trigger this pass?
        @error "Skipping Typedef: CXType_Unexposed, $cursor, please report this on Github."
    end

    if kind(td_type) == CXType_FunctionProto
        # TODO: need to find a test case too
        if !haskey(buffer, td_sym)
            buffer[td_sym] = ExprUnit(string("# Skipping Typedef: CXType_FunctionProto ", spelling(cursor)))
        end
        return ctx
    end

    td_target = clang2julia(td_type)

    if td_target == td_sym
        return wrap!(ctx, typedecl(typedef_type(cursor)))
    end

    if !haskey(buffer, td_sym)
        buffer[td_sym] = ExprUnit(:(const $td_sym = $td_target), [td_target])
        wrap_type!(ctx, typedef_type(cursor))
    end
    return ctx
end

"""
    wrap!(ctx::AbstractContext, cursor::CLMacroDefinition)
Subroutine for handling macro declarations.
"""
function wrap!(ctx::AbstractContext, cursor::CLMacroDefinition)

    # normalize literal with a size suffix
    function literally(tok)
        # note: put multi-character first, or it will break out too soon for those!
        literalsuffixes = ["ULL", "Ull", "uLL", "ull", "LLU", "LLu", "llU", "llu",
                           "LL", "ll", "UL", "Ul", "uL", "ul", "LU", "Lu", "lU", "lu",
                           "U", "u", "L", "l", "F", "f"]

        function literal_totype(literal, txt)
            literal = lowercase(literal)

            # Floats following http://en.cppreference.com/w/cpp/language/floating_literal
            float64 = occursin(".", txt) && occursin("l", literal)
            float32 = occursin("f", literal)

            if float64 || float32
                float64 && return "Float64"
                float32 && return "Float32"
            end

            # Integers following http://en.cppreference.com/w/cpp/language/integer_literal
            unsigned = occursin("u", literal)
            nbits = count(x -> x == 'l', literal) == 2 ? 64 : 32
            return "$(unsigned ? "U" : "")Int$nbits"
        end

        txt = tok.text |> strip
        for sfx in literalsuffixes
            if endswith(txt, sfx)
                type = literal_totype(sfx, txt)
                txt = txt[1:end-length(sfx)]
                txt = "$(type)($txt)"
                break
            end
        end
        return txt
    end

    tokens = tokenize(cursor)

    # Skip any empty definitions
    tokens.size < 2 && return ctx
    startswith(name(cursor), "_") && return ctx

    buffer = ctx.common_buffer

    exprn = ""
    deps = []
    prev_kind = nothing
    for i in 2:tokens.size
        token = tokens[i]
        token_kind = kind(token)
        token_text = token.text
        if token_kind == CXToken_Literal
            if prev_kind == CXToken_Literal
                @warn "Skipping CLMacroDefinition: $cursor"
                return ctx
            end
            exprn *= literally(token)
        elseif token_kind == CXToken_Identifier
            exprn *= token.text
            push!(deps, symbol_safe(token_text))
        elseif token_kind == CXToken_Punctuation

            # Do not translate macro containing function calls
            if token_text == "(" && prev_kind == CXToken_Identifier
                @warn "Skipping CLMacroDefinition: $cursor"
                return ctx
            end

            if token_text ∈ ["+" "-" "*" "~" ">>" "<<" "/" "\\" "%" "|" "||" "^" "&" "&&" "(" ")"]
                exprn *= token.text
            else
                @warn "Skipping CLMacroDefinition: $cursor"
                return ctx
            end
        else
            @warn "Skipping CLMacroDefinition: $cursor"
            return ctx
        end
        prev_kind = token_kind
    end

    use_sym = symbol_safe(tokens[1].text)

    try
        target = Meta.parse(exprn)
        if use_sym == target
            return ctx
        end

        e = Expr(:const, Expr(:(=), use_sym, target))
        buffer[use_sym] = ExprUnit(e, deps)
    catch err
        @warn "Skipping CLMacroDefinition: $cursor"
    end

    return ctx
end

"""
    wrap!(ctx::AbstractContext, cursor::CLTypeRef)
For now, we just skip CXCursor_TypeRef cursors.
"""
function wrap!(ctx::AbstractContext, cursor::CLTypeRef)
    @warn "Skipping CXCursor_TypeRef cursor: $cursor"
    return ctx
end

function wrap!(ctx::AbstractContext, cursor::CLCursor)
    @warn "not wrapping $(cursor)"
    return ctx
end

function wrap!(ctx::AbstractContext, cursor::CLMacroInstantiation)
    # tokens = tokenize(cursor)
    # for token in tokens
    #     print(token.text, " ")
    # end
    println(cursor, children(cursor))
    @warn "not wrapping $(cursor)"
    return ctx
end

function wrap!(ctx::AbstractContext, cursor::CLLastPreprocessing)
    @debug "not wrapping $(cursor)"
    return ctx
end


text:
module Hydrogen_FEM_module
    using LinearAlgebra

    struct Hydrogen_FEM_param
        EIGENFUNC_FILENAME::String
        EIGENVAL_FILENAME::String
        NODE_TOTAL::Int64
        ELE_TOTAL::Int64
        R_MAX::Float64
        R_MIN::Float64
    end

    mutable struct Hydrogen_FEM_variables
        hg::Symmetric{Float64,Array{Float64,2}}
        length::Array{Float64, 1}
        mat_A_ele::Array{Float64, 3}
        mat_B_ele::Array{Float64, 3}
        node_num_seg::Array{Int64, 2}
        node_r_ele::Array{Float64, 2}
        node_r_glo::Array{Float64, 1}
        phi::Array{Float64, 1}
        ug::Symmetric{Float64,Array{Float64,2}}
    end
end


text:
# abstract monte carlo flavor definition
"""
Abstract definition of a Monte Carlo flavor.
"""
abstract type MonteCarloFlavor end

# MonteCarloFlavor interface: mandatory
"""
    init!(mc)

Initialize the Monte Carlo simulation. Has an alias function `reset!`.
"""
init!(mc::MonteCarloFlavor) = error("MonteCarloFlavor $(typeof(mc)) doesn't implement `init!`!")

"""
    run!(mc)

Run the Monte Carlo Simulation.
"""
run!(mc::MonteCarloFlavor) = error("MonteCarloFlavor $(typeof(mc)) doesn't implement `run!`!")

# abstract lattice definition
"""
Abstract definition of a lattice.
"""
abstract type Lattice end

# Lattice interface: mandatory
# TODO: This needs to be updated. There must be a general way to access sites and bonds.
"""
    nsites(l::Lattice)

Number of lattice sites.
"""
nsites(l::Lattice) = error("Lattice $(typeof(l)) doesn't implement `nsites`.")

# Typically, you also want to implement

#     - `neighbors_lookup_table(lattice)`: return a neighbors matrix where
#                                         row = neighbors and col = siteidx.

"""
Abstract cubic lattice.

- 1D -> Chain
- 2D -> SquareLattice
- ND -> NCubeLattice
"""
abstract type AbstractCubicLattice <: Lattice end

# abstract model definition
"""
Abstract model.
"""
abstract type Model end

# Model interface: mandatory
"""
    nsites(m::Model)

Number of lattice sites of the given model.
"""
nsites(m::Model) = error("Model $(typeof(m)) doesn't implement `nsites`!")

# general functions
"""
	observables(mc::MonteCarloFlavor)

Get a list of all observables defined for a given Monte Carlo simulation.

Returns a `Dict{String, String}` where values are the observables names and
keys are short versions of those names. The keys can be used to
collect correponding observable objects from the Monte Carlo simulation, e.g. like `mc.obs[key]`.

Note, there is no need to implement this function for a custom `MonteCarloFlavor`.
"""
function observables(mc::MonteCarloFlavor)
	obs = Dict{String, String}()
	obsobjects = prepare_observables(mc, mc.model)
	for (s, o) in obsobjects
		obs[s] = MonteCarloObservable.name(o)
	end
	return obs
end

"""
    reset!(mc::MonteCarloFlavor)

Resets the Monte Carlo simulation `mc`.
Previously set parameters will be retained.
"""
reset!(mc::MonteCarloFlavor) = init!(mc) # convenience mapping

max_stars_repo_name: yosefcap/MC

max_issues_repo_name: yosefcap/MC

max_forks_repo_name: yosefcap/MC


text:
push!!(df::DataFrames.DataFrame, row) = df_append_rows!!(df, (row,))
append!!(df::DataFrames.DataFrame, source) = df_append!!(df, source)


text:
module Network
using SHA
using Dates
using Random

include("./Ledger.jl")
using .Ledger: Transaction, Blockchain, Block, appendblock, gethash, checkdifficulty

mutable struct Node
    pool::Dict{String,Transaction}
    blockchain::Blockchain
    Node() = new(Dict{String,Transaction}(), Blockchain("litcoin"))
    #Node() = new(Dict{String,Transaction}())
end

# Initialize a fixed number of nodes
nodes = [Node() for i in 1:10]

function getNTransactions(node::Node, n::Int)
    sorted = sort(collect(values(node.pool)), by=x -> x.fee, rev=true)
    return sorted[1:min(n, size(sorted, 1))]
end

function getRandomNode()
    return nodes[rand(1:size(nodes, 1))]
end

function addTransaction(node::Node, tx::Transaction)
    node.pool[tx.signature] = tx
end

function addBlock(node::Node, parent_hash::String, transactions::Vector{Transaction}, nonce::String)
    node.blockchain.append_block(parent_hash, transactions, nonce)
end

function mine()
    # Select a random Node for getting the blockchain data
    node = getRandomNode()

    # Start mining
    while true
        # Get transactions to verify
        transactions = getNTransactions(node, 5)

        # Get the previous block hash
        parent_hash = node.blockchain.blocks[1].hash

        # Mine by trying different nonces
        max_nonce = 2 ^ 32
        mined = false
        block = 0
        for i in 0:max_nonce
            nonce = randstring(64)
            block = Block(0, Dates.now(), transactions, parent_hash, nonce)
            if checkdifficulty(node.blockchain, block)
                mined = true
                break
            end
        end

        # If succesful, alert the network
        if mined
            accepted = appendblock(node.blockchain, block)
            if accepted
                #println("MINED")
            else
                println("NOPE")
            end
        end

        # Sleep a while
        sleep(0.5)
    end
end

function transact()
    # Select a random Node for inserting new transactions
    node = getRandomNode()

    while true
        # Create new transaction
        tx = Transaction(0, "aki", 0, "marc", 0, "hi", randstring(12), 1)

        # Add transaction to pool
        addTransaction(node, tx)

        # Sleep a while
        sleep(1)
    end
end

function observe()
    while true
        n_nodes = size(nodes, 1)
        n_tx_all = 0
        longest_block = 0

        for n in nodes
            n_tx_all += length(n.pool)
            longest_block = max(longest_block, size(n.blockchain.blocks, 1))
        end

        println("Number of nodes: $(lpad(n_nodes, 7))")
        println("Number of pooled transactions: $(lpad(n_tx_all, 7))")
        println("Longest block: $(lpad(longest_block, 7))")

        # Sleep a while
        sleep(0.5)
        run(`clear`)
    end
end

end

# Init a transaction loop
@async Network.transact()

# Init an observer loop
@async Network.observe()

# Init a mining loop
@sync @async Network.mine()

max_stars_repo_name: marchunter/litcoin

max_issues_repo_name: marchunter/litcoin

max_forks_repo_name: marchunter/litcoin


text:
"""
    ellipsoid_level_set(image_shape::Tuple, center=nothing, semi_axis=nothing)

Initialize a 2D or 3D ellipsoid for level set problems. Returns a
binary array.

# Arguments
- `image_shape::Tuple`: The 2D or 3D shape of the ellipsoid
- `center::Tuple`: Coordinates for the center of the ellipsoid. Defaults to center of
    of the image.
- `semi-axis::Tuple`: Lengths of the semi-axis of the ellipsoid. Defaults to half of the
    image dimensions.
"""
function ellipsoid_level_set(image_shape::Tuple, center=nothing, semi_axis=nothing)
    if isnothing(center)
        center = [i / 2 for i in image_shape]
    else
        center = collect(center)
    end

    if isnothing(semi_axis)
        semi_axis = [i / 2 for i in image_shape]
    else
        semi_axis = collect(semi_axis)
    end

    length(center) == length(image_shape) ||
        throw(ArgumentError("`center` and `image_shape` must have the same length."))

    length(semi_axis) == length(image_shape) ||
        throw(ArgumentError("`semi_axis` and `image_shape` must have the same length."))

    if length(image_shape) == 2
        xc, yc = center[1], center[2]
        rx, ry = semi_axis[1], semi_axis[2]
        phi = zeros(image_shape)
        for x in 1:image_shape[1]
            for y in 1:image_shape[2]
                phi[x, y] = 1 - ((((x - 1) - xc) / rx)^2 + (((y - 1) - yc) / ry)^2)
            end
        end
    elseif length(image_shape) == 3
        xc, yc, zc = center[1], center[2], center[3]
        rx, ry, rz = semi_axis[1], semi_axis[2], center[3]
        phi = zeros(image_shape)
        for x in 1:image_shape[1]
            for y in 1:image_shape[2]
                for z in 1:image_shape[3]
                    phi[x, y, z] =
                        1 - (
                            (((x - 1) - xc) / rx)^2 +
                            (((y - 1) - yc) / ry)^2 +
                            (((z - 1) - zc) / rz)^2
                        )
                end
            end
        end
    else
        error("`image_shape` must be a 2- or 3-tuple.")
    end
    res = phi .> 0
    return res
end
"""
    lazy_ellipsoid_level_set(image_shape, center=nothing, semi_axis=nothing)

Initialize a 2D or 3D ellipsoid for level set problems. Returns a
LazySets `Ellipsoid` type. ~~Probably faster than `ellipsoid_level_set`~~.

# Arguments
- `image_shape::Tuple`: The 2D or 3D shape of the ellipsoid
- `center::Tuple`: Coordinates for the center of the ellipsoid. Defaults to center of
    of the image.
- `semi-axis::Tuple`: Lengths of the semi-axis of the ellipsoid. Defaults to half of the
    image dimensions.
"""
function lazy_ellipsoid_level_set(image_shape::Tuple, center=nothing, semi_axis=nothing)
    if isnothing(center)
        center = [i / 2 for i in image_shape]
    else
        center = collect(center)
    end

    if isnothing(semi_axis)
        semi_axis = [i / 2 for i in image_shape]
    else
        semi_axis = collect(semi_axis)
    end

    length(center) == length(image_shape) ||
        throw(ArgumentError("`center` and `image_shape` must have the same length."))

    length(semi_axis) == length(image_shape) ||
        throw(ArgumentError("`semi_axis` and `image_shape` must have the same length."))

    if length(image_shape) == 2
        rx, ry = semi_axis
        ellipse = Ellipsoid(center, Diagonal([rx^2, ry^2]))
    elseif length(image_shape) == 3
        rx, ry, rz = semi_axis
        ellipse = Ellipsoid(center, Diagonal([rx^2, ry^2, rz^2]))
    else
        error("`image_shape` must be a 2- or 3-tuple.")
    end
    return ellipse
end

"""
    to_array(image_shape::Tuple, E::Ellipsoid)

Turn an `Ellipsoid` type from LazySets.jl into an array
where the pixels within the `Ellipsoid` are 1 and 0 everywhere
else

# Arguments
- `image_shape::Tuple`: The 2D or 3D shape of the `Ellipsoid`
- `E::Ellipsoid`: LazySets `Ellipsoid` type
"""
function to_array(image_shape, E::Ellipsoid)
    B = box_approximation(E)
    Bs = split(B, collect(image_shape))
    res = [center(Bi) in E for Bi in Bs]
    M = reshape(res, image_shape)
    return M
end


text:
## Copied from Base.print_to_string. There is probably a trick to avoid this.
## FIXME. May want to update the copied part to agree with latest implementation in base
function print_with_function_to_string(printfunc::Function, xs...; env=nothing)
    # specialized for performance reasons
    siz::Int = 0
    for x in xs
        siz += Base._str_sizehint(x)
    end
    s = IOBuffer(sizehint=siz)
#    s = IOBuffer(Array{UInt8}(undef, Base.tostr_sizehint(xs[1])), true, true)
    # specialized version of truncate(s,0)
    # s.size = 0
    # s.ptr = 1
    if env !== nothing
        env_io = IOContext(s, env)
        foreach( x -> printfunc(env_io, x), xs)
    else
        foreach( x -> printfunc(s, x), xs)
    end
    String(resize!(s.data, s.size))
end

"""
    @runtime_include filename initfunc

defines a function with name `initfunc` to load code in `filename` (in the src
directory) at runtime and subsequently check if it is loaded.  `initfunc()` will
return true if the code is already loaded.

A similar macro should be written to "use" a module at runtime.
"""
macro runtime_include(fname, initfunc)
    fpath = gensym()
    quote
        const $fpath = joinpath(dirname(@__FILE__), $(esc(fname)))
        let polys_inited = false
            global $(esc(initfunc))
            function ($(esc(initfunc)))()
                if ! polys_inited
                    try
                        info("loading ", $fpath)
                        if ! isfile($fpath)
                            @warn("unable to find '", $fpath, "'")
                        end
                        @eval include($fpath)
                        polys_inited = true
                        return true
                    catch e
                        @warn(e)
                        @warn("Unable to load '", $fpath, "'")
                        return false
                    end
                end
                true
            end
        end
    end
end

## In the example below, we gave _poly_path the scope of the let.
## But, this requires escaping, as seen below. I didn't have the patience to get this
## working inside a macro. So we use a global constant (gensym) above.
## But, it should be possible to reproduce the example below

# let polys_inited = false, _poly_path = joinpath(dirname(@__FILE__), "PolynomialSequences.jl")
#     global _init_polys
#     function _init_polys()
#     if ! polys_inited
#         try
#             info("loading polynomial functions")
#             if ! isfile(_poly_path)
#                 @warn("unable to find '$_poly_path'")
#             end
#             @eval include($(_poly_path)) # The functions are in the Symata module.
#             polys_inited = true
#             return true
#         catch e
#             @warn(e)
#             @warn("Unable to load 'PolynomialSequences'.")
#             return false
#         end
#     end
#     true
#     end
# end


text:
# returns a new Vector{NodeData} with constants simplified
# basically, lob off all of the children of constant nodes
function simplify_constants(forward_storage::Vector{T},nd::Vector{NodeData},adj,const_values,linearity) where T

    new_nd = NodeData[]
    old_index_to_new_index = fill(-1,length(nd))

    for k in 1:length(nd) # process parents first
        nod = nd[k]
        new_parentidx = nod.parent != -1 ? old_index_to_new_index[nod.parent] : -1
        if new_parentidx != -1 && linearity[nod.parent] == CONSTANT
            # if parent is constant, we skip this
            old_index_to_new_index[k] = 0
            continue
        elseif linearity[k] != CONSTANT || nod.nodetype == VALUE || nod.nodetype == PARAMETER
            # put it back on the tape
            new_nod = NodeData(nod.nodetype,nod.index,new_parentidx)
            push!(new_nd, new_nod)
            old_index_to_new_index[k] = length(new_nd)
        else
            # this node is constant and needs to be replaced
            push!(const_values, forward_storage[k])
            idx = length(const_values)
            new_nod = NodeData(VALUE, idx, new_parentidx)
            push!(new_nd, new_nod)
            old_index_to_new_index[k] = length(new_nd)
        end
    end

    return new_nd
end

export simplify_constants


text:
###### MLE ######

"""
Shrinkage of small eigen values.
"""
function eigen_truncation(S::AbstractVector{<:Real}, ρ::Real; ptrunc::Real=2., kwargs...)
    @assert 0<=ρ<=1  "Relative threshold must be in [0,1]"
    @assert Inf > ptrunc > 0

    Sp = abs.(S).^ptrunc
    sidx = sortperm(Sp, lt=≥)  # index in decreasing order
    Sn = (cumsum(view(Sp, sidx))/sum(Sp)).^(1/ptrunc)  # decreasing order
    k = findfirst(Sn .>= 1-ρ)
    return isnothing(k) ? (1:length(S)) : sidx[1:k]
end

"""
    xiAx(A, X; ρ=0)

Safe evaluation of the inverse quadratic form
    trace(X' * inv(A) * X)
where the matrix `A` is symmetric and positive definite. Small eigen values of `A` are truncated.
"""
function xiAx(A::AbstractMatrix{<:Real}, X::AbstractVecOrMat{<:Real}; ρ::Real=0, kwargs...)
    # # Sanity check
    # @assert issymmetric(A)
    # @assert size(X, 1) == size(A, 1)

    # # Direct inversion
    # return tr(X' * pinv(A + 1e-3*I) * X)

    # Eigen decomposition, S are the eigen values in increasing order
    S, U = eigen(A)  # so that U * Diagonal(S) * inv(U) == A, in particular, U' == inv(U)
    # On covariance matrix this is equivalent to
    # U, S, V = svd(A)

    # shrinkage of small eigen values for stability
    idx = eigen_truncation(S, ρ; kwargs...)

    return sum((U[:,idx]'*X).^2 ./ S[idx])  # this may raise error if idx is empty
end

"""
    log_likelihood(A, X)

Log-likelihood of a general Gaussian vector.

# Args
- A: covariance matrix
- X: sample vector or matrix. For matrix each column is a sample.
"""
function log_likelihood(A::AbstractMatrix{<:Real}, X::AbstractMatrix{<:Real}; kwargs...)
    # # Sanity check
    # @assert issymmetric(A)
    # @assert size(X, 1) == size(A, 1)

    N = size(X,2) # number of i.i.d. samples in data
    return -1/2 * (xiAx(A,X, kwargs...) + N*logdet(A) + length(X)*log(2π))
end

log_likelihood(A::AbstractMatrix, X::AbstractVector; kwargs...) = log_likelihood(A, reshape(X,:,1), kwargs...)

"""
    log_likelihood_H(A, X)

Safe evaluation of the log-likelihood of a fBm model with the implicite σ (optimal in the MLE sense).

# Args
- A: covariance matrix
- X: sample vector or matrix. For matrix each column is a sample.

# Notes
- This function is common to all MLEs with the covariance matrix of form `σ²A(h)`, where `{σ, h}` are unknown parameters. This kind of MLE can be carried out in `h` uniquely and `σ` is obtained from `h`.
- `log_likelihood_H(Σ, X)` and `log_likelihood(σ^2*Σ, X)` are equivalent with the optimal `σ = sqrt(xiAx(Σ, X) / length(X))`
"""
function log_likelihood_H(A::AbstractMatrix{<:Real}, X::AbstractMatrix{<:Real}; ρ::Real=0, kwargs...)
    # Sanity check
    # @assert issymmetric(A)
    # @assert size(X, 1) == size(A, 1)

    N = size(X,2) # number of i.i.d. samples in data

    # Compute the log-likelihood: log_likelihood(A,X)
    S, U = eigen(A)  # so that U * Diagonal(S) * inv(U) == A, in particular, U' == inv(U)

    # shrinkage of small eigen values for stability
    idx = eigen_truncation(S, ρ; kwargs...)

    val = -1/2 * (length(X)*log(sum((U[:,idx]'*X).^2 ./ S[idx])) + N*sum(log.(S[idx])))  # non-constant part of log-likelihood

    return val - length(X) * log(2π*exp(1)/length(X))/2  # with the constant part
end

# function log_likelihood_H_partial(A::AbstractMatrix{<:Real}, X::AbstractMatrix{<:Real}; kwargs...)
#     log_likelihood_H_full(diagm(0=>diag(A)), X; kwargs...)
# end

# function log_likelihood_H(A::AbstractMatrix, X::AbstractMatrix; partial::Bool=false, kwargs...)
#     return if partial
#         log_likelihood_H_partial(A, X; kwargs...)
#     else
#         log_likelihood_H_full(A, X; kwargs...)
#     end
# end

log_likelihood_H(A::AbstractMatrix, X::AbstractVector; kwargs...)  = log_likelihood_H(A, reshape(X,:,1); kwargs...)

#### fWn (bank)-MLE ####

"""
    fWn_log_likelihood_H(X, H, F, G)

Log-likelihood of a fWn bank model with the optimal volatility.
"""
function fWn_log_likelihood_H(X::AbstractVecOrMat{<:Real}, H::Real, F::AbstractVector{<:AbstractVector{<:Real}}, G::AbstractVector{<:Integer}; mode::Symbol=:causal, kwargs...)
    # # Sanity check
    # @assert size(X,1) == length(G) * length(F)  "Mismatched dimensions."

    # the process and the number of filters in the filter bank
    proc = FractionalWaveletNoiseBank(H, F, mode)  # mode of wavelet transform

    return log_likelihood_H(covmat(proc, G; kwargs...), X; kwargs...)
end

"""
Efficient evaluation of `fWn_log_likelihood_H`. This function reuses the precomputed summation coefficients `A` and their supports `K` that are independent of the Hurst exponent `H` hence avoids the re-evaluation. Note that `A` and `K` depend on the mode of the wavelet transform.
"""
function fWn_log_likelihood_H(X::AbstractVecOrMat{<:Real}, H::Real, A::AbstractMatrix{<:AbstractVector{<:Real}}, K::AbstractMatrix{<:AbstractVector{<:Real}}, G::AbstractVector{<:Integer}; kwargs...)
    # # Sanity check
    # @assert size(X,1) == length(G) * length(F)  "Mismatched dimensions."

    # the process and the number of filters in the filter bank
    return log_likelihood_H(fWn_covmat(A, K, H, G; kwargs...), X; kwargs...)
end

"""
    fWn_MLE_estim(X, F, G; method)

Maximum likelihood estimation of Hurst exponent and volatility for fWn bank.

# Args
- X: sample matrix.
- F: array of filters used for computing `X`
- G: integer time grid of `X`.
- method: :optim for optimization based or :table for look-up table based solution.

# Returns
- hurst, σ: estimation
- loglikelihood: log-likelihood of estimation
- optimizer: object of optimizer, for method==:optim only

# Notes
- The fWnb process is multivariate. An observation at time `t` is a vector that the dimension equals to the number of filters used in fWnb. A sample vector is the concatenation of observations made on some time grid `G` and the row dimension of `X` must be `length(F) * length(G)`. Columns of `X` are treated as i.i.d. sample vectors hence the horizontal axis (i.e. last dimension) should NOT be interpreted as time axis (even it is physically the case, e.g. wavelet coefficients of a time series). In fact the time axis is "in" the vertical axis (i.e. the first dimension) that the grid `G` must be conformal with.
"""
function fWn_MLE_estim(X::AbstractMatrix{<:Real}, F::AbstractVector{<:AbstractVector{<:Real}}, G::AbstractVector{<:Integer}; method::Symbol=:optim, mode::Symbol=:causal, kwargs...)
    if length(X) == 0 || any(isnan.(X))  # for empty input or input containing nans
        return (hurst=NaN, σ=NaN, loglikelihood=NaN, optimizer=nothing)
    else
        # Sanity check
        @assert size(X,1) == length(F) * length(G)  "Mismatched dimensions."
        @assert length(G) == length(unique(G))  "All elements of the grid must be distinct."

        # X = X .- mean(X, dims=2)  # force the zero-mean condition. Attention: this can cause the estimation of H to fail!

        proc = FractionalWaveletNoiseBank(0.5, F, mode)
        # func = h -> -fWn_log_likelihood_H(X, h, F, G; kwargs...)
        func = h -> -fWn_log_likelihood_H(X, h, proc.coeffs, proc.supps, G; kwargs...)

        opm = nothing
        hurst = nothing
        ε::Real=1e-2  # search Hurst in the range [ɛ, 1-ɛ]

        if method == :optim
            # Gradient-free constrained optimization
            opm = Optim.optimize(func, ɛ, 1-ɛ, Optim.Brent())
            # # Gradient-based optimization
            # optimizer = Optim.GradientDescent()  # e.g. Optim.BFGS(), Optim.GradientDescent()
            # opm = Optim.optimize(func, ε, 1-ε, [0.5], Optim.Fminbox(optimizer))
            hurst = Optim.minimizer(opm)[1]
        elseif method == :table
            Hs = collect(ε:ε:1-ε)
            hurst = Hs[argmin([func(h) for h in Hs])]
        else
            throw("Unknown method: ", method)
        end

        # proc = FractionalWaveletNoiseBank(hurst, F, mode)
        # Σ = covmat(proc, G)
        Σ = fWn_covmat(proc.coeffs, proc.supps, hurst, G; kwargs...)

        # Estimation of volatility
        σ = sqrt(xiAx(Σ, X) / length(X))

        # Log-likelihood
        L = log_likelihood_H(Σ, X)
        # # or equivalently
        # L = log_likelihood(σ^2*Σ, X)
        L /= length(X)  # normalization

        return (hurst=hurst, σ=σ, loglikelihood=L, optimizer=opm)
    end
end

"""
Accelerated MLE by dividing a large vector of samples into smaller ones.

# Args
- X, F: wavelet coefficient matrix and filters
- s: sub window size
- u: downsampling factor
- l: length of decorrelation
"""
function fWn_MLE_estim(X::AbstractMatrix{<:Real}, F::AbstractVector{<:AbstractVector{<:Real}}, s::Integer, u::Integer, l::Integer; kwargs...)
    t, V = rolling_vectorize(X, s, u, l; mode=:causal)  # TODO: case s = size(X,2)
    fWn_MLE_estim(V, F, u*(1:s); kwargs...)  # regular grid is implicitely used here.
end

#### B-Spline MLE ####

"""
Filter of a fWn.

# Note
- The extra 1/sqrt(s) factor is due to the definition of B-Spline wavelet filter and DCWT.
"""
bspline_fWn_filter = (s,v) -> intscale_bspline_filter(s,v) / sqrt(s)

"""
    bspline_MLE_estim(X::AbstractMatrix{<:Real}, sclrng::AbstractVector{<:Integer}, vm::Integer, G::AbstractVector{<:Integer}; kwargs...)

Maximum likelihood estimation of Hurst exponent and volatility for fractional Wavelet noise bank.

# Args
- X: matrix of B-Spline wavelet coefficients. Each row corresponds to a scale.
- sclrng: scales of the rows in `X`.
- vm: vanishing moment of the B-Spline wavelet.
- G: integer grid correpsonding to the columns of `X`.

# Notes
- In rolling window estimation, to avoid recomputing the B-Spline filters one can use directly `fWn_MLE_estim`.
"""
function bspline_MLE_estim(X::AbstractMatrix{<:Real}, sclrng::AbstractVector{<:Integer}, vm::Integer, G::AbstractVector{<:Integer}; kwargs...)
    # @assert size(X,1) == length(sclrng) * length(G)  "Mismatched dimensions."

    F = [bspline_fWn_filter(s, vm) for s in sclrng]
    fWn_MLE_estim(X, F, G; kwargs...)
end

"""
    bspline_MLE_estim(X::AbstractMatrix{<:Real}, sclrng::AbstractVector{<:Integer}, vm::Integer, s::Integer, u::Integer, l::Integer; kwargs...)

Cross-scale and cross-time B-Spline MLE, by concatenating adjacent columns into a larger vector and using both the cross-scale and the cross-time correlations.
"""
function bspline_MLE_estim(X::AbstractMatrix{<:Real}, sclrng::AbstractVector{<:Integer}, vm::Integer, s::Integer, u::Integer, l::Integer; kwargs...)
    t, V = rolling_vectorize(X, s, u, l; mode=:causal)
    bspline_MLE_estim(V, sclrng, vm, u*(1:s); kwargs...)
end

"""
    bspline_MLE_estim(X::AbstractMatrix{<:Real}, sclrng::AbstractVector{<:Integer}, vm::Integer; kwargs...)

Cross-scale only B-Spline MLE, by treating the columns of `X` as independent observations and using only the cross-scale correleation.

# Notes
- Setting the keyword argument `partial=true` to use the local Whittle wavelet estimator.
"""
function bspline_MLE_estim(X::AbstractMatrix{<:Real}, sclrng::AbstractVector{<:Integer}, vm::Integer; kwargs...)
    bspline_MLE_estim(X, sclrng, vm, 1:1; kwargs...)
end

"""
    bspline_MLE_estim(X::AbstractVector{<:Real}, scl::Integer, vm::Integer, args...;  kwargs...)

Cross-time only B-Spline MLE (with acceleration).
"""
function bspline_MLE_estim(X::AbstractVector{<:Real}, scl::Integer, vm::Integer, args...; kwargs...)
    # Call two different functions depending on whether the arguments `s, u, l` are passed or not in `args...`.
    if isempty(args)
        # reshape to a column vector with a regular grid
        bspline_MLE_estim(reshape(X,:,1), [scl], vm, 1:length(X); kwargs...)
    else
        bspline_MLE_estim(reshape(X,1,:), [scl], vm, args...; kwargs...)
    end
end

#### fGn-MLE ####

"""
Filter of a fGn.
"""
fGn_filter = d -> vcat(1, zeros(d-1), -1)

"""
Maximum likelihood estimation of Hurst exponent and volatility for fractional Gaussian noise bank.

# Args
- X: sample matrix.
- lags: time lags of the finite difference operator used for computing `X`.
- G: integer time grid of `X`.
"""
function fGn_MLE_estim(X::AbstractMatrix{<:Real}, lags::AbstractVector{<:Integer}, G::AbstractVector{<:Integer}; kwargs...)
    # @assert size(X,1) == length(lags) * length(G)  "Mismatched dimensions."

    F = [fGn_filter(l) for l in lags]
    fWn_MLE_estim(X, F, G; kwargs...)
end

"""
Cross-scale and cross-time fGn MLE.
"""
function fGn_MLE_estim(X::AbstractMatrix{<:Real}, lags::AbstractVector{<:Integer}, s::Integer, u::Integer, l::Integer; kwargs...)
    t, V = rolling_vectorize(X, s, u, l; mode=:causal)
    fGn_MLE_estim(V, lags, u*(1:s); kwargs...)
end

"""
Cross-scale only fGn MLE.
"""

function fGn_MLE_estim(X::AbstractMatrix{<:Real}, lags::AbstractVector{<:Integer}; kwargs...)
    fGn_MLE_estim(X, lags, 1:1; kwargs...)
end

"""
Cross-time only fGn MLE (with acceleration).
"""
function fGn_MLE_estim(X::AbstractVector{<:Real}, lag::Integer, args...; kwargs...)
    # Call two different functions depending on whether the arguments `s, u, l` are passed or not in `args...`.
    if isempty(args)
        # reshape to a column vector with a regular grid
        fGn_MLE_estim(reshape(X,:,1), [lag], 1:length(X); kwargs...)
    else
        fGn_MLE_estim(reshape(X,1,:), [lag], args...; kwargs...)
    end
end

max_stars_repo_name: yanncalec/FracFin

max_issues_repo_name: yanncalec/FracFin

max_forks_repo_name: yanncalec/FracFin


text:
using Documenter
using Literate
using Markdown
using Reagents

_BANNER_TEXT_ = """
!!! note
    Reagents.jl is still work-in-progress.
"""

_BANNER_ = Markdown.parse(_BANNER_TEXT_)

PAGES = [
    "index.md",
    "Tutorials" => [
        "Treiber stack" => "tutorials/treiberstack.md",
        "Michael and Scott queue" => "tutorials/msqueue.md",
        "Cancellable containers" => "tutorials/cancellablecontainers.md",
        "Catalysts" => "tutorials/catalysts.md",
        "Locks" => "tutorials/locks.md",
        "Negative acknowledgement (NACK)" => "tutorials/nack.md",
        "Promises and Futures" => "tutorials/promises.md",
    ],
    # "How-to guides" => [
    #     "" => "howto/.md",
    # ],
    "Reference" => [
        "API" => "reference/api.md",
        # ...
    ],
    # "Explanation" => [
    #     "Dual data structures" => "explanation/dualcontainers.md",
    # ],
]

function preprocess_example(str)
    output = IOBuffer()

    for ln in eachline(IOBuffer(_BANNER_TEXT_); keep = true)
        print(output, "# ", ln)
    end
    print(output, "\n")

    input = IOBuffer(str)
    while !eof(input)
        ln = readline(input; keep = true)

        # Always treat indented comments as in-code comments
        m = match(r"^( +)# (.*)"s, ln)
        if m !== nothing
            print(output, m[1], "## ", m[2])
            continue
        end

        # De-indent multi-line comments
        m = match(r"^( +)#=\s*$"s, ln)
        if m !== nothing
            nindent = length(m[1])
            print(output, "#=\n")
            while !eof(input)
                ln = readline(input; keep = true)
                m = match(r"^( +)=#\s*$"s, ln)
                if m !== nothing
                    print(output, "=#\n")
                    break
                end
                m = match(Regex("^ {0,$(nindent)}(.*)\$", "s"), ln)
                if m !== nothing
                    print(output, m[1])
                    continue
                end
                print(output, ln)
            end
            continue
        end

        print(output, ln)
    end
    return String(take!(output))
end

let example_dir = joinpath(dirname(@__DIR__), "examples")
    examples = Pair{String,String}[]

    for subpages in PAGES
        subpages isa Pair || continue
        for (_, mdpath) in subpages[2]::Vector
            stem, _ = splitext(basename(mdpath))
            jlpath = joinpath(example_dir, "$stem.jl")
            if !isfile(jlpath)
                @debug "`$jlpath` does not exist. Skipping..."
                continue
            end
            push!(examples, jlpath => joinpath(@__DIR__, "src", dirname(mdpath)))
        end
    end

    @info "Compiling example files" examples
    for (jlpath, dest) in examples
        Literate.markdown(
            jlpath,
            dest;
            preprocess = preprocess_example,
            codefence = "````julia" => "````",
        )
        # Note: Not using Documenter's `@example` since these examples do not
        # have outputs and they are tested via `test/runtests.jl`.
    end
end

makedocs(
    sitename = "Reagents",
    format = Documenter.HTML(),
    modules = [Reagents],
    pages = PAGES,
    doctest = false,  # tested via test/runtests.jl
    checkdocs = :exports,  # ignore complains about `Reagents.:*`
    strict = lowercase(get(ENV, "CI", "false")) == "true",
    # See: https://juliadocs.github.io/Documenter.jl/stable/lib/public/#Documenter.makedocs
)

deploydocs(
    repo = "github.com/JuliaConcurrent/Reagents.jl",
    push_preview = true,
    # See: https://juliadocs.github.io/Documenter.jl/stable/lib/public/#Documenter.deploydocs
)


text:
module BitOps

export swapbit, iszero, setbit, getbit,
       clearbit, flipbit, removebit, todecimal

# Guarantee a 64-bit unsigned integer
const ONE = 0x00000001

function todecimal(bindigits::Vector{Int})
    result = 0
    offset = length(bindigits) - 1
    for d in bindigits
        result |= (d << offset)
        offset -= 1
    end
    return result
end

function swapbit(num::Int, from::Int, to::Int)
    temp = ((num >>> from) $ (num >>> to)) & ONE
    return num $ ((temp << from) | (temp << to))
end

function iszero(num::Int, bit::Int)
    return (num & (ONE << bit)) == 0
end

function getbit(num::Int, bit::Int)
    return (num & (ONE << bit)) >>> bit
end

function setbit(num::Int, bit::Int)
    return num | (ONE << bit)
end

function flipbit(num::Int, bit::Int)
    return num $ (ONE << bit)
end

function clearbit(num::Int, bit::Int)
    return num & ~(ONE << bit)
end

function removebit(num::Int, bit::Int)
    upper = (num & (0xffffffff << (bit + 1))) >>> 1
    lower = num & ((ONE << bit) - 1)
    return upper | lower
end

end

max_stars_repo_name: leios/quantum

max_issues_repo_name: leios/quantum

max_forks_repo_name: leios/quantum