/**
 * Copyright INRIA , contributors Peterlongo
 * pierre.peterlongo@inria.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.
 */

/*
 * interface between libchash and the generic hash functions used in mapsembler
 */


#include "../commons.h"

#ifdef __cplusplus
extern "C"
{
#endif
#include "libchash.h"
#include <assert.h>
#include "hash.h"
#include <string.h> /* for strdup  */
#include <ctype.h> /* for toupper */
#include <stdint.h>

#include "list.h" /* for hash_add_int_to_list */


extern unsigned int nbits_nbseeds;
extern uint64_t  mask_nbseed ;
extern uint64_t  mask_offset_seed; 
  //#define debug_libchash_access

/*
 * prepare the key for the hash table:
 * we only use uppercase keys
 */
// useless to apply this intensivelly. The input data is already transformed in upper case if necessary.
//char *prepare_key(const char *key)
//{
//	char *key_upped=strdup(key);
//	int i;
//	for (i = 0; key_upped[i]; i++)
//	    key_upped[i] = toupper(key_upped[i]);
//	return key_upped;
//}

 /*
  * 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
  * this is a FIXME if we need to call hash_insert to copy data longer than a pointer.
  */
 int hash_insert(hash_t map, const char *key, const void * data, size_t len){
	 HTItem *res;
#ifdef debug_libchash_access
		 printf("inserting key %s and data %d of length %d\n",key,*((int*)data),(int)len);
#endif
	 res=HashFindOrInsert( (struct HashTable*)map, PTR_KEY((struct HashTable*)map,key),(ulong) data);
     if (res!=NULL)
	     	return 0;
     return -1;
 }

 /*
  * create the hash table
  *
  * for now we dont care about nbuckets for hash_create
  */
 hash_t hash_create(unsigned int nbuckets){
 /**
  *  arguments for AllocateHashTable():
  *
  *  cchKey: if it's a positive number, then each key is a
  *                fixed-length record of that length.  If it's 0,
  *                the key is assumed to be a \0-terminated string.
  *        fSaveKey: normally, you are responsible for allocating
  *                  space for the key.  If this is 1, we make a
  *                  copy of the key for you.
  */
	 return (hash_t)AllocateHashTable(0,1);
 }


hash_t hash_create_binarykey(unsigned int nbuckets){
    /**
     *  arguments for AllocateHashTable():
     *
     *  cchKey: if it's a positive number, then each key is a
     *                fixed-length record of that length.  If it's 0,
     *                the key is assumed to be a \0-terminated string.
     *        fSaveKey: normally, you are responsible for allocating
     *                  space for the key.  If this is 1, we make a
     *                  copy of the key for you.
     */
    return (hash_t)AllocateHashTable(sizeof(uint64_t),1);
}


void hash_clear(hash_t map, void (*specific_free)(const void *)){
	 hash_iter iterator=hash_iterator_init(map);

	 /*	 is the hash table empty?  */
	 if (iterator < 0)
		 return;

	char *key;
	void *data;

	/*
	 * free all lists
	 */

	while (!hash_iterator_is_end(map,iterator))
	{
		hash_iterator_return_entry(map,iterator,&key,(void **)&data);
		specific_free(data);
		iterator=hash_iterator_next(map,iterator);
	}

	/*
	 * free hash table
	 */
//	printf("map->table : %lu\n", ((struct HashTable*)map)->table);
	ClearHashTable((struct HashTable*)map);
 }

 /*
  * fully delete and free the hash table
  *
  * in hashmap, hash_delete recursively deletes while calling specific free function at each element,
  * so we do this too
  *
  */
 int hash_delete(hash_t map, void (*specific_free)(const void *)){
	 hash_iter iterator=hash_iterator_init(map);

	 /*	 is the hash table empty?  */
	 if (iterator < 0)
		 return 0;

	char *key;
	void *data;

	/*
	 * free all lists
	 */
	while (!hash_iterator_is_end(map,iterator))
	{
		hash_iterator_return_entry(map,iterator,&key,(void **)&data);
		specific_free(data);
		iterator=hash_iterator_next(map,iterator);
	}

	/*
	 * free hash table
	 */
	FreeHashTable((struct HashTable*)map);
	return 0;
 }
  
  void hash_free(hash_t map){
    hash_iter iterator=hash_iterator_init(map);

    /*	 is the hash table empty?  */
    //    printf("freeing from iterator %llx\n", iterator);
    if ((long int)iterator < 0)
      return;

    char *key;
    void *data;

    /*
     * free all lists
     */
    while (!hash_iterator_is_end(map,iterator))
      {
	hash_iterator_return_entry(map,iterator,&key,(void **)&data);
	//		printf("freeing data %llu\n", data);
	//	if(data) free(data);
	iterator=hash_iterator_next(map,iterator);
      }

    /*
     * free hash table
     */
    FreeHashTable((struct HashTable*)map);
  }

  /*
  * retrieve an element from the hash table
  *
  */
 ssize_t hash_entry_by_key(hash_t map, const char *key, void **data){
	 HTItem *res;
//	 char *prepared_key=prepare_key(key);
	 res=HashFind((struct HashTable*)map, PTR_KEY((struct HashTable*)map,key));
//	 free(prepared_key);
	 if (res!=NULL)
	 {
	    	*data=(void **)(res->data);
#ifdef debug_libchash_access
	   		 		 	 printf("accessing key %s yielding data %X\n",key,*(unsigned int*)data);
#endif
	    	return sizeof(ulong);
	 }
#ifdef debug_libchash_access
	 printf("accessing key %s -- not found\n",key);
#endif
	 return 0;// TODO: maybe make it return the length of the data, as specified in the interface,
		// but for now we don't need it in mapsembler apparently

 }


 /*
  * iterator functions
  *
  */
 hash_iter hash_iterator_init(hash_t map){
	 hash_iter iter;
 	 iter=(hash_iter) HashFirstBucket((struct HashTable*)map);
 	 if (iter==NULL)
 		return (hash_iter)-1;
 	return iter;
 }

 hash_iter hash_iterator_next(hash_t map,  hash_iter iter){
	 hash_iter next_iter;
 	 next_iter=(hash_iter)  HashNextBucket((struct HashTable*)map);
 	return next_iter;
 }

 hash_iter hash_iterator_is_end(hash_t map, hash_iter iter){
   // 	 return (hash_iter)(iter==NULL || (int)iter == -1);
   return (hash_iter)(iter==NULL || (long int)iter ==-1);
  }

  ssize_t hash_iterator_return_entry(hash_t map, hash_iter iter,
 		char **key, void **data){
 	assert(map != NULL);
 	assert(iter >= 0);
 	assert(key != NULL);
 	assert(data != NULL);
 	if (!hash_iterator_is_end(map, iter))
 	{
 		HTItem *my_iterator=(HTItem*)iter;
 		*key=KEY_PTR((struct HashTable*)map,my_iterator->key);
 		/*// that actually may happen
		if (*key==NULL)
 		{
 			printf("null key in hash at iterator %X\n",(int)iter);exit(1);
 		}*/
 		*data=(void **)(my_iterator->data);
#ifdef debug_libchash_access
 			printf("returning data key: %s data: %X\n",*key,(int)*(int **)data);
#endif
 		return sizeof(ulong);
 	}
 	else
 	{
#ifdef debug_libchash_access
 			printf("returning data key: %s -- not found\n",*key);
#endif
 	}
	return 0; // TODO: maybe make it return the length of the data, as specified in the interface,
 				// but for now we don't need it in mapsembler apparently
  }


/*
* advanced functions
*/

 ssize_t hash_search_key_int_value(hash_t map, const char *key, const int value_to_search){
	 HTItem *res;
	 res=HashFind( (struct HashTable*)map, PTR_KEY((struct HashTable*)map,key));
	 if (res != NULL)
	 {
#ifdef debug_libchash_access
			printf("searching for key: %s value: %d whereas data is: %X\n",key,value_to_search,*(int*)(res->data) );
#endif
			if((*(int *) (res->data)) == value_to_search)
			return 1;
	 }
	 return 0;

 }

 void hash_set_int_to_key(hash_t map, char * key, int value){
	 int * p_value;
	 if(hash_entry_by_key(map, key, (void **) (&p_value)) == 0){ // first time a value is added to this key
	 #ifdef debug_libchash_access
	 		printf("first time a value is set to this key %s\n", key); // DEB
	 #endif
	 		p_value= (int *)malloc(sizeof(int));
	 		*p_value = value;
	 		hash_insert(map, key, p_value, sizeof(int *));
	 	}
	 	else {
	 #ifdef debug_libchash_access
	 		printf("new time a value is set to this key %s\n", key); // DEB
	 #endif
	 		*p_value=value;
	 	}
 }

 int* hash_get_int_value(hash_t map, char * key){
 	 int * p_value;
 	 if(hash_entry_by_key(map, key, (void **) (&p_value)) != 0){
 		 return p_value;
 	 	}
 	 return NULL;
  }

 void hash_increase_int_value_if_exists(hash_t map, char * key){
	 int * p_value;
	 if(hash_entry_by_key(map, key, (void **) (&p_value)) != 0){
		 *p_value=(*p_value)+1;
	 	}
 }

 /*
  * requirement: the hash table cells are lists
  * these functions add elements to the lists
  * not sure whether it really belongs to this file.. more like to a hash_common.c file
  */
void hash_add_int_to_list(hash_t map, char * key, int value){
  int * p_value = (int *)malloc(sizeof(int));
  *p_value = value;
  hash_add_something_to_list(map, key, p_value);
}

inline void get_offset_and_nb_from_sinfo(hash_val  sinfo, uint64_t * offset_seed,uint64_t * nb_seeds )
{
    *nb_seeds = sinfo & mask_nbseed;
    *offset_seed = (sinfo >> nbits_nbseeds) & mask_offset_seed ;
}

inline void set_offset_and_nb_into_sinfo(hash_val * sinfo, uint64_t  offset_seed,uint64_t  nb_seeds )
{
    hash_val new_val = 0 ;
    new_val =  (offset_seed << nbits_nbseeds ) + nb_seeds ;
    *sinfo = new_val;
}

void iterate_and_fill_offsets(hash_t map){
    
    uint64_t offset_courant=0;
   // seedinfo sinfo;
    hash_val sinfo;
    uint64_t offset_seed;
    uint64_t nb_seeds;
    //iterate over hash table
    
    uint64_t nbdiffseeds= 0;
    HTItem *bck;
    
    for ( bck = HashFirstBucket((struct HashTable*)map); bck; bck = HashNextBucket((struct HashTable*)map) )
    {
        sinfo = (hash_val) (bck->data);
        get_offset_and_nb_from_sinfo(sinfo, &offset_seed, &nb_seeds);

        set_offset_and_nb_into_sinfo(&sinfo, offset_courant, 0);
        offset_courant += nb_seeds ;

        bck->data = sinfo;
        
        nbdiffseeds ++;
        //sinfo.offset_seed = offset_courant;
        //offset_courant += sinfo.nb_seeds ;
        //sinfo.nb_seeds = 0;
        
        //bck->data =  *((ulong*) (&sinfo)); //store back in hashtable

        //printf("offset_courant %lli \n",offset_courant);
    }
    printf("total nb diff seeds %lli  (should use %lli MB) \n",nbdiffseeds,(nbdiffseeds*16)/1024/1024);
    
}

void hash_fill_kmer_index(hash_t map, const char * key, couple * seed_table, const int fragment_id, const int position_on_fragment){
    HTItem *res;

    hash_val sinfo;
    uint64_t offset_seed;
    uint64_t nb_seeds;
    uint64_t indexseed;
    
    uint64_t graine;
    graine = codeSeed_uint64_t(key);  // quick testing, TODO :compute binary kmer once and directly

    couple new_couple;
  //  res=HashFind((struct HashTable*)map, PTR_KEY((struct HashTable*)map,graine)); //why PTR_KEY segfault when graine is binary? 
    res=HashFind((struct HashTable*)map, graine);

    assert(res!=NULL); //key should be present
    
    sinfo = (hash_val) (res->data);
    get_offset_and_nb_from_sinfo(sinfo, &offset_seed, &nb_seeds);
    indexseed = offset_seed +  nb_seeds ;
    new_couple.a = fragment_id;
    new_couple.b = position_on_fragment;
    


    seed_table[indexseed]=new_couple;
    
    Sinc24(nb_seeds) ;
    set_offset_and_nb_into_sinfo(&sinfo, offset_seed, nb_seeds);
    
    res->data = sinfo;
    
    
}


int get_seed_info(hash_t map, const char * key, uint64_t * offset_seed, uint64_t * nb_seeds){

    HTItem *res;
    hash_val sinfo;
    
    uint64_t graine;
    graine = codeSeed_uint64_t(key);
    
  //  res=HashFind((struct HashTable*)map, PTR_KEY((struct HashTable*)map,graine));
    res=HashFind((struct HashTable*)map, graine);
    
    if(res!=NULL)
    {
        sinfo = (hash_val) (res->data);
        get_offset_and_nb_from_sinfo(sinfo, offset_seed, nb_seeds);
        
        return 1;
    }
    return 0;
    
}


void hash_incr_kmer_count(hash_t map, const char * key){
    HTItem *res;
    uint64_t offset_seed;
    uint64_t nb_seeds;
    hash_val sinfo;
    
    uint64_t graine;
    graine = codeSeed_uint64_t(key);

    //res=HashFindOrInsert( (struct HashTable*)map, PTR_KEY((struct HashTable*)map,graine),(ulong) 0); // find or insert 0
    res=HashFindOrInsert( (struct HashTable*)map, graine,(ulong) 0); // find or insert 0
    
    //incr cpt
    sinfo = (hash_val) (res->data);
    get_offset_and_nb_from_sinfo(sinfo, &offset_seed, &nb_seeds);

    Sinc24(nb_seeds) ; // saturated inc to 24 bit
    
    set_offset_and_nb_into_sinfo(&sinfo, offset_seed, nb_seeds);
    res->data = sinfo;

    
}


void hash_add_something_to_list(hash_t map, const char * key, void * something){
	list_ * somethings;
	if(hash_entry_by_key(map, key, (void **) (&somethings)) == 0){ // first time a value is added to this key
#ifdef debug_libchash_access
		printf("first time a value is added to this key %s\n", key); // DEB
#endif
		somethings = list_create();
		list_add(somethings, something);
		hash_insert(map, key, somethings, sizeof(list_ *));
	}
	else {// we add the current couple (read, position) to the list of the well mapped read for this fragment
		list_add(somethings, something);
#ifdef debug_libchash_access
		printf("new time a value is added to this key %s\n", key); // DEB
#endif

	}
}


/*
 * return a flat list of reads/positions, sorted by increasing positions
 * inspired by print_fragment_cover
 */
void* hash_list_sorted_by_positions(hash_t map)
{
        char *key=NULL;
        int position_fragment;
        list_ *mapping_positions;
        list_ *sorted_list_of_reads_and_positions=list_create();
        hash_iter iterator;
        iterator=hash_iterator_init(map); // get the first element of the Mapped hashmap

        // Go through the hashmap Mapped and for each fragment
        // get the reads that have seeds that match somewhere on this fragment
        if((long int)iterator!=-1){
                while(!hash_iterator_is_end(map, iterator)){     // go through all the mapped reads
                        hash_iterator_return_entry(map, iterator, &key, (void**)&mapping_positions); // get key(read) and corresponding mapping positions (pwi)
                        if(mapping_positions){
                                cell_mapsembler *c=mapping_positions->first;
                                while(c!=NULL){
                                        position_fragment=((*(int *) (c->val)));
                                        //printf("adding read %s with position %d to sorted_list\n",key,position_fragment);
                                        /*if (key[0]==NULL)
                                        {
                                                printf("empty read added to list\n");exit(1); // that's normal, that's error correction for now (..)
                                        }*/
                                        list_add(sorted_list_of_reads_and_positions,create_charint_couple(key,position_fragment));
                                        c=c->prox;
                                }
                        }
                        iterator=hash_iterator_next(map, iterator);
                }
        }

        // printf("before sorting:\n");
        // list_of_charint_couple_print(sorted_list_of_reads_and_positions);

        list_quicksort_by_charint_couple(&sorted_list_of_reads_and_positions);

        //      printf("after sorting:\n");
        //list_of_charint_couple_print(sorted_list_of_reads_and_positions);

        return (void *)sorted_list_of_reads_and_positions;
}


#ifdef __cplusplus
}
#endif
