/*
 *  
 *  $Id: controladorvistas.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
 *
 *
 */
//#define _GINKGO_TRACE

#include <cmath>
#include <algorithm>

#include <wx/wx.h>
#include <wx/arrstr.h>
#include <wx/image.h>

#include "controladorvistas.h"


#include <api/ivista.h>
#include <eventos/modificacionfichero.h>

#include <main/gui/mainwindow/ventanaprincipal.h>
#include <main/entorno.h>

#include <main/controllers/commandcontroller.h>
#include <main/controllers/inactivitycontroller.h>
#include <main/controllers/controladorlog.h>
#include <main/controllers/controladorpermisos.h>


#include <iostream>
#include <sstream>
#include <string>

#include <main/gui/mainwindow/detacheddialog.h>

//region "Patron singleton"

GNC::GCS::ControladorVistas* GNC::GCS::ControladorVistas::m_pInstance = 0;

GNC::GCS::ControladorVistas::ControladorVistas()
{
	m_pVistaActiva   = NULL;

	//se conecta el evento Ginkgo
	GNC::GCS::Events::EventoModificacionFichero evtModificado;
	GNC::Entorno::Instance()->GetControladorEventos()->Registrar(this, evtModificado);
}

GNC::GCS::ControladorVistas::~ControladorVistas()
{
}

GNC::GCS::ControladorVistas* GNC::GCS::ControladorVistas::Instance()
{
	if (m_pInstance == NULL) {
		m_pInstance = new GNC::GCS::ControladorVistas();
	}
	return m_pInstance;
}

void GNC::GCS::ControladorVistas::FreeInstance()
{
	if (m_pInstance != NULL) {
		delete m_pInstance;
		m_pInstance = NULL;
	}
}

//endregion


wxWindow* GNC::GCS::ControladorVistas::GetRootWindow()
{
	return GNC::Entorno::Instance()->GetVentanaPrincipal()->GetRootWindow();
}

GNC::GCS::IVista* GNC::GCS::ControladorVistas::ObtenerVistaRegistrada(wxWindow* pVentana)
{
	GNC::GCS::IVista* pVista = NULL;

	MapaVentanas::iterator it = m_MapaVentanas.find(pVentana);
	if (it != m_MapaVentanas.end()) {
		pVista = (*it).second;
	}

	return pVista;
}

void GNC::GCS::ControladorVistas::Freeze()
{
	VentanaPrincipal* v = GNC::Entorno::Instance()->GetVentanaPrincipal();
	v->SuperFreeze();
}

void GNC::GCS::ControladorVistas::Thaw()
{
	VentanaPrincipal* v = GNC::Entorno::Instance()->GetVentanaPrincipal();
	v->SuperThaw();
}

void GNC::GCS::ControladorVistas::SetTitulo(GNC::GCS::IVista* pVista, const std::string& tituloNuevo) {
	//resets inactivity
	GNC::GCS::InactivityController::Instance()->ResetsInactivity();
	//
	wxWindow* pVentana = NULL;
	int numConteo = 0;
	bool cambia = true;

	pVentana = pVista->GetWindow();
	if (pVentana == NULL) {
		LOG_WARN("Core/ControladorVistas", "No se pudo obtener la ventana asociada a la vista. Inconsistencia interna");
		return;
	}
	else {
		// Buscamos el titulo actual que tiene asociado la vista compuesta, de haberlo.
		MapaTitulosRegistrados::iterator it = m_MapaTitulosRegistrados.find(pVista);
		if (it != m_MapaTitulosRegistrados.end()) { // Ya esta registrado.
			const std::string& tituloActual = (*it).second;
			if (tituloNuevo != tituloActual) { // El titulo ha cambiado

				// Buscamos el titulo antiguo del registro y lo borramos.

				MapaConteoTitulos::iterator it2 = m_MapaConteoTitulos.find(tituloActual);
				if (it2 != m_MapaConteoTitulos.end()) {
					MapaConteoRegistros& registrosConteo = (*it2).second;
					MapaConteoRegistros::iterator it3 = registrosConteo.find(pVista);
					if (it3 != registrosConteo.end()) {
						registrosConteo.erase(it3); // Eliminamos la entrada de la vista para ese titulo
						if (registrosConteo.empty()) { // Si no hay mas entradas de vista para ese titulo, eliminamos el registro de vistas asociadas a los titulos.
							m_MapaConteoTitulos.erase(it2);
						}
					}
					else {
						LOG_WARN("Core/ControladorVistas", "Se ha detectado una inconsistencia interna en el registro de conteos de titulos.");
					}
				}
				else {
					LOG_WARN("Core/ControladorVistas", "Se ha detectado una inconsistencia interna en el registro de titulos de ventanas.");
				}

				m_MapaTitulosRegistrados.erase(it); // Borramos el registro de titulo de la vista
			}
			else { // El titulo no cambia
				cambia = false;
			}
		}
	}

	if (cambia) {

		// Buscamos el titulo nuevo que tiene asociado la vista compuesta, de haberlo.
		MapaTitulosRegistrados::iterator it = m_MapaTitulosRegistrados.find(pVista);
		if (it == m_MapaTitulosRegistrados.end()) { // No esta registrado.
			m_MapaTitulosRegistrados[pVista] = tituloNuevo;
		}

		MapaConteoTitulos::iterator it2 = m_MapaConteoTitulos.find(tituloNuevo);
		if (it2 != m_MapaConteoTitulos.end()) { // Ya hay ventanas registradas con este titulo
			MapaConteoRegistros& registrosConteo = (*it2).second;
			MapaConteoRegistros::iterator it3 = registrosConteo.find(pVista);
			if (it3 != registrosConteo.end()) {
				LOG_WARN("Core/ControladorVistas", "Entrada duplicada en el registro de conteos de titulos. Inconsistencia interna");
				registrosConteo.erase(it3);
			}
			for (MapaConteoRegistros::iterator it3 = registrosConteo.begin(); it3 != registrosConteo.end(); ++it3)
			{
				const int num = (*it3).second + 1;
				numConteo = numConteo > num ? numConteo : num;
			}
			registrosConteo[pVista] = numConteo;
		}
		else {
			MapaConteoRegistros registroConteo;
			registroConteo[pVista] = 0;
			m_MapaConteoTitulos[tituloNuevo] = registroConteo;
		}
	}

	std::ostringstream os;
	if (pVista->SoportaGuardar() && pVista->EstaModificada()) {
		os << "* ";
	}
	os << tituloNuevo;
	if (numConteo > 0) {
		os << " (" << numConteo + 1 << ")";
	}

	wxAuiNotebook* nb = dynamic_cast<wxAuiNotebook*> (pVentana->GetParent());
	if (nb != NULL) {
		int idx = nb->GetPageIndex(pVentana);
		if (idx != wxNOT_FOUND) {
			nb->SetPageText(idx, wxString::FromUTF8(os.str().c_str()));
		}
		else {
			LOG_WARN("Core/ControladorVistas", "No se encontro el tab asociado. Inconsistencia interna");
		}
	} else {
		
		GNC::GUI::DetachedDialog* dd = dynamic_cast<GNC::GUI::DetachedDialog*> (pVentana->GetParent());
		if(dd != NULL)
		{
			dd->SetTitulo(wxString::FromUTF8(os.str().c_str()));
		}
		else {
			//it's startup view
			//LOG_WARN("Core/ControladorVistas", "No se encontro el panel o dialogo desencajado asociado a la vista. Inconsistencia interna");
		}
	}

}

std::string GNC::GCS::ControladorVistas::GetTitulo(GNC::GCS::IVista* pVista)
{
	//resets inactivity
	GNC::GCS::InactivityController::Instance()->ResetsInactivity();
	//

	wxWindow* pVentana = NULL;

	pVentana = pVista->GetWindow();
	if (pVentana == NULL) {
		LOG_WARN("Core/ControladorVistas", "No se pudo obtener la ventana asociada a la vista. Inconsistencia interna");
		return std::string("");
	}

	wxAuiNotebook* nb = dynamic_cast<wxAuiNotebook*> (pVentana->GetParent());
	if (nb != NULL) {
		int idx = nb->GetPageIndex(pVentana);
		if (idx != wxNOT_FOUND) {
			return std::string(nb->GetPageText(idx).ToUTF8());
		}
		else {
			LOG_WARN("Core/ControladorVistas", "No se pudo obtener el tab asociado a la vista. Inconsistencia interna");
			return std::string("");
		}
	}
	else {
		GNC::GUI::DetachedDialog* dd = dynamic_cast<GNC::GUI::DetachedDialog*> (pVentana->GetParent());
		if(dd!=NULL)
		{
			return std::string(dd->GetTitulo().ToUTF8());
		}
		else {
			LOG_WARN("Core/ControladorVistas", "No se encontro el panel o dialogo desencajado asociado a la vista. Inconsistencia interna");
			return std::string("");
		}
	}
}

void GNC::GCS::ControladorVistas::PropagarConfiguracionCambiada()
{
	GNC::GCS::ILocker lock(this);
	for (MapaVentanas::iterator it = m_MapaVentanas.begin(); it != m_MapaVentanas.end(); ++it) {
		GNC::GCS::IVista* pVista = (*it).second;
		pVista->OnConfiguracionCambiada();
	}
}

void GNC::GCS::ControladorVistas::Registrar(GNC::GCS::IVista* pVista)
{
	//resets inactivity
	GNC::GCS::InactivityController::Instance()->ResetsInactivity();
	//

	GNC::GCS::ILocker lock(this);
	if (pVista == NULL) {
		LOG_WARN("Core/ControladorVistas", "No se puede registrar una vista nula");
		return;
	}
	wxWindow* pVentana = pVista->GetWindow();
	if (pVentana == NULL) {
		LOG_WARN("Core/ControladorVistas", "No se puede registrar una vista con ventana nula");
		return;
	}

	MapaVentanas::iterator it = m_MapaVentanas.find(pVentana);
	if (it != m_MapaVentanas.end()) {
		LOG_WARN("Core/ControladorVistas", "La vista ya estaba registrada. Posible inconsistencia interna");
	}
	else {
		m_MapaVentanas[pVentana] = pVista;
	}
	GNC::Entorno::Instance()->GetVentanaPrincipal()->InsertarVentana(pVentana);
}

void GNC::GCS::ControladorVistas::Destruir(GNC::GCS::IVista* pVista)
{
	//resets inactivity
	GNC::GCS::InactivityController::Instance()->ResetsInactivity();
	//

	Lock();
	GTRACE("ControladorVistas::DestruirVista(GNC::GCS::IVista* pVista = " << pVista << ")");

	if (pVista != NULL) {
		wxWindow* pVentana = pVista->GetWindow();
		MapaVentanas::iterator it = m_MapaVentanas.find(pVentana);
		if (it != m_MapaVentanas.end()) {
			m_MapaVentanas.erase(it);
			PropagarVistaDestruida(pVista);
		}
		else {
			LOG_WARN("Core/ControladorVistas", "Error al destruir vista: La ventana asociada a la vista no estaba registrada. Posible inconsistencia interna");
		}
	}
	UnLock();
}


void GNC::GCS::ControladorVistas::Destruir(wxWindow* pVentana)
{
	pVentana->Hide();
	//resets inactivity
	GNC::GCS::InactivityController::Instance()->ResetsInactivity();
	//

	Lock();
	GTRACE("ControladorVistas::DestruirVista(wxWindow* pVentana = " << pVentana << ")");

	if (pVentana != NULL) {
		MapaVentanas::iterator it = m_MapaVentanas.find(pVentana);
		if (it != m_MapaVentanas.end()) {
			GNC::GCS::IVista* pVista = (*it).second;
			m_MapaVentanas.erase(it);
			PropagarVistaDestruida(pVista);
		}
		else {
			//LOG_WARN("Core/ControladorVistas", "Error al destruir ventana: La ventana no estaba registrada. Posible inconsistencia interna");
		}
	}
	UnLock();
}


//region "Gestion de carga y descarga de vistas"

void GNC::GCS::ControladorVistas::SolicitarActivarVista(GNC::GCS::IVista* pVista) {
	//resets inactivity
	GNC::GCS::InactivityController::Instance()->ResetsInactivity();
	//

	if (pVista == m_pVistaActiva) {
		return;
	}

	Freeze();

	if (m_pVistaActiva != NULL) {
		m_pVistaActiva->Desactivar();
		m_pVistaActiva->GetWindow()->Refresh();
	}

	m_pVistaActiva = pVista;

	GTRACE("GNC::GCS::ControladorVistas::SolicitarActivarVista(" << pVista << ")");


	if (pVista != NULL) {
		pVista->Activar();
		m_pVistaActiva->GetWindow()->Refresh();
	}

	GNC::Entorno* pEntorno = GNC::Entorno::Instance();
	Entorno::ViewsObserversList::const_iterator it;
	for (it = pEntorno->ViewsObservers.begin(); it != pEntorno->ViewsObservers.end(); ++it) {
		IObservadorVistas* oe = *it;
		oe->OnVistaActivada(pVista);
	}

	Thaw();
}


//endregion

//region "Propagación de eventos"

void GNC::GCS::ControladorVistas::PropagarVistaCreada(GNC::GCS::IVista* pVista)
{
	//resets inactivity
	GNC::GCS::InactivityController::Instance()->ResetsInactivity();
	//

	GNC::Entorno* pEntorno = GNC::Entorno::Instance();
	Entorno::ViewsObserversList::const_iterator it;
	for (it = pEntorno->ViewsObservers.begin(); it != pEntorno->ViewsObservers.end(); ++it) {
		IObservadorVistas* ov = *it;
		ov->OnVistaCreada(pVista);
	}
}

inline bool my_isnan(double x)
{
	return x != x;
}

void GNC::GCS::ControladorVistas::PropagarVistaDestruida(GNC::GCS::IVista* pVista)
{
	//resets inactivity
	GNC::GCS::InactivityController::Instance()->ResetsInactivity();
	//

	if (pVista == NULL) {
		LOG_WARN("Core/ControladorVistas", "Error al propagar destruccion de vista. Vista nula. Inconsistencia interna");
		return;
	}

	GNC::Entorno* pEntorno = GNC::Entorno::Instance();

	MapaTitulosRegistrados::iterator it = m_MapaTitulosRegistrados.find(pVista);
	if (it != m_MapaTitulosRegistrados.end()) { // Esta registrado.
		const std::string& titulo = (*it).second;
		MapaConteoTitulos::iterator it2 = m_MapaConteoTitulos.find(titulo);
		if (it2 != m_MapaConteoTitulos.end()) {
			MapaConteoRegistros& registrosConteo = (*it2).second;
			MapaConteoRegistros::iterator it3 = registrosConteo.find(pVista);
			if (it3 != registrosConteo.end()) {
				registrosConteo.erase(it3); // Eliminamos la entrada de la vista para ese titulo
				if (registrosConteo.empty()) { // Si no hay mas entradas de vista para ese titulo, eliminamos el registro de vistas asociadas a los titulos.
					m_MapaConteoTitulos.erase(it2);
				}
			}
			else {
				LOG_WARN("Core/ControladorVistas", "Se ha detectado una inconsistencia interna en el registro de conteos de titulos.");
			}
		}
		else {
			LOG_WARN("Core/ControladorVistas", "Se ha detectado una inconsistencia interna en el registro de titulos de ventanas.");
		}
		m_MapaTitulosRegistrados.erase(it); // Borramos el registro de titulo de la vista
	}
	else {
		LOG_WARN("Core/ControladorVistas", "Destruccion de vista sin titulo registrado.");
	}

	Entorno::ViewsObserversList::const_iterator ito;
	for (ito = pEntorno->ViewsObservers.begin(); ito != pEntorno->ViewsObservers.end(); ++ito) {
		IObservadorVistas* ov = *ito;
		ov->OnVistaDestruida(pVista);
	}

	//se desactiva la vista y se desreferencia si es la activa
	pVista->Desactivar();
	if (pVista == m_pVistaActiva)
	{
		SolicitarActivarVista(NULL);
	}
}

//endregion

//region "Getters de vistas activas"
GNC::GCS::IVista* GNC::GCS::ControladorVistas::GetVistaActiva() {
	return m_pVistaActiva;
}
//endregion

//region "Gestión de paneles extra"

void GNC::GCS::ControladorVistas::MostrarPanelFlotante(wxWindow* win, int x, int y)
{
	GTRACE("ControladorVistas::MostrarPanel(" << win << ")")
	VentanaPrincipal* vp = GNC::Entorno::Instance()->GetVentanaPrincipal();
	if (vp == NULL) {
		throw std::exception();
	}
	vp->Freeze();
	//std::cout << "Mostrando panel " << win << std::endl;
	vp->m_mgr.GetPane(win).Show(true);
	//se muestra abajo
	wxAuiPaneInfo& pi = vp->m_mgr.GetPane(win);
	if (!pi.IsFloating()) {
		pi.Float();
	}
	pi.FloatingSize(pi.best_size);
	pi.FloatingPosition(x,y);
	vp->m_mgr.Update();
	vp->Layout();
	vp->Thaw();
}

bool GNC::GCS::ControladorVistas::EsVisible(wxWindow* win)
{
	VentanaPrincipal* vp = GNC::Entorno::Instance()->GetVentanaPrincipal();
	if (vp == NULL) {
		throw std::exception();
	}
	wxAuiPaneInfo pi = vp->m_mgr.GetPane(win);
	return pi.IsShown();
}

void GNC::GCS::ControladorVistas::OcultarPanel(wxWindow* win)
{
	GTRACE("ControladorVistas::OcultarPanel(" << win << ")")
	VentanaPrincipal* vp = GNC::Entorno::Instance()->GetVentanaPrincipal();
	if (vp == NULL) {
		throw std::exception();
	}
	vp->SuperFreeze();
	//std::cout << "Ocultando panel " << win << std::endl;
	vp->m_mgr.GetPane(win).Hide();
	vp->m_mgr.Update();
	vp->Layout();
	vp->SuperThaw();
}

GNC::GCS::ControladorVistas::TipoListaVistas GNC::GCS::ControladorVistas::GetVistas(){
	//resets inactivity
	GNC::GCS::InactivityController::Instance()->ResetsInactivity();
	//

	GNC::GCS::ControladorVistas::TipoListaVistas result;

	for(MapaVentanas::iterator it = m_MapaVentanas.begin(); it!= m_MapaVentanas.end(); ++it){
		result.push_back((*it).second);
	}

	return result;
}


void GNC::GCS::ControladorVistas::ProcesarEvento(GNC::GCS::Events::IEvent *evt){
	GNC::GCS::Events::EventoModificacionFichero* pEvt = dynamic_cast<GNC::GCS::Events::EventoModificacionFichero*>(evt);

	GNC::GCS::IVista* pVista = NULL;

	if(pEvt != NULL && pEvt->GetVistaModificada()!=NULL){
		if(pEvt->GetTipoEvento() == GNC::GCS::Events::EventoModificacionFichero::FicheroGuardado){
			pVista = pEvt->GetVistaModificada();
		}
		else if(pEvt->GetTipoEvento() == GNC::GCS::Events::EventoModificacionFichero::FicheroModificado){
			pVista = pEvt->GetVistaModificada();
		}
	}

	if (pVista != NULL)
	{
		SetTitulo(pVista, pVista->GetTitulo());
	}

}
