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

// TODO: dirty NIF http://erlang.org/doc/man/erl_nif.html

// get_string gets an Elixir string (binary)
// the caller is responsible for freeing the returned pointer.
static char * get_string(ErlNifEnv *env, ERL_NIF_TERM term) {
  ErlNifBinary bin;
  if (!enif_inspect_iolist_as_binary(env, term, &bin)) {
    return NULL;
  }
  char *str = strndup((char*) bin.data, bin.size);
  enif_release_binary(&bin);

  return str;
}

// make_string makes an Elixir string (binary)
static ERL_NIF_TERM make_string(ErlNifEnv *env, const char *str) {
  ERL_NIF_TERM result;
  size_t size = strlen(str);
  unsigned char *chars = enif_make_new_binary(env, size, &result);
  strncpy((char *)chars, str, size);
  return result;
}

static ERL_NIF_TERM read_barcode(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
  char *file = get_string(env, argv[0]);
  if (!file) {
    return enif_make_badarg(env);
  }

  char *license = get_string(env, argv[1]);
  if (!license) {
    return enif_make_badarg(env);
  }

  // create bar code session with appropriate parameters.
  void *hBarcode = STCreateBarCodeSession();

  uint16 ival = 1;
  STSetParameter(hBarcode, ST_MULTIPLE_READ, &ival);
  STSetParameter(hBarcode, ST_READ_PDF417, &ival);
  STSetParameter(hBarcode, ST_READ_CODE128, &ival);
  STSetParameter(hBarcode, ST_LICENSE, license);

  // read bar codes
  char **bar_codes_value;
  char **bar_codes_type;

  int bar_count = STReadBarCode(hBarcode, file, 0, &bar_codes_value, &bar_codes_type);

  // return errors as an {:error, "message"} tuple.
  if (bar_count < 0) {
    ERL_NIF_TERM message;

    switch(bar_count) {
    case ST_ERROR_FILE_OPEN:
      message = make_string(env, "cannot open image file");
      break;
    default:
      message =  make_string(env, "error reading image file");
    }

    free(file);
    free(license);
    return enif_make_tuple2(env, enif_make_atom(env, "error"), message);
  }

  // make an empty list to populate
  ERL_NIF_TERM barcodes = enif_make_list(env, 0);

  // make :kind and :value atoms as keys for the maps
  ERL_NIF_TERM atom_kind = enif_make_atom(env, "kind");
  ERL_NIF_TERM atom_value = enif_make_atom(env, "rawValue");

  // make a map for each barcode and append to the list.
  for (int n = 0; n < bar_count; n++) {
    ERL_NIF_TERM barcode = enif_make_new_map(env);
    enif_make_map_put(env, barcode, atom_kind, make_string(env, bar_codes_type[n]), &barcode);
    enif_make_map_put(env, barcode, atom_value, make_string(env, bar_codes_value[n]), &barcode);

    barcodes = enif_make_list_cell(env, enif_make_list1(env, barcode), barcodes);
  }

  // clean up
  STFreeBarCodeSession(hBarcode);
  free(file);
  free(license);

  return enif_make_tuple2(env, enif_make_atom(env, "ok"), barcodes);
}

// TODO: add a function that uses ReadBarCodeFromBitmap instead of a file.

// Let's define the array of ErlNifFunc beforehand:
static ErlNifFunc nif_funcs[] = {
  // {erl_function_name, erl_function_arity, c_function}
  {"read_barcode", 2, read_barcode}
};

ERL_NIF_INIT(Elixir.Bardecode, nif_funcs, NULL, NULL, NULL, NULL)
