
#include "gdal_spatial_reference.hpp"
#include "gdal_common.hpp"
#include "utils/string_list.hpp"

namespace node_gdal {

Nan::Persistent<FunctionTemplate> SpatialReference::constructor;
ObjectCache<OGRSpatialReference, SpatialReference> SpatialReference::cache;

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

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

  Nan::SetMethod(lcons, "fromUserInput", fromUserInput);
  Nan::SetMethod(lcons, "fromWKT", fromWKT);
  Nan::SetMethod(lcons, "fromProj4", fromProj4);
  Nan::SetMethod(lcons, "fromEPSG", fromEPSG);
  Nan::SetMethod(lcons, "fromEPSGA", fromEPSGA);
  Nan::SetMethod(lcons, "fromESRI", fromESRI);
  Nan::SetMethod(lcons, "fromWMSAUTO", fromWMSAUTO);
  Nan::SetMethod(lcons, "fromXML", fromXML);
  Nan::SetMethod(lcons, "fromURN", fromURN);
  Nan::SetMethod(lcons, "fromCRSURL", fromCRSURL);
  Nan::SetMethod(lcons, "fromURL", fromURL);
  Nan::SetMethod(lcons, "fromMICoordSys", fromMICoordSys);

  Nan::SetPrototypeMethod(lcons, "toString", toString);
  Nan::SetPrototypeMethod(lcons, "toWKT", exportToWKT);
  Nan::SetPrototypeMethod(lcons, "toPrettyWKT", exportToPrettyWKT);
  Nan::SetPrototypeMethod(lcons, "toProj4", exportToProj4);
  Nan::SetPrototypeMethod(lcons, "toXML", exportToXML);

  Nan::SetPrototypeMethod(lcons, "clone", clone);
  Nan::SetPrototypeMethod(lcons, "cloneGeogCS", cloneGeogCS);
  Nan::SetPrototypeMethod(lcons, "setWellKnownGeogCS", setWellKnownGeogCS);
  Nan::SetPrototypeMethod(lcons, "morphToESRI", morphToESRI);
  Nan::SetPrototypeMethod(lcons, "morphFromESRI", morphFromESRI);
  Nan::SetPrototypeMethod(lcons, "EPSGTreatsAsLatLong", EPSGTreatsAsLatLong);
  Nan::SetPrototypeMethod(lcons, "EPSGTreatsAsNorthingEasting", EPSGTreatsAsNorthingEasting);
  Nan::SetPrototypeMethod(lcons, "getLinearUnits", getLinearUnits);
  Nan::SetPrototypeMethod(lcons, "getAngularUnits", getAngularUnits);
  Nan::SetPrototypeMethod(lcons, "isGeographic", isGeographic);
  Nan::SetPrototypeMethod(lcons, "isGeocentric", isGeocentric);
  Nan::SetPrototypeMethod(lcons, "isProjected", isProjected);
  Nan::SetPrototypeMethod(lcons, "isLocal", isLocal);
  Nan::SetPrototypeMethod(lcons, "isVectical", isVertical);
  Nan::SetPrototypeMethod(lcons, "isCompound", isCompound);
  Nan::SetPrototypeMethod(lcons, "isSameGeogCS", isSameGeogCS);
  Nan::SetPrototypeMethod(lcons, "isSameVertCS", isSameVertCS);
  Nan::SetPrototypeMethod(lcons, "isSame", isSame);
  Nan::SetPrototypeMethod(lcons, "getAuthorityName", getAuthorityName);
  Nan::SetPrototypeMethod(lcons, "getAuthorityCode", getAuthorityCode);
  Nan::SetPrototypeMethod(lcons, "getAttrValue", getAttrValue);
  Nan::SetPrototypeMethod(lcons, "autoIdentifyEPSG", autoIdentifyEPSG);
  Nan::SetPrototypeMethod(lcons, "validate", validate);

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

  constructor.Reset(lcons);
}

SpatialReference::SpatialReference(OGRSpatialReference *srs) : Nan::ObjectWrap(), this_(srs), owned_(false) {
  LOG("Created SpatialReference [%p]", srs);
}

SpatialReference::SpatialReference() : Nan::ObjectWrap(), this_(0), owned_(false) {
}

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

void SpatialReference::dispose() {
  if (this_) {
    LOG("Disposing SpatialReference [%p] (%s)", this_, owned_ ? "owned" : "unowned");
    cache.erase(this_);
    if (owned_) {
      // Decrements the reference count by one, and destroy if zero.
      this_->Release();
    }
    LOG("Disposed SpatialReference [%p]", this_);
    this_ = NULL;
  }
}

/**
 * This class respresents a OpenGIS Spatial Reference System, and contains
 * methods for converting between this object organization and well known text
 * (WKT) format.
 *
 * @constructor
 * @class gdal.SpatialReference
 * @param {String} [wkt]
 */
NAN_METHOD(SpatialReference::New) {
  Nan::HandleScope scope;
  SpatialReference *f;
  OGRSpatialReference *srs;
  std::string wkt("");

  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();
    f = static_cast<SpatialReference *>(ptr);
    f->Wrap(info.This());
  } else {
    NODE_ARG_OPT_STR(0, "wkt", wkt);
    // sets reference count to one
    srs = new OGRSpatialReference(wkt.empty() ? 0 : wkt.c_str());
    if (!wkt.empty()) {
      OGRChar *wkt_c = (OGRChar *)wkt.c_str();
      int err = srs->importFromWkt(&wkt_c);
      if (err) {
        NODE_THROW_OGRERR(err);
        return;
      }
    }
    f = new SpatialReference(srs);
    f->owned_ = true;
    f->Wrap(info.This());

    cache.add(srs, info.This());
  }

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

Local<Value> SpatialReference::New(OGRSpatialReference *srs) {
  Nan::EscapableHandleScope scope;
  return scope.Escape(SpatialReference::New(srs, false));
}

Local<Value> SpatialReference::New(OGRSpatialReference *raw, bool owned) {
  Nan::EscapableHandleScope scope;

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

  // make a copy of spatialreference owned by a layer, feature, etc
  // + no need to track when a layer is destroyed
  // + no need to throw errors when a method trys to modify an owned read-only
  // srs
  // - is slower

  OGRSpatialReference *cloned_srs = raw->Clone();

  SpatialReference *wrapped = new SpatialReference(cloned_srs);
  wrapped->owned_ = true;
  Local<Value> ext = Nan::New<External>(wrapped);
  Local<Object> obj =
    Nan::NewInstance(Nan::GetFunction(Nan::New(SpatialReference::constructor)).ToLocalChecked(), 1, &ext)
      .ToLocalChecked();

  cache.add(cloned_srs, raw, obj);

  return scope.Escape(obj);
}

NAN_METHOD(SpatialReference::toString) {
  Nan::HandleScope scope;
  info.GetReturnValue().Set(Nan::New("SpatialReference").ToLocalChecked());
}

/**
 * Set a GeogCS based on well known name.
 *
 * @method setWellKnownGeogCS
 * @param {String} name
 */
NODE_WRAPPED_METHOD_WITH_OGRERR_RESULT_1_STRING_PARAM(
  SpatialReference, setWellKnownGeogCS, SetWellKnownGeogCS, "input");

/**
 * Convert in place to ESRI WKT format.
 *
 * @throws Error
 * @method morphToESRI
 */
NODE_WRAPPED_METHOD_WITH_OGRERR_RESULT(SpatialReference, morphToESRI, morphToESRI);

/**
 * Convert in place from ESRI WKT format.
 *
 * @throws Error
 * @method morphFromESRI
 */
NODE_WRAPPED_METHOD_WITH_OGRERR_RESULT(SpatialReference, morphFromESRI, morphFromESRI);

/**
 * This method returns `true` if EPSG feels this geographic coordinate system
 * should be treated as having lat/long coordinate ordering.
 *
 * Currently this returns `true` for all geographic coordinate systems with an
 * EPSG code set, and AXIS values set defining it as lat, long. Note that
 * coordinate systems with an EPSG code and no axis settings will be assumed
 * to not be lat/long.
 *
 * `false` will be returned for all coordinate systems that are not geographic,
 * or that do not have an EPSG code set.
 *
 * @method EPSGTreatsAsLatLong
 * @return {Boolean}
 */
NODE_WRAPPED_METHOD_WITH_RESULT(SpatialReference, EPSGTreatsAsLatLong, Boolean, EPSGTreatsAsLatLong);

/**
 * This method returns `true` if EPSG feels this projected coordinate system
 * should be treated as having northing/easting coordinate ordering.
 *
 * @method EPSGTreatsAsNorthingEasting
 * @return {Boolean}
 */
NODE_WRAPPED_METHOD_WITH_RESULT(SpatialReference, EPSGTreatsAsNorthingEasting, Boolean, EPSGTreatsAsNorthingEasting);

/**
 * Check if geocentric coordinate system.
 *
 * @method isGeocentric
 * @return {Boolean}
 */
NODE_WRAPPED_METHOD_WITH_RESULT(SpatialReference, isGeocentric, Boolean, IsGeocentric);

/**
 * Check if geographic coordinate system.
 *
 * @method isGeographic
 * @return {Boolean}
 */
NODE_WRAPPED_METHOD_WITH_RESULT(SpatialReference, isGeographic, Boolean, IsGeographic);

/**
 * Check if projected coordinate system.
 *
 * @method isProjected
 * @return {Boolean}
 */
NODE_WRAPPED_METHOD_WITH_RESULT(SpatialReference, isProjected, Boolean, IsProjected);

/**
 * Check if local coordinate system.
 *
 * @method isLocal
 * @return {Boolean}
 */
NODE_WRAPPED_METHOD_WITH_RESULT(SpatialReference, isLocal, Boolean, IsLocal);

/**
 * Check if vertical coordinate system.
 *
 * @method isVertical
 * @return {Boolean}
 */
NODE_WRAPPED_METHOD_WITH_RESULT(SpatialReference, isVertical, Boolean, IsVertical);

/**
 * Check if compound coordinate system.
 *
 * @method isCompound
 * @return {Boolean}
 */
NODE_WRAPPED_METHOD_WITH_RESULT(SpatialReference, isCompound, Boolean, IsCompound);

/**
 * Do the GeogCS'es match?
 *
 * @method isSameGeogCS
 * @param {gdal.SpatialReference} srs
 * @return {Boolean}
 */
NODE_WRAPPED_METHOD_WITH_RESULT_1_WRAPPED_PARAM(
  SpatialReference, isSameGeogCS, Boolean, IsSameGeogCS, SpatialReference, "srs");

/**
 * Do the VertCS'es match?
 *
 * @method isSameVertCS
 * @param {gdal.SpatialReference} srs
 * @return {Boolean}
 */
NODE_WRAPPED_METHOD_WITH_RESULT_1_WRAPPED_PARAM(
  SpatialReference, isSameVertCS, Boolean, IsSameVertCS, SpatialReference, "srs");

/**
 * Do these two spatial references describe the same system?
 *
 * @method isSame
 * @param {gdal.SpatialReference} srs
 * @return {Boolean}
 */
NODE_WRAPPED_METHOD_WITH_RESULT_1_WRAPPED_PARAM(SpatialReference, isSame, Boolean, IsSame, SpatialReference, "srs");

/**
 * Set EPSG authority info if possible.
 *
 * @throws Error
 * @method autoIdentifyEPSG
 */
NODE_WRAPPED_METHOD_WITH_OGRERR_RESULT(SpatialReference, autoIdentifyEPSG, AutoIdentifyEPSG);

/**
 * Clones the spatial reference.
 *
 * @method clone
 * @return {gdal.SpatialReference}
 */
NAN_METHOD(SpatialReference::clone) {
  Nan::HandleScope scope;
  SpatialReference *srs = Nan::ObjectWrap::Unwrap<SpatialReference>(info.This());
  info.GetReturnValue().Set(SpatialReference::New(srs->this_->Clone()));
}

/**
 * Make a duplicate of the GEOGCS node of this OGRSpatialReference object.
 *
 * @method cloneGeogCS
 * @return {gdal.SpatialReference}
 */
NAN_METHOD(SpatialReference::cloneGeogCS) {
  Nan::HandleScope scope;
  SpatialReference *srs = Nan::ObjectWrap::Unwrap<SpatialReference>(info.This());
  info.GetReturnValue().Set(SpatialReference::New(srs->this_->CloneGeogCS()));
}

/**
 * Get the authority name for a node. The most common authority is "EPSG".
 *
 * @method getAuthorityName
 * @param {string} target_key The partial or complete path to the node to get an
 * authority from. ie. `"PROJCS"`, `"GEOGCS"`, "`GEOGCS|UNIT"` or `null` to
 * search for an authority node on the root element.
 * @return {string}
 */
NAN_METHOD(SpatialReference::getAuthorityName) {
  Nan::HandleScope scope;

  std::string key = "";
  NODE_ARG_OPT_STR(0, "target key", key);

  SpatialReference *srs = Nan::ObjectWrap::Unwrap<SpatialReference>(info.This());

  info.GetReturnValue().Set(SafeString::New(srs->this_->GetAuthorityName(key.length() ? key.c_str() : NULL)));
}

/**
 * Get the authority code for a node.
 *
 * @method getAuthorityCode
 * @param {string} target_key The partial or complete path to the node to get an
 * authority from. ie. `"PROJCS"`, `"GEOGCS"`, "`GEOGCS|UNIT"` or `null` to
 * search for an authority node on the root element.
 * @return {string}
 */
NAN_METHOD(SpatialReference::getAuthorityCode) {
  Nan::HandleScope scope;

  std::string key = "";
  NODE_ARG_OPT_STR(0, "target key", key);

  SpatialReference *srs = Nan::ObjectWrap::Unwrap<SpatialReference>(info.This());

  info.GetReturnValue().Set(SafeString::New(srs->this_->GetAuthorityCode(key.length() ? key.c_str() : NULL)));
}

/**
 * Convert this SRS into WKT format.
 *
 * @throws Error
 * @method toWKT
 * @return {string}
 */
NAN_METHOD(SpatialReference::exportToWKT) {
  Nan::HandleScope scope;

  SpatialReference *srs = Nan::ObjectWrap::Unwrap<SpatialReference>(info.This());
  char *str;
  Local<Value> result;

  int err = srs->this_->exportToWkt(&str);
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }
  result = SafeString::New(str);
  CPLFree(str);

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

/**
 * Convert this SRS into a a nicely formatted WKT string for display to a
 * person.
 *
 * @throws Error
 * @method toPrettyWKT
 * @param {Boolean} [simplify=false]
 * @return {string}
 */
NAN_METHOD(SpatialReference::exportToPrettyWKT) {
  Nan::HandleScope scope;

  int simplify = 0;
  NODE_ARG_BOOL_OPT(0, "simplify", simplify);

  SpatialReference *srs = Nan::ObjectWrap::Unwrap<SpatialReference>(info.This());
  char *str;
  Local<Value> result;

  int err = srs->this_->exportToPrettyWkt(&str, simplify);
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }
  result = SafeString::New(str);
  CPLFree(str);

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

/**
 * Export coordinate system in PROJ.4 format.
 *
 * @throws Error
 * @method toProj4
 * @return {string}
 */
NAN_METHOD(SpatialReference::exportToProj4) {
  Nan::HandleScope scope;

  SpatialReference *srs = Nan::ObjectWrap::Unwrap<SpatialReference>(info.This());
  char *str;
  Local<Value> result;

  int err = srs->this_->exportToProj4(&str);
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }

  if (str) {
    result = Nan::New(CPLString(str).Trim().c_str()).ToLocalChecked();
  } else {
    result = Nan::Null();
  }
  CPLFree(str);

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

/**
 * Export coordinate system in XML format.
 *
 * @throws Error
 * @method toXML
 * @return {string}
 */
NAN_METHOD(SpatialReference::exportToXML) {
  Nan::HandleScope scope;

  SpatialReference *srs = Nan::ObjectWrap::Unwrap<SpatialReference>(info.This());
  char *str;
  Local<Value> result;

  int err = srs->this_->exportToXML(&str);
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }
  result = SafeString::New(str);
  CPLFree(str);

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

/**
 * Fetch indicated attribute of named node.
 *
 * @method getAttrValue
 * @param {String} node_name
 * @param {Integer} [attr_index=0]
 * @return {string}
 */
NAN_METHOD(SpatialReference::getAttrValue) {
  Nan::HandleScope scope;

  SpatialReference *srs = Nan::ObjectWrap::Unwrap<SpatialReference>(info.This());
  std::string node_name("");
  int child = 0;
  NODE_ARG_STR(0, "node name", node_name);
  NODE_ARG_INT_OPT(1, "child", child);
  info.GetReturnValue().Set(SafeString::New(srs->this_->GetAttrValue(node_name.c_str(), child)));
}

/**
 * Creates a spatial reference from a WKT string.
 *
 * @static
 * @throws Error
 * @method fromWKT
 * @param {String} wkt
 * @return {gdal.SpatialReference}
 */
NAN_METHOD(SpatialReference::fromWKT) {
  Nan::HandleScope scope;

  std::string wkt("");
  NODE_ARG_STR(0, "wkt", wkt);
  OGRChar *str = (OGRChar *)wkt.c_str();

  OGRSpatialReference *srs = new OGRSpatialReference();
  int err = srs->importFromWkt(&str);
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }

  info.GetReturnValue().Set(SpatialReference::New(srs, true));
}

/**
 * Creates a spatial reference from a Proj.4 string.
 *
 * @static
 * @throws Error
 * @method fromProj4
 * @param {String} input
 * @return {gdal.SpatialReference}
 */
NAN_METHOD(SpatialReference::fromProj4) {
  Nan::HandleScope scope;

  std::string input("");
  NODE_ARG_STR(0, "input", input);

  OGRSpatialReference *srs = new OGRSpatialReference();
  int err = srs->importFromProj4(input.c_str());
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }

  info.GetReturnValue().Set(SpatialReference::New(srs, true));
}

/**
 * Creates a spatial reference from a WMSAUTO string.
 *
 * Note that the WMS 1.3 specification does not include the units code, while
 * apparently earlier specs do. GDAL tries to guess around this.
 *
 * @example
 * ```
 * var wms = 'AUTO:42001,99,8888';
 * var ref = gdal.SpatialReference.fromWMSAUTO(wms);```
 *
 * @static
 * @throws Error
 * @method fromWMSAUTO
 * @param {String} input
 * @return {gdal.SpatialReference}
 */
NAN_METHOD(SpatialReference::fromWMSAUTO) {
  Nan::HandleScope scope;

  std::string input("");
  NODE_ARG_STR(0, "input", input);

  OGRSpatialReference *srs = new OGRSpatialReference();
  int err = srs->importFromWMSAUTO(input.c_str());
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }

  info.GetReturnValue().Set(SpatialReference::New(srs, true));
}

/**
 * Import coordinate system from XML format (GML only currently).
 *
 * @static
 * @throws Error
 * @method fromXML
 * @param {String} input
 * @return {gdal.SpatialReference}
 */
NAN_METHOD(SpatialReference::fromXML) {
  Nan::HandleScope scope;

  std::string input("");
  NODE_ARG_STR(0, "xml", input);

  OGRSpatialReference *srs = new OGRSpatialReference();
  int err = srs->importFromXML(input.c_str());
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }

  info.GetReturnValue().Set(SpatialReference::New(srs, true));
}

/**
 * Initialize from OGC URN.
 *
 * The OGC URN should be prefixed with "urn:ogc:def:crs:" per recommendation
 * paper 06-023r1. Currently EPSG and OGC authority values are supported,
 * including OGC auto codes, but not including CRS1 or CRS88 (NAVD88).
 *
 * @static
 * @throws Error
 * @method fromURN
 * @param {String} input
 * @return {gdal.SpatialReference}
 */
NAN_METHOD(SpatialReference::fromURN) {
  Nan::HandleScope scope;

  std::string input("");
  NODE_ARG_STR(0, "input", input);

  OGRSpatialReference *srs = new OGRSpatialReference();
  int err = srs->importFromURN(input.c_str());
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }

  info.GetReturnValue().Set(SpatialReference::New(srs, true));
}

/**
 * Initialize from OGC URL.
 *
 * The OGC URL should be prefixed with "http://opengis.net/def/crs" per best
 * practice paper 11-135. Currently EPSG and OGC authority values are supported,
 * including OGC auto codes, but not including CRS1 or CRS88 (NAVD88).
 *
 * @static
 * @throws Error
 * @method fromCRSURL
 * @param {String} input
 * @return {gdal.SpatialReference}
 */
NAN_METHOD(SpatialReference::fromCRSURL) {
  Nan::HandleScope scope;

  std::string input("");
  NODE_ARG_STR(0, "url", input);

  OGRSpatialReference *srs = new OGRSpatialReference();
  int err = srs->importFromCRSURL(input.c_str());
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }

  info.GetReturnValue().Set(SpatialReference::New(srs, true));
}

/**
 * Initialize spatial reference from a URL.
 *
 * This method will download the spatial reference from the given URL.
 *
 * @static
 * @throws Error
 * @method fromURL
 * @param {String} url
 * @return {gdal.SpatialReference}
 */
NAN_METHOD(SpatialReference::fromURL) {
  Nan::HandleScope scope;

  std::string input("");
  NODE_ARG_STR(0, "url", input);

  OGRSpatialReference *srs = new OGRSpatialReference();
  int err = srs->importFromUrl(input.c_str());
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }

  info.GetReturnValue().Set(SpatialReference::New(srs, true));
}

/**
 * Initialize from a Mapinfo style CoordSys definition.
 *
 * @static
 * @throws Error
 * @method fromMICoordSys
 * @param {String} input
 * @return {gdal.SpatialReference}
 */
NAN_METHOD(SpatialReference::fromMICoordSys) {
  Nan::HandleScope scope;

  std::string input("");
  NODE_ARG_STR(0, "input", input);

  OGRSpatialReference *srs = new OGRSpatialReference();
  int err = srs->importFromMICoordSys(input.c_str());
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }

  info.GetReturnValue().Set(SpatialReference::New(srs, true));
}

/**
 * Initialize from an arbitrary spatial reference string.
 *
 * This method will examine the provided input, and try to deduce the format,
 * and then use it to initialize the spatial reference system.
 *
 * @static
 * @throws Error
 * @method fromUserInput
 * @param {String} input
 * @return {gdal.SpatialReference}
 */
NAN_METHOD(SpatialReference::fromUserInput) {
  Nan::HandleScope scope;

  std::string input("");
  NODE_ARG_STR(0, "input", input);

  OGRSpatialReference *srs = new OGRSpatialReference();
  int err = srs->SetFromUserInput(input.c_str());
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }

  info.GetReturnValue().Set(SpatialReference::New(srs, true));
}

/**
 * Initialize from EPSG GCS or PCS code.
 *
 * @example
 * ```
 * var ref = gdal.SpatialReference.fromEPSGA(4326);```
 *
 * @static
 * @throws Error
 * @method fromEPSG
 * @param {String} input
 * @return {gdal.SpatialReference}
 */
NAN_METHOD(SpatialReference::fromEPSG) {
  Nan::HandleScope scope;

  int epsg;
  NODE_ARG_INT(0, "epsg", epsg);

  OGRSpatialReference *srs = new OGRSpatialReference();
  int err = srs->importFromEPSG(epsg);
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }

  info.GetReturnValue().Set(SpatialReference::New(srs, true));
}

/**
 * Initialize from EPSG GCS or PCS code.
 *
 * This method is similar to `fromEPSG()` except that EPSG preferred axis
 * ordering *will* be applied for geographic and projected coordinate systems.
 * EPSG normally defines geographic coordinate systems to use lat/long, and also
 * there are also a few projected coordinate systems that use northing/easting
 * order contrary to typical GIS use).
 *
 * @example
 * ```
 * var ref = gdal.SpatialReference.fromEPSGA(26910);```
 *
 * @static
 * @throws Error
 * @method fromEPSGA
 * @param {Integer} input
 * @return {gdal.SpatialReference}
 */
NAN_METHOD(SpatialReference::fromEPSGA) {
  Nan::HandleScope scope;

  int epsg;
  NODE_ARG_INT(0, "epsg", epsg);

  OGRSpatialReference *srs = new OGRSpatialReference();
  int err = srs->importFromEPSGA(epsg);
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }

  info.GetReturnValue().Set(SpatialReference::New(srs, true));
}

/**
 * Import coordinate system from ESRI .prj format(s).
 *
 * This function will read the text loaded from an ESRI .prj file, and translate
 * it into an OGRSpatialReference definition. This should support many (but by
 * no means all) old style (Arc/Info 7.x) .prj files, as well as the newer
 * pseudo-OGC WKT .prj files. Note that new style .prj files are in OGC WKT
 * format, but require some manipulation to correct datum names, and units on
 * some projection parameters. This is addressed within importFromESRI() by an
 * automatical call to morphFromESRI().
 *
 * Currently only GEOGRAPHIC, UTM, STATEPLANE, GREATBRITIAN_GRID, ALBERS,
 * EQUIDISTANT_CONIC, TRANSVERSE (mercator), POLAR, MERCATOR and POLYCONIC
 * projections are supported from old style files.
 *
 * @static
 * @throws Error
 * @method fromESRI
 * @param {string[]} input
 * @return {gdal.SpatialReference}
 */
NAN_METHOD(SpatialReference::fromESRI) {
  Nan::HandleScope scope;

  StringList list;

  if (info.Length() < 1) {
    Nan::ThrowError("input string list must be provided");
    return;
  }

  if (list.parse(info[0])) {
    return; // error parsing string list
  }

  OGRSpatialReference *srs = new OGRSpatialReference();
  int err = srs->importFromESRI(list.get());
  if (err) {
    NODE_THROW_OGRERR(err);
    return;
  }

  info.GetReturnValue().Set(SpatialReference::New(srs, true));
}

/**
 * Fetch linear geographic coordinate system units.
 *
 * @method getLinearUnits
 * @return {Object} An object containing `value` and `unit` properties.
 */
NAN_METHOD(SpatialReference::getLinearUnits) {
  Nan::HandleScope scope;

  SpatialReference *srs = Nan::ObjectWrap::Unwrap<SpatialReference>(info.This());

  OGRChar *unit_name;
  double units = srs->this_->GetLinearUnits(&unit_name);

  Local<Object> result = Nan::New<Object>();
  Nan::Set(result, Nan::New("value").ToLocalChecked(), Nan::New<Number>(units));
  Nan::Set(result, Nan::New("units").ToLocalChecked(), SafeString::New(unit_name));

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

/**
 * Fetch angular geographic coordinate system units.
 *
 * @method getAngularUnits
 * @return {Object} An object containing `value` and `unit` properties.
 */
NAN_METHOD(SpatialReference::getAngularUnits) {
  Nan::HandleScope scope;

  SpatialReference *srs = Nan::ObjectWrap::Unwrap<SpatialReference>(info.This());

  OGRChar *unit_name;
  double units = srs->this_->GetAngularUnits(&unit_name);

  Local<Object> result = Nan::New<Object>();
  Nan::Set(result, Nan::New("value").ToLocalChecked(), Nan::New<Number>(units));
  Nan::Set(result, Nan::New("units").ToLocalChecked(), SafeString::New(unit_name));

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

/**
 * Validate SRS tokens.
 *
 * This method attempts to verify that the spatial reference system is well
 * formed, and consists of known tokens. The validation is not comprehensive.
 *
 * @method validate
 * @return {string|null} `"corrupt"`, '"unsupported"', `null` (if fine)
 */
NAN_METHOD(SpatialReference::validate) {
  Nan::HandleScope scope;

  SpatialReference *srs = Nan::ObjectWrap::Unwrap<SpatialReference>(info.This());

  OGRErr err = srs->this_->Validate();

  if (err == OGRERR_NONE) {
    info.GetReturnValue().Set(Nan::Null());
    return;
  }
  if (err == OGRERR_CORRUPT_DATA) {
    info.GetReturnValue().Set(Nan::New("corrupt").ToLocalChecked());
    return;
  }
  if (err == OGRERR_UNSUPPORTED_SRS) {
    info.GetReturnValue().Set(Nan::New("unsupported").ToLocalChecked());
    return;
  }

  NODE_THROW_OGRERR(err);
  return;
}

} // namespace node_gdal
