/*
 *  
 *  $Id: wecg.cpp $
 *  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
 *
 *
 */

#include <cmath>
#include <cstring>
#include <ostream>
#include <list>

#include <api/globals.h>
#include <api/controllers/icontroladorlog.h>
#include <api/math/geometry3d.h>
#include <api/icontexto.h>
#include <api/ievento.h>
#include <api/iwidgetsmanager.h>
#include <api/iwidgetsrenderer.h>
#include <api/controllers/ieventscontroller.h>
#include <api/ientorno.h>
#include <api/dicom/imodelodicom.h>
#include <api/westilo.h>

#include <api/controllers/icontroladorpermisos.h>

#include <eventos/modificacionimagen.h>

#include <main/entorno.h>


#include "wecg.h"
#include <widgets/openglhelper.h>

#include <vtkgl.h>
#include <vtkImageData.h>
#include <vtkImageActor.h>
#include <vtkPointData.h>
#include <vtk/vtkginkgoimageviewer.h>

#include <widgets/openglhelper.h>
#include <cairo/cairo.h>

#if defined(_GINKGO_DEBUG)
#define DEBUG_PRECISION
#endif

// Propiedades visuales

#define COLOR_FONDO 0.9f, 0.9f, 0.9f, 1.0f

// Num de pixels pantalla minimo para que aparezca la rejilla
#define GRID_1MM_NEAR_THRESHOLD 0.7
#define GRID_5MM_FAR_THRESHOLD 0.7
#define GRAPH_FAR_THRESHOLD 0.7

//greater that this text wont be shown
#define TEXT_FAR_THRESHOLD 1.2

#define GRID_1MM_LINE_WIDTH 0.25f
#define GRID_1MM_LINE_COLOR 1.0f, 0.0f, 0.0f, 0.3f

#define GRID_5MM_LINE_WIDTH 1.0f
#define GRID_5MM_LINE_COLOR 1.0f, 0.0f, 0.0f, 0.5f
#define GRID_5MM_LINE_WIDTH_FAR 0.15f
#define GRID_5MM_LINE_COLOR_FAR 1.0f, 0.0f, 0.0f, 0.5f

#define GRAPH_LINE_WIDTH 1.5f
#define GRAPH_LINE_COLOR 0.0f, 0.0f, 0.0f, 0.9f

#define GRAPH_LINE_WIDTH_FAR 0.3f
#define GRAPH_LINE_COLOR_FAR 0.0f, 0.0f, 0.0f, 0.9f

#define LEAD_TEXT_COLOR 1.0f, 0.1f, 0.1f, 1.0f

#define REFERENCE_WIDTH 0.4f //0.4 seconds


static GNC::GCS::Vector WinMarginRel[2] = {
	GNC::GCS::Vector(0.0, 0.0), GNC::GCS::Vector(1.0, 1.0)
};

//region "Implementacion de WBuilder"

GNKVisualizator::Widgets::WECGBuilder::WECGBuilder(GNC::GCS::IWidgetsManager* pManager, const GNC::GCS::TriggerButton& buttonMask, long gid) : GNC::GCS::Widgets::IWidgetBuilder(pManager, buttonMask, gid, false), IObservadorWidget(-1, gid)
{
	m_pRejillaActiva = NULL;
	m_pManager->InsertarObservador(this);

	// Comprobamos si existe alguna rejilla para esta vista, y de ser asi la asociamos.
	bool found = false;
	for( GNC::GCS::ListaWidgets::iterator it = m_pManager->GetListaWidgets().begin(); !found && it != m_pManager->GetListaWidgets().end(); ++it) {
		GNC::GCS::Widgets::IWidget* w = *it;
		if (w->GetVID() == -1 && w->GetGID() == m_GID) {
			m_pRejillaActiva = dynamic_cast<GNKVisualizator::Widgets::WECG*> (w);
			if (m_pRejillaActiva != NULL) {
				return;
			}
		}
	}

}

GNKVisualizator::Widgets::WECGBuilder::~WECGBuilder()
{
	m_pManager->EliminarObservador(this);
}

void GNKVisualizator::Widgets::WECGBuilder::AutoCallibrateImage()
{
	if (m_pRejillaActiva) {
		m_pRejillaActiva->AutoCallibrate();
	}

}

void GNKVisualizator::Widgets::WECGBuilder::OnWidgetDestruido(GNC::GCS::Widgets::IWidget* w) {
	if (m_pRejillaActiva == w) {
		m_pRejillaActiva = NULL;
	}
}

//region "Interfaz especifica"
void GNKVisualizator::Widgets::WECGBuilder::OnMouseEvents(GNC::GCS::Events::EventoRaton& )
{
	if (!m_pManager) {
		return;
	}
	GTRACE("GNKVisualizator::Widgets::WECGBuilder::OnMouseEvents(wxMouseEvent&)");
}

void GNKVisualizator::Widgets::WECGBuilder::OnKeyEvents(GNC::GCS::Events::EventoTeclado&)
{
	GTRACE("GNKVisualizator::Widgets::WECGBuilder::OnKeyEvents(wxKeyEvent&)");
}

void GNKVisualizator::Widgets::WECGBuilder::Render(GNC::GCS::Contexto3D* )
{
	return;
}

//endregion

GNKVisualizator::Widgets::RecursosECG::RecursosECG()
{
}
GNKVisualizator::Widgets::RecursosECG::~RecursosECG()
{
	if (!Texturas.empty()) {
		LOG_WARN("ECGResources", "Resource leaked");
	}
}

void GNKVisualizator::Widgets::RecursosECG::Alloc(size_t csize)
{
	if (csize == Texturas.size()) {
		return;
	}
	Dealloc();
	Texturas = TListaTexturas(csize);
	for (unsigned int i = 0; i < csize; i++) {
		Texturas[i] = new TexturasCanalECG();
	}
}

void GNKVisualizator::Widgets::RecursosECG::Dealloc()
{
	for (size_t i = 0; i < Texturas.size(); i++)
	{
		Texturas[i]->Destruir();
		delete Texturas[i];
	}
	Texturas.clear();
}

namespace GNKVisualizator {
	namespace Widgets {

		TexturasCanalECG::TexturasCanalECG() {
			tl = new GNC::GCS::TexturaCairo();
			tl->Redimensionar(2, 2);
		}

		TexturasCanalECG::~TexturasCanalECG() {
			delete tl;
		}

		void TexturasCanalECG::Destruir()
		{
			tl->Destruir();
		}

		class Channel {
		public:
			
			Channel(const ChannelInfo& info, const GNC::GCS::Vector& pos, const GNC::GCS::Vector& size, GNC::GCS::Vector& rel):
				sx(0.0),
				sy(0.0),
				Info(info),
				Pos(pos),
				Size(size),
				Rel(rel)				
			{				
			}
			
			~Channel()
			{
			}
			
			void Plot(GNC::GCS::Contexto3D* c, GNC::GCS::TexturaCairo* tl) {
				
				GNC::GCS::Vector sample;
				GNC::GCS::Vector textSize;

				
				if (!tl->EsValida() || TamTitle.EsNulo()) {
					const std::string Title = Info.GetTitle();
					tl->SetFontBold(WIDGETS_TEXT_FONT_FAMILY, GNC::GCS::IControladorPermisos::Instance()->Get("style", "ecg.label.fontSize").ObtenerValor<float>());
					textSize = GNC::GCS::GLHelper::calcularBoundingBox((*tl), Title, false, 300.0f); // Tamaņo de la region en pixels
					tl->Redimensionar(std::ceil(textSize.x), std::ceil(textSize.y));
					textSize.Asignar(tl->ancho, tl->alto);
					
					tl->SetFontBold(WIDGETS_TEXT_FONT_FAMILY, GNC::GCS::IControladorPermisos::Instance()->Get("style", "ecg.label.fontSize").ObtenerValor<float>());
					textSize = GNC::GCS::GLHelper::calcularBoundingBox((*tl), Title, false, 300.0f); // Tamaņo de la region en pixels
					GNC::GCS::GLHelper::TColor col(LEAD_TEXT_COLOR);
					TamTitle = GNC::GCS::GLHelper::dibujarTexto(*tl, Title, col, false);
				}

				if (!tl->texturaCargada) {
					tl->Cargar();
				}
				if (tl->texturaModificada) {
					tl->Actualizar();
				}	

				if (c->RefRelacionImagenPantalla().x < TEXT_FAR_THRESHOLD ) {
					//compute display position...if it's too far labels doesn't show well
					GNC::GCS::Vector planePos;
					planePos = Pos;
					GNC::GCS::Vector3D worldPos;
					GNC::GCS::Vector3D displayPos;

					c->pRenderer->m_pImageViewer->UnProyect2D(planePos.v, worldPos.v);
					c->pRenderer->WorldToDisplay(worldPos.v, displayPos.v);

					//ensure label is shown...
					displayPos.x = std::max(0.0, displayPos.x);
					displayPos.y = std::max(0.0, displayPos.y);

					wDibujarTexturaTexto2D(c, *tl, displayPos.v);
				}
								

				if (c->RefRelacionImagenPantalla().x > GRAPH_FAR_THRESHOLD ) {
					glColor4d(GRAPH_LINE_COLOR_FAR);
					glLineWidth(GRAPH_LINE_WIDTH_FAR);
				}
				else {
					glColor4d(GRAPH_LINE_COLOR);
					glLineWidth(GRAPH_LINE_WIDTH);
				}

				//draw reference...
				int i = 0;
				{
					//2 squares of 0.2 sec
					float referenceWidth = Info.SamplingFrecuency * REFERENCE_WIDTH;
					if (!Info.Samples.empty())
					{
						glBegin(GL_LINE_STRIP);
						do
						{
							if ((i < referenceWidth/4) || (i >3*referenceWidth/4))
							{
								sample = Pos + (GNC::GCS::Vector(i, 0) * Rel);
							}	
							else
							{
								sample = Pos + (GNC::GCS::Vector(i, -(1000) * Info.Multiplier * Info.Sensitivity * Info.SensitivityCorrectionFactor) * Rel);
							}
							glVertex3d( sample.x, sample.y, 0.0);
						} while (++i < referenceWidth);
						glEnd();
					}
				}

				glBegin(GL_LINE_STRIP);
				for (GNKVisualizator::ChannelInfo::TVectorSamples::const_iterator it = Info.Samples.begin(); it != Info.Samples.end(); ++it, ++i)
				{
					sample = Pos + (GNC::GCS::Vector(i, -(*it) * Info.Multiplier * Info.Sensitivity * Info.SensitivityCorrectionFactor) * Rel);
					glVertex3d( sample.x, sample.y, 0.0);
					
				}
				glEnd();
			}
			
			double sx;
			double sy;
			
			GNC::GCS::Vector Pos;
			GNC::GCS::Vector Size;
			GNC::GCS::Vector Rel;
			
			
			GNC::GCS::Vector TamTitle;
			GNC::GCS::Vector TamUnits;

			const ChannelInfo Info;
		};
		
		class Data : public std::list<Channel> {
		public:
			Data() : NumSamples(0), NumChannels(0), SamplingFreq(0.0)
			{
			}
			
			~Data() {
				clear();
			}
			
			void Setup(GNC::GCS::Vector ImageSize, GNC::GCS::Vector ImageSpacing, const GNKVisualizator::ECGStudy::TListChannelInfo& listInfoChannels) {
				clear();
				if (listInfoChannels.size() == 0) {
					return;
				}
				NumChannels = listInfoChannels.size();
				SamplingFreq = listInfoChannels.front().SamplingFrecuency;
				//we have to add reference width (0.4 seconds)
				const float referenceWidth = SamplingFreq*REFERENCE_WIDTH;
				NumSamples = listInfoChannels.front().NumSamples + referenceWidth;
				
				GNC::GCS::Vector WinMargin[2] = {ImageSize * WinMarginRel[0], ImageSize * WinMarginRel[1]};
				GNC::GCS::Vector WinSize = WinMargin[1] - WinMargin[0];	
				
				GNC::GCS::Vector CSize ( WinSize.x / (double) NumSamples, WinSize.y / (double) NumChannels);
				GNC::GCS::Vector COff(WinMargin[0].x, WinMargin[0].y  + (0.5 * CSize.y));
				GNC::GCS::Vector CRel = GNC::GCS::Vector( CSize.x , 0.1/ImageSpacing.y); //con el inverso del spacing se sacan los mV como es 10mm/mv pues 0.1mm entre el spacing
				
				GNC::GCS::Vector CPos = COff;
				
				for (GNKVisualizator::ECGStudy::TListChannelInfo::const_iterator it = listInfoChannels.begin(); it != listInfoChannels.end(); ++it) {
					push_back(Channel((*it), CPos, CSize, CRel));
					CPos.y += CSize.y;
				}
			}
			
			void Plot(GNC::GCS::Contexto3D* ct, GNKVisualizator::Widgets::RecursosECG* rec) {

				rec->Alloc(size());
				unsigned int i = 0;
				for (iterator it = begin(); it != end(); ++it, i++)
				{
					Channel& c = *it;
					c.Plot(ct, rec->Texturas[i]->tl);
				}
			}

			size_t NumSamples;
			size_t NumChannels;
			double SamplingFreq;
							
		};
		
	}
}
//-----------------------------------------------------------------------------------------------

//region "Constructor y destructor"

GNKVisualizator::Widgets::WECG::WECG(GNC::GCS::IWidgetsManager* pManager, long vid, const char* nombre) : GNC::GCS::Widgets::IWidget(pManager, vid, nombre)
{
	m_TopLevel        = false;
	m_Oculto          = false;
	m_ReservaRecursos = true;
	this->CData       = new Data();
}

GNKVisualizator::Widgets::WECG::~WECG()
{
	delete this->CData;
}

//endregion

template<class T> void Get(const std::string& str, T& val)
{
	std::istringstream is(str);
	is >> val;
}

void GNKVisualizator::Widgets::WECG::AutoCallibrate()
{
	int dims[3];
	m_pManager->GetRendererActivo()->m_pImageViewer->GetDimensions(dims);

	GNC::GCS::Vector dimsImagen = dims;

	if (CData->size() > 0) {
		double time_channels = CData->NumSamples / CData->SamplingFreq;
		GNC::GCS::Vector WinMarginSize = (WinMarginRel[1] - WinMarginRel[0]) * dimsImagen;
			
		double time_px = 25.0 * (time_channels / WinMarginSize.x); // 25 mm/s
		double amplitude_px = 10.0 * (2.0 * (double) CData->size()  / WinMarginSize.y); // 10 mm/mV => 20 mV por canal
			
		GNC::GCS::Vector3D oldSpacing;
		GNC::GCS::Vector3D newSpacing(time_px, amplitude_px, 1.0);
		GNC::GCS::Vector3D oldOrigin;
				
		m_pManager->GetRendererActivo()->m_pImageViewer->GetSpacing(oldSpacing.v);
		m_pManager->GetRendererActivo()->m_pImageViewer->GetOrigin(oldOrigin.v);

		if (oldSpacing != newSpacing) {

			GNC::GCS::Events::EventoModificacionImagen* pEvt = new GNC::GCS::Events::EventoModificacionImagen(m_pManager->GetVista(), GNC::GCS::Events::EventoModificacionImagen::ImagenRecalibrada, GetVID());
			pEvt->SetOldSpacing(oldSpacing.v);
			pEvt->SetNewSpacing(newSpacing.v);
			pEvt->SetOldOrigin(oldOrigin.v);
			pEvt->SetNewOrigin(oldOrigin.v);

			GNC::GCS::IEntorno::Instance()->GetControladorEventos()->ProcesarEvento(pEvt);
		}
	}
}


void GNKVisualizator::Widgets::WECG::Reload(const GNKVisualizator::ECGStudy::TListChannelInfo& listInfoChannels)
{
	if(listInfoChannels.size() > 0)
	{
		int dims[3];
		double spacing[3];
		m_pManager->GetRendererActivo()->m_pImageViewer->GetDimensions(dims);
		m_pManager->GetRendererActivo()->m_pImageViewer->GetSpacing(spacing);
		
		GNC::GCS::Vector imageDimensions = dims;
		GNC::GCS::Vector imageSpacing = spacing;


		CData->Setup(imageDimensions, imageSpacing, listInfoChannels);
	}
	else {
		return;
	}
	
	//base->dump(std::cout);



	//waveformSeq->dump(std::cout);
	

}


//region "Interfaz generica"

void GNKVisualizator::Widgets::WECG::OnMouseEvents(GNC::GCS::Events::EventoRaton& )
{
}

void GNKVisualizator::Widgets::WECG::OnKeyEvents(GNC::GCS::Events::EventoTeclado&)
{
}

bool GNKVisualizator::Widgets::WECG::HitTest(float , float , const GNC::GCS::Vector& /*umbral*/)
{
	return false;
}

bool GNKVisualizator::Widgets::WECG::HitTest(GNC::GCS::Vector* , int )
{
	return false;
}

void GNKVisualizator::Widgets::WECG::Render(GNC::GCS::Contexto3D* c)
{
	if(m_Oculto){
		return;
	}

	if (CData->size() == 0) {
		return;
	}

	RecursosECG* rec = NULL;
	TMapaRecursos::iterator it = m_mapaRecursos.find(c->GetRenderer());
	if(it == m_mapaRecursos.end()) {
		rec = new RecursosECG();
		m_mapaRecursos[c->GetRenderer()] = rec;
	}
	else {
		rec = (*it).second;
	}


	{
		int dims[3];
		GNC::GCS::Vector3D spacing;
		m_pManager->GetRendererActivo()->m_pImageViewer->GetDimensions(dims);
		m_pManager->GetRendererActivo()->m_pImageViewer->GetSpacing(spacing.v);

		GNC::GCS::Vector dimsImagen = dims;
		GNC::GCS::Vector step;
		
		GNC::GCS::Vector win[2] = { dimsImagen * WinMarginRel[0], dimsImagen * WinMarginRel[1] };

		glClearColor(COLOR_FONDO);
		glClear(GL_COLOR_BUFFER_BIT);
		/*
		glBegin(GL_QUADS);
		glVertex2d(0.0, 0.0);
		glVertex2d(0.0, dimsImagen.y);
		glVertex2d(dimsImagen.x, dimsImagen.y);
		glVertex2d(dimsImagen.x, 0.0);
		glEnd();
		*/
		step.Asignar(1.0 / spacing.x, 1.0 / spacing.y);

		if (c->RefRelacionImagenPantalla().x < GRID_1MM_NEAR_THRESHOLD) {

			glLineWidth(GRID_1MM_LINE_WIDTH);
			glColor4f(GRID_1MM_LINE_COLOR);
			glBegin(GL_LINES);
			for (double x = win[0].x; x < win[1].x; x += step.x) {
				glVertex2d(x, win[0].y);
				glVertex2d(x, win[1].y);
			}
			for (double y = win[0].y; y < win[1].y; y += step.y) {
				glVertex2d(win[0].x, y);
				glVertex2d(win[1].x, y);
			}
			glEnd();
		}

		step *= 5.0f;

		
		if (c->RefRelacionImagenPantalla().x > GRID_5MM_FAR_THRESHOLD) {
			glLineWidth(GRID_5MM_LINE_WIDTH_FAR);
			glColor4f(GRID_5MM_LINE_COLOR_FAR);
		}
		else {
			glLineWidth(GRID_5MM_LINE_WIDTH);
			glColor4f(GRID_5MM_LINE_COLOR);
		}
		
		glBegin(GL_LINES);
		for (double x = win[0].x; x <= win[1].x; x += step.x) {
			glVertex2d(x, win[0].y);
			glVertex2d(x, win[1].y);
		}
		for (double y = win[0].y; y <= win[1].y; y += step.y) {
			glVertex2d(win[0].x, y);
			glVertex2d(win[1].x, y);
		}
		glEnd();

		/*
		glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
		glLineWidth(2.0f);
		glBegin(GL_LINE_LOOP);
		glVertex2d(0.0, 0.0);
		glVertex2d(dims[0], 0.0);
		glVertex2d(dims[0], dims[1]);
		glVertex2d(0.0, dims[1]);
		glVertex2d(0.0, 0.0);
		glEnd();
		*/
	}

	CData->Plot(c, rec);
}

void GNKVisualizator::Widgets::WECG::Seleccionar(bool )
{
}

void GNKVisualizator::Widgets::WECG::Iluminar(bool )
{
}

void GNKVisualizator::Widgets::WECG::Ocultar(bool oculto)
{
	if (m_Oculto != oculto) {
		m_Oculto = oculto;
		m_Modificado = true;
	}
}

void GNKVisualizator::Widgets::WECG::LiberarRecursos(GNC::GCS::IWidgetsRenderer* pKey)
{
	TMapaRecursos::iterator it = m_mapaRecursos.find(pKey);
	if(it != m_mapaRecursos.end()) {
		RecursosECG* rec = (*it).second;
		rec->Dealloc();
		delete rec;
		m_mapaRecursos.erase(it);
	}
}
//endregion

