
#include <typeinfo>
#include <iostream>

#ifdef WIN32
#define usleep Sleep
#include <windows.h>
#endif


#include "mysql/mysql.h"
#include "mplugin.h"
#include "../MonitorLogging.h"

using namespace std ;

enum fieldsource {mysql=0,resultset=1} ;

typedef struct
{
	string value ;
	fieldsource source ;
} FieldInfo ;

typedef map<string,FieldInfo*> MappingInfo ;
typedef pair <string,FieldInfo*> PairMapping ;

// Class PlugInFun inherits from PlugIn
// and shows the world when one is created/destroyed
// and supplies a Show() method that announces its name


class MonitorPlugInMySQL : public MonitorPlugIn
{
 public:
 	MYSQL m_mysql ;
 	std::string hostname ;
 	std::string username ;
 	std::string password ;
 	std::string database ;
 	unsigned int port ;
	unsigned int ssl;
	std::string ssl_cacert;
	std::string ssl_cert;
	std::string ssl_key;
 	bool m_bConnected ;

 	MappingInfo fmsMapping ;
 	MappingInfo zveiMapping ;
 	MappingInfo pocsagMapping ;

 	std::string fmsTable;
 	std::string zveiTable;
 	std::string pocsagTable;

	MonitorPlugInMySQL()
	{
 		m_bConnected=false ;
	}

	virtual ~MonitorPlugInMySQL()
	{
	}

	virtual void Show()
	{
		LOG_INFO("MonitorMySQLPlugin successfully loaded" )
	}

	std::string escape_string(std::string text)
	{
		const unsigned int MAXLEN=255 ;
		char result[MAXLEN+1] ;

		memset(result,0,MAXLEN+1) ;
		mysql_real_escape_string(&m_mysql,result,text.c_str(),text.size()>MAXLEN ? MAXLEN : text.size()) ;
		return string(result) ;
	}

	virtual bool processResult(class ModuleResultBase *pRes)
	{
		LOG_DEBUG("mysql: processing Result...")  

		if (m_bConnected==false)
		{
			return false ;
		}

		int pingcounter=0 ;
		while (mysql_ping(&m_mysql) && pingcounter<100)
		{
			usleep(100) ;
			pingcounter++ ;
			LOG_INFO("mysql connection lost ... trying reconnect"  )
		}

		if (mysql_ping(&m_mysql))
		{
			LOG_ERROR(" unable to reconnect to mysql database" )
			return false ;
		}

		if (pingcounter>0)
		{
			LOG_INFO("mysql: connection re-established"  )
		}

		// wenn wir latin1 einfuegen sollten wir das mysql auch mitteilen!
		mysql_query(&m_mysql, "SET NAMES latin1");

		if ((*pRes)["typ"]=="fms")
		{
			std::string insertString ;
			insertString = createInsertString(pRes,fmsMapping,fmsTable);
			mysql_query(&m_mysql,insertString.c_str()) ;
		} else if ((*pRes)["typ"]=="pocsag")
		{
			std::string insertString ;
			insertString = createInsertString(pRes,pocsagMapping,pocsagTable);
			mysql_query(&m_mysql,insertString.c_str()) ;
		}else if ((*pRes)["typ"]=="zvei")
		{
			std::string insertString;
			insertString = createInsertString(pRes,zveiMapping,zveiTable);
			mysql_query(&m_mysql,insertString.c_str()) ;
		}


		return true ;
	}

	virtual bool initProcessing(class MonitorConfiguration* configPtr,XMLNode config)
	{
		mysql_init(&m_mysql) ;

		#if (MYSQL_VERSION_ID>50013)
		/* for mysql V5.0.3+ is auto_reconnect disabled by default*/
		my_bool reconnect = 1;
		mysql_options(&m_mysql, MYSQL_OPT_RECONNECT, &reconnect);
		#endif

		hostname=getNodeText(config,"hostname","localhost") ;
		username=getNodeText(config,"username","root") ;
		password=getNodeText(config,"password","rootpw") ;
		database=getNodeText(config,"database","demo") ;
		port=getNodeInt(config,"port",3306) ;
		ssl=getNodeInt(config,"ssl",0);
		ssl_cacert=getNodeText(config,"ssl-cacert","0");
		ssl_cert=getNodeText(config,"ssl-cert","0");
		ssl_key=getNodeText(config,"ssl-key","0");
		std::string logFile=getNodeText(config,"logfile","screen") ;
		std::string logLevel=getNodeText(config,"loglevel","INFO") ;
		#ifdef WIN32
		if (!(logFile=="screen"))
		{
			FILE* pFile = fopen(logFile.c_str(), "a");
			Output2FILE::Stream() = pFile;

		}
		FILELog::ReportingLevel() = FILELog::FromString(logLevel);
		LOG_INFO("logging started")
		#endif

		if (ssl == 1 && ssl_cacert != "0" && ssl_cert != "0" && ssl_key != "0")
		{
			mysql_ssl_set(&m_mysql,
				ssl_key.c_str(),
				ssl_cert.c_str(),
				ssl_cacert.c_str(),
				NULL,
				NULL);
			LOG_INFO("mySQL ssl support configured with key=" << ssl_key << " and cert=" << ssl_cert << " and cacert=" << ssl_cacert)
		}

		// Parameter parsen
		parseParameter(config) ;
		if (mysql_real_connect(	&m_mysql,
								hostname.c_str(),
								username.c_str(),
								password.c_str(),
								database.c_str(),
								port,
								NULL,		// Unix Socket
								0			// Options
								) ==NULL)
		{
			LOG_ERROR("Could not connect to database \"" << database << "\" on host " << hostname << " with username=\"" << username << "\""  )
			m_bConnected=false ;
		} else
		{
			LOG_INFO("successfully connected to mysql database " << database << " on host " << hostname << " with username=\"" << username << "\""  )
			m_bConnected=true ;
		}
		return m_bConnected ;
	} ;

	virtual bool quitProcessing() {return true;} ;

	void parseParameter(XMLNode config)
	{
		XMLNode mappingNode ;

		int nMapping=config.nChildNode("mapping");

		for (int num=0; num<nMapping ; ++num)
		{
			if (!((mappingNode=config.getChildNode("mapping",num))).isEmpty())
			{
				std::string typ=mappingNode.getAttribute("typ") ;

				if (typ=="fms")
				{
					readMappings(mappingNode,fmsMapping,fmsTable) ;
				} else if (typ=="pocsag")
				{
					readMappings(mappingNode,pocsagMapping,pocsagTable) ;
				} else if (typ=="zvei")
				{
					readMappings(mappingNode,zveiMapping,zveiTable) ;
				}
			}
		}
	}

	void readMappings(XMLNode config,MappingInfo &mappingInfo,std::string &dbTable)
	{
		XMLNode mappingNode ;
		std::string table=getNodeText(config,"table","status") ;
		std::string name, source, value ;

		LOG_DEBUG("reading mapping info")
		LOG_DEBUG("table=" << table)

		dbTable=table;
		FieldInfo *pFieldInfo ;

		int nMapping=config.nChildNode("field");

		for (int num=0; num<nMapping ; ++num)
		{
			if (!((mappingNode=config.getChildNode("field",num))).isEmpty())
			{

				name=mappingNode.isAttributeSet("name") ? mappingNode.getAttribute("name") : "" ;
				source=mappingNode.isAttributeSet("source") ? mappingNode.getAttribute("source") : "" ;
				value= mappingNode.getText() ;
				if (name.empty()) ThrowMonitorException("MySQL Konfiguration: Kein Feldname vergeben ! ") ;
				if (value.empty()) ThrowMonitorException("MySQL Konfiguration: Kein Feldwert vergeben ! ") ;

				LOG_DEBUG("Feld: " << name << " / " << source << ":" << value)  
				pFieldInfo=new FieldInfo ;
				pFieldInfo->value=value ;
				if (source=="mysql")
				{
					pFieldInfo->source=mysql ;
				} else
				{
					pFieldInfo->source=resultset ;
				}
				mappingInfo.insert( PairMapping(name,pFieldInfo));
			}
		}
	}

	std::string createInsertString(class ModuleResultBase *pRes,MappingInfo mapping,std::string table)
	{
		std::string insertString="";
			std::string valueString="" ;
			MappingInfo::iterator i ;
			FieldInfo* pFieldInfo ;
			std::string fieldName="" ;

			insertString=" insert into " + table +  " (" ;
			for (i=mapping.begin();i!=mapping.end();i++)
			{
				fieldName= i->first ;
				pFieldInfo= i->second ;

				LOG_DEBUG("field:" << fieldName << " | value=" << pFieldInfo->value) 
				if (i!=mapping.begin())
				{
					insertString+="," ;
					valueString+="," ;
				}

				insertString+=fieldName ;
				if (pFieldInfo->source==mysql)
				{
					valueString+= pFieldInfo->value ;
				} else
				{
					valueString+="\"" +escape_string((*pRes)[pFieldInfo->value])+"\"" ;
				}
			}
			insertString+=") values (" ;
			insertString+=valueString + ")" ;

			LOG_DEBUG("Insertstring:" << insertString) 
			return insertString ;
	}

};

//
// The PlugInFunFactory class inherits from PlugInFactory
// and creates a PlugInFun object when requested.
//


class MonitorPlugInMySQLFactory : public MonitorPlugInFactory
{
 public:
	MonitorPlugInMySQLFactory()
	{
	}

	~MonitorPlugInMySQLFactory()
	{
	}

	virtual MonitorPlugIn * CreatePlugIn()
	{
		return new MonitorPlugInMySQL() ;
	}
};


//
// The "C" linkage factory0() function creates the PlugInFunFactory
// class for this library
//

DLL_EXPORT void * factory0( void )
{
	return new MonitorPlugInMySQLFactory;
}
