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

    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 DDMAPMULTI_H
#define DDMAPMULTI_H

#include <ddstringrow.h>

using namespace std;

/// ddmapmulti: Eine ddmaprow, die für einen Schlüssel _mehrere_ Einträge enthält.
/**
	
	Neuer Versuch aus neu.newdope:
	
	ddmaprow: Die Basisklasse für alles, was Feldartige Daten enthält, die auch
	über einen string-index adressiert werden sollen.

	@see ddmapindex
	
	umgebaut in:
	
	ddmapmulti: Eine aufgehackte ddmaprow, die für einen Schlüssel (scheinbar) _mehrere_
	Einträge enhalten kann: Der Pointer auf einen Eintrag wird gegebenenfalls mehrfach verwendet.
	Nicht ganz sauber aber insofern korreckt, da Felder mit dem gleichen Schlüssel per Definition 
	auch den gleichen Wert enhalten. (z.B. id_parent etc.)
	
	Klappt nur mit Elementen, die copy-able sind und einen ==operator haben.
	
*/

template<class T>
class ddmapmulti : public ddbaseobject
{

public:


	/**
		Default constructor.
	*/

	ddmapmulti()
	{
	}

	
	/**
		Standard constructor.
		@param size Die vorgegebene Größe
	*/

	ddmapmulti( size_t size )
	{
		_data.resize( size );
	}

	
	/**
		Copy constructor. Nicht wirklich vorhanden, denn es wird
		der Assignment-Operator aufgerufen
		@see operator=
	*/

	ddmapmulti( const ddmapmulti& src )
	{
		*this=src;
	}

	
	/**
		Destructor. 
		Obacht! Das es sich bei den Elementen um Pointer handelt, werden
		diese zerstört.
		@see clear()
	*/

	~ddmapmulti()
	{
		clear();
	}	
	
	
	/**
		Assignment.
		Es müssen Deep-Copies angefertigt werden, was mit copy-constructor
		probiert wird.
	*/

	ddmapmulti& operator=( const ddmapmulti& src )
	{
		
		if( this == &src )
			return *this;		
		
		// Der einfache Teil: den index klonen
		_index = src._index;
		
		// die Elementpointer können _mehrfach_ in _data vorkommen,
		// dürfen aber nur einmal kopiert werden
		
		// Platz schaffen.
		_data.resize( src._data.size() );
		
		// alle elemente
		bool found;
		for( size_t i=0; i<src._data.size(); ++i )
		{
			
			// original merken...
			T* olditem = src._data[i]; 
			found = false;
			
			// ist das element schon da?
			for( size_t j=0; j < i; j++ )
			{
				// falls vorhanden, übernehmen
				if( src._data[j] == olditem )
				{	
					// die ältere kopie übernehmen
					_data[i] = _data[j];
					found =  true;				
					break;
				}
			}
				
			// nicht gefunden -> einbauen
			if( !found )
			{
				// klon per copy constructor...
				T* newitem = new T( *olditem ); 
				// speichern...
				_data[i] = newitem;
				
			}
		
		}		
		
		return *this;	
	}
	
	
	/**
		Schnelltest auf Leere :-)
	*/
	
	bool operator!()
	{
		return empty();
	}
	
	
	/**
		die Größe
	*/

	inline size_t size() const
	{
		return _data.size();
	}


	/**
		true für einen leeren Vector.
	*/

	inline bool empty() const
	{
		return (_data.size()==0);
	}


	/**
		Ändert die Größe des Vectors.
		@param newsize die neue Größe
	*/

	inline void resize( size_t newsize )
	{
		if( newsize > _data.size() )
			_data.resize( newsize );
	}

 
 	/**
		@returns den Eintrag an Position "index" sofern vorhanden.
		@throws out_of_range
	*/

	inline T& operator[]( size_t index  )
	{
		if( index < _data.size() )
			return *_data[index];
		log_fatal("ddmapmulti operator[ size_t index ]: out of range");
		// avoid warning
		T* t = new T();
		return *t;
	}

	
 	/**
		@returns den Eintrag an Position "index" sofern vorhanden. (const version)
		@param index die Elementposition
		@throws out_of_range
	*/
	
	inline const T& operator[]( size_t index  ) const
	{
		if( index >= _data.size() )
			log_fatal("ddmapmulti const operator[ size_t index ]: out of range");
		return *_data[index];		
	}

 	
	/**
		@returns Eine Referenz auf den Pointer (ja sowas geht) 
		an Position "index" sofern vorhanden
		( Ohne Ref. gibts ein "non L-Value in Assignment", mit gehen auch Sachen
		wie get_pointer(x) = new Bla(); Vorsicht! Altes Element vorher zerstören! )
		@param index die Elementposition		
		@throws out_of_range
	*/

	inline T*& get_pointer( size_t index  )
	{
		if( index < _data.size() )
			return _data[index];
		log_fatal("ddmapmulti get_pointer( size_t index  ): out of range");
	}
		
	
	/**
		@returns Eine Referenz auf den Pointer (ja sowas geht) 
		an Position "index" sofern vorhanden
		( Ohne Ref. gibts ein "non L-Value in Assignment", mit gehen auch Sachen
		wie get_pointer(x) = new Bla(); Vorsicht! Altes Element vorher zerstören! )
		@param index die Elementposition		
		@throws out_of_range
	*/

	inline const T*& get_pointer( size_t index  ) const
	{
		if( index < _data.size() )
			return _data[index];
		log_fatal("ddmapmulti get_pointer( size_t index  ): out of range");
	}
	
	
	/**
		"access by key": Ein Element über seinen Schlüssel finden, falls existent.
		@param key Der Schlüssel.
		@throws out_of_range
	*/

	inline T& operator[]( const ddstring& key )
	{
		ddmapindex::iterator pos = _index.find( key );
		if( pos == _index.end() )
			log_fatal( key+":multimap operator[]: key not found");
		return *_data[(*pos).second];
	}

	
	/**
		"access by key": Ein Element über seinen Schlüssel finden, falls existent. (const version)
		@param key Der Schlüssel.
		@throws out_of_range
	*/
	
	inline const T& operator[]( const ddstring& key ) const
	{
		ddmapindex::const_iterator pos = _index.find( key );
		if( pos == _index.end() )
			log_fatal( key+":multimap operator[]: key not found");
		return *_data[(*pos).second];
	}
	
	
	/**
		@returns Eine Referenz auf den Pointer (ja sowas geht) 
		an Position "index" sofern vorhanden
		( Ohne Ref. gibts ein "non L-Value in Assignment", mit gehen auch Sachen
		wie get_pointer(x) = new Bla(); Vorsicht! Altes Element vorher zerstören! )
		@param key Der Schlüssel.
		@throws out_of_range
	*/

	inline T*& get_pointer( const ddstring& key )
	{
		if( key.empty() )
			log_fatal("multimap get_pointer( const ddstring& key ): key empty");
		ddmapindex::iterator pos = _index.find( key );
		if( pos == _index.end() )
			log_fatal( key+":multimap get_pointer( const ddstring& key ): key not found");
		return _data[(*pos).second];
	}
	
	
	/**
		@returns Eine Referenz auf den Pointer (ja sowas geht) 
		an Position "index" sofern vorhanden
		( Ohne Ref. gibts ein "non L-Value in Assignment", mit gehen auch Sachen
		wie get_pointer(x) = new Bla(); Vorsicht! Altes Element vorher zerstören! )
		@param key Der Schlüssel.
		@throws out_of_range
	*/

	inline const T*& get_pointer( const ddstring& key ) const
	{
		if( key.empty() )
			log_fatal("multimap get_pointer( const ddstring& key ): key empty");
		ddmapindex::iterator pos = _index.find( key );
		if( pos == _index.end() )
			log_fatal( key+":multimap get_pointer( const ddstring& key ): key not found");
		return _data[(*pos).second];
	}
	
	
	/**
		Ein Pointer-Element hinten anhängen, ohne Indexeintrag.
		@param item Das Element.
		@return Die Elementposition des neuen Eintrags.
	*/

	size_t add( T* item )
	{
		_data.push_back( item );
		return _data.size()-1;
	}

	
	/**
		Ein Referenz-Element hinten anhängen, ohne Indexeintrag.
		Das Element muss kopiert werden, also den copy-contructor unterstützen.
		Vorsicht: Das macht mapmulti unbrauchbar für non-copy klassen.
		@param item Das Element.
		@return Die Elementposition des neuen Eintrags.
	*/

	size_t add( const T& item )
	{
		// klon-versuch per copy-constructor
		_data.push_back( new T( item ) );
		return _data.size()-1;
	}
	
	
	/**
		Ein Pointer-Element über einen Schlüssel einfügen. Falls an der gewünschten 
		Position schon ein Element vorhanden ist, wird der vorhandene Eintrag
		nochmal hinten abgehängt, ist dann also mehrfach vorhanden.
		
		OBACHT! Es wird der alte Eintrag verwendet, der neue wird gekillt !
		Das ist etwas fragwürdig!
		
		@param item the element
		@param key  the key of the new entry.
		@return Die Elementposition des neuen Eintrags.
	*/

	size_t add_by_key( const ddstring& key, T* item )
	{
		ddmapindex::iterator pos = _index.find( key );
		if( pos == _index.end() )
		{
			_index[key] = add( item );
		}
		else
		{	
			// Altes und neues Element sollen physikalisch gleich sein,
			// also müssen sind auch äquivalent sein.
			if( *_data[ (*pos).second] != *item ) 
				log_fatal( key + ":add_by_key( const ddstring& key, T* item : Elements are not equal!");
			
			// vorhandenen Pointer doppeln
			_data.push_back( _data[ (*pos).second ] );
			
			// zum verständnis: der index wird _nicht_ aktualisiert, bei index_of
			// kommt der index des _original_ elements zurück, da die beiden ja
			// physikalisch dasselbe sind.
			
			// item ist in unsere Zuständigkeit übergeben worden...
			// MEMLEAK verhindern! item löschen! 
			delete item; 
			
		}
		return _data.size()-1;
	}
	
	
	/**
		Ein Referenz-Element über einen Schlüssel einfügen. Falls an der gewünschten 
		Position schon ein Element vorhanden ist, wird der vorhandene Eintrag
		nochmal hinten abgehängt, ist dann also mehrfach vorhanden.
		
		Das Element muss kopiert werden, also den copy-contructor unterstützen.
		Vorsicht: Das macht mapmulti unbrauchbar für non-copy klassen.
		
		@param item the element
		@param key  the key of the new entry.
	*/

	void add_by_key( const ddstring& key, const T& item )
	{
		ddmapindex::iterator pos = _index.find( key );
		if( pos == _index.end() )
		{
			_index[key] = add( new T( item ) );
		}
		else
		{
			// Altes und neues Element sollen physikalisch gleich sein,
			// also müssen sind auch äquivalent sein.
			if( item != *_data[ (*pos).second] ) 
				log_fatal( key+":add_by_key( const ddstring& key, T* item : Elements are not equal!");
		
			// vorhandenen Pointer doppeln
			_data.push_back( _data[ (*pos).second ] );
			
			// zum verständnis: der index wird _nicht_ aktualisiert, bei index_of
			// kommt der index des _original_ elements zurück, da die beiden ja
			// physikalisch dasselbe sind.
		
		}
	}
	
	/**
		@return Die Feldposition für den Schlüssel "key", falls vorhanden, oder -1.
	*/

	inline int index_of( const ddstring& key ) const
	{
		return _index.index_of( key );
	}

		
	/**
		@return true wenn der Schlüssel "key" existiert.
	*/

	inline bool contains_key( const ddstring& key ) const
	{
		return _index.contains_key( key );
	}

	
	/**
  	Eine Indexposition mit Schlüssel einfügen.
		@param key Der Schlüssel
		@param index Die Indexposition
  */

  void add_key(  const ddstring& key, size_t index )
  {
		_index.add_key( key, index );
  }
	
	
	/**
		Einen "Alias Schlüssel" für einen bereits vorhandenen Schlüssel 
		einfügen
		@param key Der ursprüngliche Schlüssel
		@param alias Der zusätzliche Schlüssel
	*/

  bool add_alias( const ddstring& key, const ddstring& alias )
  {
    ddmapindex::const_iterator pos = _index.find( key );
    if( pos == _index.end() )
			return false;
    _index[ alias ] =(*pos).second;
    return true;
  }

	
	/**
		Einen vorhandenen Schlüssel durch einen neuen ersetzen
		@param oldkey Der alte Schlüssel
		@param newkey Der neue Schlüssel
		@return die Index-Position des Schlüssels
	*/
	
	int replace_key( const ddstring& oldkey, const ddstring& newkey )
	{
		int idx = _index.index_of( oldkey );
		if( idx<0 || oldkey == newkey )
			return idx;
		_index.erase( oldkey );
		_index[ newkey ] = idx;
		return idx;
	}
	
	
	/**
		Löscht den Eintrag zum angegebenen Schlüssel.
		@return true wenn der Schlüssel vorhanden war.
	*/

	/*
 
 	bool erase( const ddstring& key )
	{
		int idx = index_of( key );
		if( idx<0 )
			return false;
		return erase( (size_t) idx );
	}
	
	*/
	
	/**
		Löscht _alle_den Einträge zur angegebenen Indexposition.
		@return true wenn die Indexposition vorhanden war.
	*/
	/*
	virtual bool erase( size_t index )
	{
	 	if( index >= _data.size() )
			return false;
		// eintrag merken
		T* item = _data[index];
		int idx;
		while( ( idx=find_entry( item ) ) >-1 )
		{
			delete _data[idx];
			_data.erase( _data.begin()+idx );
		}
		
		// index updaten
		_index.update( index );
		return true;
	}
	*/
	/**
		Löscht den Eintrag zur angegebenen Indexposition.
		@return true wenn die Indexposition vorhanden war.
		
			single map version:
	*/
	/*
	virtual bool erase( size_t index )
	{
	 	if( index >= _data.size() )
			return false;
		// eintrag löschen
		delete _data[index];
		_data.erase( _data.begin()+index );
		// index updaten
		_index.update( index );
		return true;
	}
	*/
	
	
	/**
		Alle Einträge löschen
	*/

	void clear()
	{	
		// die Elementpointer können _mehrfach_ in _data vorkommen,
		// dürfen aber nur einmal deletet werden, daher null setzen
		
		for( size_t i=0; i<_data.size(); ++i )
		{
			T* item = _data[i]; 
			if( !item )
				continue;
			// ab i kann item wieder vorkommen, also suchen,
			// aber nicht nochmal deleten sondern nullen
			for( size_t j=i+1; j < _data.size(); j++ )
			{
				if( _data[j] == item )
					_data[j] = 0;
			}
			delete item;
		}
		_data.clear();
		_index.clear();
	}
	
	
	
	/**
		Gibt den internen Indexer zurück
		@see ddmapindex
	*/

	const ddmapindex& get_index() const
	{
		return _index;
	}


	/**
		Baut einen ddstring aus diesem Vector indem alle Einträge
		mit ";" concateniert werden.
		@see join();
	*/

	virtual ddstring to_string() const
	{
		return join();
	}

	
	/**
		Baut einen ddstring aus diesem Vector indem alle Einträge
		concateniert werden.
		Nur Prototyp!
		@throws runtime_error
	*/
	
	virtual ddstring join( const ddstring& sep = "", size_t from=0, size_t to = ddstring::npos )	const
	{
				
		ddstring tmp;
		size_t tmp_size = size();
		if( to != ddstring::npos )
			tmp_size = to;
		if( from > tmp_size || tmp_size > size() )
			log_fatal( "ddmapmulti::join: index 'from' out of range!" );
	
		for( size_t i = from; i < tmp_size; i++ )
		{
			tmp.append ( _data[i]->to_string() );
			if( !sep.empty() && i != tmp_size - 1)
				tmp.append ( sep );
		}
		return tmp;
	}

	
	
	virtual void dump() const
  {
  	for( size_t i=0; i<size(); ++i )
  		(*this).dump();
  }	
  
protected:
	
	int find_entry( T* entry )
	{
		for( size_t i=0; i<this->_data.size(); ++i )
		{	
			if( _data[i] == entry )
				return i;
		}
		return -1;
	}
		
	vector<T*> 	_data;
	ddmapindex 	_index;

};

#endif
