#include "erl_nif.h"
#include <netcdf.h>
#include <string.h>

#define STR_SIZE 100

// nc_create(file_name<string>) -> file_pointer<int>
static ERL_NIF_TERM nif_nc_create(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
  int ncid;
  char file_name[STR_SIZE];

  // tmp
  int retval;

  // nc_create(FILE_NAME, NC_CLOBBER, &ncid)
  // FILENAME is a string
  // NC_CLOBBER is defined in #netcdf.h
  // ncid is an int
  if(argc != 1) {
    return enif_make_badarg(env);
  }

  if(!enif_get_string(env, argv[0], file_name, STR_SIZE, ERL_NIF_LATIN1)) {
    return enif_make_badarg(env);
  }

  if((retval = nc_create(file_name, NC_CLOBBER, &ncid))) {
    //ERR(retval);
    return enif_raise_exception(env, enif_make_int(env, retval));
  }

  return enif_make_int(env, ncid);
}

// nc_close(file_pointer<int>) -> nil
static ERL_NIF_TERM nif_nc_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
  int ncid;

  // tmp
  int retval;

  if(argc != 1) {
    return enif_make_badarg(env);
  }

  if(!enif_get_int(env, argv[0], &ncid)) {
    return enif_make_badarg(env);
  }

  if((retval = nc_close(ncid))) {
    return enif_raise_exception(env, enif_make_int(env, retval));
  }

  return enif_make_list(env, 0);
}

// nc_def_dim(ncid<int>, name<char*>, size<int>) -> dimid<int>
static ERL_NIF_TERM nif_nc_def_dim(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
  int ncid;
  char dim_name[STR_SIZE];
  int size;
  int dimid;

  // tmp
  int retval;

  if(argc != 3) {
    return enif_make_badarg(env);
  }
  if(!enif_get_int(env, argv[0], &ncid)) {
    return enif_make_badarg(env);
  }
  if(!enif_get_string(env, argv[1], dim_name, STR_SIZE, ERL_NIF_LATIN1)) {
    return enif_make_badarg(env);
  }
  if(!enif_get_int(env, argv[2], &size)) {
    return enif_make_badarg(env);
  }

  if((retval = nc_def_dim(ncid, dim_name, size, &dimid))) {
    return enif_raise_exception(env, enif_make_int(env, retval));
  }

  return enif_make_int(env, dimid);
}

// nc_def_var(ncid<int>, name<string>, type<int>, ndims<int>, dimid<int>) -> varid<int>
static ERL_NIF_TERM nif_nc_def_var(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
  int ncid;
  char var_name[STR_SIZE];
  nc_type var_type;
  int ndims;
  int dimid;
  int varid;

  // tmp
  int retval;

  if(argc != 5) {
    return enif_make_badarg(env);
  }
  if(!enif_get_int(env, argv[0], &ncid)) {
    return enif_make_badarg(env);
  }
  if(!enif_get_string(env, argv[1], var_name, STR_SIZE, ERL_NIF_LATIN1)) {
    return enif_make_badarg(env);
  }
  if(!enif_get_int(env, argv[2], &var_type)) {
    return enif_make_badarg(env);
  }
  if(!enif_get_int(env, argv[3], &ndims)) {
    return enif_make_badarg(env);
  }
  if(!enif_get_int(env, argv[4], &dimid)) {
    return enif_make_badarg(env);
  }

  if((retval = nc_def_var(ncid, var_name, var_type, ndims, &dimid, &varid))) {
    return enif_raise_exception(env, enif_make_int(env, retval));
  }

  return enif_make_int(env, varid);
}

// nc_put_att_text(ncid<int>, varid<int>, name<string>, value<string>) -> nil
static ERL_NIF_TERM nif_nc_put_att_text(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
  int ncid;
  int varid;
  char name[STR_SIZE];
  char value[STR_SIZE];

  // tmp
  int retval;

  if(argc != 4) {
    return enif_make_badarg(env);
  }
  if(!enif_get_int(env, argv[0], &ncid)) {
    return enif_make_badarg(env);
  }
  if(!enif_get_int(env, argv[1], &varid)) {
    return enif_make_badarg(env);
  }
  if(!enif_get_string(env, argv[2], name, STR_SIZE, ERL_NIF_LATIN1)) {
    return enif_make_badarg(env);
  }
  if(!enif_get_string(env, argv[3], value, STR_SIZE, ERL_NIF_LATIN1)) {
    return enif_make_badarg(env);
  }

  if((retval = nc_put_att_text(ncid, varid, name, strlen(value), value))) {
    return enif_raise_exception(env, enif_make_int(env, retval));
  }

  return enif_make_list(env, 0);
}

// nc_enddef(ncid<int>) -> nil
static ERL_NIF_TERM nif_nc_enddef(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
  int ncid;

  // tmp
  int retval;

  if(argc != 1) {
    return enif_make_badarg(env);
  }
  if(!enif_get_int(env, argv[0], &ncid)) {
    return enif_make_badarg(env);
  }

  if((retval = nc_enddef(ncid))) {
    return enif_raise_exception(env, enif_make_int(env, retval));
  }

  return enif_make_list(env, 0);
}

// put a SINGLE float value into the variable
// nc_put_var_double_single(ncid<int>, varid<int>, values<float>) -> nil
static ERL_NIF_TERM nif_nc_put_var_double_single(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
  int ncid;
  int varid;
  double value[1];

  // tmp
  int retval;

  if(argc != 3) {
    return enif_make_badarg(env);
  }
  if(!enif_get_int(env, argv[0], &ncid)) {
    return enif_make_badarg(env);
  }
  if(!enif_get_int(env, argv[1], &varid)) {
    return enif_make_badarg(env);
  }
  if(!enif_get_double(env, argv[2], &value[0])) {
    return enif_make_badarg(env);
  }

  if((retval = nc_put_var_double(ncid, varid, &value[0]))) {
    return enif_raise_exception(env, enif_make_int(env, retval));
  }

  return enif_make_list(env, 0);
}

// TODO WIP (finish list iteration)
static ERL_NIF_TERM nif_nc_put_var_double(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
  int ncid;
  int varid;
  unsigned size;
  double* value;
  ERL_NIF_TERM list;
  ERL_NIF_TERM head;
  ERL_NIF_TERM tail;

  // tmp
  int retval;

  if(argc != 3) {
    return enif_make_badarg(env);
  }
  if(!enif_get_int(env, argv[0], &ncid)) {
    return enif_make_badarg(env);
  }
  if(!enif_get_int(env, argv[1], &varid)) {
    return enif_make_badarg(env);
  }

  // convert the list of erl values to a list of doubles
  list = argv[2];

  if(!enif_get_list_length(env, list, &size)) {
    return enif_make_badarg(env);
  }
  if(size == 0) {
    // list is empty, nothing to do
    return enif_make_list(env, 0);
  }
  value = malloc(sizeof(double) * size);

  for(int i = 0; i < size; i++) {
    // printf("Loop: i = %d\n", i);
    // take head and tail of the list
    if(!enif_get_list_cell(env, list, &head, &tail)) {
      return enif_make_badarg(env);
    }
    if(!enif_get_double(env, head, &value[i])) {
      return enif_make_badarg(env);
    }
    list = tail;
  }

  if((retval = nc_put_var_double(ncid, varid, &value[0]))) {
    return enif_raise_exception(env, enif_make_int(env, retval));
  }

  free(value);

  return enif_make_list(env, 0);
}

static ErlNifFunc nif_funcs[] = {
  {"nc_create", 1, nif_nc_create},
  {"nc_close", 1, nif_nc_close},
  {"nc_def_dim", 3, nif_nc_def_dim},
  {"nc_def_var", 5, nif_nc_def_var},
  {"nc_put_att_text", 4, nif_nc_put_att_text},
  {"nc_enddef", 1, nif_nc_enddef},
  {"nc_put_var_double_single", 3, nif_nc_put_var_double_single},
  {"nc_put_var_double", 3, nif_nc_put_var_double}
};

ERL_NIF_INIT(Elixir.NetcdfNif,nif_funcs,NULL,NULL,NULL,NULL);
