#include <assoc.h>

// @doc EXTERNAL               

// @mfunc Cerca l'oggetto con chiave <p k>
THash_object* TAssoc_array::_lookup(
  const char* k,  // @parm Chiave da cercare
  bool& isnew,    // @parm Viene asseganto TRUE se si tratta di una nuova chiave
  bool insert)    // @parm Permette di inserire la chiave

  // @comm Ricerca all'interno della tabella hash l'oggetto con la chiave <p k>,
  //       nel caso non venga trovato <p isnew> ritorna TRUE (si tratta di un
  //       oggetto nuovo) e viene inserito nella tabella se il parametro <p insert>
  //       e' settato a TRUE.
  //
  // @rdesc Ritorna l'oggetto corrispondente alla chiave passate come parametro.
  //        <nl>Il parametro <p isnew> assume il valore TRUE se si tratta di un
  //        nuovo oggetto.
{
  const TFixed_string key(k);
  const word hv = key.hash() % HASH_SIZE;
  TArray& arr = _data[hv];
  THash_object* o = NULL;
  isnew = FALSE;

  for (int i = 0; i < arr.items(); i++)
  {
    THash_object* ob = (THash_object*)&arr[i];
    if (ob->_key == key)
    { o = ob; break; }
    if (ob->_key > key)
      break;
  }

  if (o == NULL && insert) 
  { 
    o = new THash_object(key);
    arr.insert(o,i); 
    isnew = TRUE;
    _cnt++;
  }
  
  return o;
}

void TAssoc_array::destroy()
{
  for (int i = 0; i < HASH_SIZE; i++)
    _data[i].destroy();
  _cnt = _row = _col = 0;
}

TObject* TAssoc_array::first_item( )
{
  _rowitem = 0;
  _colitem = 0;
  return succ_item( );
}

TObject* TAssoc_array::last_item( )
{
  _rowitem = HASH_SIZE - 1;
  while( _rowitem >= 0 && _data[ _rowitem ].items() == 0 )
    _rowitem --;
  if( _rowitem < 0 )           
    return NULL;
  const TArray* arr = &_data[_rowitem];  
  _colitem = arr->items( ) - 1;
  return pred_item( );
}


TObject* TAssoc_array::succ_item( )
{
  const TArray* arr = &_data[_rowitem];
  
  for(;_rowitem < HASH_SIZE;)
  {
    if ((int)_colitem < arr->items()) 
      break;
    arr = &_data[++_rowitem];
    _colitem = 0;
  }
  if (_rowitem == HASH_SIZE)
    return NULL;
  
  THash_object* o = (THash_object*)arr->objptr(_colitem++);
  return (o == NULL || o->_obj == NULL) ? NULL : o->_obj;
}


TObject* TAssoc_array::pred_item( )
{
  const TArray* arr = &_data[_rowitem];
  
  for(;_rowitem >= 0;)
  {
    if ((int)_colitem >= 0 ) 
      break;
    _rowitem --;
    while( _rowitem >= 0 && _data[ _rowitem ].items( ) == 0 )
      _rowitem --;
    if (_rowitem < 0 )
      return NULL;      
    arr = &_data[ _rowitem ];
    _colitem = arr->items( ) - 1;
  }
  if (_rowitem < 0 )
    return NULL;
  
  THash_object* o = (THash_object*)arr->objptr(_colitem--);
  return (o == NULL || o->_obj == NULL) ? NULL : o->_obj;
}

// @mfunc Aggiunge un oggetto all'array.
bool TAssoc_array::add(
  const char* key,  // @parm Chiave d'ordinamento
  TObject* obj,     // @parm Ogetto da inserire (default=NULL)
  bool force)       // @parm Permette di forzare l'inserimento se esiste gia'
  //       un oggetto con la stessa chiave

  // @parm const TObject | &obj | Indirizzo dell'oggetto da aggiungere
  //
  // @syntax add(const char* key, TObject* obj, bool force)
  // @syntax add(const char* key, const TObject& obj, bool force)
  //
  // @comm Se l'oggetto da aggiungere esite gia' la chiave guarda il parametro <p force>:
  //       <nl>se <p force> = TRUE  lo sostituisce e ritorna TRUE,
  //       <nl>se <p force> = FALSE non sostituisce e ritorna TRUE,
  //       <nl>altrimenti ritorna FALSE.
  //       <nl><nl>Nel caso l'oggetto da aggiungere venga passato per indirizzo
  //       la funzione aggiunge una copia dell'oggetto e quindi deve essere
  //       definita <mf TObject::dup>
  //
  // @rdesc Ritorna TRUE se esisteva gia' un elemento con la stessa chiave
{
  bool isnew = FALSE;

  THash_object* o = _lookup(key,isnew,TRUE);

  if (!isnew)
  {
    if (force) 
    { 
      if (o->_obj != NULL) 
        delete o->_obj;
      o->_obj = obj; 
    }
    return TRUE;
  }
  o->_obj = obj;
  return FALSE;
}

bool TAssoc_array::add(const char* key, const TObject& obj, bool force)
{
  return  add(key,obj.dup(),force);
}

// @mfunc Elimina un oggetto.
bool TAssoc_array::remove(
  const char* k) // @parm Chiave dell'oggetto da eliminare

  // @comm Cerca nella tabella hash l'oggetto con chiave <p k> e lo elimina.
  //
  // @rdesc Ritorna il risultato dell'operazione
  //
  // @flag TRUE | Eliminazione avvenuta
  // @flag FALSE | L'oggetto non e' stato trovato
{
  const TFixed_string key(k);
  const word hv = key.hash() % HASH_SIZE;
  TArray& arr = _data[hv];
  THash_object* o = NULL;

  for (int i = 0; i < arr.items(); i++)
  {
    THash_object* ob = (THash_object*)&arr[i];
    if (ob->_key == key)
    { o = ob; break; }
    if (ob->_key > key)
      break;
  }
  if (o != NULL) 
  { arr.destroy(i,TRUE); _cnt--; return TRUE; }
  return FALSE;
}

// @mfunc Trova l'oggetto indicizzato
TObject& TAssoc_array::find(
  const char* key) // @parm Chiave dell'oggetto da trovare


  // @comm Cerca l'oggetto indicizzato con chiave <p key>. Viene controllato se
  //       non c'e' (normalmente si usa operator[key])
  //
  // @rdesc Ritorna l'oggetto cercato. Se l'oggetto aggiunto era NULL
  //        ritorna error object
{
  bool isnew = FALSE;
  THash_object* o = _lookup(key, isnew);
  if (o == NULL) error_box("INTERNAL (HASH): Unref key");
  if (o->_obj == NULL) return error;
  else return *(o->_obj);
}

// @mfunc Ritorna l'oggetto con chiave <p key>
TObject* TAssoc_array::objptr(
  const char* key) // @parm Chiave dell'oggetto da ritornare

  // @rdesc Se l'oggetto esiste ne ritorna il puntatore, altrimenti ritorna NULL
{
  bool isnew; 
  THash_object* o = NULL;
  if ((o = _lookup(key,isnew)) != NULL)
    return &(o->obj());
  return NULL;
}

// @mfunc Controlla l'esistenza di una chiave
bool TAssoc_array::is_key(
  const char* key) // @parm Chiave da cercarne l'esistenza

  // @rdesc Ritorna il risultato della ricerca
  //
  // @flag TRUE | Se la chiave esiste
  // @flag FALSE | Se la chiave non esiste
{
  bool isnew = FALSE;
  const THash_object* o = _lookup(key, isnew);
  if (o == NULL) return FALSE;
  return TRUE;
}

// @mfunc Ritorna solo l'oggetto
TObject* TAssoc_array::get()

  // @rdesc Ritorna il puntatore all'oggetto (se diverso da NULL), altrimenti
  //        ritorna error object
  //
  // @xref <mf TAssoc_array::get_hashobj>
{
  const TArray* arr = &_data[_row];
  
  for(;_row < HASH_SIZE;)
  {
    if ((int)_col < arr->items()) 
      break;
    arr = &_data[++_row];
    _col = 0;
  }
  if (_row == HASH_SIZE)
  { _row = 0; return NULL; }
  
  THash_object* o = (THash_object*)arr->objptr(_col++);
  return (o == NULL || o->_obj == NULL) ? &error : o->_obj;
}

// @mfunc Ritorna l'oggetto e la relativa chiave
THash_object* TAssoc_array::get_hashobj()

  // @comm Se l'oggetto viene trovato viene richiamata la funzione
  //       <mf TAssoc_array::objptr>
  //
  // @rdesc Se l'oggetto esiste ne ritorna il puntatore, altrimenti ritorna NULL
  //
  // @xref <mf TAssoc_array::get>
{
  const TArray* arr = &_data[_row];
  
  for(;_row < HASH_SIZE;)
  {
    if ((int)_col < arr->items()) 
      break;
    arr = &_data[++_row];
    _col = 0;
  }
  if (_row == HASH_SIZE)
  { _row = 0; return NULL; }
  
  return (THash_object*)arr->objptr(_col++);
}


// mette chiavi e opzionalmente valori (come stringa) nel
// TString_array passato                                                      
int TAssoc_array::get_keys(TString_array& kl, bool add_values)
{                                                            
  kl.destroy(); restart();
  THash_object* o = NULL;  
  TString tmp(80);
  while (o = get_hashobj())
  {
    TToken_string* tt = new TToken_string(o->key());
    if (add_values)
    {         
      tmp = "";
      tmp << o->obj();
      tt->add(tmp);
    }              
    kl.add(tt);
  }
  restart(); 
  return kl.items();
}
