#include "nif.h"
#include <inttypes.h>

#define ROTATE_LEFT(x, b) (unsigned long)(((x) << (b)) | ((x) >> (64 - (b))))

#define COMPRESS                \
  v0 += v1;                     \
  v2 += v3;                     \
  v1 = ROTATE_LEFT(v1, 13);     \
  v3 = ROTATE_LEFT(v3, 16);     \
  v1 ^= v0;                     \
  v3 ^= v2;                     \
  v0 = ROTATE_LEFT(v0, 32);     \
  v2 += v1;                     \
  v0 += v3;                     \
  v1 = ROTATE_LEFT(v1, 17);     \
  v3 = ROTATE_LEFT(v3, 21);     \
  v1 ^= v2;                     \
  v3 ^= v0;                     \
  v2 = ROTATE_LEFT(v2, 32);

NIF(apply_block){
  int arity;
  const ERL_NIF_TERM** tuple;

  enif_get_tuple(env, argv[0], &arity, &tuple);

  unsigned long v0;
  unsigned long v1;
  unsigned long v2;
  unsigned long v3;
  unsigned long m;

  enif_get_ulong(env, tuple[0], &v0);
  enif_get_ulong(env, tuple[1], &v1);
  enif_get_ulong(env, tuple[2], &v2);
  enif_get_ulong(env, tuple[3], &v3);
  enif_get_ulong(env, argv[1], &m);

  int c, i;

  enif_get_int(env, argv[2], &c);

  v3 ^= m;
  for(i = 0; i < c; i++){
    COMPRESS
  }
  v0 ^= m;

  return enif_make_tuple4(
    env,
    enif_make_ulong(env, v0),
    enif_make_ulong(env, v1),
    enif_make_ulong(env, v2),
    enif_make_ulong(env, v3)
  );
}

NIF(finalize){
  int arity;
  const ERL_NIF_TERM** tuple;

  enif_get_tuple(env, argv[0], &arity, &tuple);

  unsigned long v0;
  unsigned long v1;
  unsigned long v2;
  unsigned long v3;

  enif_get_ulong(env, tuple[0], &v0);
  enif_get_ulong(env, tuple[1], &v1);
  enif_get_ulong(env, tuple[2], &v2);
  enif_get_ulong(env, tuple[3], &v3);

  int d, i;

  enif_get_int(env, argv[1], &d);

  v2 ^= 0xff;
  for(i = 0; i < d; i++){
    COMPRESS
  }

  return enif_make_ulong(env, v0 ^ v1 ^ v2 ^ v3);
}

static ErlNifFunc nif_funcs[] = {
  { "apply_internal_block", 3, apply_block },
  { "finalize", 2, finalize },
  { "nif_loaded", 0, nif_loaded }
};

ERL_NIF_INIT(Elixir.SipHash.State,nif_funcs,NULL,NULL,upgrade,NULL)
