#include "warp_options.hpp"
#include "../gdal_common.hpp"
#include "../geometry/gdal_geometry.hpp"
#include <stdio.h>
namespace node_gdal {

WarpOptions::WarpOptions()
  : options(NULL),
    src(nullptr),
    dst(nullptr),
    additional_options(),
    src_bands("src band ids"),
    dst_bands("dst band ids"),
    src_nodata(NULL),
    dst_nodata(NULL),
    multi(false) {
  options = GDALCreateWarpOptions();
}

WarpOptions::~WarpOptions() {

  // Dont use: GDALDestroyWarpOptions( options ); - it assumes ownership of
  // everything
  if (options) CPLFree(options);
  if (src_nodata) delete src_nodata;
  if (dst_nodata) delete dst_nodata;
}

int WarpOptions::parseResamplingAlg(Local<Value> value) {
  if (value->IsUndefined() || value->IsNull()) {
    options->eResampleAlg = GRA_NearestNeighbour;
    return 0;
  }
  if (!value->IsString()) {
    Nan::ThrowTypeError("resampling property must be a string");
    return 1;
  }
  std::string name = *Nan::Utf8String(value);

  if (name == "NearestNeighbor") {
    options->eResampleAlg = GRA_NearestNeighbour;
    return 0;
  }
  if (name == "NearestNeighbour") {
    options->eResampleAlg = GRA_NearestNeighbour;
    return 0;
  }
  if (name == "Bilinear") {
    options->eResampleAlg = GRA_Bilinear;
    return 0;
  }
  if (name == "Cubic") {
    options->eResampleAlg = GRA_Cubic;
    return 0;
  }
  if (name == "CubicSpline") {
    options->eResampleAlg = GRA_CubicSpline;
    return 0;
  }
  if (name == "Lanczos") {
    options->eResampleAlg = GRA_Lanczos;
    return 0;
  }
  if (name == "Average") {
    options->eResampleAlg = GRA_Average;
    return 0;
  }
  if (name == "Mode") {
    options->eResampleAlg = GRA_Mode;
    return 0;
  }

  Nan::ThrowError("Invalid resampling algorithm");
  return 1;
}

/*
 * {
 *   options : string[] | object
 *   memoryLimit : int
 *   resampleAlg : string
 *   src: Dataset
 *   dst: Dataset
 *   srcBands: int | int[]
 *   dstBands: int | int[]
 *   nBands: int
 *   srcAlphaBand: int
 *   dstAlphaBand: int
 *   srcNoData: double
 *   dstNoData: double
 *   cutline: geometry
 *   blend: double
 * }
 */
int WarpOptions::parse(Local<Value> value) {
  Nan::HandleScope scope;

  if (!value->IsObject() || value->IsNull()) Nan::ThrowTypeError("Warp options must be an object");

  Local<Object> obj = value.As<Object>();
  Local<Value> prop;

  if (
    Nan::HasOwnProperty(obj, Nan::New("options").ToLocalChecked()).FromMaybe(false) &&
    additional_options.parse(Nan::Get(obj, Nan::New("options").ToLocalChecked()).ToLocalChecked())) {
    return 1; // error parsing string list
  }

  options->papszWarpOptions = additional_options.get();

  if (Nan::HasOwnProperty(obj, Nan::New("memoryLimit").ToLocalChecked()).FromMaybe(false)) {
    prop = Nan::Get(obj, Nan::New("memoryLimit").ToLocalChecked()).ToLocalChecked();
    if (prop->IsNumber()) {
      options->dfWarpMemoryLimit = Nan::To<int32_t>(prop).ToChecked();
    } else if (!prop->IsUndefined() && !prop->IsNull()) {
      Nan::ThrowTypeError("memoryLimit property must be an integer");
      return 1;
    }
  }
  if (Nan::HasOwnProperty(obj, Nan::New("resampling").ToLocalChecked()).FromMaybe(false)) {
    prop = Nan::Get(obj, Nan::New("resampling").ToLocalChecked()).ToLocalChecked();
    if (parseResamplingAlg(prop)) {
      return 1; // error parsing resampling algorithm
    }
  }
  if (Nan::HasOwnProperty(obj, Nan::New("src").ToLocalChecked()).FromMaybe(false)) {
    prop = Nan::Get(obj, Nan::New("src").ToLocalChecked()).ToLocalChecked();
    if (prop->IsObject() && !prop->IsNull() && Nan::New(Dataset::constructor)->HasInstance(prop)) {
      this->src_obj = prop.As<Object>();
      this->src = Nan::ObjectWrap::Unwrap<Dataset>(this->src_obj);
#if GDAL_VERSION_MAJOR == 2 && GDAL_VERSION_MINOR < 3
      options->hSrcDS = static_cast<GDALDatasetH>(this->src->get());
#else
      options->hSrcDS = GDALDataset::ToHandle(this->src->get());
#endif
      if (!options->hSrcDS) {
        Nan::ThrowError("src dataset already closed");
        return 1;
      }
    } else {
      Nan::ThrowTypeError("src property must be a Dataset object");
      return 1;
    }
  } else {
    Nan::ThrowError("Warp options must include a source dataset");
    return 1;
  }
  if (Nan::HasOwnProperty(obj, Nan::New("dst").ToLocalChecked()).FromMaybe(false)) {
    prop = Nan::Get(obj, Nan::New("dst").ToLocalChecked()).ToLocalChecked();
    if (prop->IsObject() && !prop->IsNull() && Nan::New(Dataset::constructor)->HasInstance(prop)) {
      this->dst_obj = prop.As<Object>();
      this->dst = Nan::ObjectWrap::Unwrap<Dataset>(this->dst_obj);
#if GDAL_VERSION_MAJOR == 2 && GDAL_VERSION_MINOR < 3
      options->hDstDS = static_cast<GDALDatasetH>(this->dst->get());
#else
      options->hDstDS = GDALDataset::ToHandle(this->dst->get());
#endif
      if (!options->hDstDS) {
        Nan::ThrowError("dst dataset already closed");
        return 1;
      }
    } else if (!prop->IsUndefined() && !prop->IsNull()) {
      Nan::ThrowTypeError("dst property must be a Dataset object");
      return 1;
    }
  }
  if (Nan::HasOwnProperty(obj, Nan::New("srcBands").ToLocalChecked()).FromMaybe(false)) {
    prop = Nan::Get(obj, Nan::New("srcBands").ToLocalChecked()).ToLocalChecked();
    if (src_bands.parse(prop)) {
      return 1; // error parsing number list
    }
    options->panSrcBands = src_bands.get();
    options->nBandCount = src_bands.length();
  }
  if (Nan::HasOwnProperty(obj, Nan::New("dstBands").ToLocalChecked()).FromMaybe(false)) {
    prop = Nan::Get(obj, Nan::New("dstBands").ToLocalChecked()).ToLocalChecked();
    if (dst_bands.parse(prop)) {
      return 1; // error parsing number list
    }
    options->panDstBands = dst_bands.get();

    if (!options->panSrcBands) {
      Nan::ThrowError("srcBands must be provided if dstBands option is used");
      return 1;
    }
    if (dst_bands.length() != options->nBandCount) {
      Nan::ThrowError("Number of dst bands must equal number of src bands");
      return 1;
    }
  }
  if (options->panSrcBands && !options->panDstBands) {
    Nan::ThrowError("dstBands must be provided if srcBands option is used");
    return 1;
  }
  if (Nan::HasOwnProperty(obj, Nan::New("srcNodata").ToLocalChecked()).FromMaybe(false)) {
    prop = Nan::Get(obj, Nan::New("srcNodata").ToLocalChecked()).ToLocalChecked();
    if (prop->IsNumber()) {
      src_nodata = new double(Nan::To<double>(prop).ToChecked());
      options->padfSrcNoDataReal = src_nodata;
    } else if (!prop->IsUndefined() && !prop->IsNull()) {
      Nan::ThrowTypeError("srcNodata property must be a number");
      return 1;
    }
  }
  if (Nan::HasOwnProperty(obj, Nan::New("dstNodata").ToLocalChecked()).FromMaybe(false)) {
    prop = Nan::Get(obj, Nan::New("dstNodata").ToLocalChecked()).ToLocalChecked();
    if (prop->IsNumber()) {
      dst_nodata = new double(Nan::To<double>(prop).ToChecked());
      options->padfDstNoDataReal = dst_nodata;
    } else if (!prop->IsUndefined() && !prop->IsNull()) {
      Nan::ThrowTypeError("dstNodata property must be a number");
      return 1;
    }
  }
  if (Nan::HasOwnProperty(obj, Nan::New("srcAlphaBand").ToLocalChecked()).FromMaybe(false)) {
    prop = Nan::Get(obj, Nan::New("srcAlphaBand").ToLocalChecked()).ToLocalChecked();
    if (prop->IsNumber()) {
      options->nSrcAlphaBand = Nan::To<int32_t>(prop).ToChecked();
    } else if (!prop->IsUndefined() && !prop->IsNull()) {
      Nan::ThrowTypeError("srcAlphaBand property must be an integer");
      return 1;
    }
  }
  if (Nan::HasOwnProperty(obj, Nan::New("dstAlphaBand").ToLocalChecked()).FromMaybe(false)) {
    prop = Nan::Get(obj, Nan::New("dstAlphaBand").ToLocalChecked()).ToLocalChecked();
    if (prop->IsNumber()) {
      options->nDstAlphaBand = Nan::To<int32_t>(prop).ToChecked();
    } else if (!prop->IsUndefined() && !prop->IsNull()) {
      Nan::ThrowTypeError("dstAlphaBand property must be an integer");
      return 1;
    }
  }
  if (Nan::HasOwnProperty(obj, Nan::New("blend").ToLocalChecked()).FromMaybe(false)) {
    prop = Nan::Get(obj, Nan::New("blend").ToLocalChecked()).ToLocalChecked();
    if (prop->IsNumber()) {
      options->dfCutlineBlendDist = Nan::To<double>(prop).ToChecked();
    } else if (!prop->IsUndefined() && !prop->IsNull()) {
      Nan::ThrowTypeError("cutline blend distance must be a number");
      return 1;
    }
  }
  if (Nan::HasOwnProperty(obj, Nan::New("cutline").ToLocalChecked()).FromMaybe(false)) {
    prop = Nan::Get(obj, Nan::New("cutline").ToLocalChecked()).ToLocalChecked();
    if (prop->IsObject() && !prop->IsNull() && Nan::New(Geometry::constructor)->HasInstance(prop)) {
      options->hCutline = Nan::ObjectWrap::Unwrap<Geometry>(prop.As<Object>())->get();
    } else if (!prop->IsUndefined() && !prop->IsNull()) {
      Nan::ThrowTypeError("cutline property must be a Geometry object");
      return 1;
    }
  }
  if (Nan::HasOwnProperty(obj, Nan::New("multi").ToLocalChecked()).FromMaybe(false)) {
    prop = Nan::Get(obj, Nan::New("multi").ToLocalChecked()).ToLocalChecked();
    if (prop->IsTrue()) { multi = true; }
  }
  return 0;
}

} // namespace node_gdal
