#include <stdlib.h>
#include "assoc.h"

// @ccost:(INTERNAL) HASH_SIZE | 883 | Dimensione della tabella hash
const int HASH_SIZE = 883;

THash_object::~THash_object()
{ 
  if (_obj != NULL) 
    delete _obj; 
}

TArray& TAssoc_array::bucket(int index)
{
  TArray* arr = (TArray*)_bucket.objptr(index);
  if (arr == NULL)
  {
    arr = new TArray;
    _bucket.add(arr, index);
  }
  return *arr;
}

// @doc EXTERNAL

// @mfunc Cerca l'oggetto con chiave <p k>
//
// @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.
THash_object* TAssoc_array::_lookup(
  const char* k,  // @parm Chiave da cercare
  bool& isnew,    // @parm Viene assegnato 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.
{
  const TFixed_string key(k);
  const word hv = key.hash() % HASH_SIZE;
  TArray& arr = bucket(hv);
  THash_object* o = NULL;
  isnew = FALSE;

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

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

void TAssoc_array::destroy()
{
  _bucket.destroy();
  _cnt = _row = _col = 0; 
  _rowitem = _colitem = 0;
}

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

TObject* TAssoc_array::last_item()
{
  _rowitem = _bucket.last();      
  if( _rowitem < 0 )           
    return NULL;
  _colitem = bucket(_rowitem).items() - 1;
  return pred_item( );
}

TObject* TAssoc_array::succ_item()
{
  const TArray* arr = (const TArray*)_bucket.objptr(_rowitem);
  while (_rowitem < HASH_SIZE)
  {
    if (arr && (int)_colitem < arr->items()) 
      break;
    _rowitem = _bucket.succ(_rowitem);
    if (_rowitem < HASH_SIZE)
    {
      arr = (TArray*)_bucket.objptr(_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 = (const TArray*)_bucket.objptr(_rowitem);
  while (_rowitem >= 0)
  {
    if (arr && (int)_colitem >= 0) 
      break;
    _rowitem = _bucket.pred(_rowitem);
    if (_rowitem >= 0)
    {
      arr = (TArray*)_bucket.objptr(_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;
}

THash_object* TAssoc_array::random_hash_object()
{
  THash_object* o = NULL;
  if (items() > 0)
  {
    int bucket = rand() % _bucket.size();
    if (_bucket.objptr(bucket) == NULL)
      bucket = _bucket.succ(bucket);
    if (_bucket.objptr(bucket) == NULL)
      bucket = _bucket.pred(bucket);
    const TArray* arr = (const TArray*)_bucket.objptr(bucket);
    if (arr != NULL)
    {
      const int item = rand() % arr->size();
      o = (THash_object*)arr->objptr(item);
      if (o != NULL)
      {
        _rowitem = bucket;
        _colitem = item;
      }
    }
  }
  return o;
}

// @doc EXTERNAL

// @mfunc Aggiunge un oggetto all'array.
// @rdesc Ritorna TRUE se esisteva gia' un elemento con la stessa chiave
bool TAssoc_array::add(
  const char* key,  // @parm Chiave d'ordinamento
  TObject* obj,     // @parm Oggetto 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>
{
  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)
{ 
  // Non inserire l'Hash_object se non lo trovi (ci pensa la add sotto)
  bool isnew = FALSE;
  THash_object* o = _lookup(key,isnew,FALSE); 
  if (!isnew && !force)
    return TRUE;
  return add(key,obj.dup(),force);
}

// @doc EXTERNAL

// @mfunc Elimina un oggetto.
// @rdesc Ritorna il risultato dell'operazione
//
// @flag TRUE | Eliminazione avvenuta
// @flag FALSE | L'oggetto non e' stato trovato
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.
  
{
  const TFixed_string key(k);
  const word hv = key.hash() % HASH_SIZE;
  TArray& arr = bucket(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;
}

// @doc EXTERNAL

// @mfunc Trova l'oggetto indicizzato
//
// @rdesc Ritorna l'oggetto cercato. Se l'oggetto non viene trovato 
//        ritorna un errore
TObject& TAssoc_array::find(
  const char* key) const // @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])
{
  TObject* o = objptr(key);
  CHECKS(o, "Can't find hash object with key ", key);
  return *o;
}

// @doc EXTERNAL

// @mfunc Ritorna l'oggetto con chiave <p key>
// @rdesc Se l'oggetto esiste ne ritorna il puntatore, altrimenti ritorna NULL
TObject* TAssoc_array::objptr(
  const char* key) const // @parm Chiave dell'oggetto da ritornare
{
  bool isnew = FALSE; 
  THash_object* o = ((TAssoc_array*)this)->_lookup(key,isnew);
  return o ? o->_obj : NULL;
}

// @doc EXTERNAL

// @mfunc Controlla l'esistenza di una chiave
//
// @rdesc Ritorna il risultato della ricerca
//
// @flag TRUE | Se la chiave esiste
// @flag FALSE | Se la chiave non esiste
bool TAssoc_array::is_key(
  const char* key) const // @parm Chiave di cui cercare l'esistenza
{ 
  bool isnew = FALSE; 
  THash_object* o = ((TAssoc_array *)this)->_lookup(key,isnew);
  return o != NULL;
}                           

// @doc EXTERNAL

// @mfunc Ritorna solo l'oggetto
//
// @rdesc Ritorna il puntatore all'oggetto (se diverso da NULL), altrimenti
//        ritorna error object
TObject* TAssoc_array::get()
// @xref <mf TAssoc_array::get_hashobj>
{
  const TArray* arr = (const TArray*)_bucket.objptr(_row);
  while(_row < HASH_SIZE)
  {
    if (arr && (int)_col < arr->items()) 
      break;            
    _row = _bucket.succ(_row);  
    arr = (const TArray*)_bucket.objptr(_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_object : o->_obj;
}

// @doc EXTERNAL

// @mfunc Ritorna l'oggetto e la relativa chiave
//
// @rdesc Se l'oggetto esiste ne ritorna il puntatore, altrimenti ritorna NULL
THash_object* TAssoc_array::get_hashobj()

  // @comm Se l'oggetto viene trovato viene richiamata la funzione
  //       <mf TAssoc_array::objptr>
  //
  // @xref <mf TAssoc_array::get>
{
  const TArray* arr = (const TArray*)_bucket.objptr(_row);
  while(_row < HASH_SIZE)
  {
    if (arr && (int)_col < arr->items()) 
      break;            
    _row = _bucket.succ(_row);
    arr = (const TArray*)_bucket.objptr(_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();
}
  
// @doc INTERNAL

// @mfunc Copia tutto l'array e ne duplica gli elementi
//
TAssoc_array & TAssoc_array::copy(const TAssoc_array & a) // @parm Array associativo sorgente
{                   
  destroy();
  TAssoc_array& from = (TAssoc_array&)a;
  from.restart();
  for (THash_object* obj = from.get_hashobj(); obj; obj = from.get_hashobj())
    add(obj->key(), obj->obj(), TRUE);
  return * this;
}


///////////////////////////////////////////////////////////
// TCache
// Un simpatico contenitore che pu� dimenticare i contenuti
// a patto che riusciate a fornire un metodo per ricrearli!
///////////////////////////////////////////////////////////

TObject* TCache::key2obj(const char* key)
{
	NFCHECK("Pure key2obj function not implemented");
	return new TString(key); // Place holder
}

void TCache::discarding(const THash_object* obj)
{ 
	// Nothing to do normally
}

TObject* TCache::objptr(const TString& key)
{
  const int hv = key.hash() % _data.size();
  THash_object* ho = (THash_object*)_data.objptr(hv);
	TObject* obj = NULL;
	if (ho != NULL && ho->key() == key)
	{
		obj = &ho->obj();
	}
	else
	{
		obj = key2obj(key);
		if (obj != NULL)
		{
			if (ho != NULL)
			{
			  discarding(ho);
			  _data.destroy(hv);
			}
			ho = new THash_object(key, obj);
			_data.add(ho, hv);
		}
	}
	return obj;
}

TObject* TCache::objptr(size_t s)
{
	TString16 key; key.format("%10lu", s);
	return objptr(key);
}

void TCache::destroy()
{
	for (int i = _data.last(); i >= 0; i--)
	{
		THash_object* ho = (THash_object*)_data.objptr(i);
		if (ho != NULL)
		{
			discarding(ho);
		  _data.destroy(i);
		}
	}
}

TCache::TCache(size_t size) : _data(size > 0 ? size : HASH_SIZE)
{ }

TCache::~TCache()
{
	destroy();
}