#include "dbfeaturesift.h"

int bdb_cmp_int(Db *dbp, const Dbt *a, const Dbt *b);
int bdb_cmp_ullint(Db *dbp, const Dbt *a, const Dbt *b);

int bdb_imfgetaid(Db *sdbp, const Dbt *pkey,
                  const Dbt *pdata, Dbt *skey);
int bdb_imfgetslice(Db *sdbp, const Dbt *pkey,
                    const Dbt *pdata, Dbt *skey);
int bdb_imfgetchannel(Db *sdbp, const Dbt *pkey,
                      const Dbt *pdata, Dbt *skey);
int bdb_imfgetasc(Db *sdbp, const Dbt *pkey,
                  const Dbt *pdata, Dbt *skey);
int bdb_imfgetfid(Db *sdbp, const Dbt *pkey,
                  const Dbt *pdata, Dbt *skey);

int bdb_cmp_int(Db *dbp, const Dbt *a, const Dbt *b)
{
  dbp = NULL;
  int *ai, *bi;
  // Returns: 
  // < 0 if a < b 
  // = 0 if a = b 
  // > 0 if a > b 
  ai = (int*) a->get_data(); 
  bi = (int*) b->get_data(); 
  return (*ai - *bi); 
}

int bdb_cmp_ullint(Db *dbp, const Dbt *a, const Dbt *b)
{
  dbp = NULL;
  unsigned long long int *ai, *bi;
  // Returns: 
  // < 0 if a < b 
  // = 0 if a = b 
  // > 0 if a > b 
  ai = (unsigned long long int*) a->get_data(); 
  bi = (unsigned long long int*) b->get_data(); 
  return (*ai - *bi); 
}

int bdb_imfgetaid(Db *sdbp, const Dbt *pkey,
                  const Dbt *pdata, Dbt *skey)
{
  dbfeaturesift::imfvalue *d;
  d = (dbfeaturesift::imfvalue*)(pdata->get_data());
  skey->set_data( &(d->aid) );
  skey->set_size( sizeof( int ) );
  sdbp = NULL;
  pkey = NULL;
  pdata = NULL;
  return 0;
}

int bdb_imfgetslice(Db *sdbp, const Dbt *pkey,
                    const Dbt *pdata, Dbt *skey)
{
  dbfeaturesift::imfvalue *d;
  d = (dbfeaturesift::imfvalue*)(pdata->get_data());
  skey->set_data( &(d->slice) );
  skey->set_size( sizeof( int ) );
  sdbp = NULL;
  pkey = NULL;
  pdata = NULL;
  return 0;
}

int bdb_imfgetchannel(Db *sdbp, const Dbt *pkey,
                      const Dbt *pdata, Dbt *skey)
{
  dbfeaturesift::imfvalue *d;
  d = (dbfeaturesift::imfvalue*)(pdata->get_data());
  skey->set_data( &(d->channel) );
  skey->set_size( sizeof( int ) );
  sdbp = NULL;
  pkey = NULL;
  pdata = NULL;
  return 0;
}

int bdb_imfgetasc(Db *sdbp, const Dbt *pkey,
                  const Dbt *pdata, Dbt *skey)
{
  dbfeaturesift::imfvalue *d;
  d = (dbfeaturesift::imfvalue*)(pdata->get_data());
  skey->set_data( &(d->aid) );
  skey->set_size( 3*sizeof( int ) );
  sdbp = NULL;
  pkey = NULL;
  pdata = NULL;
  return 0;
}

int bdb_imfgetfid(Db *sdbp, const Dbt *pkey,
                  const Dbt *pdata, Dbt *skey)
{
  unsigned long long int *d;
  d = (unsigned long long int*)(pdata->get_data());
  skey->set_data( d );
  skey->set_size( sizeof( unsigned long long int ) );
  sdbp = NULL;
  pkey = NULL;
  return 0;
}

dbfeaturesift::dbfeaturesift()
{
  mydb = imfdb = imf_aididx = imf_sliceidx =
    imf_ascidx = NULL;
  dbname = "sift.db";
  dbfiles.clear();
  dbfiles.push_back( dbname );
  dbfiles.push_back( "sift_to_image.db" );
  dbfiles.push_back( "sift_aididx.db" );
  dbfiles.push_back( "sift_sliceidx.db" );
  dbfiles.push_back( "sift_channelidx.db" );
  dbfiles.push_back( "sift_ascidx.db" );
}

void *start_pthread_dbinsert( void *d )
{
  dbfeaturesift_insert_td *ditd;
  ditd = (dbfeaturesift_insert_td*)d;
  pthread_dbinsert( ditd );
  return NULL;
}

int pthread_dbinsert( dbfeaturesift_insert_td *d )
{
  d->cid = d->dbsift->nextfid();
  while( true )
    {
      pthread_mutex_lock( d->toinsert_lock );
      while( d->toinsert.size() == 0 )
        {
          /// if good for exit exit
          if( d->endflag == 1 )
            {
              pthread_mutex_unlock( d->toinsert_lock );
              std::cout << "ready for exit\n";
              std::cout << "inserted " 
                        << d->cid << " items\n";
              return 0;
            }
          else
            {
              std::cout << "waiting for work\n";
              pthread_cond_wait( d->toinsert_cond, d->toinsert_lock );
            }
        }
      // insert into dbfeature
      if( d->toinsert.size() > 1000 )
        {
          std::cout << "flushing sift queue\n";
          while( d->toinsert.size() > 100 )
            {
              imsift_data myd;
              myd = d->toinsert.front();
              d->toinsert.pop();
              d->dbsift->addfeature( d->cid, myd.key, myd.data, myd.dlen );
              d->cid++;
              delete [] myd.data;
            }
          pthread_mutex_unlock( d->toinsert_lock );
        }
      else
        {
          imsift_data myd;
          myd = d->toinsert.front();
          d->toinsert.pop();
          pthread_mutex_unlock( d->toinsert_lock );
          d->dbsift->addfeature( d->cid, myd.key, myd.data, myd.dlen );
          d->cid++;
          delete [] myd.data;
        }
    }
  return 0;
}

dbfeaturesift::~dbfeaturesift()
{
  close();
}

int dbfeaturesift::close()
{
  if ( mydb != NULL )
    {
      mydb->sync(0);
      imfdb->sync(0);
      imf_aididx->sync(0);
      imf_sliceidx->sync(0);
      imf_channelidx->sync(0);
      imf_ascidx->sync(0);

      imf_aididx->close(0);
      imf_sliceidx->close(0);
      imf_ascidx->close(0);
      imf_channelidx->close(0);
      imfdb->close(0);
      mydb->close(0);

      delete mydb;
      delete imf_aididx;
      delete imf_sliceidx;
      delete imf_ascidx;
      delete imf_channelidx;
      delete imfdb;
    }
  mydb = imfdb = imf_aididx = imf_sliceidx =
    imf_ascidx = imf_channelidx = NULL;
  return 0;
}

int dbfeaturesift::init( DbEnv *env )
{
  close();
  dbenvp = env;
  mydb = new Db( dbenvp, 0 );
  imfdb = new Db( dbenvp, 0 );
  imf_aididx = new Db( dbenvp, 0 );
  imf_sliceidx = new Db( dbenvp, 0 );
  imf_ascidx = new Db( dbenvp, 0 );
  imf_channelidx = new Db( dbenvp, 0 );
  

  mydb->set_bt_compare(bdb_cmp_ullint);
  mydb->open( NULL, dbname.c_str(), NULL,
              DB_BTREE, DB_CREATE | DB_AUTO_COMMIT | DB_THREAD, 384);
  std::cout << "opening imfdb\n";
  //  imfdb->set_flags(DB_DUP);
  imfdb->open( NULL, "sift_to_image.db", NULL,
               DB_BTREE, DB_CREATE | DB_AUTO_COMMIT | DB_THREAD, 384);
  std::cout << "opening secondary databases\n";

  imf_aididx->set_flags( DB_DUP );
  imf_aididx->set_bt_compare(bdb_cmp_int);
  imf_aididx->open( NULL, "sift_aididx.db", NULL,
                    DB_BTREE, DB_CREATE | DB_AUTO_COMMIT | DB_THREAD, 384 );

  imf_sliceidx->set_flags( DB_DUP );
  imf_sliceidx->set_bt_compare(bdb_cmp_int);
  imf_sliceidx->open( NULL, "sift_sliceidx.db", NULL,
                      DB_BTREE, DB_CREATE | DB_AUTO_COMMIT | DB_THREAD, 384 );

  imf_channelidx->set_flags( DB_DUP );
  imf_channelidx->set_bt_compare(bdb_cmp_int);
  imf_channelidx->open( NULL, "sift_channelidx.db", NULL,
                        DB_BTREE, DB_CREATE | DB_AUTO_COMMIT | DB_THREAD, 384 );

  imf_ascidx->set_flags( DB_DUP );
  imf_ascidx->open( NULL, "sift_ascidx.db", NULL,
                    DB_BTREE, DB_CREATE | DB_AUTO_COMMIT | DB_THREAD, 384 );

  imfdb->associate( NULL, imf_aididx, bdb_imfgetaid,
                    DB_CREATE | DB_AUTO_COMMIT );
  imfdb->associate( NULL, imf_sliceidx, bdb_imfgetslice,
                    DB_CREATE | DB_AUTO_COMMIT );
  imfdb->associate( NULL, imf_channelidx, bdb_imfgetchannel,
                    DB_CREATE | DB_AUTO_COMMIT );
  imfdb->associate( NULL, imf_ascidx, bdb_imfgetasc,
                    DB_CREATE | DB_AUTO_COMMIT );

  return 0;
}

unsigned long long int 
dbfeaturesift::nextfid( )
{
  Dbt k, v;
  Dbc *cur;
  cur = NULL;
  unsigned long long int myid;
  k.set_data( &myid );
  k.set_flags(DB_DBT_USERMEM);
  k.set_ulen( sizeof( unsigned long long int ) );
  char *buffer;
  buffer = new char[16];
  v.set_data( buffer );
  v.set_flags(DB_DBT_USERMEM | DB_DBT_PARTIAL);
  v.set_ulen( 16 );
  v.set_doff(0);
  v.set_dlen(0);

  try
    {
      int ret;
      ret = mydb->cursor(NULL, &cur, 0 );
      ret = cur->get( &k, &v, DB_LAST );
      if(ret != 0 )
        {
          //          std::cout << "inserting first item\n";
          myid = 0;
        }
      else
        {
          //          std::cout << "inserting " << myid << "\n";
          myid = myid + 1;
        }
    }
  catch(DbException &e)
    {
      std::cout << "fail in dbfeaturesift create set, getid\n";
      std::cout << e.what() << "\n";
      exit(0);
    }
  cur->close();
  delete [] buffer;
  return myid;
}

int
dbfeaturesift::addfeature( unsigned long long int fid,
                           imfvalue imfv, double *d, int sz )
{
  Dbt k, v;
  unsigned long long int myid;
  myid = fid;

  v.set_data( d );
  v.set_size( sz );
  v.set_flags( DB_DBT_USERMEM );
  v.set_ulen( sz );

  k.set_data( &myid );
  k.set_size( sizeof( unsigned long long int ) );

  DbTxn *txn;
  dbenvp->txn_begin(NULL, &txn, DB_TXN_NOSYNC );
  int ret;
  ret = mydb->put( txn, &k, &v, DB_NOOVERWRITE );
  if( ret != 0 )
    {
      std::cout << "ERROR on insert overwrite1\n";
      exit(0);
    }

  imfvalue iv;
  imfkey ik;

  ik.fid = myid;
  iv = imfv;

  k.set_data( &ik );
  k.set_size( sizeof( imfkey ) );
  k.set_ulen( sizeof( imfkey ) );
  v.set_data( &iv );
  v.set_size( sizeof( imfvalue ) );
  v.set_ulen( sizeof( imfvalue ) );

  ret = imfdb->put( txn, &k, &v, DB_NOOVERWRITE );
  if( ret != 0 )
    {
      std::cout << "ERROR on insert overwrite2\n";
      exit(0);
    }

  txn->commit(0);

  return 0;
}

unsigned long long int 
dbfeaturesift::addfeature( int aid, int slice, 
                           int channel,
                           featuresift &f )
{
  Dbt k, v;
  Dbc *cur;
  cur = NULL;
  unsigned long long int myid;
  //  std::cout << "primary key id size: " << sizeof( unsigned long long int ) << "\n";
  k.set_data( &myid );
  k.set_flags(DB_DBT_USERMEM);
  k.set_ulen( sizeof( unsigned long long int ) );
  char *buffer;
  buffer = new char[16];
  v.set_data( buffer );
  v.set_flags(DB_DBT_USERMEM | DB_DBT_PARTIAL);
  v.set_ulen( 16 );
  v.set_doff(0);
  v.set_dlen(0);

  try
    {
      int ret;
      ret = mydb->cursor(NULL, &cur, 0 );
      ret = cur->get( &k, &v, DB_LAST );
      if(ret != 0 )
        {
          //          std::cout << "inserting first item\n";
          myid = 0;
        }
      else
        {
          //          std::cout << "inserting " << myid << "\n";
          myid = myid + 1;
        }
    }
  catch(DbException &e)
    {
      std::cout << "fail in dbfeaturesift create set, getid\n";
      std::cout << e.what() << "\n";
      exit(0);
    }
  cur->close();
  delete [] buffer;

  //  std::cout << "new fid: " << myid << "\n";

  int sz = f.serialize( &buffer );
  v.set_data( buffer );
  v.set_size( sz );
  v.set_flags( DB_DBT_USERMEM );
  v.set_ulen( sz );

  k.set_data( &myid );
  k.set_size( sizeof( unsigned long long int ) );

  DbTxn *txn;
  dbenvp->txn_begin(NULL, &txn, DB_TXN_NOSYNC );

  mydb->put( txn, &k, &v, 0 );

  delete [] buffer;

  imfvalue iv;
  imfkey ik;
  ik.fid = myid;
  iv.aid = aid;
  iv.slice = slice;
  iv.channel = channel;
  iv.size = f.size();
  k.set_data( &ik );
  k.set_size( sizeof( imfkey ) );
  k.set_ulen( sizeof( imfkey ) );
  v.set_data( &iv );
  v.set_size( sizeof( imfvalue ) );
  v.set_ulen( sizeof( imfvalue ) );

  imfdb->put( txn, &k, &v, 0 );

  txn->commit(0);

  return myid;
}

int dbfeaturesift::getfeature( featuresift &f, unsigned long long int aid )
{
  Dbt k, v;
  unsigned long long int a;
  a = aid;
  k.set_data( &a );
  k.set_size( sizeof( unsigned long long int ) );
  k.set_ulen( sizeof( unsigned long long int ) );
  k.set_flags( DB_DBT_USERMEM );
  char buffer[131*sizeof(double)];
  v.set_data( buffer );
  v.set_ulen( sizeof( double) * 131 );
  v.set_flags( DB_DBT_USERMEM );
  int ret;
  
  ret = mydb->get( NULL, &k, &v, 0 );

  if( ret == 0 )
    {
      if( v.get_size() != sizeof(double) * 131 )
        {
          std::cout << "unexpected feature size: " << v.get_size()
                    << " bytes\n";
          exit(0);
        }
      f.deserialize( buffer );
    }
  else
    return -1;
  return 0;
}

int dbfeaturesift::getfeaturelist( std::vector< unsigned long long int > &f,
                                   int aid, int slice, int channel )
{
  imfvalue kv, sv;
  kv.aid = aid;
  kv.slice = slice;
  kv.channel = channel;
  Dbt k, v, pk;
  k.set_data( &kv );
  k.set_size( 3*sizeof(int) );
  k.set_ulen( 3*sizeof(int) );
  k.set_flags( DB_DBT_USERMEM );
  unsigned long long int ul;
  pk.set_data( &ul );
  pk.set_ulen( sizeof( unsigned long long int ) );
  pk.set_flags( DB_DBT_USERMEM );
  v.set_data( &sv );
  v.set_ulen( sizeof( imfvalue ) );
  v.set_flags( DB_DBT_USERMEM );

  Dbc *cur;
  imf_ascidx->cursor( NULL, &cur, 0 );
  int ret;
  ret = cur->pget( &k, &pk, &v, DB_SET );
  while( ret == 0 )
    {
      f.push_back( ul );
      /*
      std::cout << ul << " -> " << kv.aid << " " << kv.slice << " " << kv.channel << "\n";
      std::cout << "\t" << sv.aid << " " << sv.slice << " " << sv.channel << "\n";
      std::cout << std::hex << "\t" << &(sv) << " " << &(sv.aid) << " " 
                << &(sv.slice) << " " << &(sv.channel) << std::dec << "\n";
      */
      ret = cur->pget( &k, &pk, &v, DB_NEXT_DUP );
    }

  cur->close();
  return 0;
}

