/*************************************************************************************************
 * xdptest
 *                                                       Copyright(c) 2000-2003 Mikio Hirabayashi
 *************************************************************************************************/


#include <xdepot.h>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <iomanip>

extern "C" {
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
}

using namespace std;
using namespace qdbm;


/* global constants */
const int RECBUFSIZ = 32;
const int ALIGNSIZ = 8;


/* struct for argument of threads */
struct Mission {
  Depot* depot;
  int rnum;
  int id;
  bool printer;
};


/* global variables */
const char* progname;


/* function prototypes */
int main(int argc, char** argv);
void myterminate();
void myunexpected();
void usage();
int runwrite(int argc, char** argv);
int runread(int argc, char** argv);
int runmulti(int argc, char** argv);
int runmisc(int argc, char** argv);
void pxdperror(const char* name, DBM_error &e);
int dowrite(const char* name, int rnum, int bnum);
int doread(const char* name);
int domulti(const char* name, int rnum, int bnum, int tnum);
void* mtwriter(void* arg);
int domisc(const char* name);


/* main routine */
int main(int argc, char** argv){
  int rv;
  progname = argv[0];
  set_terminate(myterminate);
  set_unexpected(myunexpected);
  if(argc < 2) usage();
  rv = 0;
  if(!strcmp(argv[1], "write")){
    rv = runwrite(argc, argv);
  } else if(!strcmp(argv[1], "read")){
    rv = runread(argc, argv);
  } else if(!strcmp(argv[1], "multi")){
    rv = runmulti(argc, argv);
  } else if(!strcmp(argv[1], "misc")){
    rv = runmisc(argc, argv);
  } else {
    usage();
  }
  return rv;
}


/* for debug */
void myterminate(){
  cout << progname << ": terminate" << endl;
  exit(1);
}


/* for debug */
void myunexpected(){
  cout << progname << ": unexpected" << endl;
  exit(1);
}


/* print the usage and exit */
void usage(){
  cerr << progname << ": test cases for Depot in C++" << endl;
  cerr << endl;
  cerr << "usage:" << endl;
  cerr << "  " << progname << " write name rnum bnum" << endl;
  cerr << "  " << progname << " read name" << endl;
  cerr << "  " << progname << " multi name rnum bnum tnum" << endl;
  cerr << "  " << progname << " misc name" << endl;
  exit(1);
}


/* parse arguments of write command */
int runwrite(int argc, char** argv){
  char *name, *rstr, *bstr;
  int i, rnum, bnum, rv;
  name = 0;
  rstr = 0;
  bstr = 0;
  for(i = 2; i < argc; i++){
    if(argv[i][0] == '-'){
      usage();
    } else if(!name){
      name = argv[i];
    } else if(!rstr){
      rstr = argv[i];
    } else if(!bstr){
      bstr = argv[i];
    } else {
      usage();
    }
  }
  if(!name || !rstr || !bstr) usage();
  rnum = atoi(rstr);
  bnum = atoi(bstr);
  if(rnum < 1 || bnum < 1) usage();
  rv = dowrite(name, rnum, bnum);
  return rv;
}


/* parse arguments of read command */
int runread(int argc, char** argv){
  char* name;
  int i, rv;
  name = 0;
  for(i = 2; i < argc; i++){
    if(argv[i][0] == '-'){
      usage();
    } else if(!name){
      name = argv[i];
    } else {
      usage();
    }
  }
  if(!name) usage();
  rv = doread(name);
  return rv;
}


/* parse arguments of multi command */
int runmulti(int argc, char** argv){
  char *name, *rstr, *bstr, *tstr;
  int i, rnum, bnum, tnum, rv;
  name = 0;
  rstr = 0;
  bstr = 0;
  tstr = 0;
  for(i = 2; i < argc; i++){
    if(argv[i][0] == '-'){
      usage();
    } else if(!name){
      name = argv[i];
    } else if(!rstr){
      rstr = argv[i];
    } else if(!bstr){
      bstr = argv[i];
    } else if(!tstr){
      tstr = argv[i];
    } else {
      usage();
    }
  }
  if(!name || !rstr || !bstr || !tstr) usage();
  rnum = atoi(rstr);
  bnum = atoi(bstr);
  tnum = atoi(tstr);
  if(rnum < 1 || bnum < 1 || tnum < 1) usage();
  rv = domulti(name, rnum, bnum, tnum);
  return rv;
}


/* parse arguments of misc command */
int runmisc(int argc, char** argv){
  char* name;
  int i, rv;
  name = 0;
  for(i = 2; i < argc; i++){
    if(argv[i][0] == '-'){
      usage();
    } else if(!name){
      name = argv[i];
    } else {
      usage();
    }
  }
  if(!name) usage();
  rv = domisc(name);
  return rv;
}


/* print an error message */
void pxdperror(const char* name, DBM_error &e){
  fprintf(stderr, "%s: %s: %s\n", progname, name, e.message());
}


/* do writing test */
int dowrite(const char* name, int rnum, int bnum){
  int i, len;
  char buf[RECBUFSIZ];
  cout << "<Writing Test>" << endl;
  cout << "  name=" << name << "  rnum=" << rnum << "  bnum=" << bnum << endl;
  cout << endl;
  Depot* depot = 0;
  bool err = false;
  try {
    // open the database
    depot = new Depot(name, Depot::OWRITER | Depot::OCREAT | Depot::OTRUNC, bnum);
    // roop for storing
    for(i = 1; i <= rnum; i++){
      // set a buffer for a key and a value
      len = sprintf(buf, "%08d", i);
      // store a record into the database
      depot->put(buf, len, buf, len);
      // print progression
      if(rnum > 250 && i % (rnum / 250) == 0){
        cout << '.';
        cout.flush();
        if(i == rnum || i % (rnum / 10) == 0){
          cout << " (" << setw(8) << setfill('0') << i << ")" << endl;
          cout.flush();
        }
      }
    }
    // close the database
    depot->close();
    cout << "ok" << endl << endl;
  } catch(Depot_error& e){
    err = true;
    // report an error
    pxdperror(name, e);
  }
  // destroy instance
  if(depot) delete depot;
  return err ? 1 : 0;
}


/* do reading test */
int doread(const char* name){
  int i, rnum, len;
  char buf[RECBUFSIZ], *val;
  cout << "<Reading Test>" << endl;
  cout << "  name=" << name << endl;
  cout << endl;
  Depot* depot = 0;
  bool err = false;
  try {
    // open the database
    depot = new Depot(name);
    // get the number of records
    rnum = depot->rnum();
    // roop for retrieving
    for(i = 1; i <= rnum; i++){
      // set a buffer for a key
      len = sprintf(buf, "%08d", i);
      // retrieve a record from the database
      val = depot->get(buf, len);
      free(val);
      // print progression
      if(rnum > 250 && i % (rnum / 250) == 0){
        cout << '.';
        cout.flush();
        if(i == rnum || i % (rnum / 10) == 0){
          cout << " (" << setw(8) << setfill('0') << i << ")" << endl;
          cout.flush();
        }
      }
    }
    // close the database
    depot->close();
    cout << "ok" << endl << endl;
  } catch(Depot_error& e){
    err = true;
    // report an error
    pxdperror(name, e);
  }
  // destroy instance
  if(depot) delete depot;
  return err ? 1 : 0;
}


/* do multi thread test */
int domulti(const char* name, int rnum, int bnum, int tnum){
  Depot* depot;
  pthread_t* tids;
  bool* tsts;
  Mission* mss;
  Depot_error* ep;
  int i, len, size;
  bool err;
  char buf[RECBUFSIZ], *val;
  cout << "<Multi-thread Test>" << endl;
  cout << "  name=" << name << "  rnum=" << rnum << "  bnum=" << bnum << "  tnum=" << tnum << endl;
  cout << endl;
  // open the database
  depot = 0;
  try {
    cout << "Creating a database ... ";
    depot = new Depot(name, Depot::OWRITER | Depot::OCREAT | Depot::OTRUNC, bnum);
    depot->setalign(ALIGNSIZ);
    cout << "ok" << endl;
  } catch(Depot_error& e){
    if(depot) delete depot;
    pxdperror(name, e);
    return 1;
  }
  // prepare threads
  tids = new pthread_t[tnum];
  tsts = new bool[tnum];
  mss = new Mission[tnum];
  err = false;
  // roop for each threads
  cout << "Writing" << endl;
  for(i = 0; i < tnum; i++){
    mss[i].depot = depot;
    mss[i].rnum = rnum;
    mss[i].id = i;
    mss[i].printer = (i == tnum / 2);
    if(pthread_create(tids + i, 0, mtwriter, (void*)(mss + i)) == 0){
      tsts[i] = true;
    } else {
      tsts[i] = false;
      err = true;
      cerr << progname << ": " << name << ": " << "pthread error" << endl;
    }
  }
  // join every threads
  for(i = 0; i < tnum; i++){
    if(tsts[i]){
      if(pthread_join(tids[i], (void**)&ep) != 0){
        err = true;
        cerr << progname << ": " << name << ": " << "pthread error" << endl;
      }
    }
    if(ep){
      err = true;
      pxdperror(name, *ep);
      delete ep;
    }
  }
  delete[] mss;
  delete[] tsts;
  delete[] tids;
  if(!err) cout << "ok" << endl;
  cout.flush();
  // check every record
  cout << "Validation checking ... ";
  try {
    for(i = 1; i <= rnum; i++){
      len = sprintf(buf, "%08d", i);
      val = depot->get(buf, len, 0, -1, &size);
      free(val);
      if(size != tnum){
        cerr << progname << ": " << name << ": " << "size error: " << size << endl;
        err = true;
      }
    }
  } catch(Depot_error& e){
    pxdperror(name, e);
    err = true;
  }
  if(!err) cout << "ok" << endl;
  // close the database
  cout << "Closing the database ... ";
  try {
    depot->close();
  } catch(Depot_error& e){
    pxdperror(name, e);
    delete depot;
    return 1;
  }
  cout << "ok" << endl;
  if(!err) cout << "all ok" << endl << endl;
  delete depot;
  return err ? 1 : 0;
}


/* writer with each thread */
void* mtwriter(void* arg){
  Mission* mp = (Mission*)arg;
  Depot* depot = mp->depot;
  int rnum = mp->rnum;
  int id = mp->id;
  bool printer = mp->printer;
  char buf[RECBUFSIZ];
  void* mc;
  int i, len;
  // roop for storing
  for(i = 1; i <= rnum; i++){
    len = sprintf(buf, "%08d", i);
    // store a record
    try {
      depot->put(buf, len, id % 2 ? "*" : "=", 1, Depot::DCAT);
    } catch(Depot_error& e){
      return new Depot_error(e);
    }
    // print progression
    if(printer && rnum > 250 && i % (rnum / 250) == 0){
      cout << '.';
      cout.flush();
      if(i == rnum || i % (rnum / 10) == 0){
        cout << " (" << setw(8) << setfill('0') << i << ")" << endl;
        cout.flush();
      }
    }
    // malloc reentrant check
    if((mc = (char*)malloc(1)) != 0){
      free(mc);
    } else {
      return new Depot_error(Depot::EALLOC);
    }
  }
  return 0;
}


/* do combination test */
int domisc(const char* name){
  bool err;
  int i, len;
  char buf[RECBUFSIZ];
  cout << "<Miscellaneous Test>" << endl;
  cout << "  name=" << name << endl;
  cout << endl;
  err = false;
  ADBM* adbm = 0;
  try {
    // open the database
    cout << "Creating a database ... ";
    adbm = new Depot(name, Depot::OWRITER | Depot::OCREAT | Depot::OTRUNC);
    cout << "ok" << endl;
    // write with datadase abstraction
    cout << "Writing with database abstraction ... ";
    for(i = 1; i <= 100; i++){
      len = sprintf(buf, "%08d", i);
      Datum key(buf, len);
      Datum val(buf, len);
      adbm->storerec(key, val, true);
    }
    cout << "ok" << endl;
    // read with datadase abstraction
    cout << "Reading with database abstraction ... ";
    for(i = 1; i <= 100; i++){
      len = sprintf(buf, "%08d", i);
      Datum key(buf, len);
      Datum* vp = adbm->fetchrec(key);
      len = vp->size();
      delete vp;
      if(len != 8) throw DBM_error("size error");
    }
    cout << "ok" << endl;
    // traversal access with database abstraction
    cout << "Traversal access with database abstraction ... ";
    Datum* kp;
    i = 0;
    try {
      for(kp = adbm->firstkey(); true; kp = adbm->nextkey()){
        delete kp;
        i++;
      }
    } catch(DBM_error& e){
      if(adbm->error()) throw;
    }
    if(i != 100) throw DBM_error("iteration error");
    cout << "ok" << endl;
    // close the database
    cout << "Closing the database ... ";
    adbm->close();
    cout << "ok" << endl;
  } catch(DBM_error& e){
    err = true;
    pxdperror(name, e);
  }
  if(adbm) delete adbm;
  if(err) return 1;
  Depot* depot = 0;
  try {
    // open the database
    cout << "Opening the database ... ";
    depot = new Depot(name);
    cout << "ok" << endl;
    // traversal access
    cout << "Traversal access ... ";
    try {
      depot->iterinit();
      for(i = 0; true; i++){
        free(depot->iternext(0));
      }
      if(i != 100) throw Depot_error();
    } catch(Depot_error& e){
      if(e != Depot::ENOITEM) throw;
    }
    cout << "ok" << endl;
    // checking information
    cout << "Checking information" << endl;
    cout << "  - fsiz ... " << depot->fsiz() << endl;
    cout << "  - bnum ... " << depot->bnum() << endl;
    cout << "  - busenum ... " << depot->busenum() << endl;
    cout << "  - rnum ... " << depot->rnum() << endl;
    cout << "  - writable ... " << depot->writable() << endl;
    cout << "  - fatalerror ... " << depot->fatalerror() << endl;
    cout << "  - inode ... " << depot->inode() << endl;
    cout << "ok" << endl;
    // close the database
    cout << "Closing the database ... ";
    depot->close();
    cout << "ok" << endl;
  } catch(Depot_error& e){
    err = true;
    pxdperror(name, e);
  }
  if(depot) delete depot;
  if(err) return 1;
  depot = 0;
  try {
    // open the database
    cout << "Opening the database ... ";
    depot = new Depot(name, Depot::OWRITER);
    cout << "ok" << endl;
    // sync the database
    cout << "Syncing the database ... ";
    depot->sync();
    cout << "ok" << endl;
    // optimize the database
    cout << "Optimizing the database ... ";
    depot->optimize();
    cout << "ok" << endl;
    // close the database
    cout << "Closing the database ... ";
    depot->close();
    cout << "ok" << endl;
  } catch(Depot_error& e){
    err = true;
    pxdperror(name, e);
  }
  if(depot) delete depot;
  if(err) return 1;
  cout << "all ok" << endl << endl;
  return 0;
}



/* END OF FILE */
