/*
# C-Code for hashing and matching
# S3 atomic 64bit integers for R
# (c) 2011-2024 Jens Oehlschägel
# (c) 2025 Michael Chirico
# Licence: GPL2
# Provided 'as is', use at your own risk
# Created: 2011-12-11
#*/

#include <Rinternals.h>
#include <R.h>
#include <R_ext/Boolean.h> // TRUE,FALSE

// This multiplicator was used in Simon Urbanek's package fastmatch for 32-bit integers
//#define HASH64(X, SHIFT) (314159265358979323ULL * ((unsigned long long)(X)) >> (SHIFT))
// This multiplicator seems to work fine with 64bit integers
#define HASH64(X, SHIFT) (0x9e3779b97f4a7c13ULL * ((unsigned long long)(X)) >> (SHIFT))

SEXP hashfun_integer64(SEXP x_, SEXP bits_, SEXP ret_){
  int i, n = LENGTH(x_);
  long long * x = (long long *) REAL(x_);
  unsigned int * ret = (unsigned int *) INTEGER(ret_);
  int shift = 64 - asInteger(bits_);
  for(i=0; i<n; i++){
    ret[i] = (unsigned int) HASH64(x[i], shift);
  }
  return ret_;
}

// this function is loosely following Simon Urbanek's package 'fastmatch'
SEXP hashmap_integer64(SEXP x_, SEXP bits_, SEXP hashpos_, SEXP nunique_){
  int i, nx = LENGTH(x_);
  int h, nh = LENGTH(hashpos_);
  long long * x = (long long *) REAL(x_);
  unsigned int * hashpos = (unsigned int *) INTEGER(hashpos_);
  int bits = asInteger(bits_);
  int shift = 64 - bits;
  long long v;
  int nunique = 0;
  for(i=0; i<nx; ){
    v = x[i++];
    h = HASH64(v, shift);
    while (hashpos[h] && x[hashpos[h] - 1] != v){
        h++;
        if (h == nh)
            h = 0;
      }
      if (!hashpos[h]){
      hashpos[h] = i;
      nunique++;
      }
  }
  INTEGER(nunique_)[0] = nunique;
  return hashpos_;
}

SEXP hashpos_integer64(SEXP x_, SEXP hashdat_, SEXP bits_, SEXP hashpos_, SEXP nomatch_, SEXP ret_){
  int i, nx = LENGTH(x_);
  int h, nh = LENGTH(hashpos_);
  long long * x = (long long *) REAL(x_);
  long long * hashdat = (long long *) REAL(hashdat_);
  unsigned int * hashpos = (unsigned int *) INTEGER(hashpos_);
  int * ret = INTEGER(ret_);
  int bits = asInteger(bits_);
  int shift = 64 - bits;
  int nomatch = asInteger(nomatch_);
  long long v;
  for(i=0; i<nx; i++){
    v = x[i];
    h = HASH64(v, shift);
    for(;;){
      if (hashpos[h]){  // this is mostly while(hashpos[h]) but we want to catch failure for the nomatch assignment
          if (hashdat[hashpos[h] - 1] == v){
            ret[i] = hashpos[h];
            break;
          }
          h++;
          if (h == nh)
            h = 0;
      }else{
        ret[i] = nomatch;
        break;
      }
    }
  }
  return ret_;
}

SEXP hashrev_integer64(SEXP x_, SEXP hashdat_, SEXP bits_, SEXP hashpos_, SEXP nunique_, SEXP nomatch_, SEXP ret_){
  int i, nx = LENGTH(x_);
  int h, nh = LENGTH(hashpos_);
  int nd = LENGTH(hashdat_);
  long long * x = (long long *) REAL(x_);
  long long * hashdat = (long long *) REAL(hashdat_);
  unsigned int * hashpos = (unsigned int *) INTEGER(hashpos_);
  int * ret = INTEGER(ret_);
  int bits = asInteger(bits_);
  int shift = 64 - bits;
  int nomatch = asInteger(nomatch_);
  int nunique = asInteger(nunique_);
  int iunique=0;
  long long v;
  for(i=0; i<nx; ){
    v = x[i++];
    h = HASH64(v, shift);
    while(hashpos[h]){
      if (hashdat[hashpos[h] - 1] == v){
        h = hashpos[h] - 1;
        if (!ret[h]){
            ret[h] = i;
            if (++iunique==nunique)
              i=nx; // break out of for as well
        }
        break;
      }
      h++;
      if (h == nh)
        h = 0;
    }
  }
  if (iunique<nd){
    if (nunique<nd){ // some gaps are duplicates
      for(i=0; i<nd; i++){
        if (!ret[i]){
            v = hashdat[i];
            h = HASH64(v, shift);
            while(hashpos[h]){  // this is mostly while(hashpos[h]) but we want to catch failure for the nomatch assignment
              if (hashdat[hashpos[h] - 1] == v){
                h = ret[hashpos[h] - 1];
                if (h)
                  ret[i] = h;
                else
                  ret[i] = nomatch;
                break;
              }
              h++;
              if (h == nh)
                h = 0;
            }
        }
      }
    }else{ // no duplicates: all gaps are nomatches
      for(i=0; i<nd; i++)
        if (!ret[i])
          ret[i] = nomatch;
    }
  }
  return ret_;
}

SEXP hashrin_integer64(SEXP x_, SEXP hashdat_, SEXP bits_, SEXP hashpos_, SEXP nunique_, SEXP ret_){
  int i, nx = LENGTH(x_);
  int h, nh = LENGTH(hashpos_);
  int nd = LENGTH(hashdat_);
  long long * x = (long long *) REAL(x_);
  long long * hashdat = (long long *) REAL(hashdat_);
  unsigned int * hashpos = (unsigned int *) INTEGER(hashpos_);
  int * ret = INTEGER(ret_);
  int bits = asInteger(bits_);
  int shift = 64 - bits;
  int nunique = asInteger(nunique_);
  int iunique=0;
  long long v;
  for(i=0; i<nx; ){
    v = x[i++];
    h = HASH64(v, shift);
    while(hashpos[h]){
      if (hashdat[hashpos[h] - 1] == v){
        h = hashpos[h] - 1;
        if (!ret[h]){
            ret[h] = TRUE;
            if (++iunique==nunique)
              i=nx; // break out of for as well
        }
        break;
      }
      h++;
      if (h == nh)
        h = 0;
    }
  }
    if (nunique<nd){ // some gaps are duplicates
      for(i=0; i<nd; i++){
        if (!ret[i]){
            v = hashdat[i];
            h = HASH64(v, shift);
            while(hashpos[h]){  // this is mostly while(hashpos[h]) but we want to catch failure for the nomatch assignment
              if (hashdat[hashpos[h] - 1] == v){
                h = ret[hashpos[h] - 1];
                if (h)
                  ret[i] = TRUE;
                break;
              }
              h++;
              if (h == nh)
                h = 0;
            }
        }
      }
    }
  return ret_;
}

SEXP hashfin_integer64(SEXP x_, SEXP hashdat_, SEXP bits_, SEXP hashpos_, SEXP ret_){
  int i, nx = LENGTH(x_);
  int h, nh = LENGTH(hashpos_);
  long long * x = (long long *) REAL(x_);
  long long * hashdat = (long long *) REAL(hashdat_);
  unsigned int * hashpos = (unsigned int *) INTEGER(hashpos_);
  int * ret = LOGICAL(ret_);
  int bits = asInteger(bits_);
  int shift = 64 - bits;
  long long v;
  for(i=0; i<nx; i++){
    v = x[i];
    h = HASH64(v, shift);
    for(;;){
      if (hashpos[h]){  // this is mostly while(hashpos[h]) but we want to catch failure for the nomatch assignment
          if (hashdat[hashpos[h] - 1] == v){
            ret[i] = TRUE;
            break;
          }
          h++;
          if (h == nh)
            h = 0;
      }else{
        ret[i] = FALSE;
        break;
      }
    }
  }
  return ret_;
}

SEXP hashdup_integer64(SEXP hashdat_, SEXP bits_, SEXP hashpos_, SEXP nunique_, SEXP ret_){
  int nu = LENGTH(ret_);
  int h, nh = LENGTH(hashpos_);
  //long long * hashdat = (long long *) REAL(hashdat_);
  unsigned int * hashpos = (unsigned int *) INTEGER(hashpos_);
  int * ret = LOGICAL(ret_);
  int nunique = asInteger(nunique_);
  for(h=0; h<nu; h++)
    ret[h] = TRUE;
  for(h=0; h<nh; h++)
    if (hashpos[h]>0){
      ret[hashpos[h]-1] = FALSE;
      nunique--;
      if (nunique<1)
        break;
    }
  return ret_;
}

SEXP hashuni_integer64(SEXP hashdat_, SEXP bits_, SEXP hashpos_, SEXP keep_order_, SEXP ret_){
  int h, nh = LENGTH(hashpos_);
  int u, nu = LENGTH(ret_);
  long long * hashdat = (long long *) REAL(hashdat_);
  unsigned int * hashpos = (unsigned int *) INTEGER(hashpos_);
  long long * ret = (long long *) REAL(ret_);
  if (asLogical(keep_order_)){
      int i;
      // int nx = LENGTH(hashdat_);
      int bits = asInteger(bits_);
      int shift = 64 - bits;
      long long v;
      for(u=0,i=0; u<nu; i++){
        v = hashdat[i];
        h = HASH64(v, shift);
        while(hashpos[h] && hashdat[hashpos[h] - 1] != v){  // this is mostly while(hashpos[h]) but we want to catch failure for the nomatch assignment
          h++;
          if (h == nh)
            h = 0;
        }
        if (i == (hashpos[h] - 1)){
          ret[u++] = v; /* unique */
        }
      }
  }else{
      for(u=0,h=0; u<nu; h++)
        if (hashpos[h]>0){
          ret[u++] = hashdat[hashpos[h]-1];
        }
  }
  return ret_;
}

SEXP hashmapuni_integer64(SEXP x_, SEXP bits_, SEXP hashpos_, SEXP nunique_){
  int i, nx = LENGTH(x_);
  int h, nh = LENGTH(hashpos_);
  int nu = 0;
  SEXP ret_;
  PROTECT_INDEX idx;
  PROTECT_WITH_INDEX(ret_ = allocVector(REALSXP, nx), &idx);
  long long * ret = (long long *) REAL(ret_);
  long long * x = (long long *) REAL(x_);
  unsigned int * hashpos = (unsigned int *) INTEGER(hashpos_);
  int bits = asInteger(bits_);
  int shift = 64 - bits;
  long long v;
  for(i=0; i<nx; ){
    v = x[i++];
    h = HASH64(v, shift);
    while(hashpos[h] && x[hashpos[h] - 1] != v){
        h++;
        if (h == nh)
            h = 0;
    }
    if (!hashpos[h]){
        hashpos[h] = i;
        ret[nu++] = v;
    }
  }
  INTEGER(nunique_)[0] = nu;
  REPROTECT(ret_ = lengthgets(ret_, nu), idx);
  UNPROTECT(1);
  return ret_;
}


SEXP hashupo_integer64(SEXP hashdat_, SEXP bits_, SEXP hashpos_, SEXP keep_order_, SEXP ret_){
  int h, nh = LENGTH(hashpos_);
  int u, nu = LENGTH(ret_);
  long long * hashdat = (long long *) REAL(hashdat_);
  unsigned int * hashpos = (unsigned int *) INTEGER(hashpos_);
  int * ret = INTEGER(ret_);
  if (asLogical(keep_order_)){
      int i;
      // int nx = LENGTH(hashdat_);
      int bits = asInteger(bits_);
      int shift = 64 - bits;
      long long v;
      for(u=0,i=0; u<nu; i++){
        v = hashdat[i];
        h = HASH64(v, shift);
        while(hashpos[h] && hashdat[hashpos[h] - 1] != v){  // this is mostly while(hashpos[h]) but we want to catch failure for the nomatch assignment
          h++;
          if (h == nh)
            h = 0;
        }
        if (i == (hashpos[h] - 1)){
          ret[u++] = hashpos[h]; /* unique */
        }
      }
  }else{
      for(u=0,h=0; u<nu; h++)
        if (hashpos[h]>0){
          ret[u++] = hashpos[h];
        }
  }
  return ret_;
}

SEXP hashmapupo_integer64(SEXP x_, SEXP bits_, SEXP hashpos_, SEXP nunique_){
  int i, nx = LENGTH(x_);
  int h, nh = LENGTH(hashpos_);
  int nu = 0;
  SEXP ret_;
  PROTECT_INDEX idx;
  PROTECT_WITH_INDEX(ret_ = allocVector(INTSXP, nx), &idx);
  int * ret = INTEGER(ret_);
  long long * x = (long long *) REAL(x_);
  unsigned int * hashpos = (unsigned int *) INTEGER(hashpos_);
  int bits = asInteger(bits_);
  int shift = 64 - bits;
  long long v;
  for(i=0; i<nx; ){
    v = x[i++];
    h = HASH64(v, shift);
    while(hashpos[h] && x[hashpos[h] - 1] != v){
        h++;
        if (h == nh)
            h = 0;
    }
    if (!hashpos[h]){
        hashpos[h] = i;
        ret[nu++] = hashpos[h];
    }
  }
  INTEGER(nunique_)[0] = nu;
  REPROTECT(ret_ = lengthgets(ret_, nu), idx);
  UNPROTECT(1);
  return ret_;
}



SEXP hashtab_integer641(SEXP hashdat_, SEXP bits_, SEXP hashpos_, SEXP nunique_){
  int i, nx = LENGTH(hashdat_);
  int h, nh = LENGTH(hashpos_);
  int u;
  long long * hashdat = (long long *) REAL(hashdat_);
  unsigned int * hashpos = (unsigned int *) INTEGER(hashpos_);
  //int * pos = INTEGER(pos_);
  SEXP ret_;
  PROTECT_INDEX idx;
  PROTECT_WITH_INDEX(ret_ = allocVector(INTSXP, nh), &idx);
  int * ret = INTEGER(ret_);
  int bits = asInteger(bits_);
  int shift = 64 - bits;
  long long v;
  for(i=0; i<nh; i++)
    ret[i]=0;
  for(i=0; i<nx; i++){
    v = hashdat[i];
    h = HASH64(v, shift);
    while(hashpos[h]){  // this is mostly while(hashpos[h]) but we want to catch failure for the nomatch assignment
      if (hashdat[hashpos[h] - 1] == v){
        ret[h]++;
        break;
      }
      h++;
      if (h == nh)
        h = 0;
    }
  }
  for (u=0,h=0;h<nh;h++){
    if (hashpos[h]){
      //pos[u]=hashpos[h];
      ret[u++]=ret[h];
    }
  }
  REPROTECT(ret_ = lengthgets(ret_, u), idx);
  UNPROTECT(1);
  return ret_;
}



SEXP hashtab_integer64(SEXP x_, SEXP bits_, SEXP hashpos_, SEXP nunique_){
  int i, nx = LENGTH(x_);
  int h, nh = LENGTH(hashpos_);
  long long * x = (long long *) REAL(x_);
  unsigned int * hashpos = (unsigned int *) INTEGER(hashpos_);
  SEXP hashtab_;
  PROTECT_INDEX idx;
  PROTECT_WITH_INDEX(hashtab_ = allocVector(INTSXP, nh), &idx);
  int *hashtab = INTEGER(hashtab_);
  int bits = asInteger(bits_);
  int shift = 64 - bits;
  long long v;
  int u, nu = INTEGER(nunique_)[0];

  for(i=0; i<nh; i++)
    hashtab[i]=0;
  for(i=0; i<nx; ){
    v = x[i++];
    h = HASH64(v, shift);
    while (hashpos[h] && x[hashpos[h] - 1] != v){
        h++;
        if (h == nh)
            h = 0;
    }
    hashtab[h]++;
  }
  SEXP tabval_;
  PROTECT(tabval_ = allocVector(REALSXP, nu));
  long long * tabval = (long long *) REAL(tabval_);
  for (u=0,h=0;u<nu;h++){
    if (hashpos[h]){
      tabval[u] = x[hashpos[h]-1];
      hashtab[u]=hashtab[h];
      u++;
    }
  }
  REPROTECT(hashtab_ = lengthgets(hashtab_, nu), idx);

  SEXP class;
  PROTECT(class = allocVector(STRSXP, 1));
  SET_STRING_ELT(class, 0, mkChar("integer64"));
  classgets(tabval_, class);

  SEXP ret_;
  PROTECT(ret_ = allocVector(VECSXP, 2));
  SET_VECTOR_ELT(ret_, 0, tabval_);
  SET_VECTOR_ELT(ret_, 1, hashtab_);

  UNPROTECT(4);
  return ret_;
}



SEXP hashmaptab_integer64(SEXP x_, SEXP bits_, SEXP hashpos_, SEXP nunique_){
  int i, nx = LENGTH(x_);
  int h, nh = LENGTH(hashpos_);
  long long * x = (long long *) REAL(x_);
  unsigned int * hashpos = (unsigned int *) INTEGER(hashpos_);
  SEXP hashtab_;
  PROTECT_INDEX idx;
  PROTECT_WITH_INDEX(hashtab_ = allocVector(INTSXP, nh), &idx);
  int *hashtab = INTEGER(hashtab_);
  int bits = asInteger(bits_);
  int shift = 64 - bits;
  long long v;
  int u, nu=0;
  for(i=0; i<nh; i++)
    hashtab[i]=0;
  for(i=0; i<nx; ){
    v = x[i++];
    h = HASH64(v, shift);
    while (hashpos[h] && x[hashpos[h] - 1] != v){
        h++;
        if (h == nh)
            h = 0;
    }
    if (!hashpos[h]){
        hashpos[h] = i;
        nu++;
    }
    hashtab[h]++;
  }
  SEXP tabval_;
  PROTECT(tabval_ = allocVector(REALSXP, nu));
  long long * tabval = (long long *) REAL(tabval_);
  for (u=0,h=0;u<nu;h++){
    if (hashpos[h]){
      tabval[u] = x[hashpos[h]-1];
      hashtab[u]=hashtab[h];
      u++;
    }
  }
  INTEGER(nunique_)[0] = nu;
  REPROTECT(hashtab_ = lengthgets(hashtab_, nu), idx);

  SEXP class;
  PROTECT(class = allocVector(STRSXP, 1));
  SET_STRING_ELT(class, 0, mkChar("integer64"));
  classgets(tabval_, class);

  SEXP ret_;
  PROTECT(ret_ = allocVector(VECSXP, 2));
  SET_VECTOR_ELT(ret_, 0, tabval_);
  SET_VECTOR_ELT(ret_, 1, hashtab_);

  UNPROTECT(4);
  return ret_;
}
