/***************************************************************************

    supportware
    Copyright (C) 2001-2008 by c.holzheuer
    c.holzheuer@sourceworx.org

    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 of the License, or
    (at your option) any later version.

***************************************************************************/


#include <dddatatable.h>
#include <ddconfig.h>
#include <ddwebcgi.h>
#include <dddataconnection.h>
#include <dditem.h>

/**
	Default constructor.
*/
	
dddatastore::dddatastore()
:	_connection( 0 ), _connectionowner( false ), _lastaffected( 0 )
{

}
		
		
/**
	Standard constructor.
*/ 

dddatastore::dddatastore( const ddstring& name )
:	dddataclient( name ), _connection( 0 ), _connectionowner( false ), _lastaffected( 0 )
{
	
}


/**
	Destructor. Falls via connect eine eigene DB-Connection aufgebaut
	wurde, muss diese geschlossen und deletet werden.
	@see dddataconnection.
*/  

dddatastore::~dddatastore()
{
	cleanup_query_results();
	if( _connection && _connectionowner )
	// _connection->close wird in deren destructor aufgerufen.		
		delete _connection;
}


/**
	Die Tabelle mit Daten aus der Config initialisieren. Liest die Queries ein.
	@see ddconfig
	@see ddconfigsection
*/  

void dddatastore::init_queries( ddconfigsection& config, const ddstring& connectstring )
{
	set_name( config.get_name() );
	for( int i=0; i<config.size(); ++i )
		add_query( config.key_of(i), config[i] );
	if( !connectstring.empty() )
		connect( connectstring );
}


/**
	Eine Query hinzufügen.
	@param Der Name bzw. Schlüssel der Query
	@param querystring Der Querystring
*/
	
void dddatastore::add_query( const ddstring& key, const ddstring& querystring )
{

	dddataquery* query = new dddataquery( key, querystring );
	
	// das ist kein Fehler, denn über vererbung können queries
	// überschrieben werden.
	
	if( contains_query( key ) )
		_queries.replace( key, query );
		//throw runtime_error( "query '"+key+"' already exits." );
	else
		_queries.add_by_key( key, query );
	
}

	
/**
	@return true wenn die Query 'key' existiert.
	@param key der query name.
*/

bool dddatastore::contains_query( const ddstring& key )
{	
	if( key.empty() )
		throw runtime_error( "contains_query: key.emtpy()" );
	return _queries.contains_key( key );
}
	

/**
	@Returns eine referenz auf die Query für "key"	
*/  	

dddataquery& dddatastore::get_query( const ddstring& key )
{
	if( key.empty() )
		throw runtime_error( "get_query: key.emtpy()" );
	return _queries.get_entry( key );
}
	
	
/**
	View als geändert markieren & alle
	Kinder benachrichtigen
*/
	
void dddatastore::invalidate()
{
	_valid = false;
	for( size_t i=0; i<_children.size(); ++i )
		_children[i]->invalidate();
}

/**
	Baut eine eingene Connection zum MySql-Server auf
	@see dddataconnection.
*/

void dddatastore::connect( const ddstring& connectstring )
{
	// Ggf. alte connection abräumen
	if( _connection && _connectionowner )
	// _connection->close wird in deren destructor aufgerufen.		
		delete _connection;
		
	_connection = new dddataconnection( connectstring );
	_connectionowner = true;

}


/**
	Baut eine eingene Connection zum MySql-Server auf
	@see dddataconnection.
*/

void dddatastore::connect( const ddstring& db, const ddstring& host, const ddstring& user, const ddstring& pass )
{
	// Ggf. alte connection abräumen
	if( _connection && _connectionowner )
	// _connection->close wird in deren destructor aufgerufen.		
		delete _connection;
		
	_connection = new dddataconnection( db, host, user, pass );
	_connectionowner = true;

}


/**
	Eine bereits bestehende DB-Connection verwenden.
	@see dddataconnection.	
*/

void dddatastore::set_data_connection( dddataconnection* connection )
{
	// Ggf. alte connection abräumen
	if( _connection && _connectionowner )
	// _connection->close wird in deren destructor aufgerufen.		
		delete _connection;
	_connection = connection;
	_connectionowner = false;
}


/**
	@Returns eine Referenz auf die interne DB-Connection.
	@see dddataconnection.	
*/

dddataconnection& dddatastore::get_data_connection()
{
	if( !_connection )
		throw runtime_error( get_name() + ": get_data_connection() == 0" );
	return *_connection;
}


/**
	Eine Query ausführen.
	@param query die query
	@param p1-p3 drei optionale Parameter
	@throws runtime_error bei fehlerhaften queries
	@returns den reader (für table.exec( "query", CGI ).read( item ) etc.)
*/

dddataproxy dddatastore::execute( dddataquery& query, const ddstring& p1, const ddstring& p2, const ddstring& p3, const ddstring& p4, const ddstring& p5 )
{	
	
	// Neu: erstmal resetten FIX! haut nicht hin!!!!!!	
	// Grund: oft wird die query vorher mit params bestückt, die werden
	// dann hier plattgebügelt, also _nichts_ resetten, obwohl es der
	// logik entsprechen würde.
	//query.clear_query_data();
	query.set_query_parameter( 1, p1 );
	query.set_query_parameter( 2, p2 );
	query.set_query_parameter( 3, p3 );
	query.set_query_parameter( 4, p4 );
	query.set_query_parameter( 5, p5 );
	
	// fast alle anderen exec-funktion rufen diese, daher ist das 
	// lock hier.
	
	ddthreadautomutex tmp( _lock );
	
	ddstring querystr = query.to_string();
	cout << "--- db log: " << get_name() << ": " << query.get_name() << ": "<< querystr  << endl;
	
	// mysql_real_query ist für bin-data mit nullen drin.
	dddataproxy proxy( this );
	
	if( 0 == ::mysql_query( get_mysql(), querystr.c_str() ) )
		return proxy;
	
	// schiefgegangen?
	create_error( query.get_name() + ": " + querystr );	
	
	// wird nie erreicht
	return proxy;

}


/**
	Eine Query ausführen.
	@param name der name der query
	@param p1-p3 drei optionale Parameter
	@throws runtime_error bei nichtvorhandenen queries
	@returns den reader (für table.exec( "query", CGI ).read( item ) etc.)	
*/

dddataproxy dddatastore::execute( const ddstring& name, const ddstring& p1, const ddstring& p2, const ddstring& p3, const ddstring& p4, const ddstring& p5 )
{
	return execute( get_query( name ), p1, p2, p3, p4, p5 );
}


/**
	Eine Query ausführen.
	@param name der name der query
	@param cgi CGI-Datem mit den Paramtern
	@throws runtime_error bei nichtvorhandenen queries
	@returns den reader (für table.exec( "query", CGI ).read( item ) etc.)	
*/

dddataproxy dddatastore::execute( const ddstring& name, ddwebcgi& cgi )
{
	dddataquery& query = get_query( name );
	// Neu: erstmal resetten
	query.clear_query_data();
	query.set_query_data( cgi );
	
	// doch
	return execute( query );
	
}


/**
	Eine Query ausführen.
	@param name der name der query
	@param cgi CGI-Datem mit den Paramtern
	@throws runtime_error bei nichtvorhandenen queries
	@returns den reader (für table.exec( "query", CGI ).read( item ) etc.)
*/

dddataproxy dddatastore::execute( ddwebcgi& cgi )
{
	return execute( cgi[ DDACTIONKEY ], cgi );
}


/**
	Liest ein mysql result in die vorher übergebene Tabelle ein.
	@param skip Anzahl der results, die übersprungen werden sollen
*/

int dddatastore::read( bool doinit )
{  	
	
	ddthreadautomutex tmp( _lock );
	
	// mysql_use_result(), you must execute mysql_fetch_row() until a NULL 
	MYSQL_RES* result  = ::mysql_store_result( get_mysql() );
	if( NULL != result )
	{
		size_t  num_fields = ::mysql_num_fields( result );
		// init result set
		if( doinit )
		{
			clear();	
			// find out field names
			MYSQL_FIELD *fields = ::mysql_fetch_fields( result );
			for(size_t x = 0; x < num_fields; x++)
				add_field_key( ddstring( fields[x].name ), x );
		}
		size_t num =mysql_num_rows( result );
		if( num > 0 )
		{
			reserve( num+1 );
			MYSQL_ROW row;
			size_t num_fields = ::mysql_num_fields( result );
			while( (row = ::mysql_fetch_row( result )) )
				add_entry( num_fields, row );
		}
	}
	// ergebniszeilen merken ...
	_lastaffected = ::mysql_affected_rows( get_mysql() );
	// ...dieses resultset freigeben ...
	::mysql_free_result( result );	
	// und das nächste holen, so vorhanden...
	get_next_result();
	return get_affected_rows();
	
}


/**
	Liest der ersten wert aus einen mysql result in
	den übergebenen string ein.
*/

int dddatastore::read( ddstring& r1 )
{
	
	ddthreadautomutex tmp( _lock );
	
	MYSQL_RES* result  = ::mysql_store_result( get_mysql() );
  if( NULL != result  && ::mysql_num_rows( result ) > 0 )
  {
  	size_t num_fields = ::mysql_num_fields( result );
    if( !num_fields )
  		throw runtime_error( "dddatastore::read( ddstring& r1 )" );
    MYSQL_ROW row = ::mysql_fetch_row( result );
    r1.assign( row[0] ? row[0] : "" );				
  }
	// ergebniszeilen merken ...
	_lastaffected = ::mysql_affected_rows( get_mysql() );
	// dieses resultset freigeben ...
	::mysql_free_result( result );
	// und das nächste holen, so vorhanden...
	get_next_result();
	return get_affected_rows();

}


/**
	Liest der ersten wert aus einen mysql result in
	die übergebenen strings ein.
*/

int dddatastore::read( ddstring& r1, ddstring& r2 )
{
	
	ddthreadautomutex tmp( _lock );
	
	MYSQL_RES* result  = ::mysql_store_result( get_mysql() );
  if( NULL != result  && ::mysql_num_rows( result ) > 0 )
  {
  	size_t num_fields = ::mysql_num_fields( result );
    if( num_fields<2 )
  		throw runtime_error( "dddatastore::read( ddstring& r1,r2 )" );
    MYSQL_ROW row = ::mysql_fetch_row( result );
    r1.assign( row[0] ? row[0] : "" );
    r2.assign( row[1] ? row[1] : "" );				
  }
	// ergebniszeilen merken ...
	_lastaffected = ::mysql_affected_rows( get_mysql() );	
	// dieses resultset freigeben ...
	::mysql_free_result( result );
	// und das nächste holen, so vorhanden...
	get_next_result();
	return get_affected_rows();

}


/**
	Liest der ersten wert aus einen mysql result in
	die übergebenen strings ein.
*/

int dddatastore::read( ddstring& r1, ddstring& r2, ddstring& r3 )
{
	
	ddthreadautomutex tmp( _lock );
	
	MYSQL_RES* result  = ::mysql_store_result( get_mysql() );
  if( NULL != result  && ::mysql_num_rows( result ) > 0 )
  {
  	size_t num_fields = ::mysql_num_fields( result );
    if( num_fields<3 )
  		throw runtime_error( "dddatastore::read( ddstring& r1,r2,r3 )" );
    MYSQL_ROW row = ::mysql_fetch_row( result );
    r1.assign( row[0] ? row[0] : "" );
    r2.assign( row[1] ? row[1] : "" ); 
    r3.assign( row[2] ? row[2] : "" );				
  }
	// dieses resultset freigeben ...
	::mysql_free_result( result );
	// und das nächste holen, so vorhanden...
	get_next_result();
	return get_affected_rows();

}


/**
	Liest der ersten wert aus einen mysql result in
	den übergebenen int ein.
*/

int dddatastore::read( int& r1 )
{
	ddthreadautomutex tmp( _lock );
	
	MYSQL_RES* result  = ::mysql_store_result( get_mysql() );
  if( NULL != result  && ::mysql_num_rows( result ) > 0 )
  {
  	size_t num_fields = ::mysql_num_fields( result );
    if( !num_fields )
  		throw runtime_error( "dddatastore::read( ddstring& r1 )" );
    MYSQL_ROW row = ::mysql_fetch_row( result );
    ddstring x1;
    x1.assign( row[0] ? row[0] : "" );	
    r1 = x1.as_int();			
    
  }
		

	// ergebniszeilen merken ...
	_lastaffected = ::mysql_affected_rows( get_mysql() );
	// dieses resultset freigeben ...
	::mysql_free_result( result );
	// und das nächste holen, so vorhanden...
	get_next_result();
	return get_affected_rows();

}


/**
	Liest der ersten wert aus einen mysql result in
	die übergebenen ints ein.
*/

int dddatastore::read( int& r1, int& r2 )
{
	
	ddthreadautomutex tmp( _lock );
	
	MYSQL_RES* result  = ::mysql_store_result( get_mysql() );
  if( NULL != result  && ::mysql_num_rows( result ) > 0 )
  {
  	size_t num_fields = ::mysql_num_fields( result );
    if( num_fields<2 )
  		throw runtime_error( "dddatastore::read( ddstring& r1,r2 )" );
    MYSQL_ROW row = ::mysql_fetch_row( result );
    ddstring x1, x2;
    x1.assign( row[0] ? row[0] : "" );
    x2.assign( row[1] ? row[1] : "" );				
    r1 = x1.as_int();
    r2 = x2.as_int();
  }
	// ergebniszeilen merken ...
	_lastaffected = ::mysql_affected_rows( get_mysql() );	
	// dieses resultset freigeben ...
	::mysql_free_result( result );
	// und das nächste holen, so vorhanden...
	get_next_result();
	return get_affected_rows();

}

  

/**
	Prototype.
	@see ddwebview
*/

int dddatastore::read( ddwebpage& pagelet, dditem& item )
{
	throw runtime_error( "dddatastore::read( dditem& item ): not done yet!" );
	return -1;
}


/**
	Da bei multistatements die fehlerüberprüfung nur beim ersten 
	teilstatement funktioniert, müssen die folgenden teilstatement extra
	auf fehler geprüft werden. Dazu wird der aufruf von 'mysql_next_result'
 	in dieser Funktion gekapselt
*/
	
void dddatastore::get_next_result()
{
  //cout << "--- db log next: " << tmpquery << endl;
	if( ::mysql_next_result( get_mysql() ) > 0 )
		create_error( "get_next_result()" );
}


/**
	Bei Multi-Statements können noch results 'rumliegen'.
	-> Weg damit.
*/

void dddatastore::cleanup_query_results()
{
	while( ::mysql_more_results( get_mysql() ) )
	{
		//cout << "--- db log clean: " << tmpquery << endl;
		// ??
		MYSQL_RES* result  = ::mysql_store_result( get_mysql() );
  	::mysql_free_result( result );
  	get_next_result();
  }
}


void dddatastore::dump_queries()
{
  cout << "xxxxxxxxxxxxx Dump queries: " << get_name() << endl;
	for( int i = 0; i < _queries.size(); ++i  )   	
		cout << _queries[i]->get_name() << " : " << _queries[i]->get_raw_query_string() << endl;
}


/**	
	Error erzeugen. throws ...
*/
	
void dddatastore::create_error( const ddstring& error )
{
	ddstring err = get_name() + ": " + error + ": " + ddstring( ::mysql_error( get_mysql() ) );
	throw runtime_error( err );
}


MYSQL* dddatastore::get_mysql()
{
 return &get_data_connection()._mysql;
}


	
