
#include "gdal_layer.hpp"
#include "collections/layer_features.hpp"
#include "collections/layer_fields.hpp"
#include "gdal_common.hpp"
#include "gdal_dataset.hpp"
#include "gdal_feature.hpp"
#include "gdal_feature_defn.hpp"
#include "gdal_field_defn.hpp"
#include "gdal_geometry.hpp"
#include "gdal_spatial_reference.hpp"

#include <sstream>
#include <stdlib.h>

namespace node_gdal {

Nan::Persistent<FunctionTemplate> Layer::constructor;
ObjectCache<OGRLayer, Layer> Layer::cache;

void Layer::Initialize(Local<Object> target) {
  Nan::HandleScope scope;

  Local<FunctionTemplate> lcons = Nan::New<FunctionTemplate>(Layer::New);
  lcons->InstanceTemplate()->SetInternalFieldCount(1);
  lcons->SetClassName(Nan::New("Layer").ToLocalChecked());

  Nan::SetPrototypeMethod(lcons, "toString", toString);
  Nan::SetPrototypeMethod(lcons, "getExtent", getExtent);
  Nan::SetPrototypeMethod(lcons, "setAttributeFilter", setAttributeFilter);
  Nan::SetPrototypeMethod(lcons, "setSpatialFilter", setSpatialFilter);
  Nan::SetPrototypeMethod(lcons, "getSpatialFilter", getSpatialFilter);
  Nan::SetPrototypeMethod(lcons, "testCapability", testCapability);
  Nan::SetPrototypeMethod(lcons, "flush", syncToDisk);

  ATTR_DONT_ENUM(lcons, "ds", dsGetter, READ_ONLY_SETTER);
  ATTR_DONT_ENUM(lcons, "_uid", uidGetter, READ_ONLY_SETTER);
  ATTR(lcons, "srs", srsGetter, READ_ONLY_SETTER);
  ATTR(lcons, "features", featuresGetter, READ_ONLY_SETTER);
  ATTR(lcons, "fields", fieldsGetter, READ_ONLY_SETTER);
  ATTR(lcons, "name", nameGetter, READ_ONLY_SETTER);
  ATTR(lcons, "geomType", geomTypeGetter, READ_ONLY_SETTER);
  ATTR(lcons, "geomColumn", geomColumnGetter, READ_ONLY_SETTER);
  ATTR(lcons, "fidColumn", fidColumnGetter, READ_ONLY_SETTER);

  Nan::Set(target, Nan::New("Layer").ToLocalChecked(), Nan::GetFunction(lcons).ToLocalChecked());

  constructor.Reset(lcons);
}

Layer::Layer(OGRLayer *layer) : Nan::ObjectWrap(), uid(0), this_(layer), parent_ds(0) {
  LOG("Created layer [%p]", layer);
}

Layer::Layer() : Nan::ObjectWrap(), uid(0), this_(0), parent_ds(0) {
}

Layer::~Layer() {
  dispose();
}

void Layer::dispose() {
  if (this_) {

    LOG("Disposing layer [%p]", this_);

    ptr_manager.dispose(uid);

    LOG("Disposed layer [%p]", this_);
    this_ = NULL;
  }
};

/**
 * A representation of a layer of simple vector features, with access methods.
 *
 * @class gdal.Layer
 */
NAN_METHOD(Layer::New) {
  Nan::HandleScope scope;

  if (!info.IsConstructCall()) {
    Nan::ThrowError("Cannot call constructor as function, you need to use 'new' keyword");
    return;
  }

  if (info[0]->IsExternal()) {
    Local<External> ext = info[0].As<External>();
    void *ptr = ext->Value();
    Layer *f = static_cast<Layer *>(ptr);
    f->Wrap(info.This());

    Local<Value> features = LayerFeatures::New(info.This());
    Nan::SetPrivate(info.This(), Nan::New("features_").ToLocalChecked(), features);

    Local<Value> fields = LayerFields::New(info.This());
    Nan::SetPrivate(info.This(), Nan::New("fields_").ToLocalChecked(), fields);

    info.GetReturnValue().Set(info.This());
    return;
  } else {
    Nan::ThrowError("Cannot create layer directly. Create with dataset instead.");
    return;
  }

  info.GetReturnValue().Set(info.This());
}

#if GDAL_VERSION_MAJOR >= 2
Local<Value> Layer::New(OGRLayer *raw, GDALDataset *raw_parent)
#else
Local<Value> Layer::New(OGRLayer *raw, OGRDataSource *raw_parent)
#endif
{
  Nan::EscapableHandleScope scope;
  return scope.Escape(Layer::New(raw, raw_parent, false));
}

#if GDAL_VERSION_MAJOR >= 2
Local<Value> Layer::New(OGRLayer *raw, GDALDataset *raw_parent, bool result_set)
#else
Local<Value> Layer::New(OGRLayer *raw, OGRDataSource *raw_parent, bool result_set)
#endif
{
  Nan::EscapableHandleScope scope;

  if (!raw) { return scope.Escape(Nan::Null()); }
  if (cache.has(raw)) { return scope.Escape(cache.get(raw)); }

  Layer *wrapped = new Layer(raw);

  Local<Value> ext = Nan::New<External>(wrapped);
  Local<Object> obj =
    Nan::NewInstance(Nan::GetFunction(Nan::New(Layer::constructor)).ToLocalChecked(), 1, &ext).ToLocalChecked();

  cache.add(raw, obj);

  // add reference to datasource so datasource doesnt get GC'ed while layer is
  // alive
  Local<Object> ds;
#if GDAL_VERSION_MAJOR >= 2
  if (Dataset::dataset_cache.has(raw_parent)) {
    ds = Dataset::dataset_cache.get(raw_parent);
  }
#else
  if (Dataset::datasource_cache.has(raw_parent)) {
    ds = Dataset::datasource_cache.get(raw_parent);
  }
#endif
  else {
    LOG("Layer's parent dataset disappeared from cache (layer = %p, dataset = %p)", raw, raw_parent);
    Nan::ThrowError("Layer's parent dataset disappeared from cache");
    return scope.Escape(Nan::Undefined());
    // ds = Dataset::New(raw_parent); //should never happen
  }

  long parent_uid = Nan::ObjectWrap::Unwrap<Dataset>(ds)->uid;

  wrapped->uid = ptr_manager.add(raw, parent_uid, result_set);
  wrapped->parent_ds = raw_parent;
  Nan::SetPrivate(obj, Nan::New("ds_").ToLocalChecked(), ds);

  return scope.Escape(obj);
}

NAN_METHOD(Layer::toString) {
  Nan::HandleScope scope;

  Layer *layer = Nan::ObjectWrap::Unwrap<Layer>(info.This());
  if (!layer->this_) {
    info.GetReturnValue().Set(Nan::New("Null layer").ToLocalChecked());
    return;
  }

  std::ostringstream ss;
  ss << "Layer (" << layer->this_->GetName() << ")";

  info.GetReturnValue().Set(SafeString::New(ss.str().c_str()));
}

/**
 * Flush pending changes to disk.
 *
 * @throws Error
 * @method flush
 */
NODE_WRAPPED_METHOD_WITH_OGRERR_RESULT(Layer, syncToDisk, SyncToDisk);

/**
 * Determines if the dataset supports the indicated operation.
 *
 * @method testCapability
 * @param {string} capability (see {{#crossLink "Constants (OLC)"}}capability
 * list{{/crossLink}})
 * @return {Boolean}
 */
NODE_WRAPPED_METHOD_WITH_RESULT_1_STRING_PARAM(Layer, testCapability, Boolean, TestCapability, "capability");

/**
 * Fetch the extent of this layer.
 *
 * @throws Error
 * @method getExtent
 * @param {Boolean} [force=true]
 * @return {gdal.Envelope} Bounding envelope
 */
NAN_METHOD(Layer::getExtent) {
  Nan::HandleScope scope;

  Layer *layer = Nan::ObjectWrap::Unwrap<Layer>(info.This());
  if (!layer->isAlive()) {
    Nan::ThrowError("Layer object has already been destroyed");
    return;
  }

  int force = 1;
  NODE_ARG_BOOL_OPT(0, "force", force);

  OGREnvelope *envelope = new OGREnvelope();
  OGRErr err = layer->this_->GetExtent(envelope, force);
  if (err) {
    Nan::ThrowError("Can't get layer extent without computing it");
    return;
  }

  Local<Object> obj = Nan::New<Object>();
  Nan::Set(obj, Nan::New("minX").ToLocalChecked(), Nan::New<Number>(envelope->MinX));
  Nan::Set(obj, Nan::New("maxX").ToLocalChecked(), Nan::New<Number>(envelope->MaxX));
  Nan::Set(obj, Nan::New("minY").ToLocalChecked(), Nan::New<Number>(envelope->MinY));
  Nan::Set(obj, Nan::New("maxY").ToLocalChecked(), Nan::New<Number>(envelope->MaxY));

  delete envelope;

  info.GetReturnValue().Set(obj);
}

/**
 * This method returns the current spatial filter for this layer.
 *
 * @throws Error
 * @method getSpatialFilter
 * @return {gdal.Geometry}
 */
NAN_METHOD(Layer::getSpatialFilter) {
  Nan::HandleScope scope;

  Layer *layer = Nan::ObjectWrap::Unwrap<Layer>(info.This());
  if (!layer->isAlive()) {
    Nan::ThrowError("Layer object has already been destroyed");
    return;
  }

  info.GetReturnValue().Set(Geometry::New(layer->this_->GetSpatialFilter(), false));
}

/**
 * This method sets the geometry to be used as a spatial filter when fetching
 * features via the `layer.features.next()` method. Only features that
 * geometrically intersect the filter geometry will be returned.
 *
 * Alernatively you can pass it envelope bounds as individual arguments.
 *
 * @example
 * ```
 * layer.setSpatialFilter(geometry);
 * layer.setSpatialFilter(minX, minY, maxX, maxY);```
 *
 * @throws Error
 * @method setSpatialFilter
 * @param {gdal.Geometry} filter
 */
NAN_METHOD(Layer::setSpatialFilter) {
  Nan::HandleScope scope;

  Layer *layer = Nan::ObjectWrap::Unwrap<Layer>(info.This());
  if (!layer->isAlive()) {
    Nan::ThrowError("Layer object has already been destroyed");
    return;
  }

  if (info.Length() == 1) {
    Geometry *filter = NULL;
    NODE_ARG_WRAPPED_OPT(0, "filter", Geometry, filter);

    if (filter) {
      layer->this_->SetSpatialFilter(filter->get());
    } else {
      layer->this_->SetSpatialFilter(NULL);
    }
  } else if (info.Length() == 4) {
    double minX, minY, maxX, maxY;
    NODE_ARG_DOUBLE(0, "minX", minX);
    NODE_ARG_DOUBLE(1, "minY", minY);
    NODE_ARG_DOUBLE(2, "maxX", maxX);
    NODE_ARG_DOUBLE(3, "maxY", maxY);

    layer->this_->SetSpatialFilterRect(minX, minY, maxX, maxY);
  } else {
    Nan::ThrowError("Invalid number of arguments");
    return;
  }

  return;
}

/**
 * Sets the attribute query string to be used when fetching features via the
 * `layer.features.next()` 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](http://www.gdal.org/ogr_sql.html). In some cases (RDBMS backed
 * drivers) the native capabilities of the database may be used to interprete
 * the WHERE clause in which case the capabilities will be broader than those
 * of OGR SQL.
 *
 * @example
 * ```
 * layer.setAttributeFilter('population > 1000000 and population < 5000000');```
 *
 * @throws Error
 * @method setAttributeFilter
 * @param {String} filter
 */
NAN_METHOD(Layer::setAttributeFilter) {
  Nan::HandleScope scope;

  Layer *layer = Nan::ObjectWrap::Unwrap<Layer>(info.This());
  if (!layer->isAlive()) {
    Nan::ThrowError("Layer object has already been destroyed");
    return;
  }

  std::string filter = "";
  NODE_ARG_OPT_STR(0, "filter", filter);

  OGRErr err;
  if (filter.empty()) {
    err = layer->this_->SetAttributeFilter(NULL);
  } else {
    err = layer->this_->SetAttributeFilter(filter.c_str());
  }

  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }

  return;
}

/*
NAN_METHOD(Layer::getLayerDefn)
{
  Nan::HandleScope scope;

  Layer *layer = Nan::ObjectWrap::Unwrap<Layer>(info.This());

  if (!layer->isAlive()) {
    Nan::ThrowError("Layer object already destroyed");
    return;
  }

  info.GetReturnValue().Set(FeatureDefn::New(layer->this_->GetLayerDefn(),
false));
}*/

/**
 * @readOnly
 * @attribute ds
 * @type {gdal.Dataset}
 */
NAN_GETTER(Layer::dsGetter) {
  Nan::HandleScope scope;
  info.GetReturnValue().Set(Nan::GetPrivate(info.This(), Nan::New("ds_").ToLocalChecked()).ToLocalChecked());
}

/**
 * @readOnly
 * @attribute srs
 * @type {gdal.SpatialReference}
 */
NAN_GETTER(Layer::srsGetter) {
  Nan::HandleScope scope;
  Layer *layer = Nan::ObjectWrap::Unwrap<Layer>(info.This());
  if (!layer->isAlive()) {
    Nan::ThrowError("Layer object has already been destroyed");
    return;
  }
  info.GetReturnValue().Set(SpatialReference::New(layer->this_->GetSpatialRef(), false));
}

/**
 * @readOnly
 * @attribute name
 * @type {String}
 */
NAN_GETTER(Layer::nameGetter) {
  Nan::HandleScope scope;
  Layer *layer = Nan::ObjectWrap::Unwrap<Layer>(info.This());
  if (!layer->isAlive()) {
    Nan::ThrowError("Layer object has already been destroyed");
    return;
  }
  info.GetReturnValue().Set(SafeString::New(layer->this_->GetName()));
}

/**
 * @readOnly
 * @attribute geomColumn
 * @type {String}
 */
NAN_GETTER(Layer::geomColumnGetter) {
  Nan::HandleScope scope;
  Layer *layer = Nan::ObjectWrap::Unwrap<Layer>(info.This());
  if (!layer->isAlive()) {
    Nan::ThrowError("Layer object has already been destroyed");
    return;
  }
  info.GetReturnValue().Set(SafeString::New(layer->this_->GetGeometryColumn()));
}

/**
 * @readOnly
 * @attribute fidColumn
 * @type {String}
 */
NAN_GETTER(Layer::fidColumnGetter) {
  Nan::HandleScope scope;
  Layer *layer = Nan::ObjectWrap::Unwrap<Layer>(info.This());
  if (!layer->isAlive()) {
    Nan::ThrowError("Layer object has already been destroyed");
    return;
  }
  info.GetReturnValue().Set(SafeString::New(layer->this_->GetFIDColumn()));
}

/**
 * @readOnly
 * @attribute geomType
 * @type {Integer} (see {{#crossLink "Constants (wkb)"}}geometry
 * types{{/crossLink}})
 */
NAN_GETTER(Layer::geomTypeGetter) {
  Nan::HandleScope scope;
  Layer *layer = Nan::ObjectWrap::Unwrap<Layer>(info.This());
  if (!layer->isAlive()) {
    Nan::ThrowError("Layer object has already been destroyed");
    return;
  }
  info.GetReturnValue().Set(Nan::New<Integer>(layer->this_->GetGeomType()));
}

/**
 * @readOnly
 * @attribute features
 * @type {gdal.LayerFeatures}
 */
NAN_GETTER(Layer::featuresGetter) {
  Nan::HandleScope scope;
  info.GetReturnValue().Set(Nan::GetPrivate(info.This(), Nan::New("features_").ToLocalChecked()).ToLocalChecked());
}

/**
 * @readOnly
 * @attribute fields
 * @type {gdal.LayerFields}
 */
NAN_GETTER(Layer::fieldsGetter) {
  Nan::HandleScope scope;
  info.GetReturnValue().Set(Nan::GetPrivate(info.This(), Nan::New("fields_").ToLocalChecked()).ToLocalChecked());
}

NAN_GETTER(Layer::uidGetter) {
  Nan::HandleScope scope;
  Layer *layer = Nan::ObjectWrap::Unwrap<Layer>(info.This());
  info.GetReturnValue().Set(Nan::New((int)layer->uid));
}

} // namespace node_gdal
