#ifndef __NODE_GDAL_GLOBAL_H__
#define __NODE_GDAL_GLOBAL_H__

// node
#include <node.h>

// nan
#include "nan-wrapper.h"

// ogr
#include <ogr_api.h>
#include <ogrsf_frmts.h>

// gdal
#include "gdal_common.hpp"
#include "gdal_dataset.hpp"
#include "gdal_driver.hpp"

#include "async/async_open.hpp"

using namespace v8;
using namespace node;

namespace node_gdal {

/*
 * Common code for sync and async opening
 */
static void _do_open(const Nan::FunctionCallbackInfo<v8::Value> &info, bool async) {
  Nan::HandleScope scope;

  std::string path;
  std::string mode = "r";

  NODE_ARG_STR(0, "path", path);
  NODE_ARG_OPT_STR(1, "mode", mode);

#if GDAL_VERSION_MAJOR < 2
  if (async) {
    Nan::ThrowError("Asynchronous opening is not supported on GDAL 1.x");
    return;
  }

  GDALAccess access = GA_ReadOnly;
  if (mode == "r+") {
    access = GA_Update;
  } else if (mode != "r") {
    Nan::ThrowError("Invalid open mode. Must be \"r\" or \"r+\"");
    return;
  }

  OGRDataSource *ogr_ds = OGRSFDriverRegistrar::Open(path.c_str(), static_cast<int>(access));
  if (ogr_ds) {
    info.GetReturnValue().Set(Dataset::New(ogr_ds));
    return;
  }

  GDALDataset *gdal_ds = (GDALDataset *)GDALOpen(path.c_str(), access);
  if (gdal_ds) {
    info.GetReturnValue().Set(Dataset::New(gdal_ds));
    return;
  }
#else
  unsigned int flags = 0;
  if (mode == "r+") {
    flags |= GDAL_OF_UPDATE;
  } else if (mode == "r") {
    flags |= GDAL_OF_READONLY;
  } else {
    Nan::ThrowError("Invalid open mode. Must be \"r\" or \"r+\"");
    return;
  }

  std::function<GDALDataset *()> doit = [path, flags]() {
    return (GDALDataset *)GDALOpenEx(path.c_str(), flags, NULL, NULL, NULL);
  };

  if (async) {
    Nan::Callback *callback;
    NODE_ARG_CB(2, "callback", callback);
    Nan::AsyncQueueWorker(new AsyncOpen(callback, doit));
    return;
  } else {
    GDALDataset *ds = doit();
    if (ds) {
      info.GetReturnValue().Set(Dataset::New(ds));
      return;
    }
  }
#endif

  Nan::ThrowError("Error opening dataset");
  return;
}

static NAN_METHOD(open) {
  _do_open(info, false);
}

static NAN_METHOD(openAsync) {
  _do_open(info, true);
}

static NAN_METHOD(setConfigOption) {
  Nan::HandleScope scope;

  std::string name;

  NODE_ARG_STR(0, "name", name);

  if (info.Length() < 2) {
    Nan::ThrowError("string or null value must be provided");
    return;
  }
  if (info[1]->IsString()) {
    std::string val = *Nan::Utf8String(info[1]);
    CPLSetConfigOption(name.c_str(), val.c_str());
  } else if (info[1]->IsNull() || info[1]->IsUndefined()) {
    CPLSetConfigOption(name.c_str(), NULL);
  } else {
    Nan::ThrowError("value must be a string or null");
    return;
  }

  return;
}

static NAN_METHOD(getConfigOption) {
  Nan::HandleScope scope;

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

  info.GetReturnValue().Set(SafeString::New(CPLGetConfigOption(name.c_str(), NULL)));
}

/**
 * Convert decimal degrees to degrees, minutes, and seconds string
 *
 * @for gdal
 * @static
 * @method decToDMS
 * @param {Number} angle
 * @param {String} axis `"lat"` or `"long"`
 * @param {Integer} [precision=2]
 * @return {String} A string nndnn'nn.nn'"L where n is a number and L is either
 * N or E
 */
static NAN_METHOD(decToDMS) {
  Nan::HandleScope scope;

  double angle;
  std::string axis;
  int precision = 2;
  NODE_ARG_DOUBLE(0, "angle", angle);
  NODE_ARG_STR(1, "axis", axis);
  NODE_ARG_INT_OPT(2, "precision", precision);

  if (axis.length() > 0) { axis[0] = toupper(axis[0]); }
  if (axis != "Lat" && axis != "Long") {
    Nan::ThrowError("Axis must be 'lat' or 'long'");
    return;
  }

  info.GetReturnValue().Set(SafeString::New(GDALDecToDMS(angle, axis.c_str(), precision)));
}
} // namespace node_gdal

#endif
