/*
 *  
 *  $Id: imodelodicomreferencial.h $
 *  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
 *
 */
#pragma once
//#define _GINKGO_TRACE
#include <api/globals.h>
#include <string>
#include <list>

class IModeloDicom2;
class IModeloEstudio2;
class IModeloSerie2;
class IModeloPaciente2;

class IModeloImagen2 {
public:

	typedef enum TipoImagen {
		TI_Invalido,
		TI_Imagen,
		TI_Diagnostico,
		TI_Clave
	} TipoImagen;

	IModeloImagen2() :
		m_pSeriePadre(NULL),
		m_pModeloPadre(NULL),
		m_Tipo(TI_Invalido)
	{
	}

	IModeloImagen2(IModeloDicom2* pModeloPadre, IModeloSerie2* pSeriePadre, const std::string& UID) :
		m_pSeriePadre(pSeriePadre),
		m_pModeloPadre(pModeloPadre),
		m_Tipo(TI_Invalido),
		m_UID(UID)
	{
	}

	IModeloImagen2( const IModeloImagen2& o) :
		m_pSeriePadre(o.m_pSeriePadre),
		m_pModeloPadre(o.m_pModeloPadre),
		m_Tipo(o.m_Tipo),
		m_UID(o.m_UID)
	{
		m_pSeriePadre = o.m_pSeriePadre;
		m_pModeloPadre = o.m_pModeloPadre;
		m_UID = o.m_UID;
	}

	IModeloImagen2& operator = (const IModeloImagen2& o) {
		m_pSeriePadre = o.m_pSeriePadre;
		m_pModeloPadre = o.m_pModeloPadre;
		m_Tipo = o.m_Tipo;
		m_UID = o.m_UID;
		return *this;
	}

	~IModeloImagen2()
	{
	}

	const std::string& GetUID() const
	{
		return m_UID;
	}

protected:
	IModeloSerie2*   m_pSeriePadre;
	IModeloDicom2*   m_pModeloPadre;
	std::string      m_UID;
	TipoImagen       m_Tipo;
	friend class IModeloDicom2;
};


class IModeloDiagnostico : public IModeloImagen
{
};

class IModeloClave : public IModeloImagen
{
};

class IModeloSerie2 {

public:

	typedef std::list<IModeloImagen2> ListaImagenesType;

	IModeloSerie2()
	{
		m_pEstudioPadre = NULL;
		m_pModeloPadre = NULL;
	}

	IModeloSerie2(const IModeloSerie2& o)
	{
		m_pEstudioPadre = o.m_pEstudioPadre;
		m_pModeloPadre = o.m_pModeloPadre;
		m_UID = o.m_UID;
		m_Tipo = o.m_Tipo;
		m_Numero = o.m_Numero;
		m_Fecha = o.m_Fecha;
		m_Hora = o.m_Hora;
		m_Descripcion = o.m_Descripcion;
		m_Doctor = o.m_Doctor;
	}

	IModeloSerie2(IModeloDicom2* pModeloPadre, IModeloEstudio2* pEstudioPadre,
			const std::string& UID,
			const std::string& tipo,
			const std::string& fecha,
			const std::string& hora,
			const std::string& descripcion,
			const std::string& numero,
			const std::string& doctor)
	{
		m_pEstudioPadre = pEstudioPadre;
		m_pModeloPadre = pModeloPadre;
		m_UID = UID;
		m_Tipo = tipo;
		m_Numero = numero;
		m_Fecha = fecha;
		m_Hora = hora;
		m_Descripcion = descripcion;
		m_Doctor = doctor;
	}

	IModeloSerie2& operator=(const IModeloSerie2& o)
	{
		m_pEstudioPadre = o.m_pEstudioPadre;
		m_pModeloPadre = o.m_pModeloPadre;
		m_UID = o.m_UID;
		m_Tipo = o.m_Tipo;
		m_Numero = o.m_Numero;
		m_Fecha = o.m_Fecha;
		m_Hora = o.m_Hora;
		m_Descripcion = o.m_Descripcion;
		m_Doctor = o.m_Doctor;
		return *this;
	}

	~IModeloSerie2()
	{
	}

	const std::string& GetUID() const
	{
		return m_UID;
	}

	const std::string& GetTipo() const
	{
		return m_Tipo;
	}

	const std::string& GetNumero() const
	{
		return m_Numero;
	}

	const std::string& GetFecha() const
	{
		return m_Fecha;
	}

	const std::string& GetHora() const
	{
		return m_Hora;
	}

	const std::string& GetDescripcion() const
	{
		return m_Descripcion;
	}

	const std::string& GetDoctor() const
	{
		return m_Doctor;
	}

	const ListaImagenesType& ListaImagenes() const
	{
		return Imagenes;
	}

	const IModeloImagen2& Imagen(int indice) const
	{
		int i=0;
		ListaImagenesType::const_iterator it;
		for(it = Imagenes.begin(); i < indice && it != Imagenes.end(); ++it, i++);

		if(it != Imagenes.end()){
			return (*it);
		} else {
			return Imagenes.front();
		}
	}

protected:
	std::string m_UID;
	std::string m_Tipo;
	std::string m_Numero;
	std::string m_Fecha;
	std::string m_Hora;
	std::string m_Descripcion;
	std::string m_Doctor;

	IModeloEstudio2*     m_pEstudioPadre;
	IModeloDicom2*       m_pModeloPadre;

	ListaImagenesType Imagenes;

	friend class IModeloDicom2;

};

class IModeloEstudio2 {

public:

	typedef std::list<IModeloSerie2> ListaSeriesType;

	IModeloEstudio2()
	{
		m_pModeloPadre = NULL;
		m_pPacientePadre = NULL;
	}

	IModeloEstudio2( const IModeloEstudio2& o )
	{
		m_pModeloPadre = o.m_pModeloPadre;
		m_pPacientePadre = o.m_pPacientePadre;
		m_UID = o.m_UID;
		m_Descripcion = o.m_Descripcion;
		m_Modalidad = o.m_Modalidad;
		m_Fecha = o.m_Fecha;
		m_Hora = o.m_Hora;
		m_Comentarios = o.m_Comentarios;
		m_Doctor = o.m_Doctor;
	}

	IModeloEstudio2( IModeloDicom2* pModeloPadre, IModeloPaciente2* pPacientePadre,
			const std::string& UID,
			const std::string& descripcion,
			const std::string& modalidad,
			const std::string& fecha,
			const std::string& hora,
			const std::string& comentarios,
			const std::string& doctor)
	{
		m_pModeloPadre = pModeloPadre;
		m_pPacientePadre = pPacientePadre;
		m_UID = UID;
		m_Descripcion = descripcion;
		m_Modalidad = modalidad;
		m_Fecha = fecha;
		m_Hora = hora;
		m_Comentarios = comentarios;
		m_Doctor = doctor;
	}

	IModeloEstudio2& operator=(const IModeloEstudio2& o)
	{
		m_UID = o.m_UID;
		m_Descripcion = o.m_Descripcion;
		m_Modalidad = o.m_Modalidad;
		m_Fecha = o.m_Fecha;
		m_Hora = o.m_Hora;
		m_Comentarios = o.m_Comentarios;
		m_Doctor = o.m_Doctor;
		return *this;
	}


	~IModeloEstudio2()
	{
	}

	const std::string& GetUID() const
	{
		return m_UID;
	}

	const std::string& GetDescripcion() const
	{
		return m_Descripcion;
	}

	const std::string& GetModalidad() const
	{
		return m_Modalidad;
	}

	const std::string& GetFecha() const
	{
		return m_Fecha;
	}

	const std::string& GetHora() const
	{
		return m_Hora;
	}

	const std::string& GetComentarios() const
	{
		return m_Comentarios;
	}

	const std::string& GetDoctor() const
	{
		return m_Doctor;
	}

	const ListaSeriesType& ListaSeries() const
	{
		return Series;
	}

	const IModeloSerie2& Serie(int indice) const
	{
		int i=0;
		ListaSeriesType::const_iterator it;
		for(it = Series.begin();i<indice && it != Series.end(); ++it, i++);

		if(it != Series.end()){
			return (*it);
		} else {
			return Series.front();
		}
	}

protected:

	std::string m_UID;
	std::string m_Descripcion;
	std::string m_Modalidad;
	std::string m_Fecha;
	std::string m_Hora;
	std::string m_Comentarios;
	std::string m_Doctor;

	IModeloDicom2* m_pModeloPadre;
	IModeloPaciente2* m_pPacientePadre;

	ListaSeriesType    Series;

	friend class IModeloDicom2;

};

class IModeloPaciente2 {
public:

	typedef std::list<IModeloEstudio2> ListaEstudiosType;

	IModeloPaciente2()
	{
		m_pModeloPadre = NULL;
	}

	IModeloPaciente2( const IModeloPaciente2& o )
	{
		m_pModeloPadre = o.m_pModeloPadre;
		m_UID = o .m_UID;
		m_Nombre = o.m_Nombre;
		m_FechaNacimiento = o.m_FechaNacimiento;
		m_Sexo = o.m_Sexo;
	}

	IModeloPaciente2( IModeloDicom2* pModeloPadre,
			const std::string& UID,
			const std::string& nombre,
			const std::string& fechaNacimiento,
			const std::string& sexo)
	{
		m_pModeloPadre = pModeloPadre;
		m_UID = UID;
		m_Nombre = nombre;
		m_FechaNacimiento = fechaNacimiento;
		m_Sexo = sexo;
	}

	IModeloPaciente2& operator=(const IModeloPaciente2& o)
	{
		m_pModeloPadre = o.m_pModeloPadre;
		m_UID = o .m_UID;
		m_Nombre = o.m_Nombre;
		m_FechaNacimiento = o.m_FechaNacimiento;
		m_Sexo = o.m_Sexo;
		return *this;
	}

	~IModeloPaciente2()
	{
	}

	const std::string& GetUID() const
	{
		return m_UID;
	}

	const std::string& GetNombre() const
	{
		return m_Nombre;
	}

	const std::string& GetFechaNacimiento() const
	{
		return m_FechaNacimiento;
	}

	const std::string& GetSexo() const
	{
		return m_Sexo;
	}

	const ListaEstudiosType& ListaEstudios() const
	{
		return Estudios;
	}

	const IModeloEstudio2& Estudio(int indice) const
	{
		int i=0;
		ListaEstudiosType::const_iterator it;
		for(it = Estudios.begin();i<indice && it != Estudios.end(); ++it, i++);

		if(it != Estudios.end()){
			return (*it);
		} else {
			return Estudios.front();
		}
	}

protected:

	std::string m_UID;
	std::string m_Nombre;
	std::string m_FechaNacimiento;
	std::string m_Sexo;

	ListaEstudiosType Estudios;

	IModeloDicom2* m_pModeloPadre;

	friend class IModeloDicom2;

};

class IModeloDicom2 {
public:

	typedef std::list<IModeloPaciente2> ListaPacientesType;

	typedef std::list<IModeloEstudio2*> ListaPunterosEstudiosType;
	typedef std::list<IModeloSerie2*>   ListaPunterosSeriesType;
	typedef std::list<IModeloImagen2*>  ListaPunterosImagenesType;

	IModeloDicom2()
	{
		GTRACE("IModeloDicom2::IModeloDicom2()");
	}

	~IModeloDicom2()
	{
		GTRACE("IModeloDicom2::~IModeloDicom2()");
	}

	const ListaPacientesType& ListaPacientes() const
	{
		return Pacientes;
	}

	const IModeloPaciente2& Paciente(int indice) const
	{
		int i=0;
		ListaPacientesType::const_iterator it;
		for(it = Pacientes.begin();i<indice && it != Pacientes.end(); ++it, i++);

		if(it != Pacientes.end()){
			return (*it);
		} else {
			return Pacientes.front();
		}
	}

	// region operaciones de busqueda externas

public:

	// Esta operacion es externa porque devuelve como resultado un puntero a constante, para no modificar/destruir el registro de paciente.
	void BuscarPaciente(const std::string& UID, IModeloPaciente2 const **  result)
	{
		IModeloPaciente2* paciente = NULL;
		BuscarPacienteInternal(UID, &paciente);
		*result = paciente;
	}

	// Esta operacion es externa porque devuelve como resultado un puntero a constante, para no modificar/destruir el registro de estudio.
	void BuscarEstudio(const std::string& UID, IModeloEstudio2 const ** result)
	{
		IModeloEstudio2* estudio = NULL;
		BuscarEstudioInternal(UID, &estudio);
		*result = estudio;
	}

	// Esta operacion es externa porque devuelve como resultado un puntero a constante, para no modificar/destruir el registro de serie.
	void BuscarSerie(const std::string& UID, IModeloSerie2 const ** result)
	{
		IModeloSerie2* serie = NULL;
		BuscarSerieInternal(UID, &serie);
		*result = serie;
	}

	// Esta operacion es externa porque devuelve como resultado un puntero a constante, para no modificar/destruir el registro de imagen.
	void BuscarImagen(const std::string& UID, IModeloImagen2 const ** result)
	{
		IModeloImagen2* imagen = NULL;
		BuscarImagenInternal(UID, &imagen);
		*result = imagen;
	}

	// endregion

	// region operaciones de busqueda internas

private:

	// Esta operacion es interna porque devuelve como resultado un puntero a constante, para no modificar/destruir el registro de paciente.
	void BuscarPacienteInternal(const std::string& UID, IModeloPaciente2 **  result)
	{
		for (ListaPacientesType::iterator it = Pacientes.begin(); *result == NULL && it != Pacientes.end(); ++it) {
			IModeloPaciente2& pi = *it;
			if (pi.GetUID() == UID) {
				*result = &(*it);
			}
		}
	}

	// Esta operacion es interna porque devuelve como resultado un puntero a constante, para no modificar/destruir el registro de estudio.
	void BuscarEstudioInternal(const std::string& UID, IModeloEstudio2 **  result)
	{
		for (ListaPunterosEstudiosType::iterator it = ListaPunterosEstudios.begin(); *result == NULL && it != ListaPunterosEstudios.end(); ++it) {
			IModeloEstudio2* pi = *it;
			if (pi->GetUID() == UID) {
				*result = (*it);
			}
		}
	}

	// Esta operacion es interna porque devuelve como resultado un puntero a constante, para no modificar/destruir el registro de serie.
	void BuscarSerieInternal(const std::string& UID, IModeloSerie2 **  result)
	{

		for (ListaPunterosSeriesType::iterator it = ListaPunterosSeries.begin(); *result == NULL && it != ListaPunterosSeries.end(); ++it) {
			IModeloSerie2* pi = *it;
			if (pi->GetUID() == UID) {
				*result = (*it);
			}
		}
	}


	// Esta operacion es interna porque devuelve como resultado un puntero a constante, para no modificar/destruir el registro de imagen.
	void BuscarImagenInternal(const std::string& UID, IModeloImagen2 **  result)
	{
		for (ListaPunterosImagenesType::iterator it = ListaPunterosImagenes.begin(); *result == NULL && it != ListaPunterosImagenes.end(); ++it) {
			IModeloImagen2* pi = *it;
			if (pi->GetUID() == UID) {
				*result = (*it);
			}
		}
	}

	// endregion

	// operaciones de inserccion

public:

	const IModeloPaciente2& InsertarPaciente(const std::string& UID, const std::string& nombre, const std::string& fechaNacimiento, const std::string& sexo)
	{

		const IModeloPaciente2* paciente = NULL;

		BuscarPaciente(UID, &paciente);

		if (paciente == NULL) {
			Pacientes.push_back(IModeloPaciente2( this, UID, nombre, fechaNacimiento, sexo));
			paciente = &Pacientes.back();
		}

		return *paciente;
	}

	const IModeloEstudio2& InsertarEstudio(const std::string& UIDPaciente, const std::string& UID, const std::string& descripcion, const std::string& modalidad, const std::string& fecha, const std::string& hora, const std::string& comentarios, const std::string& doctor)
	{

		IModeloEstudio2* estudio = NULL;

		BuscarEstudioInternal(UID, &estudio);

		if (estudio == NULL) {

			IModeloPaciente2* paciente = NULL;

			BuscarPacienteInternal(UIDPaciente, &paciente);

			if (paciente != NULL) {

				paciente->Estudios.push_back(IModeloEstudio2( this, paciente, UID, descripcion, modalidad, fecha, hora, comentarios,doctor));
				estudio = & (paciente->Estudios.back());
				ListaPunterosEstudios.push_back(estudio);
			}
			else {
				// El estudio no esta asociado a un paciente existente.
				throw std::exception();
			}
		}

		return *estudio;
	}

	const IModeloSerie2& InsertarSerie(const std::string& UIDEstudio, const std::string& UID, const std::string& tipo, const std::string& fecha, const std::string& hora, const std::string& descripcion, const std::string& numero, const std::string& doctor)
	{

		IModeloSerie2* serie = NULL;

		BuscarSerieInternal(UID, &serie);

		if (serie == NULL) {

			IModeloEstudio2* estudio = NULL;

			BuscarEstudioInternal(UIDEstudio, &estudio);

			if (estudio != NULL) {

				estudio->Series.push_back(IModeloSerie2( this, estudio, UID, tipo, fecha, hora, descripcion, numero,doctor));
				serie = & (estudio->Series.back());
				ListaPunterosSeries.push_back(serie);
			}
			else {
				// La Serie no esta asociada a un estudio existente.
				throw std::exception();
			}
		}

		return *serie;

	}

	const IModeloImagen2& InsertarImagen(const std::string& UIDSerie, const std::string& UID)
	{

		IModeloImagen2* imagen = NULL;

		BuscarImagenInternal(UID, &imagen);

		if (imagen == NULL) {

			IModeloSerie2* serie = NULL;

			BuscarSerieInternal(UIDSerie, &serie);

			if (serie != NULL) {

				serie->Imagenes.push_back(IModeloImagen2( this, serie, UID));
				imagen = & (serie->Imagenes.back());
				ListaPunterosImagenes.push_back(imagen);
			}
			else {
				// La Imagen no esta asociada a una serie existente.
				throw std::exception();
			}
		}

		return *imagen;

	}

	friend std::ostream& operator<<(std::ostream& out, const IModeloDicom2& d) {
		for (IModeloDicom2::ListaPacientesType::const_iterator it = d.Pacientes.begin(); it != d.Pacientes.end(); ++it) {
			const IModeloPaciente2& p = *it;
			out << "P: " << p.GetUID().c_str() << " [ " << p.GetNombre().c_str() << std::endl;
			for (IModeloPaciente2::ListaEstudiosType::const_iterator ite = p.ListaEstudios().begin(); ite != p.ListaEstudios().end(); ite++) {
				const IModeloEstudio2& e = *ite;
				out << "\tE: " << e.GetUID().c_str() << ", " << e.GetDescripcion().c_str() << std::endl;
				for (IModeloEstudio2::ListaSeriesType::const_iterator its = e.ListaSeries().begin(); its != e.ListaSeries().end(); its++) {
					const IModeloSerie2& s = *its;
					out << "\t\tS: " << s.GetUID().c_str() << ", " << s.GetTipo().c_str() << ", " << s.GetDescripcion().c_str() << std::endl;
					for (IModeloSerie2::ListaImagenesType::const_iterator iti = s.ListaImagenes().begin(); iti != s.ListaImagenes().end(); iti++) {
						const IModeloImagen2& i = *iti;
						out << "\t\t\t" << "I: " << i.GetUID().c_str() << std::endl;
					}
				}
			}
		}
		return out;
	}

	friend std::ostream& operator<<(std::ostream& out, IModeloDicom2* const d) {
		if (d != NULL) {
			out << *d;
		}
		else {
			out << "[ NULL ]";
		}
		return out;
	}

protected:
	ListaPacientesType Pacientes;

	ListaPunterosEstudiosType  ListaPunterosEstudios;
	ListaPunterosSeriesType    ListaPunterosSeries;
	ListaPunterosImagenesType  ListaPunterosImagenes;

};

