//-----------------------------------------------------------------------------
// Copyright (c) 1994,1995 Southeastern Universities Research Association,
//                         Continuous Electron Beam Accelerator Facility
//
// This software was developed under a United States Government license
// described in the NOTICE file included as part of this distribution.
//
//-----------------------------------------------------------------------------
//
// Description:
//      Implementation of rsvcDataStore Class
//
// Author:  Jie Chen
//
// Revision History:
//   rsvcDataStore.cc,v
// Revision 1.3  1998/03/19  18:30:58  chen
// add monitorEntry capability to server table
//
// Revision 1.2  1998/02/26  19:09:43  chen
// remove all cached data when finished
//
// Revision 1.1  1998/01/22  17:08:06  akers
// Addition of new NameServer
//
//
//
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <rsvcVirtualDbase.h>
#include <rsvcServerConfig.h>
#include <rsvcCacheData.h>
#include <rsvcIO.h>
#include "rsvcDataStore.h"
#include "rsvcDataStoreMem.h"

#ifdef _WIN32
#include <windows.h>
#include <sys/timeb.h>
#else
#include <dirent.h>
#endif

#ifndef DB_CREATE
#define DB_CREATE 0x00001
#endif

#ifdef _WIN32
#define _RSVC_DIR_CHAR '\\'
#define _RSVC_DIR_SELF ".\\"
#else
#define _RSVC_DIR_CHAR '/'
#define _RSVC_DIR_SELF "./"
#endif



rsvcDataStore::rsvcDataStore (char* name)
:rsvcHashable (), timestamp_ (0.0), tableDef_(name), 
 cache_ (rsvcServerConfig::dbaseCacheMax (),rsvcServerConfig::dbaseCacheLf ()),
 idataMonitorList_ (), odataMonitorList_ (), database_ (0)
{
#ifdef _TRACE_OBJECTS
  printf ("    Create rsvcDataStore Class Object\n");
#endif
  name_ = new char[strlen (name) + 1];
  strcpy (name_, name);
}

rsvcDataStore::~rsvcDataStore (void)
{
#ifdef _TRACE_OBJECTS
  printf ("    Delete rsvcDataStore Class Object\n");
#endif

  // remove all monitors
  removeAllMonitors ();
  delete []name_;

  if (database_)
    delete database_;

  rsvcHashIterator ite (cache_);
  rsvcCacheData* cdata = 0;

  for (ite.init (); !ite; ++ite) {
    cdata = (rsvcCacheData *) ite ();
    delete cdata;
  }
  cache_.deleteAllValues ();
}


char* 
rsvcDataStore::name (void) const
{
  return name_;
}

void
rsvcDataStore::removeAllMonitors (void)
{
  rsvcHSlistIterator ite (idataMonitorList_);
  rsvcCbk* cbk = 0;

  for (ite.init (); !ite; ++ite) {
    cbk = (rsvcCbk *)ite ();
    delete cbk;
  }
  idataMonitorList_.deleteAllValues ();

  rsvcHSlistIterator oite (odataMonitorList_);
  for (oite.init (); !oite; ++oite) {
    cbk = (rsvcCbk *)oite ();
    delete cbk;
  }
  odataMonitorList_.deleteAllValues ();
}  

int
rsvcDataStore::createDatabase (rsvcNetData& incoming,
			       rsvcNetData* out[], size_t* num)
{
  int status = RSVC_SUCCESS;

  // if table already defined, do nothing
  if (tableDef_.tableDefined ())  // table is defined
    status = RSVC_ERROR;
  else if (tableDef_.create () == RSVC_SUCCESS) // table is defined on disk
    status = RSVC_ERROR;
  else {
    // try to create table definition from incoming netdata
    rsvcData& def = incoming.data ();
    if (tableDef_.create (def) != RSVC_SUCCESS) {
#ifdef _RSVC_DEBUG
      printf ("rsvcDataStore: Cannot create table definition\n");
#endif
      status = RSVC_ERROR;
    }
    else
      tableDef_.output ();  // write to disk
  }
  
  if (status == RSVC_SUCCESS && database_) {
    // create database
    char fullname[128];
    sprintf (fullname, "%s.%s", name_, tableDef_.keyName ());
    if (database_->create (fullname, DB_CREATE|O_RDWR, tableDef_.keyType ()) 
	!= RSVC_SUCCESS)
      status = RSVC_ERROR;
  }
  
  // construct returning message
  out[0] = new rsvcNetData (incoming.cbk ());
  out[0]->cbkstatus (status);
  *num = 1;

  return status;
}


int
rsvcDataStore::openDatabase (rsvcNetData& incoming,
			     rsvcNetData* out[], size_t* num)
{
  int status = RSVC_SUCCESS;
  rsvcData def;

  // if table already defined, do nothing
  if (!tableDef_.tableDefined () && tableDef_.create () != RSVC_SUCCESS) {
#ifdef _RSVC_DEBUG
    printf ("rsvcDataStore: Cannot open table definition\n");
#endif
    status = RSVC_ERROR;
  }
  
  if (status == RSVC_SUCCESS && database_) {
    // create database
    char fullname[128];
    sprintf (fullname, "%s.%s", name_, tableDef_.keyName ());
    if (!database_->opened ())
      if (database_->open (fullname, O_RDWR, tableDef_.keyType ()) 
	  != RSVC_SUCCESS)
	status = RSVC_ERROR;
    if (status == RSVC_SUCCESS)
      tableDef_.output (def);
  }
  
  // construct returning message
  if (status == RSVC_SUCCESS)
    out[0] = new rsvcNetData (def, incoming.cbk ());
  else
    out[0] = new rsvcNetData (incoming.cbk ());
  out[0]->cbkstatus (status);
  *num = 1;

  return status;
}

int
rsvcDataStore::getTableDef (rsvcNetData& incoming,
			    rsvcNetData* out[], size_t* num)
{
  int status = RSVC_SUCCESS;
  rsvcData def;

  if (tableDef_.tableDefined () != RSVC_SUCCESS) 
    status = RSVC_ERROR;
  else 
    tableDef_.output (def);
  
  // construct returning message
  out[0] = new rsvcNetData (def, incoming.cbk ());
  out[0]->cbkstatus (status);
  *num = 1;

  return status;
}

unsigned int
rsvcDataStore::hash (void)
{
  unsigned int h = 0, g;

  for (int i = 0; name_[i] != '\0'; i++){
    h = (h << 4) + name_[i];
    // assume 32 bit integer
    if (g = h & 0xf0000000){
      h ^= g >> 24;
      h ^= g;
    }
  }
  return h;
}

int
rsvcDataStore::openDatabase (void)
{
  int status = RSVC_SUCCESS;

  // if table already defined, do nothing
  if (!tableDef_.tableDefined () && tableDef_.create () != RSVC_SUCCESS) {
#ifdef _RSVC_DEBUG
    printf ("rsvcDataStore: Cannot open table definition\n");
#endif
    status = RSVC_ERROR;
  }
  if (database_ && status == RSVC_SUCCESS) {
    char fullname[128];
    sprintf (fullname, "%s.%s", name_, tableDef_.keyName ());
    if (!database_->opened ()) 
      status = database_->open (fullname, O_RDWR, tableDef_.keyType ());
  }
  return status;
}

int
rsvcDataStore::closeDatabase (void)
{
  if (database_)
    return database_->close ();
  return RSVC_ERROR;
}

void
rsvcDataStore::updateTimeStamp (void)
{
#ifdef _WIN32
  struct _timeb tv;

  _ftime (&tv);

  timestamp_ = tv.time + tv.millitm/1000.0;

#else

  struct timeval tv;

  gettimeofday (&tv, 0);

  timestamp_ = tv.tv_sec + tv.tv_usec/1000000.0;
#endif
}

int
rsvcDataStore::notifyAnEntryToChannels (rsvcData& entry)
{
  if (idataMonitorList_.isEmpty ()) 
    return RSVC_SUCCESS;

  rsvcHSlistIterator ite (idataMonitorList_);
  rsvcCbk* cbk = 0;
  rsvcNetData* ndata = 0;
  rsvcIO*      channel = 0;

  for (ite.init (); !ite; ++ite) {
    cbk = (rsvcCbk *) ite ();

    // connection channel
    channel = (rsvcIO *)cbk->userptr ();

    cbk->cbkstatus (RSVC_SUCCESS);
    ndata = new rsvcNetData (entry, *cbk);

    channel->sendToPeer (ndata);
  }
  return RSVC_SUCCESS;
}  

rsvcDataStore *
rsvcDataStore::createDataStore (char* name)
{
  // get database home directory
  char tdir[256];

  strncpy (tdir, rsvcServerConfig::dbaseHome (), sizeof (tdir));
  char *lslash = strrchr (tdir, _RSVC_DIR_CHAR);
  if (lslash == 0)
    return 0;
  
  *lslash = '\0';

#ifdef _RSVC_DEBUG
  printf ("rsvcDataStore: database home directory is %s\n", tdir);
#endif
  
  // construct table definition filename and datafile name
  rsvcTableDef tdef (name);
  // read table definition file from disk
  if (tdef.create () == RSVC_ERROR)
    return 0;
  
  char tabledefname[128];
  char datafname[128];

  sprintf (tabledefname, "%s%s", name, rsvcServerConfig::tableNameExt ());
  sprintf (datafname, "%s.%s", name, tdef.keyName ());

#ifdef _RSVC_DEBUG
  printf ("rsvcDataStore: table definition filename %s\n", tabledefname);
  printf ("rsvcDataStore: data file name is %s\n", datafname);
#endif

#ifdef _WIN32
  HANDLE sh;
  WIN32_FIND_DATA fdata;
  char   wfname[256];
  int    len;
  int    count = 0;

  len = strlen (tdir) + 1;
  if (len > sizeof (wfname) - 1) {
#ifdef _RSVC_DEBUG
    printf ("rsvcDataStore: directory name is too long: %d\n", len);
#endif
    return 0;
  }
  strcpy (wfname, tdir);
  strcat (wfname, "*");

  // find file name one by one
  sh = FindFirstFile (wfname, &fdata);
  if (sh == INVALID_HANDLE_VALUE) {
    fprintf (stderr, "rsvcDataStore: cannot do FindFirstFile\n");
    return 0;
  }
  if (strcmp (fdata.cFileName, tabledefname) == 0)
    count ++;
  else if (strcmp (fdata.cFileName, datafname) == 0)
    count ++;

  while (FindNextFile (sh,  &fdata)) {
    if (strcmp (fdata.cFileName, tabledefname) == 0)
      count ++;
    else if (strcmp (fdata.cFileName, datafname) == 0)
      count ++; 
  }
  FindClose (sh);

#else  /* _WIN32 */

  DIR* dbasedir = opendir (tdir);
  if (dbasedir == 0) {
#ifdef _RSVC_DEBUG
    printf ("rsvcDataStore: cannot open database directory %s\n", tdir);
#endif
    return 0;
  }
  
  struct dirent* direntp = 0;
  int            count = 0;
  while ((direntp = readdir (dbasedir)) != 0) {
    if (strcmp (direntp->d_name, tabledefname) == 0)
      count ++;
    else if (strcmp (direntp->d_name, datafname) == 0)
      count ++;
  }
  closedir (dbasedir);
#endif

  rsvcDataStore* nstore = 0;
  if (count == 1) {
    nstore = new rsvcDataStoreMem (name);
  }
  else
    nstore = 0;  // eventually change to database

  return nstore;
}

