/*
 * StatusBarDecorator.cc
 *
 *  Created on: Jan 12, 2015
 *      Author: AKKA
 */

#include "StatusBarDecorator.hh"

#include <iosfwd>
#include <boost/format.hpp>
#include <tuple>

#include "log4cxx/logger.h"
#include "plplot/plplot.h"
#include "plplot/plplotP.h"

#include "boost/exception/all.hpp"

#include "PlotLogger.hh"
#include "TimeAxis.hh"
#include "Page.hh"
#include "Time/TimePlot.hh"
#include "StatusPlot.hh"
#include "ColormapManager.hh"
#include "ParamMgr.hh"
#include "PlPlotUtil.hh"
#include "CommonNode.hh"

#define BAR_HEIGHT 0.02
#define BAR_SPACE  0.005

namespace plot {

log4cxx::LoggerPtr StatusBarDecorator::_logger(
		log4cxx::Logger::getLogger("AMDA-Kernel.Plot.Time.StatusBarDecorator"));

StatusBarDecorator::StatusBarDecorator (AMDA::Parameters::ParameterManager& manager, PanelPlotOutput* decoratorPlot) :
		DefaultTimeAxisDecorator(),
		_parameterManager(manager),
		_colorMapIndex(ColormapManager::getInstance()._defaultColorAxis),
		_barPosition(PlotCommon::Position::POS_TOP),
		_decoratorPlot(decoratorPlot){
}

StatusBarDecorator::StatusBarDecorator(const StatusBarDecorator& ref_) :
		DefaultTimeAxisDecorator(ref_), _parameterManager(
				ref_._parameterManager), _barInfoList(
				ref_._barInfoList), _colorMapIndex(ref_._colorMapIndex),
				_barPosition(ref_._barPosition),
				_decoratorPlot(ref_._decoratorPlot) {
}

StatusBarDecorator& StatusBarDecorator::operator=(const StatusBarDecorator& ref_) {
	_parameterManager = ref_._parameterManager;
	_barInfoList = ref_._barInfoList;
	_colorMapIndex = ref_._colorMapIndex;
	_barPosition = ref_._barPosition;
	_decoratorPlot = ref_._decoratorPlot;
	return *this;
}

StatusBarDecorator::~StatusBarDecorator() {
}

void StatusBarDecorator::updatePlotArea(PanelPlotOutput* pplot_, Axis* axis_, const Bounds& panelBounds_, Bounds& bounds_) {
	//Space for time axis
	DefaultTimeAxisDecorator::updatePlotArea(pplot_, axis_, panelBounds_, bounds_);

	LOG4CXX_DEBUG(_logger, "StatusBarDecorator Updating plot area : "<<bounds_.toString());

	TimeAxis* pAxis = (TimeAxis*) axis_;

	// calculate char size for labels:
	Font legendFont(pAxis->_legend.getFont());
	Font panelFont(pplot_->_panel->getFont());
	if (!legendFont.isSet()) {
		legendFont = panelFont;
	}

	PlPlotUtil::setPlFont(legendFont);
	CharSize legendSize = PlPlotUtil::getCharacterSizeInPlPage(pplot_->_panel->_page);

	double barSpace = 0;

	// reserve space for all series bar :
	barSpace += ((BAR_HEIGHT + BAR_SPACE + 1.5*legendSize.second) * _barInfoList.size());

	if ((_barPosition == pAxis->_position) && (_barPosition == PlotCommon::POS_BOTTOM))
		//one additional space to not draw the bar too close to the date
		barSpace += legendSize.second;

	// to take into account bar space.
	bounds_._height -= barSpace;

	if (_decoratorPlot->isStandalone())
		bounds_._height = BAR_SPACE;

	// specific case when axis labels are displayed at bottom of the graph. Needs to update y.
	if (_barPosition == PlotCommon::POS_BOTTOM)
	{
		bounds_._y += barSpace;
	}
}

double StatusBarDecorator::computeStartDateWidth(PanelPlotOutput* pplot_,
		TimeAxis* pAxis_) {
	// not very efficient ...
	long int lTime = static_cast<long int>(pAxis_->getRange().getMin());
	tm * lTimeTm = gmtime(&lTime);
	char lTimeChr[80];

	// Format date.
	size_t datelen =
			strftime(lTimeChr, 80,
					getPlStartTimeFormat(pAxis_->_timeFormat,
							pAxis_->getRange().getMin(),
							pAxis_->getRange().getMax()).c_str(), lTimeTm);
	PlPlotUtil::setPlFont(pplot_->_panel->getFont());
	CharSize fontSize = PlPlotUtil::getCharacterSizeInPlPage(pplot_->_panel->_page);
	return fontSize.first * datelen;
}

void StatusBarDecorator::configure(PanelPlotOutput* pplot_, Axis* axis_, double start_,
		double stop_, std::map<std::string, ParameterData> *pParameterValues) {

	TimeAxis* pTimeAxis = (TimeAxis*) axis_;

	if (_decoratorPlot->isStandalone())
		pTimeAxis->setOnlyTickmarks(true);

	DefaultTimeAxisDecorator::configure(pplot_,axis_,start_,stop_,pParameterValues);

	LOG4CXX_DEBUG(_logger, "StatusBarDecorator::configure");
	buildBarList();
}

void StatusBarDecorator::draw(PanelPlotOutput* pplot_, Axis* axis_,
		std::shared_ptr<plstream> pls_) {
	DefaultTimeAxisDecorator::draw(pplot_,axis_,pls_);

	// draw a virtual axis for each entry // take into account plot orientation
	TimeAxis* pAxis = (TimeAxis*) axis_;

	// Get plot area viewport.
	Bounds vpBounds;
	PLFLT lXMin, lXMax, lYMin, lYMax;
	pls_->gvpd(lXMin, lXMax, lYMin, lYMax);
	vpBounds._x = lXMin;
	vpBounds._y = lYMin;
	vpBounds._width = lXMax - lXMin;
	vpBounds._height = lYMax - lYMin;

	// get windows
	Bounds winBounds;
	PLFLT xwmin, xwmax, ywmin, ywmax;
	pls_->gvpw(xwmin, xwmax, ywmin, ywmax);
	winBounds._x = xwmin;
	winBounds._y = ywmin;
	winBounds._width = xwmax - xwmin;
	winBounds._height = ywmax - ywmin;

	LOG4CXX_DEBUG(_logger, " viewport :"<<vpBounds.toString());
	LOG4CXX_DEBUG(_logger, " window :"<<winBounds.toString());

	// set color for drawing axis :
	Color lInitialColor = changeColor(pls_, pAxis->_color,
			pplot_->_panel->_page->_mode);

	// Set font to draw axes.
	std::vector<Font::Style> panelStyles;
	Font panelFont(pplot_->_panel->getFont());

	Font legendFont(pAxis->_legend.getFont());
	if (!legendFont.isSet()) {
		// font has not been defined for that axis, used default panel font :
		legendFont = panelFont;
	}


	// viewport translation only along y direction: we translate about n charHeight. So, gets panel character height.
	PlPlotUtil::setPlFont(panelFont);
	CharSize panelCharSize = PlPlotUtil::getCharacterSizeInPlPage(pplot_->_panel->_page);

	PlPlotUtil::setPlFont(legendFont);
	CharSize legendCharSize = PlPlotUtil::getCharacterSizeInPlPage(pplot_->_panel->_page);

	double delta = BAR_SPACE;

	//add space for tick if necessary
	if (pAxis->_visible && pAxis->_showTickMark && !_decoratorPlot->isStandalone() && (pAxis->_tick._position == Tick::TickPosition::OUTWARDS))
		delta += getVerticalTickLength(pAxis, panelCharSize.second);

	//add space for time axis
	if (pAxis->_visible && (_barPosition == pAxis->_position) && pAxis->_showLegend && !pAxis->_legend.isEmpty())
	{
		delta += 3*panelCharSize.second;
	}

	//keep status bar order
	int first = 0;
	int end   = _barInfoList.size();
	int dir   = 1;
	if (_barPosition == PlotCommon::Position::POS_TOP)
	{
		first = _barInfoList.size()-1;
		end   = -1;
		dir   = -1;
	}

	//add status bar
	for (int i = first; i != end; i+=dir)
	{
		LOG4CXX_DEBUG(_logger, "statusbar["<<i<<"]");
		// no generate label
		pls_->slabelfunc(generateNoTimeLabel, NULL);
		// translate viewport :
		if (_barPosition == PlotCommon::Position::POS_BOTTOM)
		{
			pls_->vpor(vpBounds._x, vpBounds._x + vpBounds._width,
					vpBounds._y - delta - 1.5*legendCharSize.second - BAR_HEIGHT,
					vpBounds._y - delta - 1.5*legendCharSize.second);
		}
		else
		{
			pls_->vpor(vpBounds._x, vpBounds._x + vpBounds._width,
				vpBounds._y + vpBounds._height + delta ,
				vpBounds._y + vpBounds._height + delta + BAR_HEIGHT);
		}
		// sets windows for new viewport :
		pls_->wind(winBounds._x, winBounds._x + winBounds._width, 0,
				1);

		ParamInfoSPtr paramInfo = _barInfoList[i]._paramInfoSPtr;

		//Working list of pair used to build the legend
		std::vector<std::pair<Color,std::string>> legend;

		// legend configuration
		if ((paramInfo != nullptr) && !paramInfo->getStatusDef().empty())
		{
			for (int s = 0; s < (int)paramInfo->getStatusDef().size(); ++s)
			{
				std::string colorStr = paramInfo->getStatusDef()[s].getColor();
				if (_barInfoList[i]._color.isSet()) // we use the color set by the user
				{
					Color color;
					color = _barInfoList[i]._color;
					legend.push_back(std::make_pair(color, paramInfo->getStatusDef()[s].getName()));
				}
				else if (!colorStr.empty()) // if the color is not set by the user we take the StatusDef
				{
					Color color;

					createColor(color, colorStr);
					legend.push_back(std::make_pair(color, paramInfo->getStatusDef()[s].getName()));
				}
				else // if none of them is given, we set the colorMapIndex color
				{
					Color color(_colorMapIndex, s);
					legend.push_back(std::make_pair(color, paramInfo->getStatusDef()[s].getName()));
				}
			}
		}

		// set color map
		pls_->spal1(
			ColormapManager::getInstance().getColorAxis(_colorMapIndex).c_str(), true);

		Color lInitialColor;
		pls_->gcol0(0, lInitialColor._red, lInitialColor._green, lInitialColor._blue);

		// Draw Status data
		PLFLT x[4], y[4];
		for (int t = 0; t < _barInfoList[i]._dataPtr->getSize() - 1; ++t)
		{
			x[0] = _barInfoList[i]._dataPtr->getTimes()[t];
			x[1] = _barInfoList[i]._dataPtr->getTimes()[t];
			x[2] = _barInfoList[i]._dataPtr->getTimes()[t + 1];
			x[3] = _barInfoList[i]._dataPtr->getTimes()[t + 1];
			y[0] = 0.;
			y[1] = 1.;
			y[2] = 1.;
			y[3] = 0.;

			double paramValue = _barInfoList[i]._dataPtr->getValues(_barInfoList[i]._index)[t];

			if (isNAN(paramValue))
			{
				continue;
			}
			if ((paramInfo != nullptr) && !paramInfo->getStatusDef().empty())
			{
				for (int s = 0; s < (int)paramInfo->getStatusDef().size(); ++s)
				{
					if ((paramValue >= paramInfo->getStatusDef()[s].getMinValue()) &&
						(paramValue <= paramInfo->getStatusDef()[s].getMaxValue()))
					{
						std::string colorStr = paramInfo->getStatusDef()[s].getColor();
						if (_barInfoList[i]._color.isSet()) // we use the color set by the user
						{
							restoreColor(pls_, _barInfoList[i]._color, pplot_->_panel->_page->_mode);
							pls_->fill(4, x, y);
							pls_->spal1(ColormapManager::getInstance().getColorAxis(_colorMapIndex).c_str(), true);
						}
						else if (!colorStr.empty()) // if the color is not set by the user we take the StatusDef
						{
							Color color;

							createColor(color, colorStr);
							restoreColor(pls_, color, pplot_->_panel->_page->_mode);
							pls_->fill(4, x, y);
							pls_->spal1(ColormapManager::getInstance().getColorAxis(_colorMapIndex).c_str(), true);
						}
						else // if none of them is given, we set the colorMapIndex color
						{
							Color dataValueColor(_colorMapIndex, s);
							restoreColor(pls_, dataValueColor, pplot_->_panel->_page->_mode);
							pls_->fill(4, x, y);
							pls_->spal1(ColormapManager::getInstance().getColorAxis(_colorMapIndex).c_str(), true);
						}
						break;
					}
				}
			}
		}

		// restore to initial color context
		pls_->spal1(
			ColormapManager::getInstance().get(pplot_->_panel->_page->_mode,
											   ColormapManager::DEFAULT_COLORMAP_1)
				.c_str(),
			true);
		pls_->scol0(0, lInitialColor._red, lInitialColor._green, lInitialColor._blue);
		// Restore color.
		restoreColor(pls_, lInitialColor, pplot_->_panel->_page->_mode);

		// draw bar box
		pls_->box("bc", 0, 0, "bc", 0, 0);

		// draw legend
		PlPlotUtil::setPlFont(legendFont);
		std::string legendText = _barInfoList[i]._name;
		legendText += ": ";
		// plot parameter name
		pls_->mtex("t", 1, 0, 0, legendText.c_str());

		PLFLT xmin2, xmax2, ymin2, ymax2;
		pls_->gspa(xmin2, xmax2, ymin2, ymax2);

		double textFactor = (xmax2 - xmin2) * vpBounds._width;

		double legendPos = plstrl(legendText.c_str()) / textFactor;
		std::string separator = ", ";
		// plot each status name with the associated color
		for (int p = 0; p < (int)legend.size(); ++p)
		{
			Color initColor = changeColor(pls_, legend[p].first, pplot_->_panel->_page->_mode);
			pls_->mtex("t", 1, legendPos, 0, legend[p].second.c_str());
			legendPos += plstrl(legend[p].second.c_str()) / textFactor;
			restoreColor(pls_, initColor, pplot_->_panel->_page->_mode);
			if (p != (int)legend.size() - 1)
			{
				pls_->mtex("t", 1, legendPos, 0, separator.c_str());
				legendPos += plstrl(separator.c_str()) / textFactor;
			}
		}

		delta += (BAR_HEIGHT + BAR_SPACE + 1.5 * legendCharSize.second);
	}

	// restore viewport.
	pls_->vpor(vpBounds._x, vpBounds._x + vpBounds._width, vpBounds._y,
			vpBounds._y + vpBounds._height);

	// restore window
	pls_->wind(winBounds._x, winBounds._x + winBounds._width, winBounds._y,
			winBounds._y + winBounds._height);

	// Restore initial color.
	restoreColor(pls_, lInitialColor, pplot_->_panel->_page->_mode);

}

void StatusBarDecorator::buildBarList() {
	LOG4CXX_DEBUG(_logger, "StatusBarDecorator::buildBarList");

	// Retrieve ParamInfo Manager
	ParamMgr 		*piMgr =ParamMgr::getInstance();

	_barInfoList.clear();
	for (auto p : _decoratorPlot->_parameterAxesList)
	{
		ParameterSPtr param  = _parameterManager.getParameter(p._originalParamId);
		ParamInfoSPtr paramInfo = piMgr->getParamInfoFromId(param->getInfoId());

		std::string name;

		if ((paramInfo == nullptr) || (paramInfo->getShortName().empty()))
			name = p._originalParamId;
		else
			name = paramInfo->getShortName();

		LOG4CXX_DEBUG(_logger, " inspecting parameter : " << name);
		// list asked series :
		for(auto lSeriesProperties : p.getYSeriePropertiesList()) {
			for (auto index : lSeriesProperties.getIndexList(_pParameterValues)) {
				BarInfo barInfo;

				std::ostringstream osstr;
				osstr << name;
				// display index only if non scalar
				if(index.getDim1Index() != -1){
					osstr << "[" << index.getDim1Index() ;
					if (index.getDim2Index() != -1)
						osstr << "," << index.getDim2Index();
					osstr << "]";
				}
				LOG4CXX_DEBUG(_logger, " Display name : " << osstr.str());

				barInfo._name = osstr.str();
				barInfo._index = index;
				barInfo._dataPtr = &(*_pParameterValues)[lSeriesProperties.getParamId()];
				barInfo._paramInfoSPtr = paramInfo;
				barInfo._color = lSeriesProperties.getColor();
				_barInfoList.push_back(barInfo);
			}
		}
	}
}

} /* namespace plot */