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

    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.

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


#ifndef DDDATATABLE_H
#define DDDATATABLE_H

#include <mysql.h>
#include <dddataconnection.h>
#include <dddataquery.h>
#include <ddmapptr.h>
#include <ddconfig.h>
#include <ddwebcgi.h>

class ddwebpage;
class dddatastore;
class dddataproxy;
class ddwebview;

/// dddataclient : Etwas was auf veränderungen in Datenstrukturen reagieren kann
/**
	Tabellen, Cacheeinträge sind voneinander abhängig und müssen entsprechend 
	aktualisiert werden.
	Hier soll ein simples Model-View-Controller-Interface implementiert werden.
	Was war dann noch das Observer Pattern?
*/

class dddataclient : public ddbasename
{

public:
 	
 	dddataclient()
 	: _valid( false )
 	{
 	
 	}
 	
 	dddataclient( const ddstring& name )
 	: ddbasename( name ), _valid( false )
 	{
 	
 	}


 	/**
  	@return 'true' wenn die View 'uptodate' ist.
  */
    
  inline bool is_valid()
  {
		return _valid;
  }  
	  
	  
  /**
  	View als geändert markieren.
  */
    
  virtual void invalidate()
  {
  	_valid = false;
  }  


	/**
  	View als 'uptodate' markieren.
  */

  inline void validate()
  {
  	_valid = true;
  }

protected:

	bool							_valid;

};

typedef vector<dddataclient*>  dddataclientmap;


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

    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.

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


/// dddatastore : Neufassung von dddbtablebase.
/**
	
	Ddatastore :  Etwas bizarre Zwischenklasse, welche
	die Infrastruktur der Datenbankschicht implementiert.
	Es werden die Interfaces zu 
	
		@see dddataconnection
		@see dddatereader
		
	definiert. Als template geht das nicht so einfach, da müssten
	dann alle beteiligten klassen templates sein.	
	
*/


typedef ddmapptr<dddataquery> dddataquerymap;


class dddatastore : public dddataclient
{

public: 
	
	dddatastore();
	dddatastore( const ddstring& name );
	~dddatastore();
	
	void init_queries( ddconfigsection& config, const ddstring& connectstring="" );
	void add_query( const ddstring& key, const ddstring& querystring );
	bool contains_query( const ddstring& key );
	dddataquery& get_query( const ddstring& key );
	void connect( const ddstring& connectstring );
	void connect( const ddstring& db, const ddstring& host, const ddstring& user, const ddstring& pass );
	void set_data_connection( dddataconnection* connection );
	dddataconnection& get_data_connection();
	
	// wg. 'extends', d.h. vererbung
	const dddataquerymap& get_queries() const 
	{
		return _queries;
	}
	
	virtual void invalidate();
  
	// wg. dataclient !?
	void add_child( dddataclient* child )
	{
		_children.push_back( child );
	} 
	
	dddataproxy execute( dddataquery& query, const ddstring& p1="", const ddstring& p2="", const ddstring& p3="", const ddstring& p4="", const ddstring& p5="" );
	dddataproxy execute( const ddstring& name, const ddstring& p1="", const ddstring& p2="", const ddstring& p3="", const ddstring& p4="", const ddstring& p5="" );
	dddataproxy execute( const ddstring& name, ddwebcgi& cgi );
	dddataproxy execute( ddwebcgi& cgi );
	
	virtual int read( bool doinit = true );
	virtual int read( ddstring& r1 );
	virtual int read( ddstring& r1, ddstring& r2 );	
	virtual int read( ddstring& r1, ddstring& r2, ddstring& r3 );	
	//virtual int read( dditem& item );
	virtual int read( ddwebpage& pagelet, dditem& item );
	virtual int read( int& r1 );
	virtual int read( int& r1, int& r2 );	

 	/**
 		Da beim 'weiterschieben' der resultsets mysql_affected_rows resettet wird,
 		muss das zwischengespeichert werden.
 	*/
	
	inline int get_affected_rows()
	{
		return _lastaffected;
	}
	
	void get_next_result();
	void cleanup_query_results();
	
	virtual void add_entry( size_t num_fields, MYSQL_ROW row ) = 0;
	
	virtual void dump_queries();
			 
  
  /**
  	fehlermeldung basteln und exception schmeissen
  */
		
	virtual void create_error( const ddstring& error );
	
	virtual void add_field_key( const ddstring& key, size_t index ) = 0;
	virtual void clear() = 0;
	virtual void reserve( size_t count ) = 0;
	
protected: 
	
	MYSQL* get_mysql();
	
	dddataconnection* _connection;
	bool							_connectionowner;		
	int								_lastaffected;
  dddataquerymap   	_queries;	
	dddataclientmap   _children;
	  
	ddthreadmutex _lock;
  
private:
	
	// warum ist das Verboten, wenn wir doch nicht
	// mit pointern sondern mit objecten arbeiten?
	
	dddatastore( const dddatastore& src )
	{
		(void) src;
	}
	
	dddatastore& operator=( const dddatastore& src )
	{
		return *this;
	}

};


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

    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.

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


/// dddataproxy : hilfsklasse zum für datastore
/**
	
	Wenn ein die Einzelergebnisse eines Mehrfachstatements nicht alle mit 'read()'
	bzw. ::mysql_next_result abgearbeitet werden, gibts beim nächsten Aufruf 
	den Error 'commands out of sync'.
	 
	Der proxy wird als rückgabewert von execute erzeugt und räumt die 'hängenden'
	Statements in seinem destructor weg.
	
	reads() werden zurück an den datastore geleitet.
	
	@see dddatastore	
*/

class dddataproxy
{

public:
	
	dddataproxy( dddatastore* parent )
	: _parent( parent )
	{
	}
	
	~dddataproxy()
	{
		// im sterben eventuelle 'überhängende' results entfernen,
		// sonst gibbet 'Commands out of sync; you can't run this command now'
		_parent->cleanup_query_results();
	}
	
	inline bool operator!() const
	{
		return ( _parent->get_affected_rows() == 0 );
	}

	int read( bool doinit = true )
	{
		return _parent->read( doinit );
	}
	
	int read( ddstring& r1 )
	{
		return _parent->read( r1 );
	}
	
	int read( ddstring& r1, ddstring& r2 )	
	{
		return _parent->read( r1, r2 );
	}
	
	int read( ddstring& r1, ddstring& r2, ddstring& r3 )	
	{
		return _parent->read( r1, r2, r3 );
	}
	
	int read( int& r1 )	
	{
		return _parent->read( r1 );
	}
	
	int read( int& r1, int& r2 )	
	{
		return _parent->read( r1, r2 );
	}
	
	/*
	int read( dditem& item )
	{
		return _parent->read( item );
	}
	*/
	
	int read( ddwebpage& pagelet, dditem& item )
	{
		return _parent->read( pagelet, item );
	}
		  
protected:

	dddatastore* _parent;
		  
};

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

    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.

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

	
/**	
	
	Neufassung von dddbtablebase, diesmal eher KISS: 
	Ein dddatastore ist ein vector mit Ts. Um die Templates unterzubringen,
	wird mit 'ddmaprow' die 'Speicherinfrastruktur drangeerbt'.
	
	Für die konkrete Variante @see ddwebview.
	
	Es werden diesmal kopien und keine pointer gespeichert, schlicht
	und ergreifend deswegen, die bei pointern (basis ddmapptr<T>) der
	operator= auch pointer liefert. Will man nicht.
	
	Die alte hybrid-konstruktion "pointer speichern, referenzen zurückgeben"
	(dddbtablebase) wurde wegrationalisiert.
	
	Der operator["key"] bezieht sich jetzt auf ID des Eintrags, so vorhanden.
	D.h. _rowindex und index_of sind gestrichen, index_of bezieht auf Objekte,
	wie man es erwarten würde. Der index der Felder des Eintrags ist privat.
	
	Tabellen sind grundsätzlich _dumm_. Alles was fancy sein soll, machen adapter.
	
	- table.exec( "query", param1 ... ).read( moo ) 
	- table.exec( "query", CGI ).read( item ) 
	...moo
	
	exec und read macht die conn ...
	
	FIX! exec.into direkt in der renderer via cursor spart speicher ....	

*/


template<class T>
class dddatatable : public dddatastore, public ddmaprow<T>
{

friend class dddataconnection;

public: 
	
	
	dddatatable()
	{
	}
	
	
	dddatatable( const ddstring& name )
	:	dddatastore( name )
	{
	}
	
	~dddatatable()
	{
	}
	
	/**
		Abkürzung für execute("select").
		@see execute.
		@return die Anzahl Ergebniszeilen
	*/
	
	int fill( dddataquery& query, const ddstring& p1="", const ddstring& p2="", const ddstring& p3="", const ddstring& p4="", const ddstring& p5="" )
	{
		this->clear();
		validate();
		return execute( query, p1, p2, p3, p4, p5 ).read();	
	}	
	
	
	/**
		Abkürzung für execute("select").
		@see execute.
		@return die Anzahl Ergebniszeilen
	*/
	
	int fill( const ddstring& cmd = "select", const ddstring& p1="", const ddstring& p2="", const ddstring& p3="", const ddstring& p4="", const ddstring& p5="" )
	{
		return fill( get_query( cmd ), p1, p2, p3, p4, p5 );		
	}
	
	
	/**
		Ruft 'fill' auf wenn die Tabelle leer oder als 'invalid'
		gekennzeichnet ist.
	*/
	
	int fill_if_empty( dddataquery& query, const ddstring& p1="", const ddstring& p2="", const ddstring& p3="", const ddstring& p4="", const ddstring& p5="" )
	{
		int numrows = this->size();
		if( numrows <= 0 || !is_valid() )
			return fill( query, p1, p2, p3, p4, p5 );  
		return numrows;
	}
	
	
	/**
		Ruft 'fill' auf wenn die Tabelle leer oder als 'invalid'
		gekennzeichnet ist.
	*/
	
	int fill_if_empty( const ddstring& cmd = "select", const ddstring& p1="", const ddstring& p2="", const ddstring& p3="", const ddstring& p4="", const ddstring& p5="" )
	{
		return fill_if_empty( get_query( cmd ), p1, p2, p3, p4, p5 );		
	}
	
	
	/**
		@Returns Die indexposition für einen Schlüssel oder -1.
		DIESER COMPILER STINKT!
	*/

	int index_of( const ddstring& key ) const
	{
		return ddmaprow<T>::index_of( key );
	}
	
	
	/**
		@return den index des ersten Eintrags, bei dem das Feld an Position
		'cellidx' den Wert 'cellval' enthält oder -1 falls nicht vorhanden.
	*/
	
	int index_of( size_t cellidx, const ddstring& cellval ) const
	{  
		if( this->_data.empty() )
			return -1;
		if( this->_data[0].size() <= cellidx )
			throw out_of_range( "index_of: cellidx out of range." );
		int x = this->size();
		for( int i=0; i < x; ++i )
		{
			// gefunden
			if( this->_data[i][cellidx] == cellval )
				return i;
		}
		return -1;
	}
		
	
	/**
		@return den index des ersten Eintrags, bei dem das Feld namens
		'cellname' den Wert 'cellval' enthält oder -1 falls nicht vorhanden.
	*/
	
	int index_of( const ddstring& cellname, const ddstring& cellval ) const
	{
		int x = _fieldindex.index_of( cellname );
		if( x<0 )
			throw out_of_range( "index_of: no such field:"+cellname );
		return index_of( x, cellval );
	}
			
	void add_field_key( const ddstring& key, size_t index ) 
  {
		this->_fieldindex[key] = index;
  }
	
	void reserve( size_t count )
	{
		this->_data.reserve( count );
	}
	

	/**
		Alles Löschen.
	*/

	void clear()
	{
		ddmaprow<T>::clear();
		_fieldindex.clear();
	}
	
  virtual void dump() const
  {
  	for( size_t i=0; i<this->_data.size(); ++i )
  		this->_data[i].dump();
  }	
  
  
  /**
		@return the last auto id value created by MySql.
	*/

	ddstring get_last_insert_id()
	{
		ddstring lastid;	
		size_t id =  ::mysql_insert_id( get_mysql() );
		lastid.from_size_t( id );
		return lastid; 
	}

 	/*
 	
 	Das frisst der compiler nicht.
 	
 	void add_entry( size_t num_fields, MYSQL_ROW row )
	{
	
			// FIX! Die Felder sind nicht zwangsläufig nullterminiert!
		// Das wird noch grob in die Hose gehen!!!
		size_t idx = add( T( this ) );
		T& newobj = (*this)[idx];
		//FIX! PFUSCH!!
		for( size_t i=0; i < num_fields; i++)
			newobj.add( row[i] ? row[i] : "" );		
		// 'id'	speichern für index_of
		if( newobj.contains_key( "id" ) )
			this->_index[ newobj["id"] ] = idx;
		// den object state setzen
		// FIX! das müsste eigentlich das object irgenwie selbermachen...
		if( newobj.contains_key( "id_state" ) )
			newobj.set_state_flags( newobj["id_state"].as_size_t() );
		
	}
	*/
	/**
		@Returns den feldindex.
		@see ddmapindex
		@see dddataobject
	*/

	inline const ddmapindex& get_field_index() const
	{
		return _fieldindex;
	}

	inline bool contains_field( const ddstring& key )const
	{
		return ( _fieldindex.contains_key( key ) );
	}
	
protected:
 
	ddmapindex _fieldindex;
	
private:
	
	dddatatable( const dddatatable& src )
	{
		(void) src;
	}
	
	dddatatable& operator=( const dddatatable& src )
	{
		return *this;
	}

};


#endif
