///////////////////////////////////////////////////////////////////////////////
//                                                         
// cgwrap.cc
// ---------
// Cego C wrapper API
// 
// Design and Implementation by Bjoern Lemke               
//                                                         
// (C)opyright 2009 Bjoern Lemke                        
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or (at your option)
// any later version.
// 
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; see the file COPYING.  If not, write to
// the Free Software Foundation, 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.
//
// Description: This is a wrapper module which implements a plain C client interface to the
//              cego database system
//
// Status: QG-2.6
//
///////////////////////////////////////////////////////////////////////////////


#include "cgwrap.h"

#include <lfc/SetT.h>
#include <lfc/Tokenizer.h>
#include <lfc/Net.h>
#include <lfc/NetHandler.h>
#include <lfc/Datetime.h>

#include "CegoDbHandler.h"
#include "CegoDefs.h"
#include "CegoModule.h"

#include <string.h>
#include <stdlib.h>

#define NETMSG_BUFLEN 8192
#define MAXERRMSGLEN 100

#define MODNAME "cgwrap"

char cgerrmsg[MAXERRMSGLEN];
unsigned long modId;

static int setCGValue(CGVal* cgval, const CegoFieldValue& fv);
static void updateModLog(CegoModule* pModule);

static SetT<Chain> modLogSet;

extern "C" {

    void cego_modlog(char* module, int level)
    {
	Chain modEntry = *module;

	if ( level == CG_LOG_ERROR )
	    modEntry += Chain(":ERROR");
	else if ( level == CG_LOG_NOTICE )
	    modEntry += Chain(":NOTICE");
	else if ( level == CG_LOG_DEBUG )
	    modEntry += Chain(":DEBUG");

	modLogSet.Insert(modEntry);
    }

    CGDB* cego_connect(char *servername, 
		       int portno,
		       char *prot,
		       char *tsname, 
		       char *user, 
		       char *pass, 
		       char* logfile)
    {

	CGDB* cgdb = new CGDB;
	
	if ( logfile == 0 )
	    cgdb->pModule = new CegoModule();
	else
	    cgdb->pModule = new CegoModule(logfile);

	updateModLog(cgdb->pModule);

	Net n ( NETMSG_BUFLEN, NETMNG_SIZEBUFLEN );
	
	Chain serverName( servername );

	CegoDbHandler::ProtocolType protType;

	if ( strcmp(prot, "serial") == 0 )
	{
	    protType = CegoDbHandler::SERIAL;
	}
	else if ( strcmp(prot, "xml") == 0)
	{
	    protType = CegoDbHandler::XML;
	}
	else
	{
	    Chain msg = Chain("Invalid protocol ") + Chain(prot);
	    strcpy(cgerrmsg, (char*)msg); 
	    return 0;
	}

	try {
	    
	    cgdb->pNet = n.connect(serverName, portno);
	    
	}
	catch ( Exception e )
	{
	    Chain msg = Chain("Cannot connect to server ") + serverName + Chain(" on port ") + Chain(portno);
	    strcpy(cgerrmsg, (char*)msg); 
	    return 0;
	}
	
	
	cgdb->pDB = new CegoDbHandler(cgdb->pNet, protType, cgdb->pModule);
	
	Chain tableSet(tsname);
	Chain dbUser(user);
	Chain dbPWD(pass);
	
	CegoDbHandler::ResultType res = cgdb->pDB->requestSession(tableSet, dbUser, dbPWD);
	if ( res != CegoDbHandler::DB_OK )
	{
	    Chain msg = cgdb->pDB->getMsg();
	    strcpy(cgerrmsg, (char*)msg); 
	    return 0;
	}
	
	return cgdb;
    }

    void cego_disconnect (CGDB* cgdb)
    {
	cgdb->pModule->log(modId, Logger::DEBUG, Chain("Disconnecting ..."));

	cgdb->pDB->closeSession();
	delete cgdb->pDB;
	delete cgdb->pNet;
	delete cgdb->pModule;
    }

    CGFetch* cego_allocate_fetch()
    {
	return new CGFetch();
    }

    void cego_free_fetch(CGFetch *cgfetch)
    {
	if ( cgfetch->pSchema )
	    delete cgfetch->pSchema;
	delete cgfetch;
    }

    int cego_num_col(CGFetch* cgfetch)
    {
	return cgfetch->pSchema->Size();
    }

    int cego_getpos(CGFetch* cgfetch, char* attr)
    {
	int pos = 0;
	CegoField *pF = cgfetch->pSchema->First();
	while ( pF )
	{
	    if ( pF->getAttrName() == Chain(attr) )
		return pos;
	    pF = cgfetch->pSchema->Next();
	    pos++;
	}
	return -1;
    }

    char* cego_getcol(CGFetch* cgfetch, int pos)
    {
	if ( cgfetch->pSchema->Size() < pos )
	    return NULL;

	return (*(cgfetch->pSchema))[pos].getAttrName();
    }

    CGStmt* cego_prepare (char *stmt)
    {

	CGStmt *pStmt = new CGStmt;
	pStmt->pParamList = 0;

	pStmt->numParams=0;
	Chain c = Chain(stmt);
	if ( c.cutTrailing(" ").subChain(1,1) == Chain("?") )
	    pStmt->numParams++;
	
	Tokenizer tok(Chain(stmt), "?");
	pStmt->pStmtChunks = new ListT<Chain>;

	Chain token;
	
	tok.nextToken(token);
	pStmt->pStmtChunks->Insert(token);
	
	while ( tok.nextToken(token) == true) 	
	{
	    pStmt->pStmtChunks->Insert(token);
	    pStmt->numParams++;
	}
	
	if ( pStmt->numParams > 0 )
	    pStmt->pParamList = new ListT<CegoBindValue>;

	pStmt->isOut = false;
	return pStmt;
    }
    

    int cego_bind_in (CGStmt * cgstmt, CGVal* cgval, int pos)
    {
	CegoBindValue bval;
	bval.cgval = cgval;
	bval.pos = pos;
	bval.type = CegoBindValue::BINDIN;
	CegoBindValue *pBV = cgstmt->pParamList->Find(CegoBindValue(pos));
	if ( pBV )
	{
	    if ( pBV->cgval )
		free ( pBV->cgval );
	    cgstmt->pParamList->Remove(CegoBindValue(pos));
	}
	
	cgstmt->pParamList->Insert(bval);
	return 0;
    }
    
    int cego_bind_out (CGStmt * cgstmt, CGVal* cgval, int pos)
    {
	CegoBindValue bval;
	bval.cgval = cgval;
	bval.pos = pos;
	bval.type = CegoBindValue::BINDOUT;
	cgstmt->pParamList->Insert(bval);
	cgstmt->isOut = true;
	return 0;
    }
    
    int cego_bind_inout (CGStmt * cgstmt, CGVal* cgval, int pos)
    {
	CegoBindValue bval;
	bval.cgval = cgval;
	bval.pos = pos;
	bval.type = CegoBindValue::BINDINOUT;
	cgstmt->pParamList->Insert(bval);
	cgstmt->isOut = true;
	return 0;
    }
        
    int cego_execute (CGDB *cgdb, CGStmt* cgstmt, CGFetch* cgfetch)
    {

	cgdb->pModule->log(modId, Logger::DEBUG, Chain("Executing stmt"));

	Chain stmtString;
	if ( cgstmt->numParams == 0 )
	{
	    stmtString = *(cgstmt->pStmtChunks->First());
	}
	else
	{
	    
	    int pos=1;
	    
	    Chain* pChunk = cgstmt->pStmtChunks->First();

	    if ( cgstmt->numParams == cgstmt->pStmtChunks->Size() )
	    {
		CegoBindValue* pParam = cgstmt->pParamList->Find( CegoBindValue(pos) );
		stmtString = Chain(":p") + Chain(pos) + *pChunk;   
		pos++;
	    }
	    else
	    {
		stmtString = *pChunk;
	    }
	

	    pChunk = cgstmt->pStmtChunks->Next();
	    while ( pChunk )
	    {

		CegoBindValue* pParam = cgstmt->pParamList->Find( CegoBindValue(pos) );
		
		if ( pParam )
		{
		    if ( pParam->type == CegoBindValue::BINDIN || pParam->type == CegoBindValue::BINDINOUT )
		    {
			Chain val;

			switch ( pParam->cgval->type )
			{
			case CG_VARCHAR:
			{
			    val = Chain("'") + Chain((char*)pParam->cgval->val) + Chain("'");
			    break;
			}
			case CG_INT:
			{
			    val = Chain(*(int*)pParam->cgval->val);
			    break;
			}   
			case CG_LONG:
			{
			    val = Chain(*(long*)pParam->cgval->val);
			    break;
			}
			case CG_BOOL:
			{
			    if ( *(char*)pParam->cgval->val == 0 )			      
				val = Chain("false");
			    else
				val = Chain("true");
			    break;
			}
			case CG_DATETIME:
			{
			    int i;
			    memcpy(&i, pParam->cgval->val, sizeof(int));
			    if ( i == 0 )
			    {
				val = Chain("sysdate");
			    }
			    else
			    {
				Datetime dt ( i );
				val = Chain("date('") + DTFORMAT + Chain("','") + dt.asChain("%d.%m.%Y %H:%M:%S %Z") + Chain("')");
			    }
			    break;
			}
			case CG_BIGINT:
			{
			    val = Chain("(bigint)") + Chain((char*)pParam->cgval->val);
			    break;
			}
			case CG_FLOAT:
			{
			    val = Chain(*(float*)pParam->cgval->val);
			    break;
			}
			case CG_DOUBLE:
			{
			    val = Chain("(double)") + Chain(*(double*)pParam->cgval->val);
			    break;
			}
			case CG_DECIMAL:
			{
			    val = Chain("(decimal)") + Chain((char*)pParam->cgval->val);
			    break;
			}
			case CG_FIXED:
			{
			    val = Chain("(fixed)") + Chain((char*)pParam->cgval->val);
			    break;
			}
			case CG_SMALLINT:
			{
			    short s = *(short*)pParam->cgval->val;
			    val = Chain((int)s);
			    break;
			}
			case CG_TINYINT:
			{
			    char c = *(char*)pParam->cgval->val;
			    val = Chain((int)c);
			    break;
			}
			case CG_BLOB:
			{
			    int fileId = *(int*)pParam->cgval->val;
			    int pageId = *(int*)((long)pParam->cgval->val + sizeof(int));			    
			    val = Chain("[") + Chain(fileId) + Chain(",") + Chain(pageId) + Chain("]");
			    break;
			}
			case CG_NULL:
			{
			    val = Chain("null");
			    break;
			}
			}

			stmtString += val + *pChunk; 
		    }
		    else
		    {
			stmtString += Chain(":p") + Chain(pos) + *pChunk;
		    }
		    pChunk = cgstmt->pStmtChunks->Next();
		}
		else
		{
		    Chain msg = Chain("Missing parameter at position ") + Chain(pos);
		    strcpy(cgerrmsg, (char*)msg);
		    return -1;		   
		}
		pos++;
	    }
	}
	
	if (  cego_query(cgdb, (char*)stmtString, cgfetch) != 0 )
	{
	    cgdb->pModule->log(modId, Logger::LOGERR, Chain("Query failed"));	
	    return -1;	    
	}
	cgdb->pModule->log(modId, Logger::DEBUG, Chain("Query ok"));	
	
	if ( cgstmt->isOut )
	{
	    ListT<CegoProcVar> outParamList;
	    CegoFieldValue retValue;
	    
	    cgdb->pDB->getProcResult(outParamList, retValue);
	    
	    CegoBindValue* pParam = cgstmt->pParamList->First();
	    while ( pParam )
	    {
		
		if ( pParam->type == CegoBindValue::BINDOUT || pParam->type == CegoBindValue::BINDINOUT )
		{		
		    CegoProcVar* pVar = outParamList.Find(CegoProcVar(Chain("p") + Chain(pParam->pos)));
		    if ( pVar )
		    {
			setCGValue ( pParam->cgval, pVar->getValue()); 
		    }
		    else if ( pParam->pos == 1 )
		    {
			setCGValue ( pParam->cgval, retValue); 
		    }		    
		}
		pParam = cgstmt->pParamList->Next();
	    }
	}
	cgdb->pModule->log(modId, Logger::DEBUG, Chain("Query returns 0"));	
	return 0;
    }

    void cego_free_stmt(CGStmt* cgstmt)
    {
	
	if ( cgstmt->pParamList )
	{
	    CegoBindValue *pBindValue = cgstmt->pParamList->First();
	    while ( pBindValue )
	    {
		if ( pBindValue->cgval )
		    free ( pBindValue->cgval );
		pBindValue = cgstmt->pParamList->Next();
	    }
	    delete cgstmt->pParamList;
	}
	if ( cgstmt->pStmtChunks )
	    delete cgstmt->pStmtChunks;
	delete cgstmt;
    }
    
    int cego_query (CGDB *cgdb, char *stmt, CGFetch* cgfetch)
    {
	
	CegoDbHandler::ResultType res = cgdb->pDB->reqQueryOp(Chain(stmt));
	
	if ( res == CegoDbHandler::DB_ERROR )
	{
	    Chain msg;
	    if ( cgfetch )
		cgfetch->isActive = false;
	    msg = cgdb->pDB->getMsg();
	    strcpy(cgerrmsg, (char*)msg);
	    return -1;
	}
	else if ( res == CegoDbHandler::DB_OK )
	{
	    if ( cgfetch )
		cgfetch->isActive = false;
	    return 0;
	}
	else if ( res == CegoDbHandler::DB_INFO )
	{
	    if ( cgfetch )
		cgfetch->isActive = false;
	    return 0;
	}
	else if ( res == CegoDbHandler::DB_DATA )
	{
	    if ( cgfetch != NULL )
	    {
		cgfetch->pSchema = new ListT<CegoField>;
		cgfetch->cgdb = cgdb;
		cgfetch->isActive = true;
		*(cgfetch->pSchema) = cgdb->pDB->getSchema();

		return 0;
	    }
	    else
	    {
		strcpy(cgerrmsg, "Invalid fetch handle");
		return -1;
	    }
	}
	return -1;
    }

    int cego_fetch (CGFetch *cgfetch, CGVal cgval[], int numCol)
    {

	if ( cgfetch->isActive == false )
	    return 0;
	
	CegoDbHandler::ResultType res;
	
	ListT<CegoFieldValue> fvl;
	if ( ( res = cgfetch->cgdb->pDB->receiveTableData(*(cgfetch->pSchema), fvl) ) == CegoDbHandler::DB_DATA )
	{
	    int pos=0;
	    CegoFieldValue *pFV = fvl.First();
	    while ( pFV )
	    {
		if ( pos >= numCol )
		{
		    strcpy(cgerrmsg, "Column number exceeded");
		    return -1;
		}
		setCGValue(&cgval[pos], *pFV);
		pFV = fvl.Next();
		pos++;
	    }
	    fvl.Empty();
	    return pos;
	}
	else
	{	    
	    cgfetch->isActive = false;
	    return 0;
	}
    }

    int cego_abort (CGFetch *cgfetch)
    {
	if ( cgfetch->isActive == false )
	    return -1;

	cgfetch->cgdb->pDB->abortQuery();
	cgfetch->isActive = false;
	return 0;
    }

    int cego_reset (CGFetch *cgfetch)
    {
	if ( cgfetch->isActive == false )
	    return -1;

	cgfetch->cgdb->pDB->resetQuery();
	return 0;
    }

    int cego_putblob (CGDB *cgdb, CGBlob* cgblob)
    {
	CegoBlob blob;
	blob.allocate(cgblob->len);
	blob.reset();
	blob.putChunk(cgblob->buf, cgblob->len);
	
	if ( cgdb->pDB->putBlob(cgdb->pDB->getTableSet(), blob) != CegoDbHandler::DB_OK)
	    return -1;

	cgblob->fileId = blob.getFileId();
	cgblob->pageId = blob.getPageId();

	return 0;
    }

    int cego_getblob (CGDB *cgdb, CGBlob* cgblob)
    {
	CegoBlob blob(cgblob->fileId, cgblob->pageId);
	if ( cgdb->pDB->getBlob(cgdb->pDB->getTableSet(), blob) != CegoDbHandler::DB_OK)
	    return -1;
	if ( cgblob->len < blob.getSize() )
	{
	    cgblob->buf = (unsigned char*)realloc( cgblob->buf, blob.getSize());
	    cgblob->len = blob.getSize();
	}
	blob.reset();
	blob.nextChunk(blob.getSize());
	memcpy( cgblob->buf, blob.getChunkPtr(), blob.getSize());
	
	return 0;
    }

    int cego_delblob (CGDB *cgdb, CGBlob* cgblob)
    {
	CegoBlob blob(cgblob->fileId, cgblob->pageId);
	if ( cgdb->pDB->delBlob(cgdb->pDB->getTableSet(), blob) != CegoDbHandler::DB_OK)
	    return -1;
	return 0;
    }

    char* cego_error()
    {
	return cgerrmsg;
    }
}

/* private functions */

static int setCGValue(CGVal* cgval, const CegoFieldValue& fv)
{

    if ( cgval->val )
	free ( cgval->val );

    switch ( fv.getType() )
    {
    case VARCHAR_TYPE:
    {
	cgval->type = CG_VARCHAR;
	Chain val = fv.valAsChain();
	cgval->val = new char[val.length()];
	memcpy((char*)cgval->val, (char*)val, val.length());
	cgval->len=val.length();
	return 0;
    }
    case INT_TYPE:
    {
	cgval->type = CG_INT;
	cgval->val = new int;
	memcpy((int*)cgval->val, (int*)fv.getValue(), sizeof(int));
	cgval->len=sizeof(int);
	return 0;
    }   
    case LONG_TYPE:
    {
	cgval->type = CG_LONG;
	cgval->val = new long;
	memcpy((long*)cgval->val, (long*)fv.getValue(), sizeof(long));
	cgval->len=sizeof(long);
	return 0;
    }   
    case BOOL_TYPE:
    {
	cgval->type = CG_BOOL;
	cgval->val = new char;
	memcpy((char*)cgval->val, (char*)fv.getValue(), sizeof(char));
	cgval->len=sizeof(char);
	return 0;
    }   
    case DATETIME_TYPE:
    {
	cgval->type = CG_DATETIME;
	cgval->val = new int;
	memcpy((int*)cgval->val, (int*)fv.getValue(), sizeof(int));
	cgval->len=sizeof(int);
	return 0;
    }   
    case FLOAT_TYPE:
    {
	cgval->type = CG_FLOAT;
	cgval->val = new float;
	memcpy((float*)cgval->val, (float*)fv.getValue(), sizeof(float));
	cgval->len=sizeof(float);
	return 0;
    }   
    case DOUBLE_TYPE:
    {
	cgval->type = CG_DOUBLE;
	cgval->val = new double;
	memcpy((double*)cgval->val, (double*)fv.getValue(), sizeof(double));
	cgval->len=sizeof(double);
	return 0;
    }   
    case BIGINT_TYPE:
    {
	cgval->type = CG_BIGINT;
	Chain val = fv.valAsChain();
	cgval->val = new char[val.length()];
	memcpy((char*)cgval->val, (char*)val, val.length());
	cgval->len=val.length();
	return 0;
    }	
    case DECIMAL_TYPE:
    {
	cgval->type = CG_DECIMAL;
	Chain val = fv.valAsChain();
	cgval->val = new char[val.length()];
	memcpy((char*)cgval->val, (char*)val, val.length());
	cgval->len=val.length();
	return 0;
    }
    case FIXED_TYPE:
    {
	cgval->type = CG_FIXED;
	Chain val = fv.valAsChain();
	cgval->val = new char[val.length()];
	memcpy((char*)cgval->val, (char*)val, val.length());
	cgval->len=val.length();
	return 0;
    }
    case SMALLINT_TYPE:
    {
	cgval->type = CG_SMALLINT;
	cgval->val = new short;
	memcpy((short*)cgval->val, (short*)fv.getValue(), sizeof(short));
	cgval->len=sizeof(short);
	return 0;
    }   
    case TINYINT_TYPE:
    {
	cgval->type = CG_TINYINT;
	cgval->val = new char;
	memcpy((char*)cgval->val, (char*)fv.getValue(), sizeof(char));
	cgval->len=sizeof(char);
	return 0;
    }   
    case BLOB_TYPE:
    {
	cgval->type = CG_BLOB;
	cgval->val = malloc ( 2 * sizeof(int));
	memcpy((int*)cgval->val, (int*)fv.getValue(), 2 * sizeof(int));
	cgval->len= 2 * sizeof(int);
	return 0;	
    }
    case NULL_TYPE:
    {
	cgval->val = NULL;
	cgval->len=0;
	return 0;
    }
    }
}

static void updateModLog(CegoModule* pModule)
{
    Chain *pMod = modLogSet.First();
    while ( pMod )
    {
	Tokenizer tok(*pMod, ":");

	Chain module;
	Chain levelString;
	
	tok.nextToken(module);
	tok.nextToken(levelString);

	Logger::LogLevel level;
	
	if ( levelString == Chain("NOTICE") )
	    level = Logger::NOTICE;
	else if ( levelString == Chain("ERROR") )
	    level = Logger::LOGERR;
	else if ( levelString == Chain("DEBUG") )
	    level = Logger::DEBUG;
		

	if ( module == Chain("ALL") )
	{
	    int mapSize = pModule->getMapSize();
	    for ( int i=0; i<mapSize; i++)
	    {	       
		pModule->logModule(i, pModule->getModName(i), level);
	    }
	}
	else
	{
	    modId = pModule->getModId(module);
	    pModule->logModule(modId, module, level);
	}

	pMod = modLogSet.Next();
    }
}
