/**
 * Copyright INRIA and ENS, contributors Peterlongo and Chikhi
 * pierre.peterlongo@inria.fr
 * rayan.chikhi@irisa.fr
 *
 * This software is a computer program whose purpose is to detect the
 * presence of a starter in a set of NGS reads, and to provide the neighbor
 * in case of success..
 *
 * This software is governed by the CeCILL license under French law and
 * abiding by the rules of distribution of free software.  You can  use,
 * modify and/ or redistribute the software under the terms of the CeCILL
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info".

 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability.

 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or
 * data to be ensured and,  more generally, to use and operate it in the
 * same conditions as regards security.
 *
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL license and that you accept its terms.
 */

/*
 * a simple implementation of a hash table, designed to be fast for key lookup
 */
#ifdef __cplusplus
extern "C"
{
#endif

#include <assert.h>
#include "simplehash.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

extern char keyi2nucleotide_array ['T'+1];








//-------------------------------------------------
///////////// SIMPLE HASH FUNCTIONS /////////////

uint64_t compute_intermediate_hash(const char *key)
{
	uint64_t intermediate_hash=0L;
	int i=0;
	char keyi = key[0];
  	while (keyi != '\0')
  	{
	  uint64_t nucleotide = keyi2nucleotide_array[keyi];
	  //keyi2nucleotide()
	  intermediate_hash|=(nucleotide&3)<<((uint64_t)2*i);
	  keyi = key[++i];
  	}
  	return intermediate_hash;
}


int POOL_CHUNK = 100000;
int nBUCKETS = 0;
int NBUCKETS = 1000;

void custom_finalize_hash(int NBUCKETS)
{
    nBUCKETS = NBUCKETS;
}

uint64_t finalize_hash(uint64_t intermediate)
{
	// do some quick hashing, I think it's from a final step of murmurhash3
	uint64_t tmp=intermediate;
	tmp^= tmp >> 33LL;
	tmp *= 0xff51afd7ed558ccdLL;
	tmp^= tmp>> 33LL;
	tmp *= 0xc4ceb9fe1a85ec53LL;
	tmp^= tmp>> 33LL;
    if (nBUCKETS)
        tmp=tmp%nBUCKETS;
	return tmp;
}

// also known as slow_hash_key
int slow_hash_key(const char *key)
{
  	return finalize_hash(compute_intermediate_hash(key));
}

// faster hashing by taking advantage of consecutive kmers

int fast_hash_key(const char *key, int k, uint64_t *intermediate_hash)
{
	if (*intermediate_hash == 0L)
	{
		*intermediate_hash=compute_intermediate_hash(key);
		return finalize_hash(*intermediate_hash);
	}
	(*intermediate_hash)/=4;
	char keyi = key[k-1];
	uint64_t nucleotide=keyi2nucleotide_array[keyi];
	//keyi2nucleotide()
	(*intermediate_hash)|=(nucleotide&3)<<((uint64_t)2*(k-1));
	return finalize_hash(*intermediate_hash);
}



//-------------------------------------------------
///////////// END SIMPLE HASH FUNCTIONS /////////////








void *pool_malloc(simplehash *map, signed int alloc_size) // needs to be signed int because it'll be substracted later
{
    if (alloc_size>POOL_CHUNK)
    {
        printf("the only limitation of this pool is that you can't malloc anything larger than it (%d bytes)\n",POOL_CHUNK);
    }
    if (map->memory_pools == NULL || map->current_pool_pointer >= (POOL_CHUNK - alloc_size))
    {
      map->memory_pools = (void **)realloc(map->memory_pools,(++(map->nb_pools)) * sizeof(void *));
      map->memory_pools[map->nb_pools-1] = malloc(POOL_CHUNK);
      map->current_pool_pointer = 0;
#ifdef DEBUG_SIMPLE_HASH
            printf("resizing memory pool to %d pools \n",map->nb_pools);
#endif
    }
    map->current_pool_pointer += alloc_size;
    return (bucket*)(map->memory_pools[map->nb_pools-1] + map->current_pool_pointer - alloc_size);
}


bucket * bucket_malloc(simplehash * map)
{
    return (bucket *)pool_malloc(map,sizeof(bucket));
}

char*  pool_strdup(simplehash * map, const char *string)
{
  char *new_string = (char *)pool_malloc(map,strlen(string)+1);
    strcpy(new_string,string);
    return new_string;
}

void pools_free(simplehash *map)
{
    int i;
    for (i=0;i<map->nb_pools;i++)
        free(map->memory_pools[i]);
    free(map->memory_pools);
}



/*
 * create the hash table
 */
hash_t hash_create(unsigned int nbuckets){
  simplehash *map = (simplehash *) malloc(sizeof(simplehash));
  map->buckets = (bucket**) calloc(NBUCKETS,sizeof(bucket*)); // forcing nbuckets=NBUCKETS for now, because that argument is still = 1 in some parts of mapsembler code (no big deal)
  custom_finalize_hash(NBUCKETS); // tell finalize_hash to return values in [1..NBUCKETS]
    map->memory_pools = NULL;
    map->nb_pools = 0;
    map->current_pool_pointer = 0;
    map->which_bucket = 0;
    map->where_in_the_bucket = NULL;
    return (hash_t)map;
}

/*
 * free the structure
 */
void hash_free(hash_t given_map)
{
    simplehash *map =(simplehash*)given_map;
    pools_free(map);    
    free((void*)(map->buckets));
    free(map);
}

/*
 * just clear the structure, don't free
 */
void hash_clear_structure(hash_t given_map)
{
    simplehash *map =(simplehash*)given_map;
    memset((void*)(map->buckets),0,NBUCKETS*sizeof(bucket*));
    // still, free the pool and make it as new 
    pools_free(map);    
    map->memory_pools = NULL;
    map->nb_pools = 0;
    map->current_pool_pointer = 0;
    map->which_bucket = 0;
    map->where_in_the_bucket = NULL;
}


int compare_key(const char *key1,const char *key2)
{
    int i=0;
    char char1,char2;
    while (1)
    {
        char1=key1[i];
        char2=key2[i];
        if (char1!=char2)
            return 0;
        if (char1=='\0' && char2=='\0')
            return 1;
        i++;
    }
    return 1;
}

/*
 * insert an element in the hash table
 *
 * hash_insert returns 0 on success
 *
 * in the case of mapsembler, hash_insert will only be used to insert pointers, so we dont need to reserve data space
 */
    int hash_insert(hash_t given_map, const char *key, const void * data, size_t len){
#ifdef DEBUG_SIMPLE_HASH
            printf("inserting key %s and data %d of length %d\n",key,*((int*)data),(int)len);
#endif
        simplehash *map =(simplehash*)given_map;
        int hash = slow_hash_key(key)%NBUCKETS;
        // empty bucket: create a new one
        if (map->buckets[hash] == NULL)
        {
            map->buckets[hash] = bucket_malloc(map);
            map->buckets[hash]->key = pool_strdup(map,key);
            map->buckets[hash]->value = data;
            map->buckets[hash]->next = NULL;
            return 0;
        }
        // else check the list
        else
        {
            int found_key = 0;
            bucket *current_bucket = map->buckets[hash];
            while (1)
            {
                if (compare_key(current_bucket->key,key))
                {
                    found_key = 1;
                    return 0;
                }
                if (current_bucket->next == NULL)
                    break;
                current_bucket=current_bucket->next;
            }
            if (!found_key)
            {
                current_bucket->next = bucket_malloc(map);
                current_bucket->next->key = pool_strdup(map,key);
                current_bucket->next->value = data;
                current_bucket->next->next = NULL;
                return 0;
            }
        }
        return -1; // given libchash specification, it apparently only returns -1 when the table is null
    }


/*
 * remove an element from the hash table
 */
int hash_remove(hash_t map, const char *key){
    printf("hash_remove is not implementated (because mapsembler apparently never uses it)\n"); exit(1);
    return -1;
}


/*
 * retrieve an element from the hash table
 *
 * if mode==2, take advantage of the fact that we're hashing consecutive kmers
 * mode==1 computes the full hash everytime
 */
ssize_t generic_hash_entry_by_key(hash_t given_map, const char *key, int k, uint64_t *fast_hash, void **data, int mode){
    simplehash *map =(simplehash*)given_map;
    int hash;

    if (mode==1)
        hash = slow_hash_key(key)%NBUCKETS;
    else
        hash = fast_hash_key(key,k,fast_hash)%NBUCKETS;

    if (map->buckets[hash] == NULL)
        return 0;
    bucket *current_bucket = map->buckets[hash];
    while (1)
    {
        if (compare_key(current_bucket->key,key))
        {
            *data=(void **)(current_bucket->value);

#ifdef DEBUG_SIMPLE_HASH
                printf("accessing key %s yielding data %X\n",key,*(unsigned int*)data);
#endif

            return sizeof(ulong); // dunno why
        }
        if (current_bucket->next == NULL)
            break;
        current_bucket=current_bucket->next;
    }

#ifdef DEBUG_SIMPLE_HASH
        printf("accessing key %s -- not found\n",key);
#endif
    return 0;

}

ssize_t hash_entry_by_key(hash_t given_map, const char *key, void **data){
    return generic_hash_entry_by_key(given_map, key, 0, NULL, data, 1);
}

ssize_t fast_hash_entry_by_key(hash_t given_map, const char *key, int k, uint64_t *fast_hash,void **data){
    // that trick currently works only for small kmers, but it could be extended with a cached_intermediate_hash[k/32] array
     if (k>32)
        return generic_hash_entry_by_key(given_map, key, 0, NULL, data, 1);
     return generic_hash_entry_by_key(given_map, key, k, fast_hash, data, 2);
}


/*
 * iterator functions
 *
 */
hash_iter hash_iterator_init(hash_t given_map){
    simplehash *map =(simplehash*)given_map;
    map->which_bucket = -1;
    map->where_in_the_bucket = NULL;
    return hash_iterator_next(given_map,(hash_iter)0);
}

hash_iter hash_iterator_next(hash_t given_map,  hash_iter iter){
    simplehash *map =(simplehash*)given_map;
    // test if it's over
    // reached end of bucket
    if (map->where_in_the_bucket == NULL || map->where_in_the_bucket->next == NULL)
      {
        // go to next non-empty bucket
        while (1)
	  {
            map->which_bucket++;
            // test if end of iterator
            if (map->which_bucket==NBUCKETS)
            {
                map->where_in_the_bucket = NULL;
                break;
            }
            map->where_in_the_bucket=map->buckets[map->which_bucket];
            if (map->where_in_the_bucket != NULL)
                break;
        }
    }
    else
    {
        // just go to the next item in the bucket
        map->where_in_the_bucket = map->where_in_the_bucket->next;
    }
    if (hash_iterator_is_end(given_map,iter))
        return (hash_iter)-1; // respects interface_libchash implementation
    return (hash_iter)map->which_bucket;
}

int hash_iterator_is_end(hash_t given_map, hash_iter iter){
    simplehash *map =(simplehash*)given_map;
    return (map->which_bucket==NBUCKETS);
}

ssize_t hash_iterator_return_entry(hash_t given_map, hash_iter iter,
        char **key, void **data){
    simplehash *map =(simplehash*)given_map;
    assert(map != NULL);
    assert(key != NULL);
    assert(data != NULL);
    if (hash_iterator_is_end((hash_t)map,iter))
    {
        return 0;
    }
    *key=(char*)map->where_in_the_bucket->key;
    *data=(char*)map->where_in_the_bucket->value;

#ifdef DEBUG_SIMPLE_HASH
        printf("returning map=%X iterator key: %s data: %X\n",map,*key,(int)*(int **)data);
#endif
    return sizeof(ulong);
}
#ifdef __cplusplus
}
#endif
