/*
 *  YASS 1.14
 *  Copyright (C) 2004-2010
 *  the YASS team
 *  Laurent Noe, Gregory Kucherov, Mikhail Roytberg, 
 *  Steven Corroy, Antoine De Monte, Christophe Valmir.
 *
 *  laurent.noe|<A>|lifl.fr
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the CeCILL License as published by
 *  the CEA-CNRS-INRIA; either version 2 of the License, or (at your
 *  option) any later version, and the GNU General Public License as
 *  published by the Free Software Foundation; either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This software contains code derived from the GNU libavl library.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* 1) include utils macro */
#include "util.h"
/* 2) include global variables */
#include "global_var.h"
/* 3) the current file defs */
#include "hash.h"
/* 4) other files */


/*   delHash (&table,4); */
/*   putHashOneDiagValue (&table,4, 5) ; */
/*   delHash (&table,4); */
/*   putHashOneDiagValue (&table,4, 5) ; */
/*   putHashOneDiagValue (&table,1024*1024+4, 5) ; */
/*   putHashOneDiagValue (&table,2*1024*1024+4, 5) ; */
/*   delHash (&table,2*1024*1024+4); */
/*   delHash (&table,2*1024*1024+4); */
/*   delHash (&table,1024*1024+4); */
/*   hashDisplay (&table); */
/*   putHashOneDiagValue (&table,2*1024*1024+4, 5) ; */
/*   putHashOneDiagValue (&table,3*1024*1024+4, 5) ; */
/*   putHashOneDiagValue (&table,4*1024*1024+4, 5) ; */
/*   putHashOneDiagValue (&table,5*1024*1024+4, 5) ; */
/*   hashDisplay (&table); */
/*   putHashOneDiagValue (&table,6*1024*1024+4, 5) ; */
/*   hashDisplay (&table); */
/*   delHash (&table,2*1024*1024+4); */
/*   hashDisplay (&table); */
/*   putHashOneDiagValue (&table,5, 5) ; */
/*   delHash (&table,6*1024*1024+4); */
/*   hashDisplay (&table); */
/*   exit(0); */


#define FONCTION_HASH(code,size,mask) (code&mask);

/*
 *
 *  Prend un element libre de la table hash et la mettre 
 *  dans la liste chainée
 *
 */

HashElement *getNewElement (Table_hash *table) {
  HashElement *element;
  element=table->elementFree;
/*   hashVerifie (table,"getNewElement",0);   */
/*   fprintf(stderr,"new un element\n"); */
  if (element!=NULL) {
    table->elementFree=element->next;
  }
  else {
    long int code=FONCTION_HASH(table->element_old->diagonal,table->size,table->mask);
    /*Delie de la table de hash Le plus vieux est toujours en premiere position*/
    /*puis qu on insere dans cette liste chaine le plus jeune a la fin */
    element=table->element[code];

    table->element[code]=element->next;
    if (table->element[code]!=NULL)
      table->element[code]->prev=NULL;
    
    /*Le plus vieux devient celui qui est venu juste deriere le plus vieux*/
    element=table->element_old;
    table->element_old=element->young_next;
    table->element_old->old_next=NULL;
    
 

  }
  
  /*Existance du plus vieux*/
  if (table->element_old==NULL)
    table->element_old=element;
  
  if (table->element_young!=NULL) {
    element->old_next=table->element_young;
    table->element_young->young_next=element;
  }
  else
    element->old_next=NULL;
  
/*   fprintf(stderr,"New un element @=%ld\n",element); */
  /*Le plus jeune deviens notre element*/
  table->element_young=element;

  element->young_next=NULL;
  element->next=NULL;
  element->prev=NULL;
  return element;
}

/*
 *
 * delHash : supprime l element pour la mettre dans la free
 *
 */

long int delHash (Table_hash *table,long int diagonal) {
  long int code;
  HashElement *element, *elementprev = NULL;
  
  code         = FONCTION_HASH(diagonal,table->size,table->mask);
  element      = table->element[code];

/*   hashVerifie(table,"pb avant supression",diagonal); */
  while (element!=NULL) {
    if (element->diagonal==diagonal) {

      /*Dechaine sur la table de hach*/
      if (element->prev!=NULL) {
	element->prev->next=element->next;
      }
      else
	table->element[code]=element->next;
      
      if (element->next!=NULL)
	element->next->prev=element->prev;
      else
	if (element->prev!=NULL) 
	  element->prev->next=NULL;
	else
	  table->element[code]=NULL;


      /*Dechaine de la liste chainee*/
      if (element->young_next!=NULL)
	element->young_next->old_next=element->old_next;
      else
	table->element_young=element->old_next;

      if (element->old_next!=NULL) 
	element->old_next->young_next=element->young_next; 
      else
	table->element_old=element->young_next; 

      element->next=table->elementFree;
      table->elementFree=element;
      
 /*      hashVerifie(table,"pb dans supression"); */
      return 1;
    }
    elementprev=element;
    element=element->next;
  }
    
  return -1;
}




/*
 *
 *  Initialisation de la table de Table
 *  de taille SIZE
 *
 *  Plus la taille est grande plus l'algorithme est
 *  rapide mais prends beaucoup de memoire.
 *
 */
Table_hash initHash(long int size,long int nbreElement) {
  Table_hash table;
  long int t;
  HashElement *precElement;

  table.size    = size;
  table.element = (HashElement **) MALLOC(size*sizeof   (HashElement *));
  memset (table.element,0,size*sizeof (HashElement *));

  table.element_old   = NULL;
  table.element_young = NULL;
  
  /*Allocation */
  table.firstElement = table.elementFree = (HashElement *) MALLOC(nbreElement*sizeof(HashElement));
  memset(table.elementFree, 0, nbreElement*sizeof(HashElement));

  /*Recherche le plus grand bit du size pour evaluer le mask*/
  table.mask=1;
  while((table.mask|size)!=table.mask) 
    table.mask=(table.mask<<1)+1;
  table.mask=table.mask>>1;

  /*Chainge des elements entre eux dans la liste free*/
  precElement=table.elementFree;
  for (t=0;t<nbreElement-1;t++) {
    precElement->next=precElement+1;
    precElement=precElement->next;
  }
  precElement->next=NULL;
  return table;
}


/*
 *   Recupere la valeur d une clef et elimine toute valeur inferieur
 *   Retourne:  -1 si le nombre n'existe pas (voir existHashKey)
 *              la valeur
 *
 */
long int getHashValueMin (Table_hash *table,long int diagonal,long int valueMin) {
  long int         code;
  HashElement *element, *elementNext ;

  code    =  FONCTION_HASH(diagonal,table->size,table->mask);
  element = table->element[code];
/*   hashVerifie (table,"getHashValueMin",diagonal);   */
  if (table->element[code]!=NULL) {
    while (table->element[code]->diagonal<valueMin) {
      /*Free element*/
      element=table->element[code];
      elementNext=element->next;

      /*Dechaine sur la table de hach*/
      table->element[code]=elementNext;
      if (elementNext!=NULL)
	elementNext->prev=NULL;
      
      /*Dechaine de la liste chainee*/
      if (element->young_next!=NULL)
	element->young_next->old_next=element->old_next;
      else
	table->element_young=element->old_next;
      if (element->old_next!=NULL)
	element->old_next->young_next=element->young_next;
      else
	table->element_old=element->young_next;

      element->next=table->elementFree;
      table->elementFree=element;
      if (element->diagonal==diagonal)
	return -1;
      if (elementNext==NULL)
	return -1;

    }
  }
  element=table->element[code];
  while (element!=NULL) {    
    if (element->diagonal==diagonal)  {
      return element->value;
    }
    element=element->next;
    
  }
  return -1;
}


/*
 *   Recupere la valeur d une clef
 *   Retourne:  -1 si le nombre n'existe pas (voir existHashKey)
 *              la valeur
 *
 */
long int getHashValue (Table_hash *table,long int diagonal) {
  long int  code;
  HashElement *element;
  
  code = FONCTION_HASH(diagonal,table->size,table->mask);
  element=table->element[code];
  /*   hashVerifie (table,"getHash",diagonal); */
  while (element!=NULL) {
    if (element->diagonal==diagonal)  {
	return element->value;
      }
    element=element->next;
  }
  return -1;
}

/*
 *  Etablie l'existance de la diagonal
 *  Return: -1 si la clef n'existe pas
 *          1 si elle existe
 *
 */
long int existHashDiag (Table_hash *table,long int diagonal) {
  long int code;
  HashElement *element;
  
  code    = FONCTION_HASH(diagonal,table->size,table->mask);
  element = table->element[code];
  while (element != NULL) {
    if (element->diagonal == diagonal)
      return 1;
    element = element->next;
  }  
  return 0;
}


/*
 * 
 * supprime la table de hash
 *
 */
long int freeHash (Table_hash *table) {
  FREE(table->firstElement,sizeof(HashElement));
  FREE(table->element,sizeof(HashElement));  
  return 1;
}



/*
 * Insere une diagonale unique et sa valeur
 * Si jamais la clef existe la valeur sera ecrasée
 *
 */
long int putHashOneDiagValue (Table_hash *table,long int diagonal, long int value) {
  long int code;
  HashElement *element,*elementPrev = NULL;
  code = FONCTION_HASH(diagonal,table->size,table->mask);
  element = table->element[code];
  
  /*   hashVerifie (table,"avant putHashOneDiagValue",diagonal); */
 
   /*Verifie si la diagonal existe*/
  while (element!=NULL){
    if (element->diagonal==diagonal){
      /*Delier l element de toutes les chaines*/
      /*De la table de hash*/

      if(elementPrev==NULL)
	table->element[code]=element->next;
      else
	elementPrev->next=element->next;
      if(element->next!=NULL)
	element->next->prev=elementPrev;
      
      /*Liste des inseres*/
      if(element->young_next!=NULL) {
	/*Element n'est pas le dernier insere*/	
	element->young_next->old_next=element->old_next;
	if (element->old_next!=NULL)
	  element->old_next->young_next=element->young_next;
	else
	  table->element_old=element->young_next;
	element->old_next=table->element_young; 
	element->young_next=NULL; 
	element->old_next->young_next=element; 
	table->element_young=element;

      }
      element->value=value;
      
      /*Mets la valeur en fin de chaine de la table de hach*/
      elementPrev=table->element[code];
      if (elementPrev==NULL) {
	table->element[code]=element;
	element->next=NULL;
	element->prev=NULL;
/* 	hashVerifie(table,"ajout existe deb",diagonal); */
	return 1;
      }
      while (elementPrev->next!=NULL) {
/* 	fprintf(stderr,"elementPrev=%ld\n",elementPrev); */
	elementPrev=elementPrev->next;
      }
      elementPrev->next=element;
      element->prev=elementPrev;
      element->next=NULL;
/*       hashVerifie(table,"ajout existe mili",diagonal); */
      return 1;
    }
    elementPrev=element;
    element=element->next;
  }

  /*Elle existe pas donc on prends un element dans la liste free*/
  element= getNewElement(table);

  elementPrev=table->element[code];
  if (elementPrev!=NULL)
    while (elementPrev->next!=NULL)
      elementPrev=elementPrev->next;
    


  element->diagonal  =diagonal;
  element->value=value;
  element->next=NULL;
  element->prev=elementPrev;



/*   fprintf(stderr,"element=%p  element->next=%p  element->prev=%p\n",(void*)element,(void*)element->next,(void*)element->prev); */
/*   fprintf(stderr,"table->element[code]=%ld\n",table->element[code]); */
  /*Inseretion dans la liste de la table de hash*/
  if (table->element[code]==NULL) 
    table->element[code]=element;
  else
    elementPrev->next=element;
/*   fprintf(stderr,"Je mets une diagonal rajouter\n",diagonal); */
/*   hashVerifie(table,"Ajout normal",diagonal); */
  return 1;
}

/*
 * 
 * reset: enleve tous les elements de la table pour les mettre dans free
 *
 */
long int resetHash (Table_hash *table) {
  HashElement *element=table->element_old;
  long int code;

  while (element!=NULL) {
    /*Place l element dans la liste free*/
    element->next=table->elementFree;
    table->elementFree=element;
    code=FONCTION_HASH(element->diagonal,table->size,table->mask);
    /*Vide la table Hash*/
    table->element[code]=NULL;
    element=element->young_next;
  }
  
  table->element_old=NULL;
  table->element_young=NULL;
  return 1;
}




/*
 *
 * Affiche toutes la table de hack
 *
 */
long int hashDisplay (Table_hash *table) {
  long int i,cpt2=0;
  HashElement *element;

  fprintf(stderr,"Liste la table hash\n");
  for (i=0;i<table->size;i++) {
    long int cpt=0;
    element    =table->element[i];
    while (element!=NULL) {
      fprintf(stderr,"%ld:@=%p[%ld,%ld]    diagonal:%ld value:%ld @prec:%p @next:%p\n",
	      cpt2++,(void*)element,
	      i,cpt,element->diagonal,element->value,
	      (void*)element->prev,
	      (void*)element->next
	      );
      element=element->next;
      cpt++;
    }
  }
  fprintf(stderr,"\n");
  fprintf(stderr,"Liste insertion\n");
  element=table->element_old;
  while (element!=NULL) {
    fprintf(stderr,"@=%p->",(void*)element);
    element=element->young_next;
  }
  fprintf(stderr,"NULL\n");

  fprintf(stderr,"Liste insertion (invers)\n");
  element=table->element_young;
  while (element!=NULL) {
    fprintf(stderr,"@=%p->",(void*)element);
    element=element->old_next;
  }
  fprintf(stderr,"NULL\n");

  fprintf(stderr,"Liste Free \n");
  element=table->elementFree;
  while (element!=NULL) {
    fprintf(stderr,"@=%p->",(void*)element);
    element=element->next;
  }
  fprintf(stderr,"NULL\n\n\n");

  return -1;
}


/*
 *
 * Affiche le nombre d element dans la table de hach
 *
 */
long int hashDisplayNbre (Table_hash *table) {
  long int cpt=0;
  long int i;
  HashElement *element;
  
  cpt=0;
  for (i=0;i<table->size;i++) {
    element=table->element[i];
    while (element!=NULL) {
      cpt++;
      element=element->next;
    }
  }
  fprintf(stderr,"Nbre element :%ld  ",cpt);


  cpt=0;
  element=table->element_old;
  while (element!=NULL) {
    cpt++;
    element=element->young_next;
  }
  fprintf(stderr," vieux:%ld  ",cpt);

  cpt=0;
  element=table->element_young;
  while (element!=NULL) {
    cpt++;
    element=element->old_next;
  }
  fprintf(stderr,"jeune:%ld  ",cpt);
  
  cpt=0;
  element=table->elementFree;
  while (element!=NULL) {
    cpt++;
    element=element->next;
  }
  fprintf(stderr,"free:%ld\n",cpt);
  return -1;
}


long int hashVerifie (Table_hash *table, char *mess,long int diag) {
  long int cpt=0;
  long int i,j;
  long int code;
  HashElement *element;
  HashElement *elementVieux; 
  HashElement *elementJeune; 
  HashElement *elementFree; 
 
  
  for (i=0;i<table->size;i++) {
    element=table->element[i];
    while (element!=NULL) {

      
      /*Verifie les doublons de diagonal et la double liste*/
      for (cpt=0,j=0;j<table->size;j++) {
	elementVieux=table->element[j];
	elementJeune=NULL;
	while (elementVieux!=NULL) {
	  if (element->diagonal==elementVieux->diagonal)
	    cpt++;
	  if (elementJeune!=NULL)
	    if (elementJeune!=elementVieux->prev) {
	      fprintf(stderr,"%s:Erreur de consistence entre la double liste chainee du hash\n",mess);
	      exit(0);
	    }
	      
	  elementJeune=elementVieux;
	  elementVieux=elementVieux->next;
	}
      }
      if (cpt!=1) {
	fprintf(stderr,"%s:Erreur de doublons liste chainee\n",mess);
	exit(0);
      }
      
      /*Verifie si tous les elements de la table hach sont dans la liste insere sur les vieux*/
      cpt=0;
      elementVieux=table->element_old;
      while (elementVieux!=NULL) {
	if (elementVieux==element)
	  {cpt=1;elementVieux=NULL;}
	else
	  elementVieux=elementVieux->young_next;
      }
      if (cpt==0) {
	fprintf(stderr,"%s:Erreur de concistence entre la liste insere(vieux) et la table (i_current=%d)\nL element @:%p n 'est pas representer dans la liste\n",mess,0/*gv_i_current*/,(void*)element);
 	exit(0); 
      }

      elementJeune=table->element_young;
      /*Verifie si tous les elements de la table hach sont dans la liste insere sur les jeunes*/
      cpt=0;
      while (elementJeune!=NULL) {
	if (elementJeune==element)
	  {cpt=1;elementJeune=NULL;}
	else
	  elementJeune=elementJeune->old_next;
      }
      if (cpt==0) {
	fprintf(stderr,"%s:Erreur de concistence entre la liste insere(jeune) et la table (i_current=%d)\n",mess,0/*gv_i_current*/);
 	exit(0); 
      }
      
      /*Verifie si tous les elements ne sont pas dans la liste free*/
      elementFree=table->elementFree;
      cpt=0;
      while (elementFree!=NULL) {
	if (elementFree==element)
	  {cpt=1;elementFree=NULL;}
	else
	  elementFree=elementFree->next;
      }
      if (cpt==1) {
	fprintf(stderr,"%s:Erreur de concistence entre la liste insere et la liste free (i_current=%d)\n",mess,0/*gv_i_current*/);
 	exit(0); 
      }


      code=FONCTION_HASH(element->diagonal,table->size,table->mask)
      if (code!=i) {
	fprintf(stderr,"%s:Erreur la diagonale %ld(mal stocker i_current=%d)\n",mess,element->diagonal,0/*gv_i_current*/);
 	exit(0); 
      }
	
      element=element->next;
    }
  }
 
  /*On verifie si tous les elements de insere sont dans la table*/
  elementVieux=table->element_old;
  while (elementVieux!=NULL) {
    cpt=0;
    /*Parcourt la table a la racherche de lelement vieux*/
    for (i=0;i<table->size;i++) {
      element=table->element[i];
      while (element!=NULL) {
	if (element==elementVieux) {
	  cpt=1;i=table->size;
	  element=NULL;
	}
	else
	  element=element->next;
      }
    }
    /*Pas ete trouver dans la table*/
    if (cpt==0) {
      fprintf(stderr,"%s:Dans la liste insere par les vieux il y a un element en trop (i_current=%d)\n",mess,0/*gv_i_current*/);
	exit(0);
    }
    elementVieux=elementVieux->young_next;
  }
  
  /*Verifie si la liste colision dans la table de hash est correct*/
  for (i=0;i<table->size;i++) {
    element=table->element[i];
    if (element!=NULL) {
      if (element->prev!=NULL) {
	fprintf(stderr,"%s:ERREUR Dans la table hash le premiere element de la liste est fausse premierElement=%p\n",mess,(void*)element);
	exit(0);
      }
      element=element->next;
    }
    while (element!=NULL) {
      if (element->prev!=NULL)
	if (element->prev->next!=element) {
	  fprintf(stderr,"%s:ERREUR de la liste des collistions\n",mess);
	  exit(0);
	}
	else ;
      else
	fprintf(stderr,"%s:ERREUR dans la liste des collision\n",mess);
      element=element->next;
    }
  }
  return 1;
}
