/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "NcExaminer.h"

#include "LogHandler.h"
#include "MvMiscelaneous.h"
#include "MvNetCDF.h"
#include "MvQAbout.h"
#include "MvQFileInfoLabel.h"
#include "MvQLogBrowser.h"
#include "MvQPlainTextEditSearchLine.h"

#include <QAction>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QSettings>
#include <QSplitter>
#include <QStatusBar>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QVBoxLayout>

NcExaminer::NcExaminer(QWidget *parent) :
		MvQMainWindow(parent),
		data_ (0),
		actionFileInfo_ (0),
		treeParam_ (0),
		initDump_ (false)
{
 	setAttribute(Qt::WA_DeleteOnClose);	
	setWindowTitle(tr("Metview - Netcdf Examiner"));

	//Initial size
	setInitialSize(1100,800);

	// Main splitter
	mainSplitter_= new QSplitter;
	mainSplitter_->setOrientation(Qt::Vertical);
	mainSplitter_->setOpaqueResize(false);
	setCentralWidget(mainSplitter_);

	// The main layout (the upper part of mainSplitter)
	QVBoxLayout *mainLayout = new QVBoxLayout;
	mainLayout->setObjectName(QString::fromUtf8("vboxLayout"));
	mainLayout->setContentsMargins(0,0,0,0);
	mainLayout->setSpacing(1);
	QWidget* w = new QWidget;
	w->setLayout(mainLayout);
	mainSplitter_->addWidget(w);

	// File info area
	fileInfoLabel_= new MvQFileInfoLabel;
	mainLayout->addWidget(fileInfoLabel_);

	// Central tab
	metaTab_= new QTabWidget;	
	mainLayout->addWidget(metaTab_);

	// Set up tabs
	setupParamBox();
	setupDumpBox();

	// Set up Log Area
	setupLogArea();

//	mainSplitter_->setCollapsible(1,false);

	// Initialize user interface actions
	setupViewActions();
	setupHelpActions();
	
	//----------------------------
	// Setup menus and toolbars
	//----------------------------

	setupMenus(menuItems_);
	
	// Status bar
 	statusMessageLabel_ = new QLabel("");
    	statusMessageLabel_->setFrameShape(QFrame::NoFrame);
    	statusBar()->addPermanentWidget(statusMessageLabel_,1);  // '1' means 'please stretch me when resized'

	// Signals and slots
	connect(this,SIGNAL(statusMessage(QString)),
		this,SLOT(slotStatusMessage(QString)));

	connect(metaTab_,SIGNAL(currentChanged(int)),
		this,SLOT(slotTabIndexChanged(int)));

	readSettings();
}
 
NcExaminer::~NcExaminer()
{
	// Save settings
	writeSettings();

	// Clean memory
	delete data_;
}

void NcExaminer::setupViewActions()
{
	actionFileInfo_ = new QAction(this);
	actionFileInfo_->setObjectName(QString::fromUtf8("actionFileInfo"));
	actionFileInfo_->setText(tr("File info"));
	actionFileInfo_->setCheckable(true);
	actionFileInfo_->setChecked(true);
	actionFileInfo_->setToolTip(tr("View file info"));
	QIcon icon;
	icon.addPixmap(QPixmap(QString::fromUtf8(":/examiner/fileInfo.svg")), QIcon::Normal, QIcon::Off);
 	actionFileInfo_->setIcon(icon);	

	actionLog_ = new QAction(this);
	actionLog_->setObjectName(QString::fromUtf8("actionLog"));
	actionLog_->setText(tr("&Log"));
	actionLog_->setCheckable(true);
	actionLog_->setChecked(false);
	actionLog_->setToolTip(tr("View log"));
	QIcon icon1;
	icon1.addPixmap(QPixmap(QString::fromUtf8(":/examiner/log.svg")), QIcon::Normal, QIcon::Off);
 	actionLog_->setIcon(icon1);	

	logPanel_->hide(); // hide log area
	
	// Signals and slots
	connect(actionFileInfo_,SIGNAL(toggled(bool)),
		fileInfoLabel_,SLOT(setVisible(bool)));

	connect(actionLog_,SIGNAL(toggled(bool)),
		logPanel_,SLOT(setVisible(bool)));

	MvQMainWindow::MenuType menuType=MvQMainWindow::ViewMenu;
	menuItems_[menuType].push_back(new MvQMenuItem(actionFileInfo_));	
	menuItems_[menuType].push_back(new MvQMenuItem(actionLog_));
}

void NcExaminer::setupHelpActions()
{
    	// About
    	QAction *actionAbout = new QAction(this);
    	actionAbout->setObjectName(QString::fromUtf8("actionAbout"));
    	QIcon icon;
    	icon.addPixmap(QPixmap(QString::fromUtf8(":/window/metview_logo")), QIcon::Normal, QIcon::Off);
    	actionAbout->setIcon(icon);
    	actionAbout->setText(tr("&About NetCDF Examiner"));

	connect(actionAbout, SIGNAL(triggered()), this, SLOT(slotShowAboutBox()));

	MvQMainWindow::MenuType menuType=MvQMainWindow::HelpMenu;
	menuItems_[menuType].push_back(new MvQMenuItem(actionAbout,MvQMenuItem::MenuTarget));
}

void NcExaminer::setupParamBox()
{
	// Build a box layout
	QVBoxLayout* textRepLayout = new QVBoxLayout;
	textRepLayout->setContentsMargins(0,0,0,0);
	QWidget* textRepPanel = new QWidget;
	textRepPanel->setLayout(textRepLayout);
	metaTab_->addTab(textRepPanel,tr("Meta data"));

	// Build a tree to show the data representation
	treeParam_ = new QTreeWidget;
	treeParam_->setObjectName("ncTreeParam");
	treeParam_->setProperty("mvStyle",0);
	treeParam_->setAlternatingRowColors(true);
	treeParam_->setAllColumnsShowFocus(true);
	textRepLayout->addWidget(treeParam_);

	// Build the text headers
	QStringList headerTexts;
	headerTexts << tr("Parameters") << tr("Values");
	treeParam_->setHeaderLabels(headerTexts);

	// Build the three main leaves
	QTreeWidgetItem *top1 = new QTreeWidgetItem(treeParam_);
	top1->setText(0, tr("Variables"));
	top1->setExpanded(true);

	QTreeWidgetItem *top2 = new QTreeWidgetItem(treeParam_);
	top2->setText(0, tr("Dimensions"));
	top2->setExpanded(true);

	QTreeWidgetItem *top3 = new QTreeWidgetItem(treeParam_);
	top3->setText(0, tr("Global Attributes"));
	top3->setExpanded(true);

	return;
}

void NcExaminer::setupDumpBox()
{
	// Build a box layout
	QVBoxLayout* dumpLayout = new QVBoxLayout;
	dumpLayout->setContentsMargins(0,0,0,0);
	QWidget* dumpPanel = new QWidget;
	dumpPanel->setLayout(dumpLayout);
	metaTab_->addTab(dumpPanel,tr("Ncdump"));

	// Build a tree to show the data representation
	dumpText_ = new QPlainTextEdit;
	dumpText_->setReadOnly(true);
//	dumpText_->setLineWrapMode(QPlainTextEdit::NoWrap);
	dumpLayout->addWidget(dumpText_);

	// Build the search widget
	MvQPlainTextEditSearchLine* getSearch = new MvQPlainTextEditSearchLine(dumpText_,tr("&Find:"));
	dumpLayout->addWidget(getSearch);

	return;
}

bool NcExaminer::initMetaData( const string& filename )
{
	// Clean metadata if already exists
	if ( data_ )
	{
		delete data_;
		data_ = 0;
	}

	// Write inicial message in the log area
	LogItem *log = new LogItem;
	LogHandler::instance()->add(log);
	log->description("Loading file");
	log->method("NetCDF API");

	// Initialize netcdf metadata object
	data_ = new MvNetCDF( filename, 'r' );

	// Update File info label
	updateFileInfoLabel();

	// Check if the netcdf file is correct
	if ( !data_->isValid() )
	{
		delete data_;
		data_ = 0;
		log->error("File does not seem to be a valid NetCDF file");
		slotUpdateLogOutput();
		QString buf = tr("<b>Status: </b><font color=red>FAILED</font>");
		emit statusMessage(buf);
		return false;
	}
	slotUpdateLogOutput();
	QString buf = tr("<b>Status:</b> <font color=green>OK</font>");
	emit statusMessage(buf);

	// Update Parameters box
	// It assumes the following order: Variables, Dimensions, Global Attributes
	if ( treeParam_->topLevelItemCount() != 3 )
		return false;

	// Variables
	this->showVariables();

	// Dimensions
	int i;
	NcDim* dim;
	QTreeWidgetItem* item = treeParam_->topLevelItem(1);
	for (i = 0; i < data_->getNumberOfDimensions(); i++)
	{
		dim = data_->getDimension(i);
        	QTreeWidgetItem *sub = new QTreeWidgetItem(item);
        	sub->setText(0, QString(dim->name()));
        	sub->setText(1, QString::number(dim->size()));
	}

	// Global attributes
	item = treeParam_->topLevelItem(2);
	if ( data_->getNumberOfAttributes() == 0 )
		item->setDisabled(true);
	else
	{
		string values;
		MvNcAtt *att;
		for (i = 0; i < data_->getNumberOfAttributes();i++)
		{
			att = data_->getAttribute(i);
			att->getValues(values);
        		QTreeWidgetItem *sub = new QTreeWidgetItem(item);
        		sub->setText(0, QString(att->name()));
        		sub->setText(1, QString::fromStdString(values));
		}
	}
	// Fits the two columns nicely
	treeParam_->resizeColumnToContents (0);

	return true;
}

void NcExaminer::showDumpBox()
{
	stringstream in, err;

	string buf = "ncdump '" + data_->path() + "'";

	// Send initial info to the log area
	LogItem *log = new LogItem;
	LogHandler::instance()->add(log);
	log->description("Calling netCDF ncdump command");
	log->command(buf);
	
	// Run dump command
	metview::shellCommand(buf,in,err);

	// Any error?
	if ( !err.str().empty() )
	{
		log->error(err.str());
		slotUpdateLogOutput();
		QString qbuf = tr("<b>Status: </b><font color=red>FAILED</font>");
		emit statusMessage(qbuf);
		return;
	}

	// Get stdout of the ncdump command
	dumpText_->setPlainText(QString(in.str().c_str()));

	// Update log area
	slotUpdateLogOutput();
	QString qbuf = tr("<b>Status:</b> <font color=green>OK</font>");
	emit statusMessage(qbuf);

	return;
}

void NcExaminer::slotTabIndexChanged (int index)
{
	// Deal with Dump tab only and initialized it only once
	// Build the netcdf dump text output into the DumpBox tab
	if( index == 1 && !initDump_ )
	{
		this->showDumpBox();
		initDump_ = true;
	}
}

void NcExaminer::slotUpdateLogOutput()
{
	logBrowser_->update();
}

void NcExaminer::slotClearLog(bool)
{
	LogHandler::instance()->clear();
	logBrowser_->clear();
}

void NcExaminer::slotStatusMessage(QString s)
{
	statusMessageLabel_->setText(s);
}

void NcExaminer::showVariables()
{
	int i,j;
	MvNcVar *var;
	NcDim *dim;
	QTreeWidgetItem* item = treeParam_->topLevelItem(0);

	// Main loop over variables
	for ( i = 0; i < data_->getNumberOfVariables(); i++ )
	{
		var = data_->getVariable(i);  // variable information

		// Get variable's type
		string str;
		var->getStringType(str);

		// Add variable name to the interface
		QTreeWidgetItem *sub = new QTreeWidgetItem(item);
        	sub->setText(0, QString::fromStdString(var->name()));
		sub->setData(0,Qt::UserRole,i);
		sub->setExpanded(false);

		// Add variable type and dimensions
		int numDim = var->getNumberOfDimensions();
		QString qstr = " ( ";
		for (j = 0; j < numDim; j++)
		{
  			dim = var->getDimension(j);
			if ( j == (numDim-1) )
				qstr += dim->name() + (QString)" )";
			else
				qstr += dim->name() + (QString)", ";
		}
		QTreeWidgetItem *sub1 = new QTreeWidgetItem(sub);
		sub1->setText(0, QString("Type"));
        	sub1->setText(1, QString::fromStdString(str));
		QTreeWidgetItem *sub2 = new QTreeWidgetItem(sub);
		sub2->setText(0, QString("Dimensions"));
        	sub2->setText(1, qstr);

		// Add variable attributes
		if ( var->getNumberOfAttributes() )
		{
			QTreeWidgetItem *subat = new QTreeWidgetItem(sub);
			subat->setText(0, QString("Attributes"));
			for (j = 0; j < var->getNumberOfAttributes(); j++)
			{
				MvNcAtt *att = var->getAttribute(j);
				att->getValues(str);
        			QTreeWidgetItem *sub1 = new QTreeWidgetItem(subat);
        			sub1->setText(0, QString(att->name()));
        			sub1->setText(1, QString::fromStdString(str));
			}
		}

		// Add first n values
		long nvals = getNumberValues(var); // total number of values
		long maxvals = 20; 				// number of values to be printed
		QStringList strvals;
		getDataValues(var,strvals,maxvals); // get values
 		QTreeWidgetItem *sub3 = new QTreeWidgetItem(sub);
		sub3->setText(0, "Data values");
		for (j = 0; j < maxvals; j++)
		{
			QTreeWidgetItem *sub1 = new QTreeWidgetItem(sub3);
			sub1->setText(1, strvals[j]);
		}
		if ( maxvals < nvals )
		{
			QTreeWidgetItem *sub1 = new QTreeWidgetItem(sub3);
			qstr = "  ... " + QString::number(nvals-maxvals) + " more values";
			sub1->setText(1,qstr);
		}
	}
}

void NcExaminer::getDataValues( MvNcVar* var, QStringList& sdata, long& nvals )
{
	// Get data
	long *edges = var->edges();
	if (var->type() == ncChar )
	{
		vector<Cached> datavec;
		var->get(datavec,edges,nvals);
		print_data(datavec,sdata,nvals);
	}
	else if ( var->type() == ncFloat  )
	{
		vector<float> datavec;
		var->get(datavec,edges,nvals);
		print_data(datavec,sdata,nvals);
	}
	else if ( var->type() == ncDouble )
	{
		vector<double> datavec;
		var->get(datavec,edges,nvals);
		print_data(datavec,sdata,nvals);
	}
	else if ( var->type() == ncByte )
	{
		vector<ncbyte> datavec;
		var->get(datavec,edges,nvals);
		print_data(datavec,sdata,nvals);
	}
	else if ( var->type() == ncShort )
	{
		vector<short> datavec;
		var->get(datavec,edges,nvals);
		print_data(datavec,sdata,nvals);
	}
	else if ( var->type() == ncLong )
	{
		vector<nclong> datavec;
		var->get(datavec,edges,nvals);
		print_data(datavec,sdata,nvals);
	}

	nvals = sdata.size();
	return;
}

// Template function to write values to Re widget
template <class T>
void NcExaminer::print_data(vector<T>&datavec, QStringList& sdata, long nvals)
{
	long num = (datavec.size() < (unsigned)nvals) ? datavec.size() : nvals;
	for ( long j = 0; j < num; j++ )
	{
		stringstream oss;
		oss << datavec[j];
		sdata.append( QString::fromStdString(oss.str()) );
	}
}

long NcExaminer::getNumberValues( MvNcVar* var )
{
	// Special code for type "char" because NetCDF does not
	// nullterminate strings; so, it provides an extra info:
	// "maximum string size", which is written as the last dimension.
	int ndim = var->getNumberOfDimensions();
	if (var->type() == ncChar )
		--ndim;

 	// Get total number of values
	long nval = 1L;
 	long *edges = var->edges();
	for ( int i = 0; i < ndim; i++)
		nval *= edges[i];

	return nval;
}

void NcExaminer::updateFileInfoLabel()
{
	fileInfoLabel_->setTextLabel(QString::fromStdString(data_->path()));
}

void NcExaminer::slotShowAboutBox()
{
	MvQAbout about("NetCDF Examiner","",MvQAbout::NetcdfVersion | MvQAbout::MetviewVersion );
	about.exec();
}

void NcExaminer::setupLogArea()
{
	// Log Area
	QVBoxLayout *logLayout = new QVBoxLayout;
	logLayout->setObjectName(QString::fromUtf8("vboxLayout"));
	logLayout->setContentsMargins(0,0,0,0);
	logLayout->setSpacing(1);
	logPanel_= new QWidget;
	logPanel_->setMinimumHeight(100);
	logPanel_->setLayout(logLayout);
	mainSplitter_->addWidget(logPanel_);

	// Define header layout
	QHBoxLayout *hb = new QHBoxLayout;
	hb->setContentsMargins(0,0,0,0);
	logLayout->addLayout(hb);

	// Add label to the header
	QLabel *label = new QLabel(tr("Log"));
	label->setFrameShape(QFrame::StyledPanel);
	hb->addWidget(label,1);

	// Add Clear button to the header
	QPushButton *clearPb = new QPushButton(tr("Clear log"));
	hb->addWidget(clearPb);

	// Log browser
	logBrowser_= new MvQLogBrowser(logLayout);

	// Signals and slots
	connect ( clearPb,SIGNAL(clicked(bool)), this,SLOT(slotClearLog(bool)) );
}

void NcExaminer::writeSettings()
{
	QSettings settings("ECMWF","MV4-NcExaminer");
	
	settings.beginGroup("mainWindow");
	settings.setValue("geometry",saveGeometry());
	settings.setValue("mainSplitter",mainSplitter_->saveState());
	settings.setValue("actionFileInfoStatus",actionFileInfo_->isChecked());
	settings.endGroup();
}

void NcExaminer::readSettings()
{
	QSettings settings("ECMWF","MV4-NcExaminer");

	QVariant value;
	settings.beginGroup("mainWindow");
	restoreGeometry(settings.value("geometry").toByteArray());
	mainSplitter_->restoreState(settings.value("mainSplitter").toByteArray());
	
	if(settings.value("actionFileInfoStatus").isNull())
		actionFileInfo_->setChecked(true);
	else
		actionFileInfo_->setChecked(settings.value("actionFileInfoStatus").toBool());

	settings.endGroup();
}
