/*
*  
*  $Id: integrationcontroller.cpp $
*  Ginkgo CADx Project
*
*  Copyright 2008-12 MetaEmotion S.L. All rights reserved.
*  http://ginkgo-cadx.com
*
*  This file is licensed under LGPL v3 license.
*  See License.txt for details
*
*
*/
#include <set>
#include <ctime>

#include <wx/statbmp.h>
#include <wx/bmpbuttn.h>

#include "integrationcontroller.h"
#include "controladorlog.h"
#include "controladorextensiones.h"
#include "controladoreventos.h"
#include "controladorcomandos.h"
#include "controladorhl7.h"
#include "configurationcontroller.h"
#include <api/integration/dict.h>
#include <main/controllers/controladorpermisos.h>
#include <main/gui/import/wxwizardimportacionginkgo.h>
#include <main/gui/acquisition/dialogoadquisicion.h>
#include <main/gui/mainwindow/ventanaprincipal.h>
#include <main/entorno.h>
#include <commands/comandopacs.h>
#include <eventos/integration.h>
#include <main/controllers/parserxmlenglish.h>
#include <main/controllers/parserxmlspanish.h>
#include <wx/filename.h>
#include <wx/sstream.h>
#include <wx/tokenzr.h>

GIL::IntegrationController* GIL::IntegrationController::m_pInstance = 0;

GIL::IXMLIntegrationParser::~IXMLIntegrationParser()
{
}

GIL::IntegrationController::IntegrationController() 
{
	GNC::GCS::ControladorEventos::Instance()->Registrar(this, GNC::GCS::Events::HL7SendRequestEvent());

	GNC::GCS::Ptr<GIL::IXMLIntegrationParser> spanish(new GIL::ParserXMLSpanish());
	std::list<std::string> keys = spanish->GetKeys();
	for (std::list<std::string>::iterator it = keys.begin(); it != keys.end(); ++it) {
		RegisterParser((*it), spanish);
	}
	GNC::GCS::Ptr<GIL::IXMLIntegrationParser> english(new GIL::ParserXMLEnglish());
	keys = english->GetKeys();
	for (std::list<std::string>::iterator it = keys.begin(); it != keys.end(); ++it) {
		RegisterParser((*it), english);
	}
}

GIL::IntegrationController::~IntegrationController()
{
	GNC::GCS::ControladorEventos::Instance()->DesRegistrar(this);
	m_mapParsers.clear();
}

GIL::IntegrationController* GIL::IntegrationController::Instance()
{
	if (m_pInstance == NULL) {
		m_pInstance = new IntegrationController();
	}
	return m_pInstance;
}

void GIL::IntegrationController::FreeInstance()
{
	if (m_pInstance != NULL) {
		delete m_pInstance;
		m_pInstance = NULL;
	}
}


void GIL::IntegrationController::RegisterParser(const std::string& xmlKey, const GNC::GCS::Ptr<IXMLIntegrationParser>& pParser)
{
	if (m_mapParsers.find(xmlKey) != m_mapParsers.end()) {
		throw IntegrationException(_Std("Fatal error! you can't register two parsers with same  key").c_str());
	}
	m_mapParsers[xmlKey] = pParser;
}

void GIL::IntegrationController::UnRegisterParser(const std::string& xmlKey) 
{
	if (m_mapParsers.find(xmlKey) != m_mapParsers.end()) {
		m_mapParsers.erase(m_mapParsers.find(xmlKey));
	}
}



bool GIL::IntegrationController::Process(GIL::IntegrationModelList& models)
{

	unsigned int numProcessed = 0;

	typedef GIL::IntegrationModelList::iterator TIteradorModelos;
	for (TIteradorModelos itModelo = models.begin(); itModelo != models.end(); ++itModelo) {
		GNC::GCS::Ptr<GIL::IModeloIntegracion> modelo = (*itModelo);
		if (modelo->atendido) {
			numProcessed++;
			continue;
		}
		else {
			modelo->atendido = true;
		}
		if(modelo->accion == GIL::IModeloIntegracion::TA_Dicomizar) {
			//se busca el dicomizador del modulo indicado, habra un mapa que transformara sid en uids de modulo
			std::string uidModulo("");
			if (modelo->idPlantilla != "") {
				uidModulo = GetUID(modelo->idPlantilla);
				if (GNC::ControladorExtensiones::Instance()->ObtenerModulo(uidModulo) == NULL) {
					throw GIL::IntegrationException("No existe el modulo asociado", "Modelo", true);
				}
			}
			bool noerror = false;

			try{
				GNC::GUI::wxWizardImportacionGinkgo ib(NULL, modelo, uidModulo);
				ib.ShowModal();
				noerror = ib.IsOK();
			}
			catch(GIL::DICOM::I2DException& i){
				LOG_ERROR("GIL/WF/Importar", (const std::string)i);
			}
			catch(std::exception& ex1){
				LOG_ERROR("GIL/WF/Importar", std::string("Error al importar: ") + ex1.what());
			}
			catch (...)
			{
				LOG_ERROR("GIL/WF/Importar", std::string("Error al importar: Error interno"));
			}
			if (noerror) {
				GNC::Entorno::Instance()->GetVentanaPrincipal()->MostrarVentana();
				numProcessed++;
			}
		}
		else if(modelo->accion == GIL::IModeloIntegracion::TA_Obtener) 
		{
			GADAPI::ComandoPACSParams * pParams = NULL;
			std::string tag;
			if (!modelo->TagsDICOMOverwrite.getTag("0008|0052", tag)) {
				//show only acquisition form
				GNC::Entorno::Instance()->GetVentanaPrincipal()->MostrarVentana();	
				GNC::GUI::DialogoAdquisicion::Instance()->Show(modelo->GlobalVariables.GetValue(GKDI_PATIENT_ID), modelo->GlobalVariables.GetValue(GKDI_GLOBAL_PACS_RETRIEVE_SID));
				numProcessed++;
			} else {
				GIL::DICOM::DicomDataset base = modelo->TagsDICOMOverwrite;
				pParams = new GADAPI::ComandoPACSParams(modelo->GlobalVariables.GetValue(GKDI_GLOBAL_PACS_RETRIEVE_SID), base);
				pParams->m_informar = true;

				GADAPI::ComandoPACS* pComandoPACS = new GADAPI::ComandoPACS(pParams);
				GNC::Entorno::Instance()->GetControladorComandos()->ProcessAsync("Descargando PACS...",pComandoPACS, NULL);
				GNC::Entorno::Instance()->GetVentanaPrincipal()->MostrarVentana();
				numProcessed++;
			} 
		}
		else if(modelo->accion == GIL::IModeloIntegracion::TA_Open) 
		{
			for (GIL::IModeloIntegracion::ListOfPaths::iterator itFile = modelo->Files.begin(); itFile != modelo->Files.end(); ++itFile) 
			{
				wxFileName fileName(wxString::FromUTF8((*itFile).c_str()));
				if (fileName.IsRelative()) {
					fileName.MakeAbsolute();
				}						
				GNC::Entorno::Instance()->GetVentanaPrincipal()->MostrarVentana();	
				GNC::Entorno::Instance()->GetVentanaPrincipal()->EjecutarParametro(fileName.GetFullPath());
			}
			numProcessed++;
		}
	}
	return numProcessed == models.size();
}


typedef std::list<const GIL::IMapOfVarsModel*> TMapStack;
typedef std::map<std::string, long> TSeqMap;

void Parse(std::ostringstream& os, GIL::IModeloIntegracion* integrationModel, wxXmlNode* root, GIL::IStudyModel* study, int studyIdx, GIL::ISeriesModel* serie, int serieIdx, int imageIdx, TMapStack& mapStack, TSeqMap& seqMap) 
{
	if (integrationModel == NULL || root == NULL) {
		return;
	}
	for (wxXmlNode* child = root->GetChildren(); child != NULL; child = child->GetNext()) {
		if (child->GetName() == wxT("seg")) {
			for (wxXmlNode* subChild = child->GetChildren(); subChild != NULL; subChild = subChild->GetNext()) {
				if ( (subChild->GetType() == wxXML_CDATA_SECTION_NODE) || (subChild->GetType() == wxXML_TEXT_NODE) ) {
					os << std::string(subChild->GetContent().ToUTF8());					
				}
				else {
					if (subChild->GetName() == wxT("idx")) {
						std::string content(subChild->GetAttribute(wxT("expr"), wxEmptyString).ToUTF8());

						long acc = 0;

						std::string::size_type lastOpIdx = 0;
						std::string::size_type currOpIdx;
						int currOp = ' ';

						std::string term;
						bool pending = true;
						do {
							std::string::size_type currOpIdx1 = content.find('+', lastOpIdx);
							std::string::size_type currOpIdx2 = content.find('*', lastOpIdx);
							if (currOpIdx1 < currOpIdx2) {
								currOp = '+';
								currOpIdx = currOpIdx1;
							}
							else {
								currOpIdx = currOpIdx2;
								if (currOpIdx2 == std::string::npos) {
									currOp = ' ';
									pending = false;
								}
								else {
									currOp = '*';
								}
							}

							term = content.substr(lastOpIdx, currOpIdx);
							long termVal = 0;

							if (term == "study:idx") {
								termVal = studyIdx;		
							}
							else if (term == "serie:idx") {
								termVal = serieIdx;
							}
							else if (term == "image:idx") {
								termVal = imageIdx;
							}
							else {
								std::string::size_type dotPos = term.find('.');
								if (dotPos != std::string::npos) {
									std::string seqName = term.substr(0, dotPos);
									std::string seqMethod = term.substr(dotPos + 1);
									for (TSeqMap::iterator it = seqMap.begin(); it != seqMap.end(); ++it) {
										const std::string& siName = (*it).first;
										if (seqName == siName) {

											long& val = (*it).second;

											if (seqMethod == "curval") {
												termVal = val;
												break;
											}
											else if (seqMethod == "nextval") {												
												termVal = ++val;
												break;
											}
											else {
												break;
											}
										}

									}
								}
								else {	// Should be a integer
									std::istringstream is (term);
									is >> termVal;
								}
							}

							switch (currOp) {
								case '*':
									acc *= termVal;
									break;
								default:
									acc += termVal;
									break;
							}

							lastOpIdx = currOpIdx + 1;

						} while (pending);

						os << acc;
					}
					else if (subChild->GetName() == wxT("repl")) {
						std::string key = std::string(subChild->GetAttribute(wxT("key"), wxEmptyString).ToUTF8());
						bool notFound = true;
						TMapStack::iterator it = mapStack.begin();
						while (it != mapStack.end() && notFound) {
							if ( (*it)->Contains(key) ) {
								os << (*it)->GetValue(key);
								notFound = false;
							}
							++it;
						}
					}					
				}				
			}
			os << "\x0D";
		}
		else if (child->GetName() == wxT("for.study")) {
			int idx = 0;
			for (GIL::IModeloIntegracion::TListOfStudies::iterator it = integrationModel->Studies.begin(); it != integrationModel->Studies.end(); ++it) {
				const GIL::IMapOfVarsModel& vars = (*it).MapOfVars;
				mapStack.push_back(&vars);
				Parse(os, integrationModel, child, &(*it), idx++, NULL, 0, 0, mapStack, seqMap);
				mapStack.pop_back();
			}
		}
		else if (child->GetName() == wxT("for.serie")) {
			if (study != NULL) {
				int idx = 0;
				for (GIL::IStudyModel::TListOfSeries::iterator it = study->Series.begin(); it != study->Series.end(); ++it) {
					const GIL::IMapOfVarsModel& vars = (*it).MapOfVars;
					mapStack.push_back(&vars);
					Parse(os, integrationModel, child, study, studyIdx, &(*it), idx++, 0, mapStack, seqMap);
					mapStack.pop_back();
				}
			}
		}
		else if (child->GetName() == wxT("for.image")) {
			if (serie != NULL) {
				int idx = 0;
				for (GIL::ISeriesModel::TListOfImages::iterator it = serie->Images.begin(); it != serie->Images.end(); ++it) {
					const GIL::IMapOfVarsModel& vars = (*it).MapOfVars;
					mapStack.push_back(&vars);
					Parse(os, integrationModel, child, study, studyIdx, serie, serieIdx, idx++, mapStack, seqMap);
					mapStack.pop_back();
				}
			}
		}
		else if (child->GetName() == wxT("sequence")) {
			std::string key(child->GetAttribute(wxT("key"), wxT("")).ToUTF8());
			if (key.size() > 0) {
				std::string val(child->GetAttribute(wxT("val"), wxT("0")).ToUTF8());
				long keyVal = 0;
				std::istringstream is (val);
				is >> keyVal;

				seqMap[key] = keyVal;
			}
		}

	}

}

std::string GIL::IntegrationController::GetMessageFromIntegrationModel(GNC::GCS::Ptr<GIL::IModeloIntegracion>& integrationModel)
{
	if (integrationModel) {
		if ( integrationModel->messageTemplate.size() == 0) {
			//
			GNC::GCS::Permisos::EstadoPermiso perm = GNC::GCS::IControladorPermisos::Instance()->Get("core.integracion", "integration_template");
			integrationModel->messageTemplate = perm.GetRawValue();
			if (integrationModel->messageTemplate.empty()) {
				LOG_ERROR("IntegrationController", "core.integracion.integration_template permission is not active");
			}
		}

		wxStringInputStream is(wxString::FromUTF8(integrationModel->messageTemplate.c_str()));
		wxXmlDocument doc;
		if (doc.Load(is, wxT("UTF-8"))) {
			wxXmlNode* root = doc.GetRoot();						
			if (root != NULL && root->GetName() == wxT("msg")) {
				std::ostringstream os;
				std::string val;

				// Add variables
				const GNC::ControladorExtensiones::ListaModulos& listaModulos = GNC::ControladorExtensiones::Instance()->Modulos();
				std::string hl7_timestamp(wxDateTime::Now().Format(wxT("%Y%m%d%H%M%S")).ToUTF8());
				std::string epoch_timestamp;

				{
					time_t tstamp = std::time(NULL);
					std::ostringstream os;
					os << tstamp;
					epoch_timestamp = os.str();
				}

				integrationModel->GlobalVariables.InsertVariable(GKDI_MESSAGE_EPOCH_TIMESTAMP, epoch_timestamp, "Message Epoch Timestamp");
				integrationModel->GlobalVariables.InsertVariable(GKDI_MESSAGE_HL7_TIMESTAMP, hl7_timestamp, "Message HL7 Timestamp");

				integrationModel->GlobalVariables.InsertVariable(GKDI_GINKGOCADX_MODULE_TID, integrationModel->idPlantilla, "Ginkgo CADx module TID");


				GNC::GCS::ConfigurationController::Instance()->readStringGeneral("/GinkgoCore/HCE", "IDOrganizacionReceptora", val);
				integrationModel->GlobalVariables.InsertVariable(GKDI_GINKGOCADX_CONF_RECEIVING_ORG_ID, val);

				GNC::GCS::ConfigurationController::Instance()->readStringGeneral("/GinkgoCore/HCE","IDSistemaPeticionario", val);
				integrationModel->GlobalVariables.InsertVariable(GKDI_GINKGOCADX_CONF_PETITIONER_SYS_ID, val);

				GNC::GCS::ConfigurationController::Instance()->readStringGeneral("/GinkgoCore/HCE","IDAplicacionRellenadora", val);
				integrationModel->GlobalVariables.InsertVariable(GKDI_GINKGOCADX_CONF_FILLER_APP_ID, val);

				GNC::GCS::ConfigurationController::Instance()->readStringGeneral("/GinkgoCore/Estacion","CentroNombre", val);
				integrationModel->GlobalVariables.InsertVariable(GKDI_GINKGOCADX_CONF_ORGANIZATION_NAME, val);

				GNC::GCS::ConfigurationController::Instance()->readStringGeneral("/GinkgoCore/Estacion","CentroId", val);
				integrationModel->GlobalVariables.InsertVariable(GKDI_GINKGOCADX_CONF_ORGANIZATION_ID, val);

				GNC::GCS::ConfigurationController::Instance()->readStringGeneral("/GinkgoCore/HCE","IDGinkgoApplication", val);
				integrationModel->GlobalVariables.InsertVariable(GKDI_GINKGOCADX_CONF_GINKGO_APP_ID, val);

				integrationModel->GlobalVariables.InsertVariable(GKDI_GLOBAL_PACS_LOCAL_AET, GNC::Entorno::Instance()->GetDicomLocalAET());

				try {
					std::string uidModulo = GetUID(integrationModel->idPlantilla);
					integrationModel->GlobalVariables.InsertVariable(GKDI_GINKGOCADX_MODULE_UID, uidModulo, "Ginkgo CADx module UID");

					GNC::ControladorExtensiones::IteradorConstanteListaModulos it = listaModulos.find(uidModulo);
					if (it != listaModulos.end()) {									
						integrationModel->GlobalVariables.InsertVariable(GKDI_GINKGOCADX_MODULE_NAME, (*it).second->GetImporterDescription(), "Ginkgo CADx module name");
					}
				}
				catch(...) {}

				TSeqMap seqMap;
				TMapStack mapStack;
				mapStack.push_back(& (integrationModel->GlobalVariables) );
				Parse(os, integrationModel.GetRawPointer(), root, NULL, 0, NULL, 0, 0, mapStack, seqMap);
				mapStack.clear();
				seqMap.clear();

				return os.str();
			}
		}
	}
	return "";
}

// Event listener realization
void GIL::IntegrationController::ProcesarEvento(GNC::GCS::Events::IEvent *rawEvent) 
{

	if (rawEvent->GetCodigoEvento() == ginkgoEVT_Core_HL7SendRequest) {
		GNC::GCS::Events::HL7SendRequestEvent* pEvt = dynamic_cast<GNC::GCS::Events::HL7SendRequestEvent*>(rawEvent);
		if (pEvt != NULL) {
			GIL::HL7::ControladorHL7 ch(GIL::HL7::ControladorHL7::TP_MLLP);
			ch.EnviarMensaje(GIL::HL7::Message::FromER7(pEvt->GetMessage()), true);			
		}
	}
}


std::string GIL::IntegrationController::GetUID(const std::string& idPlantilla) const
{
	std::string uid;
	wxString plantillawx(idPlantilla.c_str(), wxConvUTF8);

	wxStringTokenizer tkid(plantillawx,wxString::FromUTF8("^"));

	size_t numTokens = tkid.CountTokens();

	if (numTokens != 3){
		std::ostringstream os;
		os << _Std("Malformed template Id (") << idPlantilla.c_str() << _Std("):\nIs not a valid HL7 identifier (ie: code^description^namespaceId)");
		throw IntegrationException(os.str(),"PID");
	}
	std::string codigo(tkid.GetNextToken().ToUTF8());
	std::string descripcion(tkid.GetNextToken().ToUTF8());
	std::string namespaceid(tkid.GetNextToken().ToUTF8());

	GNC::ControladorExtensiones* pCE = GNC::ControladorExtensiones::Instance();

	for (GNC::ControladorExtensiones::IteradorConstanteListaModulos it = pCE->Modulos().begin(); it != pCE->Modulos().end(); ++it) {
		const GNC::GCS::IModuleController* m = (*it).second;

		LOG_TRACE("HL7", "Parsing module: " << ", " << m->GetUID().c_str() );

		std::string entradaConf = "TUID" + m->GetUID();

		LOG_TRACE("HL7", "EntradaConf = TUID" << m->GetUID().c_str() ); 

		GNC::GCS::ConfigurationController::Instance()->readStringGeneral("/GinkgoCore/HCE/Plantillas", entradaConf, uid);

		LOG_TRACE("HL7", "tuid = " << uid); 

		wxStringTokenizer tkz(wxString::FromUTF8(uid.c_str()), wxT(";"));

		while (tkz.HasMoreTokens()){
			wxString token = tkz.GetNextToken();
			wxStringTokenizer tkidmodulo(token,wxString::FromUTF8("^"));
			numTokens = tkidmodulo.CountTokens();
			if (numTokens != 3){
				std::ostringstream os;
				os << _Std("Id malformed template in the module configuration") << "(" << uid.c_str() << _Std("):\nIs not a valid HL7 identifier (ie: code^description^namespaceId)");
				throw IntegrationException(os.str(),"PID");
			}

			std::string codigo_m(tkidmodulo.GetNextToken().ToUTF8());
			std::string descripcion_m(tkidmodulo.GetNextToken().ToUTF8());
			std::string namespaceid_m(tkidmodulo.GetNextToken().ToUTF8());
			if ( (codigo_m == codigo) && (namespaceid_m == namespaceid) )
			{
				std::ostringstream os;
				LOG_TRACE("HL7", "Found entry:" << m->GetUID().c_str());
				os << m->GetUID();
				return os.str();
			}
		}
	}

	throw IntegrationException(_Std("Template Id unsupported (") + (idPlantilla) + _Std("):\nCheck the configuration and attach it if necessary"),"PID");

}


std::string GIL::IntegrationController::GetUIDModulo(const std::string& idPlantilla) const
{
	std::string uidModulo = GetUID(idPlantilla);
	std::string sidModulo("");

	const GNC::ControladorExtensiones::ListaModulos& listaModulos = GNC::ControladorExtensiones::Instance()->Modulos();
	for(GNC::ControladorExtensiones::IteradorConstanteListaModulos itModulos = listaModulos.begin(); itModulos != listaModulos.end(); ++itModulos){
		std::ostringstream os;
		os << itModulos->first;
		if(os.str() == uidModulo) {
			return itModulos->second->GetUID();
		}
	}
	return sidModulo;
}

std::string GIL::IntegrationController::GetIdPlantilla(const std::string& uidModulo) const
{
	GNC::GCS::IModuleController* pModuleController = GNC::ControladorExtensiones::Instance()->ObtenerModulo(uidModulo);
	if (pModuleController == NULL) {
		std::ostringstream os;
		os << _Std("Unknown module Id (") << uidModulo.c_str();
		throw IntegrationException(os.str(),"PID");
	}

	std::string entradaConf = "TUID" + pModuleController->GetUID();
	std::string templateId;
	GNC::GCS::ConfigurationController::Instance()->readStringGeneral("/GinkgoCore/HCE/Plantillas", entradaConf, templateId);
	return templateId;
}



/* Parsea la cadena XML y construye el modelo de integracion. Los parametros no encontrados se asignan por defecto segun lo especificado en la configuracion global */
void GIL::IntegrationController::ParsearModeloIntegracion(GIL::IntegrationModelList& modelos, const std::string& xmlString, const wxString& relativePath) const
{
	GTRACE("GIL::HL7::ControladorHL7::ParsearModeloIntegracion( modelo, [\n" << xmlString.c_str() << "\n] )");
	wxXmlDocument xml;
	std::string xpp; // XML Pretty Printed for extended information info.

	wxStringInputStream flujoEntrada(wxString::FromUTF8(xmlString.c_str()));

	xml.Load(flujoEntrada,wxT("UTF 8"));

	wxXmlNode* raiz = xml.GetRoot();

	if (raiz == NULL) {
		throw IntegrationException(_Std("Empty configuration"), "CONF");
	}
	else {
		wxString xmlPrettyPrint;
		wxStringOutputStream wsos;
		if (xml.Save(wsos, 1)) {
			xpp = wsos.GetString().ToUTF8();
			LOG_TRACE("Integration", "Parsing XML Integration: " << std::endl << xpp.c_str());
		}
	}

	std::string parserKey (raiz->GetName().ToUTF8());

	if (m_mapParsers.find(parserKey) != m_mapParsers.end()) {
		m_mapParsers.find(parserKey)->second->ParseIntegrationXML(modelos, raiz);
		for (GIL::IntegrationModelList::iterator it = modelos.begin(); it != modelos.end(); ++it) {
			GNC::GCS::Ptr<GIL::IModeloIntegracion>& modelo = (*it);
			GIL::IModeloIntegracion::ListOfPaths absoluteFiles;
			for (GIL::IModeloIntegracion::ListOfPaths::iterator itFile = modelo->Files.begin(); itFile != modelo->Files.end(); ++itFile) 
			{
				wxFileName fileName(wxString::FromUTF8((*itFile).c_str()));
				if (fileName.IsRelative()) {
					fileName.MakeAbsolute(relativePath);
				}						
				std::string pathStd(fileName.GetFullPath().ToUTF8());
				absoluteFiles.push_back(pathStd);
			}
			modelo->Files = absoluteFiles;
		}

	} else {
		std::ostringstream ostr;
		ostr << _Std("XML Parser Not Found. Key=") << parserKey; 
		throw IntegrationException(ostr.str(), xpp, "CONF");
	}
}

