#include "../gdal_common.hpp"
#include "../gdal_feature.hpp"
#include "feature_fields.hpp"

namespace node_gdal {

Nan::Persistent<FunctionTemplate> FeatureFields::constructor;

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

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

  Nan::SetPrototypeMethod(lcons, "toString", toString);
  Nan::SetPrototypeMethod(lcons, "toObject", toObject);
  Nan::SetPrototypeMethod(lcons, "toArray", toArray);
  Nan::SetPrototypeMethod(lcons, "count", count);
  Nan::SetPrototypeMethod(lcons, "get", get);
  Nan::SetPrototypeMethod(lcons, "getNames", getNames);
  Nan::SetPrototypeMethod(lcons, "set", set);
  Nan::SetPrototypeMethod(lcons, "reset", reset);
  Nan::SetPrototypeMethod(lcons, "indexOf", indexOf);

  ATTR_DONT_ENUM(lcons, "feature", featureGetter, READ_ONLY_SETTER);

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

  constructor.Reset(lcons);
}

FeatureFields::FeatureFields() : Nan::ObjectWrap() {
}

FeatureFields::~FeatureFields() {
}

/**
 * An encapsulation of all field data that makes up a {{#crossLink
 * "gdal.Feature"}}Feature{{/crossLink}}.
 *
 * @class gdal.FeatureFields
 */
NAN_METHOD(FeatureFields::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();
    FeatureFields *f = static_cast<FeatureFields *>(ptr);
    f->Wrap(info.This());
    info.GetReturnValue().Set(info.This());
    return;
  } else {
    Nan::ThrowError("Cannot create FeatureFields directly");
    return;
  }
}

Local<Value> FeatureFields::New(Local<Value> layer_obj) {
  Nan::EscapableHandleScope scope;

  FeatureFields *wrapped = new FeatureFields();

  v8::Local<v8::Value> ext = Nan::New<External>(wrapped);
  v8::Local<v8::Object> obj =
    Nan::NewInstance(Nan::GetFunction(Nan::New(FeatureFields::constructor)).ToLocalChecked(), 1, &ext).ToLocalChecked();
  Nan::SetPrivate(obj, Nan::New("parent_").ToLocalChecked(), layer_obj);

  return scope.Escape(obj);
}

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

inline bool setField(OGRFeature *f, int field_index, Local<Value> val) {
  if (val->IsInt32()) {
    f->SetField(field_index, Nan::To<int32_t>(val).ToChecked());
  } else if (val->IsNumber()) {
    f->SetField(field_index, Nan::To<double>(val).ToChecked());
  } else if (val->IsString()) {
    std::string str = *Nan::Utf8String(val);
    f->SetField(field_index, str.c_str());
  } else if (val->IsNull() || val->IsUndefined()) {
    f->UnsetField(field_index);
  } else {
    return true;
  }
  return false;
}

/**
 * Sets feature field(s).
 *
 * @example
 * ```
 * // most-efficient, least flexible. requires you to know the ordering of the
 * fields: feature.fields.set(['Something']); feature.fields.set(0,
 * 'Something');
 *
 * // most flexible.
 * feature.fields.set({name: 'Something'});
 * feature.fields.set('name', 'Something');
 * ```
 *
 * @method set
 * @throws Error
 * @param {String|Integer} key Field name or index
 * @param {mixed} value
 */
NAN_METHOD(FeatureFields::set) {
  Nan::HandleScope scope;
  int field_index;
  unsigned int i, n, n_fields_set;

  Local<Object> parent =
    Nan::GetPrivate(info.This(), Nan::New("parent_").ToLocalChecked()).ToLocalChecked().As<Object>();
  Feature *f = Nan::ObjectWrap::Unwrap<Feature>(parent);
  if (!f->isAlive()) {
    Nan::ThrowError("Feature object already destroyed");
    return;
  }

  if (info.Length() == 1) {
    if (info[0]->IsArray()) {
      // set([])
      Local<Array> values = info[0].As<Array>();

      n = f->get()->GetFieldCount();
      if (values->Length() < n) { n = values->Length(); }

      for (i = 0; i < n; i++) {
        Local<Value> val = Nan::Get(values, i).ToLocalChecked();
        if (setField(f->get(), i, val)) {
          Nan::ThrowError("Unsupported type of field value");
          return;
        }
      }

      info.GetReturnValue().Set(Nan::New<Integer>(n));
      return;
    } else if (info[0]->IsObject()) {
      // set({})
      Local<Object> values = info[0].As<Object>();

      n = f->get()->GetFieldCount();
      n_fields_set = 0;

      for (i = 0; i < n; i++) {
        // iterate through field names from field defn,
        // grabbing values from passed object, if not undefined

        OGRFieldDefn *field_def = f->get()->GetFieldDefnRef(i);

        const char *field_name = field_def->GetNameRef();

        field_index = f->get()->GetFieldIndex(field_name);

        // skip value if field name doesnt exist
        // both in the feature definition and the passed object
        if (field_index == -1 || !Nan::HasOwnProperty(values, Nan::New(field_name).ToLocalChecked()).FromMaybe(false)) {
          continue;
        }

        Local<Value> val = Nan::Get(values, Nan::New(field_name).ToLocalChecked()).ToLocalChecked();
        if (setField(f->get(), field_index, val)) {
          Nan::ThrowError("Unsupported type of field value");
          return;
        }

        n_fields_set++;
      }

      info.GetReturnValue().Set(Nan::New<Integer>(n_fields_set));
      return;
    } else {
      Nan::ThrowError("Method expected an object or array");
      return;
    }

  } else if (info.Length() == 2) {
    // set(name|index, value)
    ARG_FIELD_ID(0, f->get(), field_index);

    // set field value
    if (setField(f->get(), field_index, info[1])) {
      Nan::ThrowError("Unsupported type of field value");
      return;
    }

    info.GetReturnValue().Set(Nan::New<Integer>(1));
    return;
  } else {
    Nan::ThrowError("Invalid number of arguments");
    return;
  }
}

/**
 * Resets all fields.
 *
 * @example
 * ```
 * feature.fields.reset();```
 *
 * @method reset
 * @throws Error
 * @param {Object} [values]
 * @param {mixed} value
 */
NAN_METHOD(FeatureFields::reset) {
  Nan::HandleScope scope;
  int field_index;
  unsigned int i, n;

  Local<Object> parent =
    Nan::GetPrivate(info.This(), Nan::New("parent_").ToLocalChecked()).ToLocalChecked().As<Object>();
  Feature *f = Nan::ObjectWrap::Unwrap<Feature>(parent);
  if (!f->isAlive()) {
    Nan::ThrowError("Feature object already destroyed");
    return;
  }

  n = f->get()->GetFieldCount();

  if (info.Length() == 0) {
    for (i = 0; i < n; i++) { f->get()->UnsetField(i); }
    info.GetReturnValue().Set(Nan::New<Integer>(n));
    return;
  }

  if (!info[0]->IsObject()) {
    Nan::ThrowError("fields must be an object");
    return;
  }

  Local<Object> values = info[0].As<Object>();

  for (i = 0; i < n; i++) {
    // iterate through field names from field defn,
    // grabbing values from passed object

    OGRFieldDefn *field_def = f->get()->GetFieldDefnRef(i);

    const char *field_name = field_def->GetNameRef();

    field_index = f->get()->GetFieldIndex(field_name);
    if (field_index == -1) continue;

    Local<Value> val = Nan::Get(values, Nan::New(field_name).ToLocalChecked()).ToLocalChecked();
    if (setField(f->get(), field_index, val)) {
      Nan::ThrowError("Unsupported type of field value");
      return;
    }
  }

  info.GetReturnValue().Set(Nan::New<Integer>(n));
}

/**
 * Returns the number of fields.
 *
 * @example
 * ```
 * feature.fields.count();```
 *
 * @method count
 * @return {Integer}
 */
NAN_METHOD(FeatureFields::count) {
  Nan::HandleScope scope;

  Local<Object> parent =
    Nan::GetPrivate(info.This(), Nan::New("parent_").ToLocalChecked()).ToLocalChecked().As<Object>();
  Feature *f = Nan::ObjectWrap::Unwrap<Feature>(parent);
  if (!f->isAlive()) {
    Nan::ThrowError("Feature object already destroyed");
    return;
  }

  info.GetReturnValue().Set(Nan::New<Integer>(f->get()->GetFieldCount()));
}

/**
 * Returns the index of a field, given its name.
 *
 * @example
 * ```
 * var index = feature.fields.indexOf('field');```
 *
 * @method indexOf
 * @param {String} name
 * @return {Integer} Index or, `-1` if it cannot be found.
 */
NAN_METHOD(FeatureFields::indexOf) {
  Nan::HandleScope scope;

  Local<Object> parent =
    Nan::GetPrivate(info.This(), Nan::New("parent_").ToLocalChecked()).ToLocalChecked().As<Object>();
  Feature *f = Nan::ObjectWrap::Unwrap<Feature>(parent);
  if (!f->isAlive()) {
    Nan::ThrowError("Feature object already destroyed");
    return;
  }

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

  info.GetReturnValue().Set(Nan::New<Integer>(f->get()->GetFieldIndex(name.c_str())));
}

/**
 * Outputs the field data as a pure JS object.
 *
 * @throws Error
 * @method toObject
 * @return {Object}
 */
NAN_METHOD(FeatureFields::toObject) {
  Nan::HandleScope scope;

  Local<Object> parent =
    Nan::GetPrivate(info.This(), Nan::New("parent_").ToLocalChecked()).ToLocalChecked().As<Object>();
  Feature *f = Nan::ObjectWrap::Unwrap<Feature>(parent);
  if (!f->isAlive()) {
    Nan::ThrowError("Feature object already destroyed");
    return;
  }

  Local<Object> obj = Nan::New<Object>();

  int n = f->get()->GetFieldCount();
  for (int i = 0; i < n; i++) {

    // get field name
    OGRFieldDefn *field_def = f->get()->GetFieldDefnRef(i);
    const char *key = field_def->GetNameRef();
    if (!key) {
      Nan::ThrowError("Error getting field name");
      return;
    }

    // get field value
    Local<Value> val = FeatureFields::get(f->get(), i);
    if (val.IsEmpty()) {
      return; // get method threw an exception
    }

    Nan::Set(obj, Nan::New(key).ToLocalChecked(), val);
  }
  info.GetReturnValue().Set(obj);
}

/**
 * Outputs the field values as a pure JS array.
 *
 * @throws Error
 * @method toArray
 * @return {Array}
 */
NAN_METHOD(FeatureFields::toArray) {
  Nan::HandleScope scope;

  Local<Object> parent =
    Nan::GetPrivate(info.This(), Nan::New("parent_").ToLocalChecked()).ToLocalChecked().As<Object>();
  Feature *f = Nan::ObjectWrap::Unwrap<Feature>(parent);
  if (!f->isAlive()) {
    Nan::ThrowError("Feature object already destroyed");
    return;
  }

  int n = f->get()->GetFieldCount();
  Local<Array> array = Nan::New<Array>(n);

  for (int i = 0; i < n; i++) {
    // get field value
    Local<Value> val = FeatureFields::get(f->get(), i);
    if (val.IsEmpty()) {
      return; // get method threw an exception
    }

    Nan::Set(array, i, val);
  }
  info.GetReturnValue().Set(array);
}

Local<Value> FeatureFields::get(OGRFeature *f, int field_index) {
  //#throws : caller must check if return_val.IsEmpty() and bail out if true
  Nan::EscapableHandleScope scope;

  if (!f->IsFieldSet(field_index)) return scope.Escape(Nan::Null());

  OGRFieldDefn *field_def = f->GetFieldDefnRef(field_index);
  switch (field_def->GetType()) {
    case OFTInteger: return scope.Escape(Nan::New<Integer>(f->GetFieldAsInteger(field_index)));
#if defined(GDAL_VERSION_MAJOR) && (GDAL_VERSION_MAJOR >= 2)
    case OFTInteger64: return scope.Escape(Nan::New<Number>(f->GetFieldAsInteger64(field_index)));
    case OFTInteger64List: return scope.Escape(getFieldAsInteger64List(f, field_index));
#endif
    case OFTReal: return scope.Escape(Nan::New<Number>(f->GetFieldAsDouble(field_index)));
    case OFTString: return scope.Escape(SafeString::New(f->GetFieldAsString(field_index)));
    case OFTIntegerList: return scope.Escape(getFieldAsIntegerList(f, field_index));
    case OFTRealList: return scope.Escape(getFieldAsDoubleList(f, field_index));
    case OFTStringList: return scope.Escape(getFieldAsStringList(f, field_index));
    case OFTBinary: return scope.Escape(getFieldAsBinary(f, field_index));
    case OFTDate:
    case OFTTime:
    case OFTDateTime: return scope.Escape(getFieldAsDateTime(f, field_index));
    default: Nan::ThrowError("Unsupported field type"); return scope.Escape(Nan::Undefined());
  }
}

/**
 * Returns a field's value.
 *
 * @example
 * ```
 * value = feature.fields.get(0);
 * value = feature.fields.get('field');```
 *
 * @method get
 * @param {String|Integer} key Feature name or index.
 * @return {mixed|Undefined}
 */
NAN_METHOD(FeatureFields::get) {
  Nan::HandleScope scope;

  Local<Object> parent =
    Nan::GetPrivate(info.This(), Nan::New("parent_").ToLocalChecked()).ToLocalChecked().As<Object>();
  Feature *f = Nan::ObjectWrap::Unwrap<Feature>(parent);
  if (!f->isAlive()) {
    Nan::ThrowError("Feature object already destroyed");
    return;
  }

  if (info.Length() < 1) {
    Nan::ThrowError("Field index or name must be given");
    return;
  }

  int field_index;
  ARG_FIELD_ID(0, f->get(), field_index);

  Local<Value> result = FeatureFields::get(f->get(), field_index);

  if (result.IsEmpty()) {
    return;
  } else {
    info.GetReturnValue().Set(result);
    return;
  }
}

/**
 * Returns a list of field name.
 *
 * @method getNames
 * @throws Error
 * @return {Array} List of field names.
 */
NAN_METHOD(FeatureFields::getNames) {
  Nan::HandleScope scope;

  Local<Object> parent =
    Nan::GetPrivate(info.This(), Nan::New("parent_").ToLocalChecked()).ToLocalChecked().As<Object>();
  Feature *f = Nan::ObjectWrap::Unwrap<Feature>(parent);
  if (!f->isAlive()) {
    Nan::ThrowError("Feature object already destroyed");
    return;
  }

  int n = f->get()->GetFieldCount();
  Local<Array> result = Nan::New<Array>(n);

  for (int i = 0; i < n; i++) {

    // get field name
    OGRFieldDefn *field_def = f->get()->GetFieldDefnRef(i);
    const char *field_name = field_def->GetNameRef();
    if (!field_name) {
      Nan::ThrowError("Error getting field name");
      return;
    }
    Nan::Set(result, i, Nan::New(field_name).ToLocalChecked());
  }

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

Local<Value> FeatureFields::getFieldAsIntegerList(OGRFeature *feature, int field_index) {
  Nan::EscapableHandleScope scope;

  int count_of_values = 0;

  const int *values = feature->GetFieldAsIntegerList(field_index, &count_of_values);

  Local<Array> return_array = Nan::New<Array>(count_of_values);

  for (int index = 0; index < count_of_values; index++) {
    Nan::Set(return_array, index, Nan::New<Integer>(values[index]));
  }

  return scope.Escape(return_array);
}

#if defined(GDAL_VERSION_MAJOR) && (GDAL_VERSION_MAJOR >= 2)
Local<Value> FeatureFields::getFieldAsInteger64List(OGRFeature *feature, int field_index) {
  Nan::EscapableHandleScope scope;

  int count_of_values = 0;

  const long long *values = feature->GetFieldAsInteger64List(field_index, &count_of_values);

  Local<Array> return_array = Nan::New<Array>(count_of_values);

  for (int index = 0; index < count_of_values; index++) {
    Nan::Set(return_array, index, Nan::New<Number>(values[index]));
  }

  return scope.Escape(return_array);
}
#endif

Local<Value> FeatureFields::getFieldAsDoubleList(OGRFeature *feature, int field_index) {
  Nan::EscapableHandleScope scope;

  int count_of_values = 0;

  const double *values = feature->GetFieldAsDoubleList(field_index, &count_of_values);

  Local<Array> return_array = Nan::New<Array>(count_of_values);

  for (int index = 0; index < count_of_values; index++) {
    Nan::Set(return_array, index, Nan::New<Number>(values[index]));
  }

  return scope.Escape(return_array);
}

Local<Value> FeatureFields::getFieldAsStringList(OGRFeature *feature, int field_index) {
  Nan::EscapableHandleScope scope;
  char **values = feature->GetFieldAsStringList(field_index);

  int count_of_values = CSLCount(values);

  Local<Array> return_array = Nan::New<Array>(count_of_values);

  for (int index = 0; index < count_of_values; index++) {
    Nan::Set(return_array, index, SafeString::New(values[index]));
  }

  return scope.Escape(return_array);
}

Local<Value> FeatureFields::getFieldAsBinary(OGRFeature *feature, int field_index) {
  Nan::EscapableHandleScope scope;

  int count_of_bytes = 0;

  char *data = (char *)feature->GetFieldAsBinary(field_index, &count_of_bytes);

  if (count_of_bytes > 0) {
    // GDAL Feature->GetFieldAsBinary returns a pointer to an internal buffer
    // that should not be freed
    // Nan::NewBuffer expects to receive ownership of the buffer
    // =>
    // So no other solution than copy :-(
    char *reallocated_data = (char*)malloc(count_of_bytes);
    if (!reallocated_data) {
      Nan::ThrowError("Memory allocation failed");
    } else {
      memcpy(reallocated_data, data, count_of_bytes);
      return scope.Escape(Nan::NewBuffer(reallocated_data, count_of_bytes).ToLocalChecked());
    }
  }

  return scope.Escape(Nan::Undefined());
}

Local<Value> FeatureFields::getFieldAsDateTime(OGRFeature *feature, int field_index) {
  Nan::EscapableHandleScope scope;

  int year, month, day, hour, minute, second, timezone;

  year = month = day = hour = minute = second = timezone = 0;

  int result = feature->GetFieldAsDateTime(field_index, &year, &month, &day, &hour, &minute, &second, &timezone);

  if (result == TRUE) {
    Local<Object> hash = Nan::New<Object>();

    if (year) { Nan::Set(hash, Nan::New("year").ToLocalChecked(), Nan::New<Integer>(year)); }
    if (month) { Nan::Set(hash, Nan::New("month").ToLocalChecked(), Nan::New<Integer>(month)); }
    if (day) { Nan::Set(hash, Nan::New("day").ToLocalChecked(), Nan::New<Integer>(day)); }
    if (hour) { Nan::Set(hash, Nan::New("hour").ToLocalChecked(), Nan::New<Integer>(hour)); }
    if (minute) { Nan::Set(hash, Nan::New("minute").ToLocalChecked(), Nan::New<Integer>(minute)); }
    if (second) { Nan::Set(hash, Nan::New("second").ToLocalChecked(), Nan::New<Integer>(second)); }
    if (timezone) { Nan::Set(hash, Nan::New("timezone").ToLocalChecked(), Nan::New<Integer>(timezone)); }

    return scope.Escape(hash);
  } else {
    return scope.Escape(Nan::Undefined());
  }
}

/**
 * Parent feature
 *
 * @readOnly
 * @attribute feature
 * @type {gdal.Feature}
 */
NAN_GETTER(FeatureFields::featureGetter) {
  Nan::HandleScope scope;
  info.GetReturnValue().Set(Nan::GetPrivate(info.This(), Nan::New("parent_").ToLocalChecked()).ToLocalChecked());
}

} // namespace node_gdal
