#include "dbimage.h"

int bdb_slicekey(Db *sdbp,   
                 const Dbt *pkey,   
                 const Dbt *pdata,  
                 Dbt *skey);

int bdb_channelkey( Db *sdbp,
                    const Dbt *pkey,   
                    const Dbt *pdata,  
                    Dbt *skey);

int bdb_stack2key(Db *sdbp,
                  const Dbt *pkey, 
                  const Dbt *pdata,
                  Dbt *skey);

int bdb_stack1key(Db *sdbp,
                  const Dbt *pkey,   // primary db record's key
                  const Dbt *pdata,  // primary db record's data
                  Dbt *skey);

int bdb_slicekey(Db *sdbp,          // secondary db handle
                 const Dbt *pkey,   // primary db record's key
                 const Dbt *pdata,  // primary db record's data
                 Dbt *skey)         // secondary db record's key
{
  sdbp = NULL;
  pdata = NULL;
  dbimage::key *k;
  k = (dbimage::key *)pkey->get_data();
  skey->set_data( &(k->slice) );
  skey->set_size( sizeof(int) );
  return 0;
}

int bdb_channelkey( Db *sdbp,
                 const Dbt *pkey,   // primary db record's key
                 const Dbt *pdata,  // primary db record's data
                 Dbt *skey)         // secondary db record's key
{
  sdbp = NULL;
  pdata = NULL;
  dbimage::key *k;
  k = (dbimage::key *)pkey->get_data();
  skey->set_data( &(k->channel) );
  skey->set_size( sizeof(int) );
  return 0;
}

int bdb_stack2key(Db *sdbp,
                  const Dbt *pkey,   // primary db record's key
                  const Dbt *pdata,  // primary db record's data
                  Dbt *skey)         // secondary db record's key
{
  sdbp = NULL;
  pdata = NULL;
  dbimage::key *k;
  k = (dbimage::key *)pkey->get_data();
  skey->set_data( &(k->stack2) );
  skey->set_size( sizeof(int) );
  return 0;
}

int bdb_stack1key(Db *sdbp,
                  const Dbt *pkey,   // primary db record's key
                  const Dbt *pdata,  // primary db record's data
                  Dbt *skey)         // secondary db record's key
{
  sdbp = NULL;
  pdata = NULL;
  dbimage::key *k;
  k = (dbimage::key *)pkey->get_data();
  skey->set_data( &(k->stack1) );
  skey->set_size( sizeof(int) );
  return 0;
}

dbimage::dbimage()
{
  dbname = "image.db";
  dbfiles.push_back( "image.db" );
  dbfiles.push_back( "image_lidx.db" );
  dbfiles.push_back( "image_t1idx.db" );
  dbfiles.push_back( "image_t2idx.db" );
  dbfiles.push_back( "image_cidx.db" );
}

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

int dbimage::close()
{
  if( mydb != NULL )
    {
      mydb->sync(0);
      mydb_stack1idx->sync(0);
      mydb_stack2idx->sync(0);
      mydb_sliceidx->sync(0);
      mydb_channelidx->sync(0);

      mydb_stack1idx->close(0);
      mydb_stack2idx->close(0);
      mydb_sliceidx->close(0);
      mydb_channelidx->close(0);
      mydb->close(0);

      delete mydb_stack1idx;
      delete mydb_stack2idx;
      delete mydb_sliceidx;
      delete mydb_channelidx;
      delete mydb;

    }
  mydb_stack1idx = mydb_stack2idx = mydb_sliceidx = mydb = NULL;
  return 0;
}

int dbimage::addimage( int stack1, int stack2, int slice,
                       int channel, image2d &im )
{
  int ret;
  image2d myim;
  key dbkey;
  dbkey.stack1 = stack1;
  dbkey.stack2 = stack2;
  dbkey.slice = slice;
  dbkey.channel = channel;
  myim.load( &im );
  myim.multquantize( QUANTIZEFACTOR );
  wavelet wlt;
  wltcompress wltc;
  wlt.analyze( myim, 1 );
  wltc.compress(wlt);
  int cs;
  cs = wltc.compresssize();
  Dbt rec;
  Dbt dbk( &dbkey, sizeof(key) );
  rec.set_data( wltc.compressdata() );
  rec.set_size( cs );
  try
    {
      std::cout << "writing " << cs << " bytes to db\n";
      std::cout << "compression: " << ((double)(cs))/( (double)(512*512*sizeof(int)) )
                << "\n";
      ret = mydb->put(NULL, &dbk, &rec, 0);
    }
  catch( DbException &e) 
    {   
      std::cout << "DB EXCEPTION: " << e.what() << "\n";
      exit(0);
    }
  return 0;
}

int dbimage::getimage( int stackx, int stacky,
                       int slice, int channel,
                       image2d &im )
{
  int ret;
  key dbkey;
  dbkey.stack1  = stackx;
  dbkey.stack2  = stacky;
  dbkey.slice   = slice;
  dbkey.channel = channel;
  wavelet wlt;
  wltcompress wltc;
  //  int cs;
  Dbt rec;
  Dbt dbk( &dbkey, sizeof(key) );
  char *buf;
  int bufsize;
  bufsize = 1024*16;
  buf = new char [bufsize];

  rec.set_flags(DB_DBT_USERMEM);
  rec.set_data( buf );
  rec.set_ulen( bufsize );
  int flag = 1;
  while( flag == 1 )
    {
      try
        {
          ret = mydb->get(NULL, &dbk, &rec, 0 );
          flag = 0;
        }
      catch(DbException &e)
        {
          if(e.get_errno() == DB_BUFFER_SMALL )
            {
              delete [] buf;
#ifdef __DEBUG__
              std::cout << "resizing buffer\n";
              std::cout << bufsize << " -> ";
#endif
              bufsize = ( (rec.get_size()/1024)+2 )*1024;
#ifdef __DEBUG__
              std::cout << bufsize << "\n";
#endif
              buf = new char[bufsize];
              rec.set_ulen(bufsize);
              rec.set_data(buf);
              flag = 1;
            }
          else
            {
              std::cout << "DB GET ERROR" << "\n";
              std::cout << e.what() << "\n";
              exit(0);
            }
        }
    }
  if(ret != 0)
    {
      delete [] buf;
      std::cout << "failed to get\nstack ";
      std::cout << dbkey.stack1 << ":"
                << dbkey.stack2 << " slice " 
                <<  dbkey.slice << " channel "
                <<  dbkey.channel << "\n";
      std::cout << "ret: " << ret << "\n";
      return ret;
    }
  wltc.decompress( wlt, buf, rec.get_size() );
  wlt.istrans( im, 1 );
  im.dividefactor( QUANTIZEFACTOR );
  delete [] buf;
  return 0;
}

int dbimage::rowstacks( int stackx, std::vector< int > &stacky )
{
  int ret;
  key dbkey;
  Dbc *mycurid1, *mycurid, *join;
  Dbt rec;
  int imid;
  imid = 0;
  int imch;
  imch = 0;
  int skx;
  skx = stackx;
  Dbt pkey( &dbkey, sizeof(key) );
  Dbt keyid1( &skx, sizeof(int) );
  Dbt keych( &imch, sizeof( int ) );
  Dbt keyid( &imid, sizeof( int ) );
  Dbc *jcurs[4];

  join = NULL;
  mydb_stack1idx->cursor( NULL, &mycurid1, 0 );
  mydb_sliceidx->cursor( NULL, &mycurid, 0 );
  //mydb_channelidx->cursor( NULL, &mycurch, 0 );
  jcurs[0] = mycurid1;
  jcurs[1] = mycurid;
  jcurs[2] = NULL;
  jcurs[3] = NULL;
  std::set< int > uk2;
  char *buf;
  buf = NULL;
  int bufsize;
  bufsize = 1;
  buf = new char [bufsize];
  pkey.set_flags(DB_DBT_USERMEM);
  pkey.set_ulen( sizeof(key) );
  rec.set_flags(DB_DBT_USERMEM | DB_DBT_PARTIAL);
  rec.set_data( buf );
  rec.set_size( bufsize );
  rec.set_ulen( bufsize );
  rec.set_dlen( 0 );
  rec.set_doff( 0 );
  try
    {
      //      std::cout << "setting stack1 cursor\n";
      ret = mycurid1->get( &keyid1, &rec, DB_SET );
      //std::cout << "setting id cursor\n";
      ret = mycurid->get( &keyid, &rec, DB_SET );
      //std::cout << "setting channel cursor\n";
      //ret = mycurch->get( &keych, &rec, DB_SET );
      //std::cout << "joining cursor\n";
      ret = mydb->join( jcurs, &join, 0 );
      //std::cout << "getting stuff\n";
      while( (ret = join->get(&pkey, &rec, 0)) == 0)
        {
          uk2.insert( dbkey.stack2 );
        }
    }
  catch( DbException &e) 
    {
      std::cout << "got exception in get stacks in row\n";
      std::cout << e.what() << "\n";
      mycurid1->close();
      mycurid->close();
      //mycurch->close();
      join->close();
      delete [] buf;
      exit(0);
    }
  if( buf != NULL )
    delete [] buf;

  stacky.clear();
  for( std::set< int >::iterator i = uk2.begin(); i != uk2.end(); i++ )
    stacky.push_back( *i );

  mycurid1->close();
  mycurid->close();
  //mycurch->close();
  join->close();
  return 0;
}

int dbimage::getslices( std::vector< int > &slice )
{
  Dbc *cur;
  int id;
  mydb_sliceidx->cursor( NULL, &cur, 0 );
  try
    {
      Dbt key( &id, sizeof(int) );
      Dbt data;
      data.set_data( NULL );
      data.set_ulen(0);
      data.set_dlen(0);
      data.set_doff(0);
      key.set_flags( DB_DBT_USERMEM );
      key.set_ulen( sizeof(int) );
      data.set_flags( DB_DBT_USERMEM | DB_DBT_PARTIAL );
      int ret;
      ret = cur->get( &key, &data, DB_FIRST );
      while( ret == 0 )
        {
          slice.push_back( id );
          ret = cur->get( &key, &data, DB_NEXT_NODUP );
        }
    }
  catch( DbException &e)
    {
      std::cout << "ERROR GETCHANNELS\n";
      std::cout << e.what() << "\n";
      exit(0);
    }
  cur->close();
  return 0;
}

int dbimage::getchannels( std::vector< int > &channels )
{
  Dbc *cur;
  int id;
  mydb_channelidx->cursor( NULL, &cur, 0 );
  try
    {
      Dbt key( &id, sizeof(int) );
      Dbt data;
      data.set_data( NULL );
      data.set_ulen(0);
      data.set_dlen(0);
      data.set_doff(0);
      key.set_flags( DB_DBT_USERMEM );
      key.set_ulen( sizeof( int ) );
      data.set_flags( DB_DBT_USERMEM | DB_DBT_PARTIAL );
      int ret;
      ret = cur->get( &key, &data, DB_FIRST );
      while( ret == 0 )
        {
          channels.push_back( id );
          ret = cur->get( &key, &data, DB_NEXT_NODUP );
        }
    }
  catch( DbException &e)
    {
      std::cout << "ERROR GETCHANNELS\n";
      std::cout << e.what() << "\n";
      exit(0);
    }
  cur->close();
  return 0;
}

int dbimage::rows( std::vector< int > &stackx )
{
  Dbc *mycur;
  char dbkc = 'z';
  //  char dbkd = 'x';
  //  std::cout << "getting rows\n";
  mydb_stack1idx->cursor( NULL, &mycur, 0 );
  try
    {
      int ret;
      Dbt rec;
      Dbt dbk( &dbkc, sizeof(int) );
      dbk.set_flags(DB_DBT_USERMEM);
      dbk.set_ulen( sizeof(int) );
      char *buf;
      int bufsize;
      bufsize = 1;
      buf = new char [bufsize];
      rec.set_flags(DB_DBT_USERMEM | DB_DBT_PARTIAL);
      rec.set_data( buf );
      rec.set_size( bufsize );
      rec.set_ulen( bufsize );
      rec.set_doff( 0 );
      rec.set_dlen( 0 );
      
      //      std::cout << "setting cursor and getting\n";
      ret = mycur->get(&dbk, &rec, DB_FIRST);
      while( ret == 0 )
        {
          stackx.push_back( dbkc );
          ret = mycur->get(&dbk, &rec, DB_NEXT_NODUP);
        }
        
      delete [] buf;
      mycur->close();
    }
  catch( DbException &e) 
    {
      std::cout << "got exception\n";
      std::cout << e.what() << "\n";
      mycur->close();
      return -1;
    }
  return 0;
}

int dbimage::init( DbEnv *env )
{
  close();
  std::cout << "opening imagedb\n";
  dbenvp = env;
  lsn_reset();
  mydb = new Db(dbenvp, 0);
  //  mydb->set_pagesize( 1024*16);
  unsigned int openflag, myopenflag;
  env->get_open_flags( &openflag );
  if( (openflag & DB_INIT_TXN) == 0 )
    {      
      myopenflag = DB_CREATE | DB_THREAD;
    }
  else
    myopenflag = DB_CREATE | DB_AUTO_COMMIT | DB_THREAD;

  mydb->open( NULL, dbname.c_str(), NULL,
              DB_BTREE, myopenflag, 384);
  mydb_sliceidx = new Db(dbenvp, 0);
  mydb_stack1idx = new Db(dbenvp, 0);
  mydb_stack2idx = new Db(dbenvp, 0);
  mydb_channelidx = new Db(dbenvp, 0);
  mydb_channelidx->set_flags( DB_DUPSORT );
  mydb_sliceidx->set_flags( DB_DUPSORT );
  mydb_stack1idx->set_flags( DB_DUPSORT );
  mydb_stack2idx->set_flags( DB_DUPSORT );

  mydb_sliceidx->open( NULL, "image_lidx.db", NULL,
                       DB_BTREE, myopenflag, 384);
  mydb_stack1idx->open( NULL, "image_t1idx.db", NULL,
                        DB_BTREE, myopenflag, 384);
  mydb_stack2idx->open( NULL, "image_t2idx.db", NULL,
                        DB_BTREE, myopenflag, 384);
  mydb_channelidx->open( NULL, "image_cidx.db", NULL,
                         DB_BTREE, myopenflag, 384);
  
  if( (openflag & DB_INIT_TXN) == 0 )
    myopenflag = DB_CREATE;
  else
    myopenflag = DB_CREATE | DB_AUTO_COMMIT;
  std::cout << "associating...\n";

  mydb->associate( NULL, mydb_sliceidx, bdb_slicekey,
                   myopenflag);
  std::cout << "associating...\n";
  mydb->associate( NULL, mydb_stack1idx, bdb_stack1key, 
                   myopenflag);
  std::cout << "associating...\n";
  mydb->associate( NULL, mydb_stack2idx, bdb_stack2key, 
                   myopenflag);
  std::cout << "associating...\n";
  mydb->associate( NULL, mydb_channelidx, bdb_channelkey, 
                   myopenflag);
  std::cout << "imagedb opened\n";

  return 0;
}
