// ------------------------------- //
// -------- Start of File -------- //
// ------------------------------- //
// ----------------------------------------------------------- // 
// C++ Source Code File Name: testprog.cpp
// Compiler Used: MSVC, BCC32, GCC, HPUX aCC, SOLARIS CC
// Produced By: glNET Software
// File Creation Date: 05/17/2000
// Date Last Modified: 06/12/2001
// Copyright (c) 2001 glNET Software
// ----------------------------------------------------------- // 
// ------------- Program Description and Details ------------- // 
// ----------------------------------------------------------- // 
/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
 
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  
USA

This is a test program used demonstrate the use of the database 
engine in a multi-threaded application.
*/
// ----------------------------------------------------------- //   
#include <iostream.h>
#include <string.h>
#include "gxthread.h"
#include "gxmutex.h"
#include "gxcond.h"
#include "gxdbase.h"
#include "gxdstats.h"

// Constants
const int NUM_THREADS = 26;
const int MAX_NUM_TRY = 3;
const int name_length = 16;
const char *name_string = "File Object ";

struct DatabaseObject {
  char name[name_length];
  int id;
};

// Class used to perform multi-threaded reads
class gxdReadThread : public gxThread
{
public:
  gxdReadThread(gxDatabase *gxdfile) { f = gxdfile; curr_offset = (FAU)0; }
  ~gxdReadThread() { }

private: // Base class interface
  void *ThreadEntryRoutine(gxThread_t *thread);

private:
  gxDatabase *f;         // Pointer to the open database file
  gxMutex offset_lock;   // Mutex used to serialize access to curr_offset
  FAU curr_offset;       // Current file position following a file read
};

// Class used to perform multi-threaded writes
class gxdWriteThread : public gxThread
{
public:
  gxdWriteThread(gxDatabase *gxdfile) { f = gxdfile; }
  ~gxdWriteThread() { }

private: // Base class interface
  void *ThreadEntryRoutine(gxThread_t *thread);

private:
  gxMutex write_lock;     // Mutex object used to lock the file
  gxCondition write_cond; // Condition variable used to block other threads
  gxDatabase *f;          // Pointer to the open database file
};

void *gxdReadThread::ThreadEntryRoutine(gxThread_t *thread)
{
  offset_lock.MutexLock(); // Serialize access to curr_offset
  curr_offset = f->FindFirstObject(curr_offset);
  FAU block_address = curr_offset - f->BlockHeaderSize();
  if(curr_offset) {
    DatabaseObject ob;
    f->LockRecord(gxDBASE_READLOCK, block_address);
    f->Read(&ob, sizeof(DatabaseObject), curr_offset);
    cout << "Reading: \"" << ob.name << "\" at address: " << (long)curr_offset 
	 << "\n" << flush;
    f->UnlockRecord(gxDBASE_READLOCK, block_address);
  }
  offset_lock.MutexUnlock();

  return 0;
}

void *gxdWriteThread::ThreadEntryRoutine(gxThread_t *thread)
// Thread safe write function that will not allow access to
// the critical section until the write operation is complete.
{
  DatabaseObject *ob = (DatabaseObject *)thread->GetThreadParm();

  write_lock.MutexLock();

  // Tell other threads to wait until this write is complete
  int num_try = 0;
  while(f->LockFile() != 0) {
    // Block this thread from its own execution if a another thread
    // is writing to the file
    if(++num_try < MAX_NUM_TRY) {
      write_cond.ConditionWait(&write_lock);
    }
    else {
      cout << "Could not write object to the file.\n" << flush;
      return 0;
    }
  }

  // ********** Enter Critical Section ******************* //
  f->Write(ob, sizeof(DatabaseObject), f->Alloc(sizeof(DatabaseObject)));
  // ********** Leave Critical Section ******************* //

  f->UnlockFile(); // Tell other threads that this write is complete
 
  // Wake up the next thread waiting on this condition
  write_cond.ConditionSignal();
  write_lock.MutexUnlock();

  return 0;
}

int main()
{
  gxDatabase *f = new gxDatabase; 
  const char *fname = "testfile.gxd";
  f->Create(fname, FAU(0), 'C'); // Persistent lock revision
  if(CheckError(f) != 0) return 1;

  // Initialize the multi-threaded database objects
  gxdReadThread *read_thread = new gxdReadThread(f);
  gxdWriteThread *write_thread = new gxdWriteThread(f);

  // Arrays used to hold the read and write threads
  gxThread_t *wthreads[NUM_THREADS];
  gxThread_t *rthreads[NUM_THREADS];

  int i, j;
  cout << "Writing " << NUM_THREADS << " objects to the " << fname << " file"
       << endl;
  DatabaseObject *ob_ptr[NUM_THREADS];

  for(i = 0; i < NUM_THREADS; i++) {
    DatabaseObject *ob = new DatabaseObject; // Persistent object
    ob->id = 65+i;
    for(j = 0; j < name_length; j++) ob->name[j] = 0;
    memmove(ob->name, name_string, strlen(name_string));
    ob->name[strlen(name_string)] = char(ob->id);
    wthreads[i] = write_thread->CreateThread((void *)ob);
    ob_ptr[i] = ob;
  }

  for(i = 0; i < NUM_THREADS; i++) write_thread->JoinThread(wthreads[i]);
  cout << "Write complete" << endl;
  
  cout << "Verifing each object" << endl; 
  cout << "Press Enter to continue..." << endl;
  cin.get();
  for(i = 0; i < NUM_THREADS; i++) {
    rthreads[i] = read_thread->CreateThread();
    read_thread->sSleep(1); // Allow each thread time to print its message
  }

  // Wait for the read threads to complete
  for(i = 0; i < NUM_THREADS; i++) read_thread->JoinThread(rthreads[i]);

  // Cleanup and release all the thread resources
  for(i = 0; i < NUM_THREADS; i++) write_thread->DestroyThread(wthreads[i]);
  for(i = 0; i < NUM_THREADS; i++) read_thread->DestroyThread(rthreads[i]);

  delete write_thread; // Release the write object's file pointer
  delete read_thread;  // Release the read object's file pointer

  f->Close(); // Close the file 
  if(CheckError(f) != 0) return 1;
  delete f;

  // Free the memory allocated for each database object pointer
  for(i = 0; i < NUM_THREADS; i++) delete ob_ptr[i];

  return 0;
}
// ----------------------------------------------------------- //
// ------------------------------- //
// --------- End of File --------- //
// ------------------------------- //