///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoBlowTab.cc
// --------------                           
// Simulation program to perform random insert, update, delete and stored procedure queries.
//
// Design and Implementation by Bjoern Lemke
//     
// (C)opyright 2000-2025 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: main
// 
// Description: This program can be used to execute random queries for any kind of testing reasons.
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

// POSIX INCLUDES
#include <iostream> 
#include <stdlib.h> 

// LFC INCLUDES
#include <lfcbase/Exception.h>
#include <lfcbase/GetLongOpt.h>
#include <lfcbase/Chain.h>
#include <lfcbase/Tokenizer.h> 
#include <lfcbase/Timer.h>
#include <lfcbase/Net.h>
#include <lfcbase/NetHandler.h>
#include <lfcbase/Sleeper.h>

// CEGO INCLUDES
#include "CegoDefs.h"
#include "CegoNet.h"
#include "CegoBlob.h"
#include "CegoOutput.h"

#define DEFAULTSERVER "localhost"
#define DEFAULTPORT 2200
#define DEFAULTPROTOCOL "xml"
#define DEFAULTPROGID "cgblow"
#define DEFAULTINTERVAL 1

enum Operation { INSERT_OP, DELETE_OP, UPDATE_OP, ARB1_OP, ARB2_OP, ARB3_OP, PROC_OP, BLOB_OP, NONE_OP }; 

#define INTCOL "i" 
#define STRCOL "s" 
#define KEYCOL "i" 

#define USAGE "Usage: cgblow --id=<id> --mode=< insert | delete | update | blob | proc |arb1 | arb2 | arb3 >\n\
          --tableset=<tableset>\n\
          --user=<user>/<password>\n\
          [ --server=<host>]\n\
          [ --port=<port> ]\n\
          [ --protocol={serial|fastserial|xml} ]\n\
          [ --iset=<insert format> ] [ --pset=<proc format> ] [ --dcond=<delete condition> ] [ --uset=<update format> ] [ --ucond=<update condition>] [ --blobop=<format> ] \n\
          [ --count=<number of operations> ]\n\
          [ --proc=<procname> ]\n\
          [ --table=<tablename> ]\n\
          [ --interval=<report interval> ]\n\
          [ --simulate ]\n\
          [ --append ] [ --dotransaction ] [ --version  ] [ --help ]"

bool dispatchCmd(CegoNet* pCegoNet, Chain cmd);

extern char __lfcVersionString[];
extern char __lfcxmlVersionString[];

int main(int argc, char **argv) 
{ 
    try
    { 	
	GetLongOpt longOpt(argc, argv);

	longOpt.addOpt("version");
	longOpt.addOpt("help");
	longOpt.addOpt("logfile");
	longOpt.addOpt("mode");
	longOpt.addOpt("tableset");
	longOpt.addOpt("user");

	longOpt.addOpt("id", Chain(DEFAULTPROGID));

	longOpt.addOpt("iset");
	longOpt.addOpt("pset");
	longOpt.addOpt("dcond");
	longOpt.addOpt("uset");
	longOpt.addOpt("ucond");

	longOpt.addOpt("blobop");
	longOpt.addOpt("count");
	longOpt.addOpt("table");
	longOpt.addOpt("proc");
	longOpt.addOpt("interval", Chain(DEFAULTINTERVAL));
	longOpt.addOpt("append");
	longOpt.addOpt("dotransaction");
	longOpt.addOpt("server", DEFAULTSERVER);
	longOpt.addOpt("port", Chain(DEFAULTPORT));
	longOpt.addOpt("protocol", Chain(DEFAULTPROTOCOL));
	longOpt.addOpt("simulate");

	try
	{
	    longOpt.parseOpt(); 
	}
	catch ( Exception e )
	{
	    Chain msg;
	    e.pop(msg);
	    cerr << msg << endl;
	    cerr << USAGE << endl;
	    exit(1);	
	}

	// for random generation in CegoFunction
	unsigned seed = 5 * (time(NULL) % 100000); 	
	srandom( seed  ); 
		
	if ( longOpt.isSet( Chain("help") ) )
	{	
	    cerr << USAGE << endl;
	    exit(0);
	}

	if ( longOpt.isSet( Chain("version") ) )
	{
	    cout << CEGO_PRODUCT << " Simulation Client, (" << sizeof(void*) * 8 << " bit), Version " << CEGO_VERSION 
		 << " [ lfc: " << __lfcVersionString  
		 << ", lfcxml: " <<  __lfcxmlVersionString << " ]" << endl;
	    cout << CEGO_COPYRIGHT << endl;
	    exit(0);
	}

	Chain progid = longOpt.getOptValue("id");
	Chain mode = longOpt.getOptValue("mode");
	Chain logFile = longOpt.getOptValue("logfile");
	Chain serverName = longOpt.getOptValue("server");
	unsigned dataPort = longOpt.getOptValue("port").asUnsigned();

	Chain insertSet = longOpt.getOptValue("iset");

	Chain deleteCond = longOpt.getOptValue("dcond");

	Chain procSet = longOpt.getOptValue("pset");

	Chain updateSet = longOpt.getOptValue("uset");
	Chain updateCond = longOpt.getOptValue("ucond");

	bool appendMode=false;
	if ( longOpt.isSet("append") )
	    appendMode=true;

	bool transactionMode=false;
	if ( longOpt.isSet("dotransaction") )
	    transactionMode=true;

	bool doSimulate=false;
	if ( longOpt.isSet("simulate") )
	    doSimulate=true;

	Chain authString = longOpt.getOptValue("user");
	Tokenizer authTok(authString, Chain("/")); 

	Chain user;
	Chain password;

	authTok.nextToken(user);
	authTok.nextToken(password);

	Chain tableName = longOpt.getOptValue("table");
	Chain procName = longOpt.getOptValue("proc");
	Chain tableSet = longOpt.getOptValue("tableset");

	unsigned long long count = longOpt.getOptValue("count").asUnsignedLongLong();
	unsigned interval = longOpt.getOptValue("interval").asUnsigned();

	Operation op = NONE_OP;    
	if ( mode == Chain("insert") )
	{
	    op=INSERT_OP;
	}
	else if ( mode == Chain("delete") )
	{
	    op=DELETE_OP;
	}
	else if ( mode == Chain("update") )
	{
	    op=UPDATE_OP;
	}
	else if ( mode == Chain("proc") )
	{
	    op=PROC_OP;
	}
	else if ( mode == Chain("blob") )
	{
	    op=BLOB_OP;
	}
	else if ( mode == Chain("arb1") )
	{
	    op=ARB1_OP;	    
	}
	else if ( mode == Chain("arb2") )
	{
	    op=ARB2_OP;	    
	}
	else if ( mode == Chain("arb3") )
	{
	    op=ARB3_OP;	    
	}
	else
	{
	    cerr << "Unknown mode" << endl;
	    exit(1);	    
	}

	CegoDbHandler::ProtocolType protType;

	Chain prot = longOpt.getOptValue("protocol");
	
	if ( prot == Chain("serial") )
	{
	    protType = CegoDbHandler::SERIAL;
	}
	else if ( prot == Chain("fastserial") )
	{
	    protType = CegoDbHandler::FASTSERIAL;
	}
	else if ( prot == Chain("xml") )
	{
	    protType = CegoDbHandler::XML;
	}
	else
	{
	    cerr << "Invalid protocol " << prot;
	    exit (1);
	}

	CegoNet* pCegoNet = 0;

	if ( doSimulate == false )
	{
	    Chain logMode("notice");
	    Chain logFile; // no logfile
	    pCegoNet = new CegoNet( protType, logFile, Chain(), logMode );	    
	    pCegoNet->connect(serverName, dataPort, tableSet, user, password);
	}
		
	Chain cmd;

	if ( op == INSERT_OP || op == ARB1_OP || op == ARB2_OP || op == ARB3_OP || op == UPDATE_OP )
	{
	    if ( appendMode )
		cmd = "set append on;";
	    else
		cmd = "set append off;";
	 
	    dispatchCmd(pCegoNet, cmd);	    
	}
	
	Timer tim1(6,3);
	Timer tim2(6,3);
	
	tim1.start();
	tim2.start();

	if ( transactionMode )
	{
	    cmd = "start transaction;";	     
	    dispatchCmd(pCegoNet, cmd);
	}

	unsigned long long numInsert=0;
	unsigned long long numUpdate=0;
	unsigned long long numDelete=0;
	unsigned long long numCall=0;
	unsigned long long numSuccess=0;
	
	for ( unsigned long long i=1; i<=count; i++ ) 
	{	    
	    Operation actOp = NONE_OP;
	    if ( op == INSERT_OP )
		actOp = INSERT_OP;
	    else if ( op == DELETE_OP )
		actOp = DELETE_OP;
	    else if ( op == UPDATE_OP )
		actOp = UPDATE_OP;
	    else if ( op == PROC_OP )
		actOp = PROC_OP;
	    else if ( op == BLOB_OP )
		actOp = BLOB_OP;
	    else if ( op == ARB1_OP )
	    {
		unsigned m = random() % 2;
		if ( m == 0 )
		    actOp = INSERT_OP;
		else
		    actOp = DELETE_OP;
	    }
	    else if ( op == ARB2_OP )
	    {
		unsigned m = random() % 3;		
		if ( m == 0 )
		    actOp = INSERT_OP;
		else if ( m == 1 )
		    actOp = DELETE_OP;
		else
		    actOp = UPDATE_OP;
	    }
	    else if ( op == ARB3_OP )
	    {
		unsigned m = random() % 4;
		if ( m == 0 )
		    actOp = INSERT_OP;
		else if ( m == 1 )
		    actOp = DELETE_OP;
		else if ( m == 2 )
		    actOp = UPDATE_OP;
		else
		    actOp = PROC_OP;
	    }

	    switch ( actOp )
	    {
	    case INSERT_OP:
	    {
		numInsert++;
		cmd = "insert into " + tableName + " values ("; 
		Tokenizer tok(insertSet, Chain(",")); 
		Chain col; 
		bool moreToken = tok.nextToken(col); 
		while ( moreToken ) 
		{ 
		    // cout << "col is " << col << endl; 
		    if ( col[0] == 'i' ) 
		    { 			
			Tokenizer tok(col, Chain(":")); 
			Chain s, len; 
			tok.nextToken(s); 
			tok.nextToken(len); 
			unsigned l = len.asUnsigned();
			cmd += Chain(random() % l); 
		    } 
		    else if ( col[0] == 's' )
		    { 	       	
			Tokenizer tok(col, Chain(":")); 
			Chain s, len; 
			tok.nextToken(s); 
			tok.nextToken(len); 
			unsigned l = random() % len.asUnsigned();
			if ( l == 0 )
			    l = len.asUnsigned();
			
			cmd += Chain("'"); 
			for ( unsigned j=0; j<l; j++ ) 
			{ 
			    cmd += Chain((char)(65 + (random()%26))); 
			} 
			cmd += Chain("'"); 
		    }
		    else if ( col[0] == 'b' )
		    {
			Tokenizer tok(col, Chain(":")); 

			Chain s;
			Chain blobFile;
			tok.nextToken(s);
			tok.nextToken(blobFile);
			
			CegoBlob blob;
			blob.readBlob(blobFile);

			if ( doSimulate == false )
			{
			    pCegoNet->putBlob(blob);
			
			    cout << "Blob stored to [" << blob.getPageId() << "]" << endl;
			}
			else
			{
			    cout << "put blob .." << endl;
			}
			cmd += Chain("[") + Chain(blob.getPageId()) + Chain("]"); 
		    }
		    else if ( col[0] == 'c' )
		    {
			Tokenizer tok(col, Chain(":")); 

			Chain s;
			Chain clobString;
			tok.nextToken(s);
			tok.nextToken(clobString);
			
			CegoClob clob((char*)s, s.length());

			if ( doSimulate == false )
			{
			    pCegoNet->putClob(clob);
			
			    cout << "Clob stored to [" << clob.getPageId() << "]" << endl;
			}
			else
			{
			    cout << "put clob .." << endl;
			}
			cmd += clob.toChain();
		    }
		    else
		    {
			throw Exception(EXLOC, "Invalid insert format");
		    }
		    
		    moreToken = tok.nextToken(col); 
		    if ( moreToken ) 
			cmd += Chain(","); 
		} 
		cmd += Chain(");");
		break;
	    }
	    case DELETE_OP:
	    {
		numDelete++;
		cmd = "delete from " + tableName;
		Tokenizer tok1(deleteCond, Chain(",")); 
		Chain col; 
		bool moreToken = tok1.nextToken(col);
		if ( moreToken )
		{
		    cmd += Chain(" where "); 
		    Tokenizer tok2(col, Chain(":"));
		    Chain attr, type, arg; 
		    tok2.nextToken(attr);

		    tok2.nextToken(type); 
		    tok2.nextToken(arg);
		    
		    cmd += attr + Chain("=");
		    
		    if ( type[0] == 'i' )
		    { 
			unsigned l = arg.asUnsigned();
			cmd += Chain(random() % l);
		    }
		    else if ( type[0] == 's' )
		    {
			unsigned l = random() % arg.asUnsigned();
			if ( l == 0 )
			    l = arg.asUnsigned();
			
			cmd += Chain("'"); 
			for ( unsigned j=0; j<l; j++ ) 
			{ 
			    cmd += Chain((char)(65 + (random()%26))); 
			} 
			cmd += Chain("'");
		    }
		    else if ( type[0] == 'c' )
		    {
			cmd += arg;
		    }
		    else
		    {
			throw Exception(EXLOC, "Invalid condition format");
		    }
		}
	       
		cmd += Chain(";");
		break;
	    }
	    case PROC_OP:
	    {
		numCall++;
		cmd = "call " + procName + "("; 
		Tokenizer tok(procSet, Chain(",")); 
		Chain col; 
		bool moreToken = tok.nextToken(col); 
		while ( moreToken ) 
		{ 
		    // cout << "col is " << col << endl; 
		    if ( col[0] == 'i' ) 
		    { 	
			Tokenizer tok(col, Chain(":")); 
			Chain s, len; 
			tok.nextToken(s); 
			tok.nextToken(len); 
			unsigned l = len.asUnsigned();
			cmd += Chain(random() % l); 
		    } 
		    else if ( col[0] == 's' ) 
		    { 			
			Tokenizer tok(col, Chain(":")); 
			Chain s, len; 
			tok.nextToken(s); 
			tok.nextToken(len); 
			unsigned l = random() % len.asUnsigned();
			if ( l == 0 )
			    l = len.asUnsigned();
			
			cmd += Chain("'"); 
			for ( unsigned j=0; j<l; j++ ) 
			{ 
			    cmd += Chain((char)(65 + (random()%26))); 
			} 
			cmd += Chain("'"); 
		    } 
		    else if ( col[0] == 'c' )
		    {
			Tokenizer tok(col, Chain(":")); 
			Chain s, arg; 
			tok.nextToken(s); 
			tok.nextToken(arg); 
			cmd += arg;
		    }
		    else
		    {
			throw Exception(EXLOC, "Invalid proc format");
		    }
		    
		    moreToken = tok.nextToken(col); 
		    if ( moreToken ) 
			cmd += Chain(","); 
		} 
		cmd += Chain(");");
		break;
	    }
	    case UPDATE_OP:
	    {
		numUpdate++;
		
		cmd = "update " + tableName + " set ";
		Tokenizer tokA(updateSet, Chain(",")); 
		Chain col; 
		bool moreToken = tokA.nextToken(col); 

		while ( moreToken ) 
		{
		    Tokenizer updTok(col, Chain(":"));
		    Chain utype, uattr, ulen; 
		    
		    updTok.nextToken(uattr); 
		    updTok.nextToken(utype);
		    updTok.nextToken(ulen);
		    
		    cmd += uattr + " = ";
		    
		    if ( utype[0] == 'i' )
		    { 
			unsigned l = ulen.asUnsigned();
			cmd += Chain(random() % l);
		    }
		    else if ( utype[0] == 's' )
		    {
			unsigned l = random() % ulen.asUnsigned();
			if ( l == 0 )
			    l = ulen.asUnsigned();
			
			cmd += Chain("'"); 
			for ( unsigned j=0; j<l; j++ ) 
			{ 
			    cmd += Chain((char)(65 + (random()%26))); 
			} 
			cmd += Chain("'");
		    }
		    else
		    {
			throw Exception(EXLOC, "Invalid update format");
		    }

		    moreToken = tokA.nextToken(col); 
		    if ( moreToken ) 
			cmd += Chain(",");
		}

		cmd += " where ";
		
		Tokenizer tokB(updateCond, Chain(",")); 
		moreToken = tokB.nextToken(col);
		
		while ( moreToken ) 
		{
		    Tokenizer condTok(col, Chain(":"));
		    Chain wtype, wattr, warg;
		    
		    condTok.nextToken(wattr); 
		    condTok.nextToken(wtype); 
		    condTok.nextToken(warg);

		    cmd += wattr + " = ";
		    
		    if ( wtype[0] == 'i' )
		    { 
			unsigned l = warg.asUnsigned();
			cmd += Chain(random() % l);
		    }
		    else if ( wtype[0] == 's' )
		    {
			unsigned l = random() % warg.asUnsigned();
			if ( l == 0 )
			    l = warg.asUnsigned();
			
			cmd += Chain("'"); 
			for ( unsigned j=0; j<l; j++ ) 
			{ 
			    cmd += Chain((char)(65 + (random()%26))); 
			} 
			cmd += Chain("'");
		    }
		    else if ( wtype[0] == 'c' )
		    {
			cmd += warg;
		    }
		    
		    moreToken = tokB.nextToken(col); 
		    if ( moreToken ) 
			cmd += Chain(" and ");
		}
		
		cmd += Chain(";");

		break;
	    }
	    case BLOB_OP:
	    {
		Chain blobArg = longOpt.getOptValue("blobop");

		Tokenizer tok(blobArg, Chain(":"));
		Chain blobMode;
		tok.nextToken(blobMode);
		
		if ( blobMode == Chain("put") )
		{
		    Chain blobFile;
		    tok.nextToken(blobFile);

		    CegoBlob blob;
		    cout << "Reading blob .." << endl;
		    blob.readBlob(blobFile);
		    
		    if ( doSimulate == false )
		    {
			pCegoNet->putBlob(blob);
			cout << "Blob stored to [" << blob.getPageId() << "]" << endl;
		    }
		    else
			cout << "Put blob ..." << endl;
		}
		else if ( blobMode == Chain("get") )
		{
 		    Chain blobRef;
		    Chain blobFile;
		    tok.nextToken(blobRef);
		    tok.nextToken(blobFile);
		    Tokenizer btok(blobRef, Chain(LOBSEP));

		    Chain pstr;
		    btok.nextToken(pstr);
		    		    
		    CegoBlob blob(pstr.asUnsignedLongLong());

		    if ( doSimulate == false )
		    {			
			cout << "Getting blob [" << pstr << "] to " << blobFile << endl;
			pCegoNet->getBlob(blob);
			blob.writeBlob(blobFile);
		    }
		    else
			cout << "Get blob ..." << endl;		    
		}
		else if ( blobMode == Chain("del") )
		{
		    Chain blobRef;
		    tok.nextToken(blobRef);
		    Tokenizer btok(blobRef, Chain(LOBSEP));

		    Chain pstr;
		    btok.nextToken(pstr);
		    		    
		    CegoBlob blob(pstr.asUnsignedLongLong());
		    if ( doSimulate == false )
		    {    
			pCegoNet->deleteBlob(blob);
		    }
		    else
			cout << "Delete blob ..." << endl;
		}
		break;
	    }
	    case ARB1_OP:
	    case ARB2_OP:
	    case ARB3_OP:
	    case NONE_OP:
		break;
	    }
	    
	    if ( actOp != BLOB_OP )
		if ( dispatchCmd(pCegoNet, cmd) )
		    numSuccess++;
	
	    if ( i % interval == 0)
	    {		
		if ( transactionMode )
		{
		    cmd = "commit;";	     
		    dispatchCmd(pCegoNet, cmd);
		}
		    
		tim1.stop();

		if ( pCegoNet )
		{		    
		    Chain msg = progid + Chain(" : ") + Chain(numInsert) + Chain(" insert / ")
			+ Chain(numDelete) + Chain(" delete / ")
			+ Chain(numUpdate) + Chain(" update / ")
			+ Chain(numCall) + Chain(" call => ")
			+ Chain(numSuccess) + Chain(" sucessful, Used Time = ") + tim1.getUsed() + Chain(" sec");
		    cout << msg << endl;
		    cout.flush();
		}
		
		numInsert=0;
		numDelete=0;
		numUpdate=0;
		numCall=0;
		numSuccess=0;
		
		tim1.start();
		
		if ( transactionMode && i<count )
		{
		    cmd = "start transaction;";	     
		    dispatchCmd(pCegoNet, cmd);
		}	  
	    }
	}
	
	if ( transactionMode && pCegoNet)
	{
	    cmd = "commit;";	     
	    dispatchCmd(pCegoNet, cmd);
	}
		
	if ( pCegoNet )
	{	    
	    tim1.stop();

	    if ( numInsert || numDelete || numUpdate || numCall )
	    {
		Chain msg = progid + Chain(" : ") + Chain(numInsert) + Chain(" insert / ")
		    + Chain(numDelete) + Chain(" delete / ")
		    + Chain(numUpdate) + Chain(" update / ")
		    + Chain(numCall) + Chain(" call => ")
		    + Chain(numSuccess) + Chain(" sucessful, Used Time = ") + tim1.getUsed() + Chain(" sec");
		cout << msg << endl;		
		cout.flush();
	    }
	    
	    tim2.stop();
	    Chain overall = progid + Chain(" : ") + Chain("Overall time = ") + tim2.getUsed() + Chain(" sec");
	    cout << overall << endl;
	    cout.flush();
	    
	    pCegoNet->disconnect();	 
	    delete pCegoNet;
	}	
    } 
    catch (Exception e) 
    {	
	Chain msg;
	e.pop(msg);
	cerr << "CegoBlow Error : " << msg << endl;
	exit(1);
    }   
} 

bool dispatchCmd(CegoNet *pCegoNet, Chain cmd)
{
    if ( pCegoNet == 0 )
    {
	cout << cmd << endl;
	return true;
    }
    else
    {
	try
	{
	    pCegoNet->doQuery(cmd);
	}
	catch ( Exception e )
	{	    
	    Chain msg;
	    e.pop(msg);
	    return false;	
	}
	
	if ( pCegoNet->isFetchable() )
	{		
	    ListT<CegoField> schema;
	    pCegoNet->getSchema(schema);
	    
	    Chain format;
	    pCegoNet->getFormat(format);
	    
	    CegoOutput output(schema, format);
	    
	    ListT< ListT<CegoFieldValue> > outData;
	    
	    output.headOut();
	    
	    ListT<CegoFieldValue> fvl;
		    
	    while ( pCegoNet->fetchData(schema, fvl)  )
	    {
		output.rowOut(fvl);
		fvl.Empty();
	    }
	    output.tailOut();	    
	}	
	return true;
    }
}
