///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoBlowTab.cc
// --------------                           
// Blowing data to tables
// 
// Design and Implementation by Bjoern Lemke
//     
// (C)opyright 2000-2010 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.
//
// IMPLEMENTATION MODULE
//
// Class: Main
// 
// Description: 
//
// Status: QG-2.6
//
///////////////////////////////////////////////////////////////////////////////

#include <iostream> 
#include <stdlib.h> 

#include <lfc/Exception.h>
#include <lfc/GetLongOpt.h>
#include <lfc/Chain.h>
#include <lfc/Tokenizer.h> 
#include <lfc/Timer.h>
#include <lfc/Net.h>
#include <lfc/NetHandler.h>

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

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

enum Operation { INSERT_OP, DELETE_OP, UPDATE_OP, ARBITRARY_OP, PROC_OP, BLOB_OP }; 

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

// #define USAGE "Usage: cgblow [ -i | -d | -k | -a | -l | -b <op>:<arg> ]  -s <host> -p <port> -u <user>/<password> -f <format> -e <format> -c <count> -t <tablename> -j <tableset> -n <report-interval> -g -z -v -h"

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

bool dispatchCmd(CegoDbHandler* pNetHandle, 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("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("debug");
	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
	int seed = 5 * (time(NULL) % 100000); 	
	srand( seed  ); 
	
	bool isDebug=false;
	if ( longOpt.isSet("debug") )
	    isDebug=true;
	
	if ( longOpt.isSet( Chain("help") ) )
	{	
	    cerr << USAGE << endl;
	    exit(0);
	}

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

	Chain mode = longOpt.getOptValue("mode");
	Chain logFile = longOpt.getOptValue("logfile");
	Chain serverName = longOpt.getOptValue("server");
	int dataPort = longOpt.getOptValue("port").asInteger();

	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");

	int count = longOpt.getOptValue("count").asInteger();
	int interval = longOpt.getOptValue("interval").asInteger();

	Operation 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("blob") )
	{
	    op=BLOB_OP;
	}
	else if ( mode == Chain("arbitrary") )
	{
	    op=ARBITRARY_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("xml") )
	{
	    protType = CegoDbHandler::XML;
	}
	else
	{
	    cerr << "Invalid protocol " << prot;
	    exit (1);
	}

       
	// op=BLOB_OP;		
	// blobArg=Chain(myOpt.getArg()); 


	CegoDbHandler* pSH = 0;
	CegoModule *pModule = 0;
	NetHandler* pN = 0; 

	if ( doSimulate == false )
	{
	    Net n (  NETMNG_MSG_BUFLEN, NETMNG_SIZEBUFLEN );

	    if ( serverName.length() > 0 && dataPort > 0 ) 
		pN = n.connect(serverName, dataPort);
	    
	    pModule = new CegoModule(Chain("cgblow.log"));
	    
	    if ( pN )
		pSH = new CegoDbHandler(pN, protType, pModule);
	    
	    if ( pSH )
	    {
		CegoDbHandler::ResultType res = pSH->requestSession(tableSet, user, password);
		if ( res != CegoDbHandler::DB_OK )
		{
		    Chain msg = pSH->getMsg();
		    throw Exception(EXLOC, msg);
		}
	    }
	}

		
	Chain cmd;

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


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

	for ( int i=1; i<=count; i++ ) 
	{ 

	    
	    Operation actOp;
	    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 == ARBITRARY_OP )
	    {
		int m = rand() % 4;
		if ( m == 1 )
		    actOp = INSERT_OP;
		else if ( m == 2 )
		    actOp = DELETE_OP;
		else if ( m == 3 )
		    actOp = UPDATE_OP;
		else
		    actOp = PROC_OP;
	    }

	    switch ( actOp )
	    {
	    case INSERT_OP:
	    {
		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); 
			int l = len.asInteger();
			cmd += Chain(rand() % l); 
		    } 
		    else if ( col[0] == 's' )
		    { 
			
			Tokenizer tok(col, Chain(":")); 
			Chain s, len; 
			tok.nextToken(s); 
			tok.nextToken(len); 
			int l = rand() % len.asInteger();
			if ( l == 0 )
			    l = len.asInteger();
			
			cmd += Chain("'"); 
			for ( int j=0; j<l; j++ ) 
			{ 
			    cmd += Chain((char)(65 + (rand()%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 )
			{
			    pSH->putBlob(tableSet, blob);
			
			    cout << "Blob stored to " << blob.getFileId() << " " << blob.getPageId() << endl;
			}
			else
			{
			    cout << "put blob .." << endl;
			}
			cmd += Chain("[") + Chain(blob.getFileId()) + Chain(",") + Chain(blob.getPageId()) + Chain("]"); 
		    }
		    else
		    {
			throw Exception(EXLOC, "Invalid insert format");
		    }
		    
		    moreToken = tok.nextToken(col); 
		    if ( moreToken ) 
			cmd += Chain(","); 
		} 
		cmd += Chain(");");
		break;
	    }
	    case DELETE_OP:
	    {
		
		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' )
		    { 
			int l = arg.asInteger();
			cmd += Chain(rand() % l);
		    }
		    else if ( type[0] == 's' )
		    {
			int l = rand() % arg.asInteger();
			if ( l == 0 )
			    l = arg.asInteger();
			
			cmd += Chain("'"); 
			for ( int j=0; j<l; j++ ) 
			{ 
			    cmd += Chain((char)(65 + (rand()%26))); 
			} 
			cmd += Chain("'");
		    }
		    else if ( type[0] == 'c' )
		    {
			cmd += arg;
		    }
		    else
		    {
			throw Exception(EXLOC, "Invalid condition format");
		    }
		}
	       
		cmd += Chain(";");
		break;
	    }
	    case PROC_OP:
	    {
		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); 
			int l = len.asInteger();
			cmd += Chain(rand() % l); 
		    } 
		    else if ( col[0] == 's' ) 
		    { 
			
			Tokenizer tok(col, Chain(":")); 
			Chain s, len; 
			tok.nextToken(s); 
			tok.nextToken(len); 
			int l = rand() % len.asInteger();
			if ( l == 0 )
			    l = len.asInteger();
			
			cmd += Chain("'"); 
			for ( int j=0; j<l; j++ ) 
			{ 
			    cmd += Chain((char)(65 + (rand()%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:
	    {
		
		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' )
		    { 
			int l = ulen.asInteger();
			cmd += Chain(rand() % l);
		    }
		    else if ( utype[0] == 's' )
		    {
			int l = rand() % ulen.asInteger();
			if ( l == 0 )
			    l = ulen.asInteger();
			
			cmd += Chain("'"); 
			for ( int j=0; j<l; j++ ) 
			{ 
			    cmd += Chain((char)(65 + (rand()%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' )
		    { 
			int l = warg.asInteger();
			cmd += Chain(rand() % l);
		    }
		    else if ( wtype[0] == 's' )
		    {
			int l = rand() % warg.asInteger();
			if ( l == 0 )
			    l = warg.asInteger();
			
			cmd += Chain("'"); 
			for ( int j=0; j<l; j++ ) 
			{ 
			    cmd += Chain((char)(65 + (rand()%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:
	    {

		cout << "Blob mode .." << endl;

		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 )
		    {
			pSH->putBlob(tableSet, blob);
			cout << "Blob stored to " << blob.getFileId() << " " << 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(BLOBSEP));

		    Chain fstr;
		    btok.nextToken(fstr);
		    Chain pstr;
		    btok.nextToken(pstr);
		    		    
		    CegoBlob blob(fstr.asInteger(), pstr.asInteger());
		    
		    cout << "Getting blob " << fstr << " " << pstr << " to " << blobFile << endl;
		    pSH->getBlob(tableSet, blob);
		    blob.writeBlob(blobFile);
		    
		}
		else if ( blobMode == Chain("del") )
		{
		    Chain blobRef;
		    tok.nextToken(blobRef);
		    Tokenizer btok(blobRef, Chain(BLOBSEP));

		    Chain fstr;
		    btok.nextToken(fstr);
		    Chain pstr;
		    btok.nextToken(pstr);
		    		    
		    CegoBlob blob(fstr.asInteger(), pstr.asInteger());
		    
		    pSH->delBlob(tableSet, blob);
		    
		}
		break;
	    }
	    case ARBITRARY_OP:
		break;
	    }

	    if ( actOp != BLOB_OP )
		dispatchCmd(pSH, cmd);
	
	    if ( pSH )
	    {
		if ( i % interval == 0)
		{

		    if ( transactionMode )
		    {
			cmd = "commit;";	     
			dispatchCmd(pSH, cmd);
		    }
		    
		    tim1.stop();
		    cout << interval << " operations : " << tim1 << endl;
		    tim1.start();

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


		}
	    }
	}
	
	if ( transactionMode )
	{
	    cmd = "commit;";	     
	    dispatchCmd(pSH, cmd);
	}
		
	if ( pSH )
	{
	    
	    tim1.stop();
	    cout << count << " operations : " << tim1 << endl;
	    tim2.stop();
	    cout << "Overall time : " << tim2 << endl;
	    
	    pSH->closeSession();	 
	    delete pSH;
	}

	if ( pModule )
	    delete pModule;
	if ( pN )
	    delete pN;
	
    } 
    catch (Exception e) 
    { 
	Chain msg;
	e.pop(msg);
	cerr << "CegoBlow Error : " << msg << endl;
	exit(1);
    }   
} 

bool dispatchCmd(CegoDbHandler *pSH, Chain cmd)
{

    if ( pSH == 0 )
    {
	cout << cmd << endl;
	return true;
    }
    else
    {
	Chain msg;
	CegoDbHandler::ResultType res;
	
	res = pSH->reqQueryOp(cmd);
	
	if ( res == CegoDbHandler::DB_OK )
	{
	    Chain msg =  pSH->getMsg();
	    
	    // if ( msg == Chain("Goodbye") )
	    //    return true;
	    
	    // cout << msg << endl;
	    return true;
	}
	else if ( res == CegoDbHandler::DB_DATA )
	{
	    ListT<CegoField> schema = pSH->getSchema();
	    
	    CegoField *pF = schema.First();
	    while ( pF ) 
	    {
		cout << pF->getAttrName() << "\t";
		pF = schema.Next();
	    }
	    // cout << endl;
	    
	    // print out schema
	    
	    ListT<CegoFieldValue> fvl;
	    while ( pSH->receiveTableData(schema, fvl) )
	    {
		CegoFieldValue *pFV = fvl.First();
		while ( pFV ) 
		{
		    cout << *pFV << "\t";
		    pFV = fvl.Next();
		}
		cout << endl;
		fvl.Empty();
	    }
	}
	else if ( res == CegoDbHandler::DB_ERROR )
	{
	    Chain msg = pSH->getMsg();
	    cout << msg << endl;
	    cout << "Query failed" << endl;
	    
	}
	return false;
    }
}
