///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Copyright (C) 2006 by Intel Coproration and Carnegie Mellon University    //
// Contacts: casey.j.helfrich @ intel.com                                    //
//           bdr @ cs.cmu.edu                                                //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#include "Debugging.hxx"

#include <iostream>
#include <unistd.h>
#include <sqlite3.h>
#include <pthread.h>

#include "CatomWorld.hxx"

using namespace std;

#ifndef DISABLE_DEBUGGING
static pthread_mutex_t debuggingMutex = PTHREAD_MUTEX_INITIALIZER;

static sqlite3 *db=NULL;
static sqlite3_stmt *newSignpost_stmt=NULL;
static sqlite3_stmt *newEvent_stmt=NULL;
static sqlite3_stmt *newValue_stmt=NULL;

static uint64_t currentSignpostNum=0;
static uint64_t currentEventNum=0;
static uint64_t currentValueNum=0;

static list<pair<void*,dprdebug_memItemType> > dprdebug_memoryLocations;
#endif

#define OPS_PER_TRANSACTION 100000
static inline void maybeNewTxn() {
#ifndef DISABLE_DEBUGGING
  static unsigned opsSoFar = 0;
  if(opsSoFar > OPS_PER_TRANSACTION) {
    // End old transaction, begin new transaction
    char* errMsg = NULL;
    char* command = "COMMIT; BEGIN;";
    int returnCode = sqlite3_exec(db, command, NULL, 0, &errMsg);
    if(SQLITE_OK != returnCode) {
      cerr << "Can't start new transaction! (" << (errMsg ? errMsg : "no error message") << ")\n"
	   << "Query: " << command << endl;
      exit(-1);
    }
    opsSoFar = 0;
  }
  opsSoFar ++;
#endif
}

bool dprdebug_open(char* dbPath) {
#ifndef DISABLE_DEBUGGING
  // Remove any old file previously there
  unlink(dbPath);

  // Open the database
  int returnCode = sqlite3_open(dbPath, &db);
  if(returnCode) {
    cerr << "Can't open SQLite database for debugging (" << returnCode << ", " << sqlite3_errmsg(db) << ")" << endl;
    exit(-1);
  }

  char* errMsg = NULL;
  char* command = NULL;

  // DB SETUP
  command = "PRAGMA synchronous = OFF;";
  returnCode = sqlite3_exec(db, command, NULL, 0, &errMsg);
  if(SQLITE_OK != returnCode) {
    cerr << "Can't disable synchronous behavior! (" << (errMsg ? errMsg : "no error message") << ")\n"
	 << "Query: " << command << endl;
    exit(-1);
  }

  // TABLES
  command = "CREATE TABLE 'signposts' (name, file, line, function);";
  returnCode = sqlite3_exec(db, command, NULL, 0, &errMsg);
  if(SQLITE_OK != returnCode) {
    cerr << "Can't create signposts table! (" << (errMsg ? errMsg : "no error message") << ")\n"
	 << "Query: " << command << endl;
    exit(-1);
  }
  command = "CREATE TABLE 'events' (name, value, tick, signpost, operation);";
  returnCode = sqlite3_exec(db, command, NULL, 0, &errMsg);
  if(SQLITE_OK != returnCode) {
    cerr << "Can't create events table! (" << (errMsg ? errMsg : "no error message") << ")\n"
	 << "Query: " << command << endl;
    exit(-1);
  }
  command = "CREATE TABLE 'values' (value, object, type, derived1, derived2, containedBy);";
  returnCode = sqlite3_exec(db, command, NULL, 0, &errMsg);
  if(SQLITE_OK != returnCode) {
    cerr << "Can't create values table! (" << (errMsg ? errMsg : "no error message") << ")\n"
	 << "Query: " << command << endl;
    exit(-1);
  }
 
  // COMPILED COMMANDS
  command = "INSERT INTO 'signposts' (name, file, line, function) VALUES (?, ?, ?, ?);";
  returnCode = sqlite3_prepare(db, command, -1, &newSignpost_stmt, NULL);
  if((SQLITE_OK != returnCode) || !newSignpost_stmt) {
    cerr << "Can't compile new signpost command! (" << sqlite3_errmsg(db) << ")\n"
	 << "Command: " << command << endl;
    exit(-1);
  }
  command = "INSERT INTO 'events' (name, value, tick, signpost, operation) VALUES (?, ?, ?, ?, ?);";
  returnCode = sqlite3_prepare(db, command, -1, &newEvent_stmt, NULL);
  if((SQLITE_OK != returnCode) || !newEvent_stmt) {
    cerr << "Can't compile new event command! (" << sqlite3_errmsg(db) << ")\n"
	 << "Command: " << command << endl;
    exit(-1);
  }
  command = "INSERT INTO 'values' (value, object, type, derived1, derived2, containedBy) VALUES (?, ?, ?, ?, ?, ?);";
  returnCode = sqlite3_prepare(db, command, -1, &newValue_stmt, NULL);
  if((SQLITE_OK != returnCode) || !newValue_stmt) {
    cerr << "Can't compile new value command! (" << sqlite3_errmsg(db) << ")\n"
	 << "Command: " << command << endl;
    exit(-1);
  }

  // START A TRANSACTION
  command = "BEGIN;";
  returnCode = sqlite3_exec(db, command, NULL, 0, &errMsg);
  if(SQLITE_OK != returnCode) {
    cerr << "Can't start first transaction! (" << (errMsg ? errMsg : "no error message") << ")\n"
	 << "Query: " << command << endl;
    exit(-1);
  }
#endif
  return true;
}

bool dprdebug_close() {
#ifndef DISABLE_DEBUGGING
  if(newSignpost_stmt)
    sqlite3_finalize(newSignpost_stmt);
  if(newEvent_stmt)
    sqlite3_finalize(newEvent_stmt);
  if(newValue_stmt)
    sqlite3_finalize(newValue_stmt);

  // Commit final transaction
  char* errMsg = NULL;
  char* command = "COMMIT;";
  int returnCode = sqlite3_exec(db, command, NULL, 0, &errMsg);
  if(SQLITE_OK != returnCode) {
    cerr << "Can't commit final transaction! (" << (errMsg ? errMsg : "no error message") << ")\n"
	 << "Query: " << command << endl;
    exit(-1);
  }

  sqlite3_close(db);
#endif
  return true;
}

// Signpost support
void dprdebug_signpost(string name, string file, uint32_t line, string function) {
#ifndef DISABLE_DEBUGGING
  pthread_mutex_lock(&debuggingMutex);

  maybeNewTxn();

  int returnCode = sqlite3_reset(newSignpost_stmt);
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_text(newSignpost_stmt, 1, name.c_str(),     -1, SQLITE_TRANSIENT);
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_text(newSignpost_stmt, 2, file.c_str(),     -1, SQLITE_TRANSIENT);
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_int (newSignpost_stmt, 3, line                                  );
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_text(newSignpost_stmt, 4, function.c_str(), -1, SQLITE_TRANSIENT);
  if(SQLITE_OK != returnCode) {
    cerr << "Can't prepare new signpost command: " << sqlite3_errmsg(db) << endl;
    exit(-1);
  }

  do {
    returnCode = sqlite3_step(newSignpost_stmt);
  } while(SQLITE_BUSY == returnCode);
  if(SQLITE_DONE != returnCode) {
    cerr << "Can't add signpost to debugging db!:" << sqlite3_errmsg(db) << endl;
    exit(-1);
  }

  currentSignpostNum = sqlite3_last_insert_rowid(db);

  pthread_mutex_unlock(&debuggingMutex);
#endif
}

// Events support
void dprdebug_event(string name, uint64_t value, uint8_t operation) {
#ifndef DISABLE_DEBUGGING
  pthread_mutex_lock(&debuggingMutex);

  maybeNewTxn();

  int returnCode = sqlite3_reset(newEvent_stmt);
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_text (newEvent_stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_int64(newEvent_stmt, 2, value);
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_int  (newEvent_stmt, 3, worldPtr->current_time);
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_int64(newEvent_stmt, 4, currentSignpostNum);
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_int  (newEvent_stmt, 5, operation);
  if(SQLITE_OK != returnCode) {
    cerr << "Can't prepare new signpost command: " << sqlite3_errmsg(db) << endl;
    exit(-1);
  }

  do {
    returnCode = sqlite3_step(newEvent_stmt);
  } while(SQLITE_BUSY == returnCode);
  if(SQLITE_DONE != returnCode) {
    cerr << "Can't add event to debugging db!:" << sqlite3_errmsg(db) << endl;
    exit(-1);
  }

  pthread_mutex_unlock(&debuggingMutex);
#endif
}

uint64_t dprdebug_value(uint64_t value, void* object, string type, uint64_t derived1, uint64_t derived2) {
#ifndef DISABLE_DEBUGGING
  pthread_mutex_lock(&debuggingMutex);

  maybeNewTxn();

  int returnCode = sqlite3_reset(newValue_stmt);
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_int64(newValue_stmt, 1, value);
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_int64(newValue_stmt, 2, (uint64_t)object);
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_text (newValue_stmt, 3, type.c_str(), -1, SQLITE_TRANSIENT);
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_int64(newValue_stmt, 4, derived1);
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_int64(newValue_stmt, 5, derived2);
  if(SQLITE_OK == returnCode)
    returnCode = sqlite3_bind_int64(newValue_stmt, 6, (uint64_t)dprdebug_find_owner_of(object).first);
  if(SQLITE_OK != returnCode) {
    cerr << "Can't prepare new value command: " << sqlite3_errmsg(db) << endl;
    exit(-1);
  }

  do {
    returnCode = sqlite3_step(newValue_stmt);
  } while(SQLITE_BUSY == returnCode);
  if(SQLITE_DONE != returnCode) {
    cerr << "Can't add value to debugging db!:" << sqlite3_errmsg(db) << endl;
    exit(-1);
  }

  uint64_t valueNum = sqlite3_last_insert_rowid(db);

  pthread_mutex_unlock(&debuggingMutex);

  return valueNum;
#else
  return 0;
#endif
}

void dprdebug_insert_memory_item(void* address, dprdebug_memItemType type) {
#ifndef DISABLE_DEBUGGING
  list<pair<void*,dprdebug_memItemType> >::iterator iter = dprdebug_memoryLocations.begin();
  while((iter != dprdebug_memoryLocations.end()) 
	&& ((*iter).first < address)) {
    iter++;
  }

  dprdebug_memoryLocations.insert(iter,pair<void*,dprdebug_memItemType>(address,type));
#endif
}

void dprdebug_remove_memory_item(void* address) {
#ifndef DISABLE_DEBUGGING
  list<pair<void*,dprdebug_memItemType> >::iterator iter = dprdebug_memoryLocations.begin();
  while((iter != dprdebug_memoryLocations.end()) 
	&& ((*iter).first != address)) {
    iter++;
  }

  if(iter != dprdebug_memoryLocations.end())
    dprdebug_memoryLocations.erase(iter);
#endif
}

pair<void*,void*> dprdebug_find_owner_of(void* address) {
#ifndef DISABLE_DEBUGGING
  void* classInfo = NULL;

  list<pair<void*,dprdebug_memItemType> >::iterator iter = dprdebug_memoryLocations.begin();
  while((iter != dprdebug_memoryLocations.end()) 
	&& ((*iter).first < address)) {
    iter++;
  }

  while (iter != dprdebug_memoryLocations.begin() ) {
    iter--;

    switch((*iter).second) {
    case dprdebug_classinfo_type:
      if(!classInfo)
	classInfo = (*iter).first;
      break;
    case dprdebug_class_type:
      return pair<void*,void*>((*iter).first,classInfo);
    default:
      assert(false);
    }
  }

  if(((*iter).first < address) && ((*iter).second == dprdebug_classinfo_type))
    return pair<void*,void*>((*iter).first,classInfo);
  else
    return pair<void*,void*>(NULL, classInfo);
#endif
  return pair<void*,void*>(NULL, NULL);
}
