#include "erl_nif.h"
#include "mmath.h"

#include <math.h>

static int
load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
{
  return 0;
}

static int
upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM load_info)
{
  return 0;
}

static ERL_NIF_TERM
min(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  ErlNifBinary bin;
  ERL_NIF_TERM r;
  ffloat* vs;
  ffloat* target;
  ErlNifSInt64 chunk;         // size to be compressed
  uint64_t target_i = 0; // target position
  ffloat aggr;         // target position
  double confidence;
  uint32_t pos;
  uint32_t count;
  uint32_t target_size;

  if (argc != 2)
    return enif_make_badarg(env);

  GET_CHUNK(chunk);
  GET_BIN(0, bin, count, vs);

  target_size = ceil((double) count / chunk) * sizeof(ffloat);
  if (! (target = (ffloat*) enif_make_new_binary(env, target_size, &r)))
    return enif_make_badarg(env); // TODO return propper error

  // If we don't have any input data we can return right away.
  if (count == 0)
    return r;

  // We know we have at least one element in the list so our
  // aggregator will start with this
  aggr = vs[0];
  confidence = aggr.confidence;
  pos = 1;
  // We itterate over the remining i .. count-1 elements
  for (uint32_t i = 1; i < count; i++, pos++) {
    if (pos == chunk) {
      aggr.confidence = confidence / chunk;
      target[target_i] = aggr;
      target_i++;
      aggr = vs[i];
      confidence = aggr.confidence;
      pos = 0;
    } else {
      confidence += vs[i].confidence;
      if (vs[i].value < aggr.value) {
        aggr = vs[i];
      };
    }
  }
  // Making sure the last aggregate is saved.
  if (target_i < target_size) {
    // We use chunk here to reflect the additional loss
    // in certenty of not computing a whole chunk.
    aggr.confidence = confidence / chunk;
    target[target_i] = aggr;
  }

  return r;
}

static ERL_NIF_TERM
max(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  ErlNifBinary bin;
  ERL_NIF_TERM r;
  ffloat* vs;
  ffloat* target;
  ErlNifSInt64 chunk;         // size to be compressed
  ErlNifSInt64 target_i = 0; // target position
  ffloat aggr;         // target position
  double confidence = 0;
  uint32_t pos;
  uint32_t count;
  uint32_t target_size;

  if (argc != 2)
    return enif_make_badarg(env);

  GET_CHUNK(chunk);
  GET_BIN(0, bin, count, vs);

  target_size = ceil((double) count / chunk) * sizeof(ffloat);
  if (! (target = (ffloat*) enif_make_new_binary(env, target_size, &r)))
    return enif_make_badarg(env); // TODO return propper error

  // If we don't have any input data we can return right away.
  if (count == 0)
    return r;

  // We know we have at least one element in the list so our
  // aggregator will start with this
  aggr = vs[0];
  confidence = aggr.confidence;
  pos = 1;
  // We itterate over the remining i .. count-1 elements
  for (uint32_t i = 1; i < count; i++, pos++) {
    if (pos == chunk) {
      aggr.confidence = confidence / chunk;
      target[target_i] = aggr;
      target_i++;
      aggr = vs[i];
      confidence = aggr.confidence;
      pos = 0;
    } else {
      confidence += vs[i].confidence;
      if (vs[i].value >  aggr.value) {
        aggr = vs[i];
      };
    }
  }
  // Making sure the last aggregate is saved.
  if (target_i < target_size) {
    // We use chunk here to reflect the additional loss
    // in certenty of not computing a whole chunk.
    aggr.confidence = confidence / chunk;
    target[target_i] = aggr;
  }
  return r;
}

static ERL_NIF_TERM
sum(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  ErlNifBinary bin;
  ErlNifSInt64 chunk;         // size to be compressed

  ERL_NIF_TERM r;
  ffloat* vs;
  ffloat* target;
  ffloat aggr;          // Aggregator
  double confidence;

  uint32_t target_i = 0;      // target position
  uint32_t count;
  uint32_t pos = 0;
  uint32_t target_size;

  if (argc != 2)
    return enif_make_badarg(env);

  GET_CHUNK(chunk);
  GET_BIN(0, bin, count, vs);

  target_size = ceil((double) count / chunk) * sizeof(ffloat);
  if (! (target = (ffloat*) enif_make_new_binary(env, target_size, &r)))
    return enif_make_badarg(env); // TODO return propper error
  if (count > 0) {
    aggr = vs[0];
    confidence = aggr.confidence;
    pos = 1;
    //We will be overwriting the confidence generated by dec_add because
    //it would give a false impression based on the later values having
    //a higher influence.
    for (uint32_t i = 1; i < count; i++, pos++) {
      if (pos == chunk) {
        aggr.confidence = confidence / chunk;
        target[target_i] = aggr;
        target_i++;
        aggr = vs[i];
        confidence = aggr.confidence;

        pos = 0;
      } else {
        confidence += vs[i].confidence;
        aggr.value += vs[i].value;
      }
    }
    if (count % chunk) {
      aggr = float_add(aggr, float_mulc(vs[count - 1], (chunk - (count % chunk))));
    }
    aggr.confidence = confidence / chunk;
    target[target_i] = aggr;
  }
  return r;
}

static ERL_NIF_TERM
avg(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
  ErlNifBinary bin;
  ERL_NIF_TERM r;
  ffloat* vs;
  ffloat* target;
  ErlNifSInt64 chunk;         // size to be compressed
  ffloat aggr;               // Aggregator
  double confidence;
  uint32_t target_i = 0;      // target position
  uint32_t count;
  uint32_t pos = 0;
  uint32_t target_size;


  if (argc != 2)
    return enif_make_badarg(env);

  GET_CHUNK(chunk);
  GET_BIN(0, bin, count, vs);

  target_size = ceil((double) count / chunk) * sizeof(ffloat);
  if (! (target = (ffloat*) enif_make_new_binary(env, target_size, &r)))
    return enif_make_badarg(env); // TODO return propper error

  if (count == 0)
    return r;

  aggr = vs[0];
  confidence = aggr.confidence;
  pos++;

  for (uint32_t i = 1; i < count; i++, pos++) {
    if (pos == chunk) {
      aggr.confidence = confidence / chunk;
      target[target_i] =  float_divc(aggr, chunk);
      target_i++;
      aggr = vs[i];
      confidence = aggr.confidence;
      pos = 0;
    } else {
      confidence += vs[i].confidence;
      aggr = float_add(aggr, vs[i]);
    }
  }
  if (count % chunk) {
    aggr = float_add(aggr, float_mulc(vs[count - 1], (chunk - (count % chunk))));
  }
  aggr.confidence = confidence / chunk;
  target[target_i] = float_divc(aggr, chunk);

  return r;
}


static ErlNifFunc nif_funcs[] = {
  {"min",        2, min},
  {"max",        2, max},
  {"sum",        2, sum},
  {"avg",        2, avg}
};

// Initialize this NIF library.
//
// Args: (MODULE, ErlNifFunc funcs[], load, reload, upgrade, unload)
// Docs: http://erlang.org/doc/man/erl_nif.html#ERL_NIF_INIT

ERL_NIF_INIT(mmath_aggr, nif_funcs, &load, NULL, &upgrade, NULL);
