/*
 *  digester.h
 *  
 *
 *  Created by Andreas Vox on 02.06.06.
 *  Copyright 2006 under GPL2. All rights reserved.
 *
 */



#ifndef DIGESTER_H
#define DIGESTER_H

#include <cassert>
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <typeinfo>

#include "desaxe_conf.h"
#include "saxhandler.h"


//#define DESAXE_DEBUG 1

namespace desaxe {

class Action;
class RuleState;


namespace PRIVATE {

	class VarPtr
	{
	public:
		void* ptr { nullptr };
		std::string type;
	};


	template<class ObjType>
	inline
	VarPtr mkcell(ObjType* obj)
	{
		VarPtr result;
		result.ptr = obj;//	result.ptr = const_cast<void*>(static_cast<const void*>(obj)); //??
		result.type = typeid(obj).name();
		return result;
	}


	template<class ObjType>
	inline
	void chkcell(const VarPtr& cell, std::vector<VarPtr>* stack = nullptr)
	{
		ObjType* dummy = nullptr;
		if( cell.type != typeid(dummy).name() )
		{
			std::cerr << "requested type '" << typeid(dummy).name() << "' doesn't match cell type '" << cell.type << "'\n";
			if (stack)
			{
				int i=0;
				std::vector<VarPtr>::iterator it;
				for (it = stack->begin(); it != stack->end(); ++it)
				{
					std::cerr << i++ << "\t" << (*it).type << "\t" << (*it).ptr << "\n";
				}
			}
			assert (false);
		}
	}



class Patch
{
public:
	// internal linked list
	Patch* next { nullptr };
	Patch(Patch* nxt) : next(nxt) {}

	virtual void run(VarPtr lnk) = 0;
	virtual ~Patch() {}
};


} // namespace PRIVATE	



/**
   Digester helps you to create C++ objects from a SAX stream.
   All you have to do is register actions for patterns of element tags
   you expect in your XML stream. These actions can create new objects, set
   attributes, or call methods. Actions operate on intermediate objects which 
   are hold on a stack maintained by Digester.  
 */
class Digester : public SaxHandler {
public:
	Digester();
	Digester& operator=(const Digester& other);
	virtual ~Digester();
	void reset();
	void addRule(const Xml_string& pattern, Action action);

	void parseFile(const Xml_string& filename);
	void parseMemory(const char* data, unsigned int length);
	void parseMemory(const Xml_string& data);

	template<class ObjType>
		ObjType*  result();	
	int nrOfErrors() const;
	const Xml_string &getError(int i) const;

// called by SAX parser:
	void beginDoc();
	void endDoc();
	void begin(const Xml_string& tag, Xml_attr attr);
	void end(const Xml_string& tag);
	void chars(const Xml_string& text);

// used by actions:
	void fail();
	void error(const Xml_string& msg);

	template<class ObjType>
	ObjType*  top(unsigned int offset = 0);

	template<class ObjType>
	ObjType*  bottom(unsigned int offset = 0);

	template<class ObjType>
	void setResult(	ObjType* res );

	void pop();
	void popn(unsigned int number);

	template<class ObjType>
	void push(ObjType* obj);

//  used to resolve idrefs and for general storage	
	template<class ObjType>
		ObjType*  lookup(const Xml_string& idref);	
	template<class ObjType>
		void store(const Xml_string& idref, ObjType* res );

//  used to resolve idrefs if use is before definition
	template<class LinkType>
		void patchCall(const Xml_string& idref, void (*fun)(LinkType*) );
	template<class ObjType, class LinkType>
		void patchInvoke(const Xml_string& idref, ObjType* obj, void (ObjType::*fun)(LinkType*) );

	// used to insert "/" where necessary
	static Xml_string concat(const Xml_string& pattern1, const Xml_string& pattern2);

private:
	RuleState* 
		m_state;

	std::vector<PRIVATE::VarPtr> 
		m_objects;

	std::map<Xml_string, PRIVATE::VarPtr> 
		m_storage;

	std::map<Xml_string, PRIVATE::Patch*>
		m_patches;

	PRIVATE::VarPtr 
		m_result_;

	std::vector<Xml_string> 
		m_errors;
};



template<class ObjType>
inline
ObjType*  Digester::top(unsigned int offset) 
{
#ifdef DESAXE_DEBUG
	std::cerr << "top(" << offset << ") of " << objects.size() << "\n";
#endif
	unsigned int count = m_objects.size();
	assert (offset < count); 
	PRIVATE::chkcell<ObjType>(m_objects[count - offset - 1], &m_objects);
#ifdef DESAXE_DEBUG
	std::cerr << "stack-> " << static_cast<ObjType*>(objects[count - offset - 1].ptr) << "\n";
#endif
	
	return static_cast<ObjType*>(m_objects[count - offset - 1].ptr);
}


template<class ObjType>
inline
ObjType*  Digester::bottom(unsigned int offset) 
{ 
#ifdef DESAXE_DEBUG
	std::cerr << "bottom(" << offset << ") of " << objects.size() << "\n";
#endif
	//unsigned int count = objects.size();
	assert (offset < m_objects.size());
	PRIVATE::chkcell<ObjType> (m_objects[offset]);
	return static_cast<ObjType*>(m_objects[offset].ptr);
}


template<class ObjType>
inline
ObjType*  Digester::result() 
{ 
	ObjType* dummy = nullptr;
	if (m_result_.type != typeid(dummy).name())
		return nullptr;
#ifdef DESAXE_DEBUG
	std::cerr << "result-> " << static_cast<ObjType*>(result_.ptr) << "\n";
#endif
	return static_cast<ObjType*>(m_result_.ptr);
}


template<class ObjType>
inline
void Digester::setResult(ObjType* res) 
{ 
#ifdef DESAXE_DEBUG
	std::cerr << res << " ->result\n";
#endif
	m_result_ = PRIVATE::mkcell(res);
}


inline
void Digester::pop()
{
	assert (1 <= (unsigned int) m_objects.size());
	m_objects.pop_back();
}

inline
void Digester::popn(unsigned int number)
{
	unsigned int count = (unsigned int) m_objects.size();
	assert (number <= count);
	m_objects.resize(count - number);
}


template<class ObjType>
inline
void Digester::push(ObjType* obj)
{
#ifdef DESAXE_DEBUG
	std::cerr << "stack<- " << obj << "\n";
#endif
	m_objects.push_back(PRIVATE::mkcell(obj));
}


// now lookup / store / patch business

namespace PRIVATE {
	
	template <class LinkType>
	struct Patch1 : public Patch
	{
		typedef void (*FunType1)(LinkType*);
		FunType1 fun;

		Patch1(FunType1 fn, Patch* nxt = nullptr) : Patch(nxt), fun(fn) {}

		void run(VarPtr link) 
		{ 
			fun( static_cast<LinkType*>(link.ptr) ); 
		}
	};
	

	template <class ObjType, class LinkType>
		struct Patch2 : public Patch
	{
		typedef void (ObjType::*FunType2)(LinkType*);
		ObjType* obj { nullptr };
		FunType2 fun;
		
		Patch2(ObjType* ob, FunType2 fn, Patch* nxt = nullptr) : Patch(nxt), obj(ob), fun(fn) {}

		void run(VarPtr link)
		{
			(obj->*fun)( static_cast<LinkType*>(link.ptr) ); 
		}
	};


	inline
	void runPatches(Patch*& list, VarPtr link)
	{
		while (list)
		{
			Patch* nxt = list->next;
			list->run(link);
			delete list;
			list = nxt;
		}
	}

	inline
	void deletePatches(std::map<Xml_string, Patch*>& patches)
	{
		std::map<Xml_string, Patch*>::iterator it;
		for (it = patches.begin(); it != patches.end(); ++it)
		{
			Patch* list = it->second;
			while (list)
			{
				Patch* nxt = list->next;
				delete list;
				list = nxt;
			}
		}
		patches.clear();
	}


//	template<> class Patch1<void>;
//	template<> class Patch2<VarPtr,void>;
	
} //namespace PRIVATE



template<class ObjType>
inline
ObjType*  Digester::lookup(const Xml_string& idref) 
{ 
	using namespace PRIVATE;

	std::map<Xml_string, VarPtr>::iterator cell = m_storage.find(idref);
	if (cell == m_storage.end())
	{
#ifdef DESAXE_DEBUG
		std::cerr << "lookup[" << idref << "]-> nullptr\n";
#endif
		return nullptr;
	}
	else
	{
		chkcell<ObjType> (cell->second);
#ifdef DESAXE_DEBUG
		std::cerr << "lookup[" << idref << "]-> " << static_cast<ObjType*>(cell->second.ptr) << "\n";
#endif
		return static_cast<ObjType*>(cell->second.ptr);
	}
}



template<class ObjType>
inline
void Digester::store(const Xml_string& idref, ObjType* obj) 
{
	using namespace PRIVATE;
#ifdef DESAXE_DEBUG
	std::cerr << "store[" << idref << "] <- " << obj << "\n";
#endif
	m_storage[idref] = mkcell(obj);
	runPatches(m_patches[idref], m_storage[idref]);
}



template<class LinkType>
void Digester::patchCall(const Xml_string& idref, void (*fun)(LinkType*) )
{
	using namespace PRIVATE;

	std::map<Xml_string, VarPtr>::iterator cell = m_storage.find(idref);
	if (cell == m_storage.end())
	{
		m_patches[idref] = new Patch1<LinkType>(fun, m_patches[idref] );
	}
	else
	{
		Patch1<LinkType>(fun).run(cell->second);
	}
}


template<class ObjType, class LinkType>
void Digester::patchInvoke(const Xml_string& idref, ObjType* obj, void (ObjType::*fun)(LinkType*) )
{
	using namespace PRIVATE;

	std::map<Xml_string, VarPtr>::iterator cell = m_storage.find(idref);
	if (cell == m_storage.end())
	{
		m_patches[idref] = new Patch2<ObjType,LinkType>(obj, fun, m_patches[idref] );
	}
	else
	{
		Patch2<ObjType,LinkType>(obj, fun).run(cell->second);
	}
}

} // namespace desaxe

#endif
