//============================================================================
// Copyright 2009-2020 ECMWF.
// This software is licensed under the terms of the Apache Licence version 2.0
// which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
// In applying this licence, ECMWF does not waive the privileges and immunities
// granted to it by virtue of its status as an intergovernmental organisation
// nor does it submit to any jurisdiction.
//
//============================================================================

#include "Dashboard.hpp"

#include "DashboardDialog.hpp"
#include "DashboardDock.hpp"
#include "DashboardTitle.hpp"
#include "InfoPanel.hpp"
#include "NodeWidget.hpp"
#include "ServerHandler.hpp"
#include "ServerFilter.hpp"
#include "TableNodeWidget.hpp"
#include "TreeNodeWidget.hpp"
#include "UiLog.hpp"
#include "VFilter.hpp"
#include "VConfig.hpp"
#include "VSettings.hpp"
#include "WidgetNameProvider.hpp"

#include <QContextMenuEvent>
#include <QDebug>
#include <QVBoxLayout>
#include <QLabel>
#include <QDockWidget>
#include <QToolButton>
#include "NodeSearchWindow.hpp"
#include "NodeSearchWidget.hpp"

int Dashboard::maxWidgetNum_=20;

Dashboard::Dashboard(QString rootNode,QWidget *parent) :
   QMainWindow(parent),
   settingsAreRead_(false)
{
	//We use the mainwindow as a widget. Its task is
	//to dock all the component widgets!
	setWindowFlags(Qt::Widget);

    setObjectName("db");

	//The serverfilter. It holds the list of servers displayed by this dashboard.
	serverFilter_=new ServerFilter();
	serverFilter_->addObserver(this);

	titleHandler_=new DashboardTitle(serverFilter_,this);

	//Central widget - we need to create it but we do not
	//use it. So we can hide it!
	QWidget *w=new QLabel("centre",this);
    w->setObjectName("dbc");
    setCentralWidget(w);
	w->hide();

	layout()->setContentsMargins(0,0,0,0);

	setDockOptions(QMainWindow::AnimatedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::AllowNestedDocks);
}

Dashboard::~Dashboard()
{
    widgets_.clear();
    popupWidgets_.clear();

    Q_EMIT aboutToDelete();

	serverFilter_->removeObserver(this);
	delete serverFilter_;
}


DashboardWidget* Dashboard::addWidgetCore(const std::string& type,bool userAddedView)
{
	DashboardWidget *w=nullptr;

	//Create a dashboard widget
	if(type == "tree")
	{
		NodeWidget* ctl=new TreeNodeWidget(serverFilter_,this);

		connect(ctl,SIGNAL(popInfoPanel(VInfo_ptr,QString)),
				this,SLOT(slotPopInfoPanel(VInfo_ptr,QString)));

		connect(ctl,SIGNAL(dashboardCommand(VInfo_ptr,QString)),
				this,SLOT(slotCommand(VInfo_ptr,QString)));

		w=ctl;
	}
	else if(type == "table")
	{
        NodeWidget* ctl=new TableNodeWidget(serverFilter_,userAddedView,this);

		connect(ctl,SIGNAL(popInfoPanel(VInfo_ptr,QString)),
				this,SLOT(slotPopInfoPanel(VInfo_ptr,QString)));

		connect(ctl,SIGNAL(dashboardCommand(VInfo_ptr,QString)),
				this,SLOT(slotCommand(VInfo_ptr,QString)));

		w=ctl;
	}
	else if(type == "info")
	{
        auto* ctl=new InfoPanel(this);

        connect(ctl,SIGNAL(popInfoPanel(VInfo_ptr,QString)),
                this,SLOT(slotPopInfoPanel(VInfo_ptr,QString)));

        connect(ctl,SIGNAL(dashboardCommand(VInfo_ptr,QString)),
                this,SLOT(slotCommand(VInfo_ptr,QString)));

		w=ctl;
	}

    if(w)
    {
        connect(w,SIGNAL(selectionChanged(VInfo_ptr)),
                this,SLOT(slotSelectionChanged(VInfo_ptr)));

        connect(w,SIGNAL(maximisedChanged(DashboardWidget*)),
                this,SLOT(slotMaximisedChanged(DashboardWidget*)));
    }

	return w;
}


void Dashboard::slotSelectionChanged(VInfo_ptr info)
{
    auto* s=static_cast<DashboardWidget*>(sender());

    Q_FOREACH(DashboardWidget* dw,widgets_)
    {
         if(dw != s)
             dw->setCurrentSelection(info);
    }
    Q_FOREACH(DashboardWidget* dw,popupWidgets_)
    {
         if(dw != s)
             dw->setCurrentSelection(info);
    }

    Q_EMIT selectionChanged(info);
}


DashboardWidget* Dashboard::addWidget(const std::string& type)
{
	//Get a unique dockId stored as objectName
	QString dockId=uniqueDockId();

    if(hasMaximised())
    {
        resetMaximised();
    }

    bool popupTableFilter=false;
    if(VProperty* prop=VConfig::instance()->find("view.table.popupFilter"))
    {
        popupTableFilter=prop->value().toBool();
    }

    DashboardWidget* w=addWidget(type,dockId.toStdString(),popupTableFilter);

	//At this point the widgets can be inactive. Reload will make them active!!!
	w->reload();

	if(type == "info")
	{
		VInfo_ptr info=currentSelectionInView();
		if(info && info.get())
		{
			if(auto* ip=static_cast<InfoPanel*>(w))
			{
				ip->slotReload(info);
			}
		}
	}

	return w;
}

DashboardWidget* Dashboard::addWidget(const std::string& type,const std::string& dockId,bool userAddedView)
{
    DashboardWidget *w=Dashboard::addWidgetCore(type,userAddedView);

	//If the db-widget creation fails we should do something!!!
	if(!w)
		return nullptr;

	//Store dockId in the db-widget
	w->id(dockId);

	widgets_ << w;

    //Create a dockwidget
	auto *dw = new DashboardDock(w, this);

    dw->setAllowedAreas(Qt::RightDockWidgetArea);

    //Store the dockId  in the dockwidget (as objectName)
    dw->setObjectName(QString::fromStdString(dockId));

    //Add the dockwidget to the dashboard
    addDockWidget(Qt::RightDockWidgetArea, dw);

    connect(dw,SIGNAL(closeRequested()),
    		this,SLOT(slotDockClose()));

    checkMaximisedState();

    return w;
}

DashboardWidget* Dashboard::addDialog(const std::string& type)
{
    DashboardWidget *w=Dashboard::addWidgetCore(type,false);

	//If the db-widget creation fails we should do something!!!
	if(!w)
		return nullptr;

	//The DashBoard or any of its children cannot be the parent of the
	//dialog because in this case it would be always on top its parent. This is
	//the behaviour when the dialog's parent is QMainWindow.
	auto* dia=new DashboardDialog(nullptr);

    //So the parent is 0 and we will emit a signal from the Dashboard
    //destructor to notify the dialog about the deletion. Then we can be
	//sure that the dialog deletes itself when the Dashboard gets deleted.
    connect(this,SIGNAL(aboutToDelete()),
    		dia,SLOT(slotOwnerDelete()));

    connect(dia,SIGNAL(finished(int)),
            this,SLOT(slotDialogFinished(int)));

    connect(dia,SIGNAL(aboutToClose()),
            this,SLOT(slotDialogClosed()));

    //The dialog will reparent the widget
    dia->add(w);
	dia->show();

    return w;
}

void Dashboard::addSearchDialog()
{
	//It will delete itself on close!!
	//The parent is 0, for the reason see the comment in addDialog()
	auto* d=new NodeSearchWindow(nullptr);
	d->queryWidget()->setServerFilter(serverFilter_);

    connect(d->queryWidget(),SIGNAL(selectionChanged(VInfo_ptr)),
            this,SLOT(slotSelectionChanged(VInfo_ptr)));

    connect(d->queryWidget(),SIGNAL(infoPanelCommand(VInfo_ptr,QString)),
            this,SLOT(slotPopInfoPanel(VInfo_ptr,QString)));

	//The dashboard signals the dialog on deletion
	connect(this,SIGNAL(aboutToDelete()),
	    	d,SLOT(slotOwnerDelete()));

	d->show();
}

void Dashboard::addSearchDialog(VInfo_ptr info)
{
	//It will delete itself on close!!
	//The parent is 0, for the reason see the comment in addDialog()
	auto* d=new NodeSearchWindow(nullptr);
	d->queryWidget()->setServerFilter(serverFilter_);
	d->queryWidget()->setRootNode(info);

    connect(d->queryWidget(),SIGNAL(selectionChanged(VInfo_ptr)),
            this,SLOT(slotSelectionChanged(VInfo_ptr)));

    connect(d->queryWidget(),SIGNAL(infoPanelCommand(VInfo_ptr,QString)),
            this,SLOT(slotPopInfoPanel(VInfo_ptr,QString)));

	//The dashboard signals the dialog on deletion
	connect(this,SIGNAL(aboutToDelete()),
		    d,SLOT(slotOwnerDelete()));

	d->show();
}

void Dashboard::slotDockClose()
{
	if(auto *dock=static_cast<DashboardDock*>(sender()))
	{
		if(auto* dw=static_cast<DashboardWidget*>(dock->widget()))
		{
			widgets_.removeOne(dw);
			disconnect(this,nullptr,dw,nullptr);

            if(dw->isMaximised())
                resetMaximised();

            checkMaximisedState();

            dw->deleteLater();
		}
        dock->deleteLater();
	}
}

VInfo_ptr Dashboard::currentSelection()
{
	return currentSelectionInView();
}

void Dashboard::currentSelection(VInfo_ptr n)
{
	//if(NodeWidget *ctl=handler_->currentControl())
	//	ctl->currentSelection(n);
}

void Dashboard::slotPopInfoPanel(QString name)
{
	if(DashboardWidget *dw=addDialog("info"))
	{
		if(auto* ip=static_cast<InfoPanel*>(dw))
		{
            ip->setDetached(true);
			ip->setCurrent(name.toStdString());
		}

        popupWidgets_ <<  dw;
	}
}

void Dashboard::slotPopInfoPanel(VInfo_ptr info,QString name)
{
	if(DashboardWidget *dw=addDialog("info"))
	{
		if(auto* ip=static_cast<InfoPanel*>(dw))
		{
            //ip->setDetached(true);
            ip->slotReload(info);
            ip->setCurrent(name.toStdString());
		}

        popupWidgets_ <<  dw;
	}
}

void Dashboard::slotCommand(VInfo_ptr info,QString cmd)
{
	if(!info || !info.get() )
		return;

	if(cmd == "search")
	{
		addSearchDialog(info);
	}
}

//-------------------------------
// Maximise panel
//-------------------------------

void Dashboard::slotMaximisedChanged(DashboardWidget* w)
{
    QList<DashboardDock*> dLst=findChildren<DashboardDock*>(QString());

    //maximise the given panel
    if(w->isMaximised())
    {
        bool hasMaxApp=hasMaximisedApplied();
        //nothing can be maximised in practice at this point
        Q_ASSERT(hasMaxApp == false);
        savedDockState_=saveState();

        Q_FOREACH(DashboardDock* d,dLst)
        {
            if(d->widget() == w)
                d->setVisible(true);
            else
            {
                auto* dw=static_cast<DashboardWidget*>(d->widget());
                Q_ASSERT(dw);
                dw->resetMaximised();
                d->setVisible(false);
            }
        }
    }
    //restore the previous state
    else
    {
        resetMaximised();
    }
}

bool Dashboard::hasMaximised() const
{
    for(int i=0; i < widgets_.count(); i++)
    {
        if(widgets_[i]->isMaximised())
            return true;
    }
    return false;
}

bool Dashboard::hasMaximisedApplied() const
{
    QList<DashboardDock*> dLst=findChildren<DashboardDock*>(QString());
    Q_FOREACH(DashboardDock* d,dLst)
    {
        if(d->isVisible() == false)
            return true;
    }
    return false;
}

void Dashboard::resetMaximised()
{
    QList<DashboardDock*> dLst=findChildren<DashboardDock*>(QString());
    Q_FOREACH(DashboardDock* d,dLst)
    {
        auto* dw=static_cast<DashboardWidget*>(d->widget());
        Q_ASSERT(dw);
        dw->resetMaximised();
        d->setVisible(true);
    }

    if(!savedDockState_.isEmpty())
    {
        restoreState(savedDockState_);
        savedDockState_.clear();
    }
}

void Dashboard::checkMaximisedState()
{
    bool st=(widgets_.count() > 1);
    for(int i=0; i < widgets_.count(); i++)
        widgets_.at(i)->setEnableMaximised(st);
}

//------------------------
// Dialogs
//------------------------

void Dashboard::slotDialogFinished(int)
{
	if(auto* dia=static_cast<DashboardDialog*>(sender()))
	{
		disconnect(this,nullptr,dia->dashboardWidget(),nullptr);
        popupWidgets_.removeOne(dia->dashboardWidget());
	}
}

void Dashboard::slotDialogClosed()
{
    if(auto* dia=static_cast<DashboardDialog*>(sender()))
    {
        disconnect(this,nullptr,dia->dashboardWidget(),nullptr);
        popupWidgets_.removeOne(dia->dashboardWidget());
    }
}

//------------------------
// Rescan
//------------------------

void Dashboard::reload()
{
	for(int i=0; i < widgets_.count(); i++)
		widgets_.at(i)->reload();
}

void Dashboard::rerender()
{
	for(int i=0; i < widgets_.count(); i++)
		widgets_.at(i)->rerender();

	titleHandler_->updateTitle();
}

//------------------------
// ViewMode
//------------------------

Viewer::ViewMode Dashboard::viewMode()
{
 	//return handler_->currentMode();
	return Viewer::TreeViewMode;
}

void Dashboard::setViewMode(Viewer::ViewMode mode)
{
	//handler_->setCurrentMode(mode);
}

//----------------------------------
// Save and load settings!!
//----------------------------------

void Dashboard::writeSettings(VComboSettings* vs)
{
	serverFilter_->writeSettings(vs);

	//Qt settings
    if(savedDockState_.isEmpty() == false)
    {
        vs->putQs("state",savedDockState_);
    }
    else
    {
        vs->putQs("state",saveState());
    }

	//Other setting
	vs->put("widgetCount",findChildren<QDockWidget*>().count());

	for(int i=0; i < widgets_.count(); i++)
	{
		std::string id=Dashboard::widgetSettingsId(i);
		vs->beginGroup(id);
		widgets_.at(i)->writeSettings(vs);
		vs->endGroup();
	}
}

void Dashboard::readSettings(VComboSettings* vs)
{
	settingsAreRead_=true;

    //This will create the ServerHandler objects
	serverFilter_->readSettings(vs);

    //At this point each ServerHandler is running its reset()!

	Q_FOREACH(QWidget* w,findChildren<QDockWidget*>())
	{
        UiLog().dbg() << "DashBoard::readSettings() dock: " <<  w->objectName();
	}

	//Read the information about the dashboard widgets.
	auto cnt=vs->get<int>("widgetCount",0);
	for(int i=0; i < cnt; i++)
	{
		std::string id=Dashboard::widgetSettingsId(i);
		if(vs->contains(id))
		{
			vs->beginGroup(id);
			std::string type=vs->get<std::string>("type","");
			std::string dockId=vs->get<std::string>("dockId","");

			//Create a dashboard widget
            if(DashboardWidget *dw=addWidget(type,dockId,false))
			{
                //This will make the widgets active!!! The ServerHandler reset() can
                //be still running at this point!
				dw->readSettings(vs);
			}
			vs->endGroup();
		}
	}

	//Restore state of the dockwidgets. It will not create the dockwidgets
	//themselves but set their states and geometry. This is based on
	//the the dockwidgets's objectname, so that has to be unique. We need to call
	//it when the dockwidgets have already been created.
	if(vs->containsQs("state"))
	{
		restoreState(vs->getQs("state").toByteArray());
	}

    initialSelectionInView();

	settingsAreRead_=false;
}

#if 0
void Dashboard::slotInfoPanelSelection(VInfo_ptr info)
{
    selectInTreeView(info);
}
#endif


void Dashboard::initialSelectionInView()
{
	Q_FOREACH(DashboardWidget* w,widgets_)
	{
        if(w->initialSelectionInView())
		{
			return;
		}
	}
}

bool Dashboard::selectInTreeView(VInfo_ptr info)
{
    if(!info)
        return false;

    Q_FOREACH(DashboardWidget* w,widgets_)
    {
        if(w->type() == "tree")
        {
            w->setCurrentSelection(info);
            return serverFilter_->isFiltered(info->server());
        }
    }

    return false;
}

VInfo_ptr Dashboard::currentSelectionInView()
{
	Q_FOREACH(DashboardWidget* w,widgets_)
	{
		VInfo_ptr info=w->currentSelection();
		if(info && info.get())
			return info;
	}

	return VInfo_ptr();
}

//Find an unique id for a new dockwidget
QString Dashboard::uniqueDockId()
{
	QSet<QString> ids;
	Q_FOREACH(QDockWidget* dw, findChildren<QDockWidget*>())
	{
		ids << dw->objectName();
	}

	for(unsigned i=0; i < 1000; i++)
	{
		QString uid="dock_" + QString::number(i);
		if(!ids.contains(uid))
		{
			return uid;
		}
	}

	//We should handle this situation!!
	assert(0);

	return "dock_1000";
}

std::string Dashboard::widgetSettingsId(int i)
{
	return "widget_" + boost::lexical_cast<std::string>(i);
}

void Dashboard::notifyServerFilterAdded(ServerItem* item)
{
	if(!settingsAreRead_)
    {
        //If there are no views we automatically add a tree view
        if(widgets_.count() == 0)
            addWidget("tree");

        Q_EMIT contentsChanged();
    }
}

void Dashboard::notifyServerFilterRemoved(ServerItem* item)
{
	if(!settingsAreRead_)
		Q_EMIT contentsChanged();
}

void Dashboard::notifyServerFilterChanged(ServerItem*)
{
	if(!settingsAreRead_)
		Q_EMIT contentsChanged();
}

void Dashboard::notifyServerFilterDelete()
{
	//if(!settingsAreRead_)
	//	Q_EMIT contentsChanged();
}

//We need to do this in order to prevent the dock window context menu from popping up.
//See ECFLOW-894
void Dashboard::contextMenuEvent(QContextMenuEvent * e)
{
    e->accept();
}


