/*

   HL7Daemon - SQL Manager

   Loads and manages SQL scripts

*/

#include <windows.h>
#include <iostream>

#include "argvs.h"
#include "scanner.h"
#include "sqlmanager.h"
#include "stringutils.h"
#include "utils.h"

#pragma hdrstop
#include "util.h"

#pragma warn -8060

using namespace std;

//=============================================================================
//
// MCPP Library
//
// This provides a pre-processor for the SQL scripts.
//

// MCPP symbols - must match the mcpp source!
typedef enum {
    MCPP_OUT,                        /* ~= fp_out    */
    MCPP_ERR,                        /* ~= fp_err    */
    MCPP_DBG,                        /* ~= fp_debug  */
    MCPP_NUM_OUTDEST,
    dummy = 0xffffffff               // jhaley - make sure it's 32-bit.
} OUTDEST;

typedef void *(__cdecl *rwopen_t)  (const char *, const char *);
typedef void  (__cdecl *rwcclose_t)(void *mem);

typedef int   (__cdecl *PMCPP_LIB_MAIN)(int, char **);
typedef void  (__cdecl *PMCPP_RESET_DEF_OUT_FUNC)();
typedef void  (__cdecl *PMCPP_SET_OUT_FUNC)(int (*)(int, OUTDEST),
                                            int (*)(const char *, OUTDEST),
                                            int (*)(OUTDEST od, const char *, ...));
typedef void  (__cdecl *PMCPP_USE_MEM_BUFFERS)(int);
typedef char *(__cdecl *PMCPP_GET_MEM_BUFFER)(OUTDEST);
typedef void  (__cdecl *PMCPP_SETOPENCALLBACK)(rwopen_t);
typedef void  (__cdecl *PMCPP_SETCLOSECALLBACK)(rwcclose_t);
typedef void *(__cdecl *PMCPP_OPENMEMORY)(void *, size_t);

static PMCPP_LIB_MAIN           mcpp_lib_main;
static PMCPP_RESET_DEF_OUT_FUNC mcpp_reset_def_out_func;
static PMCPP_SET_OUT_FUNC       mcpp_set_out_func;
static PMCPP_USE_MEM_BUFFERS    mcpp_use_mem_buffers;
static PMCPP_GET_MEM_BUFFER     mcpp_get_mem_buffer;
static PMCPP_SETOPENCALLBACK    mcpp_setopencallback;
static PMCPP_SETCLOSECALLBACK   mcpp_setclosecallback;
static PMCPP_OPENMEMORY         mcpp_openmemory;

#define LOADFUNC(type, func) \
   if(!(func = reinterpret_cast<type>(GetProcAddress(mcpp_lib, #func)))) \
      return false

//
// open_callback
//
// This is the file open callback for the MCPP library.
//
static void * __cdecl open_callback(const char *fn, const char *mode)
{
   string script = SQLManager::GetScript(fn);

   // duplicate the contents of the script
   if(script != "")
   {
      char *dupScript = strdup(script.c_str());
      if(dupScript)
         return mcpp_openmemory(dupScript, script.length());
   }

   return NULL; // failed
}

//
// close_callback
//
// This is the file close callback for the MCPP library.
//
static void __cdecl close_callback(void *data)
{
   if(data)
      free(data);
}

//
// SQLManager::LoadMcpp
//
// Protected function, load the mcpp library if it's not already loaded.
//
bool SQLManager::LoadMcpp()
{
   static HMODULE mcpp_lib;

   if(!mcpp_lib)
   {
      if(!(mcpp_lib = LoadLibrary("mcpp.dll")))
         return false;

      LOADFUNC(PMCPP_LIB_MAIN,           mcpp_lib_main);
      LOADFUNC(PMCPP_RESET_DEF_OUT_FUNC, mcpp_reset_def_out_func);
      LOADFUNC(PMCPP_SET_OUT_FUNC,       mcpp_set_out_func);
      LOADFUNC(PMCPP_USE_MEM_BUFFERS,    mcpp_use_mem_buffers);
      LOADFUNC(PMCPP_GET_MEM_BUFFER,     mcpp_get_mem_buffer);
      LOADFUNC(PMCPP_SETOPENCALLBACK,    mcpp_setopencallback);
      LOADFUNC(PMCPP_SETCLOSECALLBACK,   mcpp_setclosecallback);
      LOADFUNC(PMCPP_OPENMEMORY,         mcpp_openmemory);

      // Setup open and close callback functions
      mcpp_setopencallback(open_callback);
      mcpp_setclosecallback(close_callback);

      // Output to memory buffer
      mcpp_use_mem_buffers(MCPP_OUT);
   }

   return true;
}

//
// SQLManager::scriptCallback
//
// Callback method for ScanForFiles, will load every .sql file in the directory
// and map them by file name.
//
void SQLManager::scriptCallback(fileinfo &fi)
{
   const char *text;
   const char *filepath = fi.GetFullyQualifiedName().c_str();
   const char *filename = fi.filename.c_str();

   AnsiString ext = ExtractFileExt(fi.filename);

   if(ext.LowerCase() != ".sql")
      return;

   if((text = LoadTextFile(filepath)))
      scripts[filename] = text;

   delete [] text;
}

//
// SQLManager::loadScripts
//
// Load all scripts in the SQL query hive directory.
//
void SQLManager::loadScripts()
{
   const char *dir;
   queue<AnsiString> paths;

   if(!(dir = globalArgMgr->getParamTo("-sql")))
      dir = ".\\sql";

   paths.push(AnsiString(dir));

   ScanForFiles(paths, this->scriptCallback);
}

//
// SQLManager::haveScript
//
// Returns true if the script has been loaded, and false otherwise.
//
bool SQLManager::haveScript(const string &scriptName) const
{
   return (scripts.find(scriptName) != scripts.end());
}

//
// SQLManager::getScript
//
// Return the contents of the script by name, if it exists. An empty string
// is returned otherwise.
//
const string &SQLManager::getScript(const string &scriptName) const
{
   static string foo;
   QueryMap::const_iterator itr = scripts.find(scriptName);

   if(itr != scripts.end())
      return itr->second;
   else
      return foo;
}

//
// VariableMapToArgvs
//
static char **VariableMapToArgvs(const SQLManager::VariableMap &variables,
                                 const string &infile,
                                 int &argc)
{
   SQLManager::VariableMap::const_iterator vi;
   size_t numVars = variables.size() + 3;
   size_t curVar  = 3;
   char **retarray = new char * [numVars];

   retarray[0] = cpp_strdup("mcpp.exe");
   retarray[1] = cpp_strdup("-P");
   retarray[2] = cpp_strdup(infile.c_str());

   for(vi = variables.begin(); vi != variables.end(); ++vi)
   {
      string argument = "-D " + vi->first + "=" + vi->second;

      retarray[curVar++] = cpp_strdup(argument.c_str());
   }

   argc = static_cast<int>(numVars);

   return retarray;
}

//
// FreeArgvs
//
static void FreeArgvs(int argc, char **argv)
{
   for(int i = 0; i < argc; i++)
      delete [] argv[i];

   delete [] argv;
}

//
// SQLManager::prepareScript
//
// Returns a copy of the script with all of the variables replaced in the
// script's code with the values provided in the VariableMap.
//
void SQLManager::prepareScript(const string &scriptName,
                               const VariableMap &variables,
                               string &output) const
{
   QueryMap::const_iterator qi = scripts.find(scriptName);

   if(qi != scripts.end())
   {
      int    argc;
      char **argv;
      char  *result = NULL;

      argv = VariableMapToArgvs(variables, qi->first, argc);

      mcpp_lib_main(argc, argv);

      if((result = mcpp_get_mem_buffer(MCPP_OUT)))
         output = result;

      FreeArgvs(argc, argv);
      // FIXME: TEMPORARY DEBUG
      cout << output.c_str();
   }
}

//
// SQLManager::GetSQLManager
//
// Returns the global singleton instance of SQLManager.
//
SQLManager &SQLManager::GetSQLManager()
{
   static SQLManager *theSQLMgr = NULL;

   if(!theSQLMgr)
      theSQLMgr = new SQLManager();

   return *theSQLMgr;
}

//
// SQLManager::LoadScripts
//
// Static convenience method to invoke loadScripts on the global singleton.
//
void SQLManager::LoadScripts()
{
   if(!LoadMcpp())
      DEBUGOUT(dbg_error, "WARNING: MCPP library failed to load!");

   GetSQLManager().loadScripts();

   // TEMPORARY
   string poop;
   PrepareScript("person_match.sql", VariableMap(), poop);
}

//
// SQLManager::HaveScript
//
// Static convenience method to invoke haveScript on the global singleton.
//
bool SQLManager::HaveScript(const string &scriptName)
{
   return GetSQLManager().haveScript(scriptName);
}

//
// SQLManager::GetScript
//
// Static convenience method to invoke getScript on the global singleton.
//
const string &SQLManager::GetScript(const string &scriptName)
{
   return GetSQLManager().getScript(scriptName);
}

//
// SQLManager::PrepareScript
//
// Static convenience method to invoke prepareScript on the global singleton.
//
void SQLManager::PrepareScript(const string &scriptName,
                               const VariableMap &variables,
                               string &output)
{
   GetSQLManager().prepareScript(scriptName, variables, output);
}

// EOF