/*
 *
 *  $Id: dicomnetclient.h $
 *  Ginkgo CADx Project
 *
 *  Copyright 2008-14 MetaEmotion S.L. All rights reserved.
 *  http://ginkgo-cadx.com
 *
 *  This file is licensed under LGPL v3 license.
 *  See License.txt for details
 *
 *  Code adapted from Aeskulap
 *
 */
#pragma once
#include <api/controllers/icontroladorlog.h>
#include <api/iproxynotificadorprogreso.h>
#include <main/controllers/controladorlog.h>
#include <main/controllers/dcmtk/dicomservers.h>
#include "dicomnetwork.h"
#include "helpers.h"

#include <iostream>

#ifdef verify
#define MACRO_QUE_ESTORBA verify
#undef verify
#endif
#include <dcmtk/dcmdata/dcdatset.h>
#include <dcmtk/dcmdata/dcdeftag.h>
#include <api/dicom/imodelodicom.h>
#include <wx/strconv.h>
#include <wx/string.h>

namespace GNC{
	class IProxyNotificadorProgreso;
}

#ifdef MACRO_QUE_ESTORBA
#define verify MACRO_QUE_ESTORBA
#endif

typedef enum CallbackType {
	CT_MoveEstudio,
	CT_MoveSerie,
	CT_MoveImagen,
	CT_None
} CallbackType;

#ifndef WXUNUSED
#define WXUNUSED(name)
#endif

template<class T>
class NetClient : public T {
private:
	GIL::DICOM::DCMTK::Network* Net;
	std::string                 ambitolog;

protected:
	GNC::IProxyNotificadorProgreso* m_pNotificadorProgreso;
public:
	NetClient(void* connectionKey, const std::string& ambitolog, GNC::IProxyNotificadorProgreso* pNotificadorProgreso = NULL) : T(ambitolog) {

		if (connectionKey == NULL) {
			LOG_ERROR(ambitolog, "NULL Connection key");
		}
		this->Net = GIL::DICOM::DCMTK::Network::Instance(connectionKey);
		this->m_pNotificadorProgreso = pNotificadorProgreso;
		this->ambitolog = ambitolog;
	};
	
	bool QueryServer(const GNC::GCS::Ptr<DicomServer>& server, const std::string& local_aet) {
		if (server == NULL) {
			return false;
		}
		if (!Net->Initiallized()) {
		
			Net->InitializeNetwork(T::GetTimeout(), T::GetRole(), T::GetAcceptorPort());
		}

		T::Create(
			server->AET,
			server->HostName,
			server->Port,
			local_aet
		);

		T::SetNotificadorProgreso(m_pNotificadorProgreso);

		LOG_DEBUG(ambitolog, "Connected: AET = " << server->AET << ", Host = " << server->HostName << ", Port = " << server->Port << ", Local AET = " << local_aet << ", PDU = " << server->PDU);
		CONDITION r = T::Connect(Net, server->PDU);

		CONDITION c = ECC_Normal;

		if (r.good() == true) {
			c = T::SendObject();
		}
		else {
			LOG_ERROR(ambitolog, "Error connecting:" << r.text());
			T::Drop();
			T::Destroy();
			throw GIL::DICOM::PACSException(r.text());
		}

		if (!c.good()) {
			LOG_ERROR(ambitolog, "Error sending object: " << c.text());
			T::Drop();
			T::Destroy();
			throw GIL::DICOM::PACSException(c.text());
		}

		LOG_DEBUG(ambitolog, "Closing association");
		T::Drop();
		return true;
	}

	bool QueryServer(DcmDataset* query, const GNC::GCS::Ptr<DicomServer>& server, std::list< GNC::GCS::Ptr<GIL::DICOM::DicomDataset> >& resultsWrapper, const std::string& local_aet) {
		if (!server.IsValid()) {
			return false;
		}

		if (!Net->Initiallized()) {
		
			Net->InitializeNetwork(T::GetTimeout(), T::GetRole(), T::GetAcceptorPort());
		}

		T::Create(
			server->AET,
			server->HostName,
			server->Port,
			local_aet
		);

		T::SetNotificadorProgreso(m_pNotificadorProgreso);

		T::SetCallbackInfo(&resultsWrapper, server);

		LOG_INFO(ambitolog, "Connecting: AET = " << server->AET << ", Host = " << server->HostName << ", Puerto = " << server->Port << ", Local AET = " << local_aet << ", PDU = " << server->PDU);
		CONDITION r = T::Connect(Net, server->PDU);

		CONDITION c = ECC_Normal;

		if (r.good() == true) {
			LOG_DEBUG(ambitolog, "Requesting object:" << std::endl << DumpDataset(query));
			c = T::SendObject(query);
		}
		else {
			LOG_DEBUG(ambitolog, "Error connecting:" << r.text());
			T::Drop();
			T::Destroy();
			LOG_INFO(ambitolog, "Disconnected");
			throw GIL::DICOM::PACSException(r.text());
		}

		if (!c.good()) {
			LOG_DEBUG(ambitolog, "Error requesting object: " << c.text());
			T::Drop();
			T::Destroy();
			LOG_INFO(ambitolog, "Disconnected");
			throw GIL::DICOM::PACSException(c.text());
		}

		LOG_INFO(ambitolog, "Disconnected");
		T::Drop();
		
		return r.good();
	}

	///DEPRECATED! IModeloDICOM has to be replaced by results wrapper
	bool QueryServer(DcmDataset* query, const GNC::GCS::Ptr<DicomServer>& server, IModeloDicom* pModelo, const std::string& local_aet, CallbackType callbackType) {
		if (pModelo == NULL && callbackType != CT_None)  {
			return false;
		}
		if (!server.IsValid()) {
			return false;
		}

		if (!Net->Initiallized()) {
		
			Net->InitializeNetwork(T::GetTimeout(), T::GetRole(), T::GetAcceptorPort());
		}

		T::Create(
			server->AET,
			server->HostName,
			server->Port,
			local_aet
		);

		T::SetNotificadorProgreso(m_pNotificadorProgreso);

		LOG_INFO(ambitolog, "Connecting: AET = " << server->AET << ", Host = " << server->HostName << ", Puerto = " << server->Port << ", Local AET = " << local_aet << ", PDU = " << server->PDU);
		CONDITION r = T::Connect(Net, server->PDU);

		CONDITION c = ECC_Normal;

		if (r.good() == true) {
			LOG_DEBUG(ambitolog, "Requesting object:" << std::endl << DumpDataset(query));
			c = T::SendObject(query);
		}
		else {
			LOG_DEBUG(ambitolog, "Error connecting:" << r.text());
			T::Drop();
			T::Destroy();
			LOG_INFO(ambitolog, "Disconnected");
			throw GIL::DICOM::PACSException(r.text());
		}

		if (!c.good()) {
			LOG_DEBUG(ambitolog, "Error requesting object: " << c.text());
			T::Drop();
			T::Destroy();
			LOG_INFO(ambitolog, "Disconnected");
			throw GIL::DICOM::PACSException(c.text());
		}

		DcmStack* result = T::GetResultStack();
		if (r.good() && c.good() && result != NULL && result->card() > 0) {
			LOG_DEBUG(ambitolog, "Num results: " << result->card());

			for (unsigned long i = 0; i < result->card(); ++i) {
				DcmDataset* dset = new DcmDataset( *(static_cast<DcmDataset*>(result->elem(i))) );
				switch (callbackType) {
					case CT_MoveEstudio:
						OnMoverEstudio(dset, pModelo);
						break;
					case CT_MoveSerie:
						OnMoverSerie(dset, pModelo);
						break;
					case CT_MoveImagen:
						OnMoverImagen(dset, pModelo);
						break;
					case CT_None:
						break;
				}
				delete dset;
			}
		}
		else {
			if (r.bad()) {
				LOG_ERROR(ambitolog, "Connection error: " << r.text());
			}
			if (c.bad()) {
				LOG_ERROR(ambitolog, "Error requesting object: " << c.text());
			}
		}

		LOG_INFO(ambitolog, "Disconnected");
		T::Drop();
		
		return r.good();
	}

	wxCSConv GetConv(DcmDataset* dset, const std::string& defaultCharset)
	 {
		 OFString Charset;
		 wxCSConv wxConv(wxFONTENCODING_ISO8859_1);
		 if (dset->findAndGetOFString(DCM_SpecificCharacterSet, Charset).bad()) {
			 Charset = defaultCharset.c_str();
		 }
		 if (Charset == "ISO_IR 192" || Charset == "ISO_IR 6" || Charset == "ISO_IR 138") {
			 wxConv = wxCSConv(wxFONTENCODING_UTF8);
		 }
		 else if (Charset == "ISO_IR 100") {
			 wxConv = wxCSConv(wxFONTENCODING_ISO8859_1);
		 }
		 else if (Charset == "ISO_IR 101") {
			 wxConv = wxCSConv(wxFONTENCODING_ISO8859_2);
		 }
		 else if (Charset == "ISO_IR 109") {
			 wxConv = wxCSConv(wxFONTENCODING_ISO8859_3);
		 }
		 else if (Charset == "ISO_IR 110") {
			 wxConv = wxCSConv(wxFONTENCODING_ISO8859_4);
		 }
		 else if (Charset == "ISO_IR 148") {
			 wxConv = wxCSConv(wxFONTENCODING_ISO8859_9);
		 }
		 else if (Charset == "ISO_IR 144") {
			 wxConv = wxCSConv(wxFONTENCODING_ISO8859_5);
		 }
		 else if (Charset == "ISO_IR 127") {
			 wxConv = wxCSConv(wxFONTENCODING_ISO8859_6);
		 }
		 else if (Charset == "ISO_IR 126") {
			 wxConv = wxCSConv(wxFONTENCODING_ISO8859_7);
		 }
		 else if (Charset == "ISO_IR 138") {
			 wxConv = wxCSConv(wxFONTENCODING_ISO8859_8);
		 }
		 return wxConv;
	 }

	void OnBuscarPacientes(DcmDataset* query, DcmDataset* dset, const GNC::GCS::Ptr<DicomServer>& server, IModeloDicom* pModelo)
	{
		wxCSConv wxConv = GetConv(dset, server->GetDefaultCharset());

		OFString OFPacienteUID;
		if (dset->findAndGetOFString(DCM_PatientID, OFPacienteUID).bad()) {
			if (!dset->isEmpty()) {
				LOG_ERROR(ambitolog, "El PACS no devolvió el PatientID del paciente. Entrada ignorada.");
			}
			return;
		}

		std::string PacienteUID;
		if (dset->findAndGetOFString(DCM_PatientID, OFPacienteUID).good()) {
			PacienteUID = wxString(OFPacienteUID.c_str(), wxConv).ToUTF8();
		}

		OFString OFPacienteNombre;
		std::string PacienteNombre;
		if (dset->findAndGetOFString(DCM_PatientName, OFPacienteNombre).good()) {
			PacienteNombre = wxString(OFPacienteNombre.c_str(), wxConv).ToUTF8();
		}

		OFString OFPacienteFechaNacimiento;
		std::string PacienteFechaNacimiento;
		if (dset->findAndGetOFString(DCM_PatientBirthDate, OFPacienteFechaNacimiento).good()) {
			PacienteFechaNacimiento = wxString(OFPacienteFechaNacimiento.c_str(), wxConv).ToUTF8();
		}

		OFString OFPacienteSexo;
		std::string PacienteSexo;
		if (dset->findAndGetOFString(DCM_PatientSex, OFPacienteSexo).good()) {
			PacienteSexo = wxString(OFPacienteSexo.c_str(), wxConv).ToUTF8();
		}

		if (PacienteUID.empty()) {
			OFString OFStr;
			if (query->findAndGetOFString(DCM_PatientID, OFStr).good()) {
				PacienteUID = wxString(OFStr.c_str(), wxConv).ToUTF8();
			}
		}

		pModelo->InsertarPaciente(PacienteUID, PacienteNombre, PacienteFechaNacimiento, PacienteSexo);
	}

	void OnBuscarSeries(DcmDataset* query, DcmDataset* dset, const GNC::GCS::Ptr<DicomServer>& server, IModeloDicom *pModelo)
	{
		wxCSConv wxConv = GetConv(dset, server->GetDefaultCharset());

		OFString OFEstudioUID;
		OFString OFSerieUID;
		if (dset->findAndGetOFString(DCM_StudyInstanceUID, OFEstudioUID).bad() || dset->findAndGetOFString(DCM_SeriesInstanceUID, OFSerieUID).bad()) {
			if (!dset->isEmpty()) {
				LOG_ERROR(ambitolog, "El PACS no devolvió el StudyInstanceUID o el SeriesInstanceUID de la serie. Entrada ignorada.");
			}
			return;
		}

		std::string EstudioUID;
		EstudioUID = wxString(OFEstudioUID.c_str(), wxConv).ToUTF8();

		std::string SerieUID;
		SerieUID = wxString(OFSerieUID.c_str(), wxConv).ToUTF8();

		OFString OFSerieTipo;
		std::string SerieTipo;
		if (dset->findAndGetOFString(DCM_Modality, OFSerieTipo).good()) {
			SerieTipo = wxString(OFSerieTipo.c_str(), wxConv).ToUTF8();
		}

		OFString OFSerieFecha;
		std::string SerieFecha;
		if (dset->findAndGetOFString(DCM_SeriesDate, OFSerieFecha).good()) {
			SerieFecha = wxString(OFSerieFecha.c_str(), wxConv).ToUTF8();
		}

		OFString OFSerieHora;
		std::string SerieHora;
		if (dset->findAndGetOFString(DCM_SeriesTime, OFSerieHora).good()) {
			SerieHora = wxString(OFSerieHora.c_str(), wxConv).ToUTF8();
		}

		OFString OFSerieDescripcion;
		std::string SerieDescripcion;
		if (dset->findAndGetOFString(DCM_SeriesDescription, OFSerieDescripcion).good()) {
			SerieDescripcion = wxString(OFSerieDescripcion.c_str(), wxConv).ToUTF8();
		}

		OFString OFDoctor;
		std::string SerieDoctor;
		if (dset->findAndGetOFString(DCM_ReferringPhysicianName, OFDoctor).good()) {
			SerieDoctor = wxString(OFDoctor.c_str(), wxConv).ToUTF8();
		}

		OFString OFNumber;
		std::string SerieNumero;
		if (dset->findAndGetOFString(DCM_NumberOfSeriesRelatedInstances, OFNumber).good()) {
			SerieNumero = wxString(OFNumber.c_str(), wxConv).ToUTF8();
		}

		if (EstudioUID.empty()) {
			OFString OFStr;
			if (query->findAndGetOFString(DCM_StudyInstanceUID, OFStr).good()) {
				EstudioUID = wxString(OFStr.c_str(), wxConv).ToUTF8();
			}
		}
		if (SerieUID.empty()) {
			OFString OFStr;
			if (query->findAndGetOFString(DCM_SeriesInstanceUID, OFStr).good()) {
				SerieUID = wxString(OFStr.c_str(), wxConv).ToUTF8();
			}
		}

		pModelo->InsertarSerie(EstudioUID, SerieUID, SerieTipo, SerieFecha, SerieHora, SerieDescripcion, SerieNumero,SerieDoctor);
	}

    void OnBuscarImagenes(DcmDataset* query, DcmDataset *dset, const GNC::GCS::Ptr<DicomServer>& server, IModeloDicom* pModelo) 
	 {
		wxCSConv wxConv = GetConv(dset, server->GetDefaultCharset());

    	OFString OFSerieUID;
    	OFString OFImagenUID;
		OFString OFInstanceNumber;
		if (dset->findAndGetOFString(DCM_SeriesInstanceUID, OFSerieUID).bad() || dset->findAndGetOFString(DCM_SOPInstanceUID, OFImagenUID).bad()) {
			if (!dset->isEmpty()) {
				LOG_ERROR(ambitolog, "El PACS no devolvió el SeriesInstanceUID o el SOPInstanceUID de la imagen. Entrada ignorada.");
			}
			return;
		}
		if (OFInstanceNumber.empty()) {
			LOG_WARN(ambitolog, "InstanceNumber not found. Setting to default");
			OFInstanceNumber = "1";
		}

		std::string SerieUID;
		std::string ImagenUID;
		std::string InstanceNumber;

		SerieUID.assign(OFSerieUID.c_str());
		ImagenUID.assign(OFImagenUID.c_str());
		InstanceNumber.assign(OFInstanceNumber.c_str());

		if (SerieUID.empty()) {
			OFString OFStr;
			if (query->findAndGetOFString(DCM_SeriesInstanceUID, OFStr).good()) {
				SerieUID = wxString(OFStr.c_str(), wxConv).ToUTF8();
			}
		}
		if (ImagenUID.empty()) {
			OFString OFStr;
			if (query->findAndGetOFString(DCM_SOPInstanceUID, OFStr).good()) {
				ImagenUID = wxString(OFStr.c_str(), wxConv).ToUTF8();
			}
		}
		if (InstanceNumber.empty()) {
			OFString OFStr;
			if (query->findAndGetOFString(DCM_InstanceNumber, OFStr).good()) {
				InstanceNumber = wxString(OFStr.c_str(), wxConv).ToUTF8();
			}
		}

		pModelo->InsertarImagen(SerieUID, ImagenUID, InstanceNumber);

	}

    void OnBuscarEstudios(DcmDataset* query, DcmDataset *dset, const GNC::GCS::Ptr<DicomServer>& server, IModeloDicom *pModelo) {
		OFString OFEstudioUID;
		if (dset->findAndGetOFString(DCM_StudyInstanceUID, OFEstudioUID).bad()) {
			if (!dset->isEmpty()) {
				LOG_ERROR(ambitolog, "El PACS no devolvió el StudyInstanceUID del estudio. Entrada ignorada.");
			}
			return;
		}

		wxCSConv wxConv = GetConv(dset, server->GetDefaultCharset());

		OFString OFPacienteUID;
		std::string PacienteUID;
		if (dset->findAndGetOFString(DCM_PatientID, OFPacienteUID).good()) {
			PacienteUID = wxString(OFPacienteUID.c_str(), wxConv).ToUTF8();
		} else {
			if (query->findAndGetOFString(DCM_PatientID, OFPacienteUID).good()) {
				PacienteUID = wxString(OFPacienteUID.c_str(), wxConv).ToUTF8();
			}
		}

		OFString OFPacienteNombre;
		std::string PacienteNombre;
		if (dset->findAndGetOFString(DCM_PatientName, OFPacienteNombre).good()) {
			PacienteNombre = wxString(OFPacienteNombre.c_str(), wxConv).ToUTF8();
		}

		OFString OFPacienteFechaNacimiento;
		std::string PacienteFechaNacimiento;

		if (dset->findAndGetOFString(DCM_PatientBirthDate, OFPacienteFechaNacimiento).good()) {
			PacienteFechaNacimiento = wxString(OFPacienteFechaNacimiento.c_str(), wxConv).ToUTF8();
		}

		OFString OFPacienteSexo;
		std::string PacienteSexo;
		if (dset->findAndGetOFString(DCM_PatientSex, OFPacienteSexo).good()) {
			PacienteSexo = wxString(OFPacienteSexo.c_str(), wxConv).ToUTF8();
		}

		pModelo->InsertarPaciente(PacienteUID, PacienteNombre, PacienteFechaNacimiento, PacienteSexo);

		std::string EstudioUID;
		if (dset->findAndGetOFString(DCM_StudyInstanceUID, OFEstudioUID).good()) {
			EstudioUID = wxString(OFEstudioUID.c_str(), wxConv).ToUTF8();
		}

		OFString OFEAccNumber;
		std::string AccNumber;
		if (dset->findAndGetOFString(DCM_AccessionNumber, OFEAccNumber).good()) {
			AccNumber = wxString(OFEAccNumber.c_str(), wxConv).ToUTF8();
		}

		OFString OFEstudioDescripcion;
		std::string EstudioDescripcion;
		if (dset->findAndGetOFString(DCM_StudyDescription, OFEstudioDescripcion).good()) {
			EstudioDescripcion = wxString(OFEstudioDescripcion.c_str(), wxConv).ToUTF8();
		}

		OFString OFEstudioModalidad;
		std::string EstudioModalidad;
		if (dset->findAndGetOFStringArray(DCM_ModalitiesInStudy, OFEstudioModalidad).good()) {
			EstudioModalidad = wxString(OFEstudioModalidad.c_str(), wxConv).ToUTF8();
		}

		OFString OFEstudioFecha;
		std::string EstudioFecha;
		if (dset->findAndGetOFString(DCM_StudyDate, OFEstudioFecha).good()) {
			EstudioFecha = wxString(OFEstudioFecha.c_str(), wxConv).ToUTF8();
		}

		OFString OFEstudioHora;
		std::string EstudioHora;
		if (dset->findAndGetOFString(DCM_StudyTime, OFEstudioHora).good()) {
			EstudioHora = wxString(OFEstudioHora.c_str(), wxConv).ToUTF8();
		}

		OFString OFDoctor;
		std::string EstudioDoctor;
		if (dset->findAndGetOFString(DCM_ReferringPhysicianName, OFDoctor).good()) {
			EstudioDoctor = wxString(OFDoctor.c_str(), wxConv).ToUTF8();
		}

		if (EstudioUID.empty()) {
			OFString OFStr;
			if (query->findAndGetOFString(DCM_StudyInstanceUID, OFStr).good()) {
				EstudioUID = wxString(OFStr.c_str(), wxConv).ToUTF8();
			}
		}
		if (AccNumber.empty()) {
			OFString OFStr;
			if (query->findAndGetOFString(DCM_AccessionNumber, OFStr).good()) {
				AccNumber = wxString(OFStr.c_str(), wxConv).ToUTF8();
			}
		}

		pModelo->InsertarEstudio(PacienteUID, EstudioUID, AccNumber, EstudioDescripcion, EstudioModalidad, EstudioFecha, EstudioHora, EstudioDoctor);

    }

	void OnMoverEstudio(DcmDataset *WXUNUSED(dset), IModeloDicom *WXUNUSED(pModelo)) {
		#ifdef _GINKGO_DEBUG
		std::cout << "move study" << std::endl;
		#endif
	}

    void OnMoverSerie(DcmDataset *WXUNUSED(dset), IModeloDicom *WXUNUSED(pModelo)) {
		#ifdef _GINKGO_DEBUG
		std::cout << "move serie" << std::endl;
		#endif
    }

	void OnMoverImagen(DcmDataset *WXUNUSED(dset), IModeloDicom *WXUNUSED(pModelo)) {
		#ifdef _GINKGO_DEBUG
		std::cout << "move image" << std::endl;
		#endif
    }

    OFBool writeToFile(const char* ofname, DcmDataset *dataset) {
		/* write out as a file format */

		DcmFileFormat fileformat(dataset); // copies dataset
		OFCondition ec = fileformat.error();
		if (ec.bad()) {
			LOG_ERROR(T::ambitolog, "Unable to read DICOM dataset: " << ec.text());
			return OFFalse;
		}

		ec = fileformat.saveFile(ofname, dataset->getOriginalXfer());
		if (ec.bad()) {
			LOG_ERROR(T::ambitolog, "Unable to write file (" << ofname << ": " << ec.text());
			return OFFalse;
		}
		return OFTrue;
    }
};
