/******************************** LICENSE ********************************

 Copyright 2007 European Centre for Medium-Range Weather Forecasts (ECMWF)

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at 

    http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

 ******************************** LICENSE ********************************/

/*! \file Axis.cc
    \brief Implementation of the Template class Axis.

    Magics Team - ECMWF 2004

    Started: Fri 7-May-2004

    Changes:

 */

#include "Axis.h"
#include "MagLog.h"
#include "Factory.h"
#include "Layout.h"

#include "Polyline.h"
#include "Text.h"
#include "Layer.h"
#include "SceneVisitor.h"

using namespace magics;

Axis::Axis() : currentLabel_(0)
{
	labelHelpers_["number"] = &Axis::number;
	labelHelpers_["label_list"] = &Axis::labellist;
	labelHelpers_["latitude"] = &Axis::latitude;
	labelHelpers_["longitude"] = &Axis::longitude;
	labelHelpers_["date"] = &Axis::basic;
	labelHelpers_["geoline"] = &Axis::basic;
	title_position_ = -1;
}

Axis::~Axis() 
{
}

/*!
 Class information are given to the output-stream.
 */
void Axis::print(ostream& out)  const
{
	out << "Axis[";
	AxisAttributes::print(out);
	out << "]";
}

VerticalAxis::VerticalAxis()
{
	if ( !magCompare(position_, "left") && !magCompare(position_, "right"))
		position_ = "left";
}

HorizontalAxis::HorizontalAxis()
{
	if ( !magCompare(position_, "bottom") && !magCompare(position_, "top"))
		position_ = "bottom";
}



void Axis::ticks(double min, double max, vector<double>& ticks)
{
	const double step = (max - min)/10.;
	const double log = log10(step);
	const double istep = pow(10., int(log));
	const double inc = ceil(step/istep)*istep;
	double first = floor(min/inc)*inc;

	if (first > min) first -= inc;

	for (double val = first; val <= max; val += inc) {
		ticks.push_back(val);
	}
	ticks.push_back(ticks.back() + inc);
}

void Axis::visit(SceneLayer& layer, vector<LayoutVisitor*>& visitors)
{
	// First we create the layer!
	// and push It to the parent layer!
	StaticLayer* axis = new NoDataLayer(this);
	axis->id(iconName_);
	axis->name(iconName_);
	layer.add(axis);

	for  (vector<LayoutVisitor*>::iterator visitor = visitors.begin(); visitor != visitors.end(); ++visitor) {
		axis->set(*visitor);
		(*visitor)->visit(*this);
	}
}

void Axis::visit(TextVisitor& )
{
	// Nothing to put in the title

}


void VerticalAxis::tick(VerticalAxisVisitor& axis)
{
	if (!tick_) return;

	double x1, x2;

	axis.tick(x1, x2, magCompare(tick_position_ , "out"));

	const Transformation& transformation = axis.transformation();

	for (AxisItems::const_iterator y = items_.begin(); y != items_.end(); ++y)
	{
		if ( !(*y)->isTick() ) continue;
		if ( !transformation.inY((*y)->position()) ) continue;

		Polyline* tick = new Polyline();
		PaperPoint p1 = PaperPoint(x1,  transformation.y((*y)->position()));
		PaperPoint p2 = PaperPoint(x2, transformation.y((*y)->position()));
		tick->push_back(p1);
		tick->push_back(p2);
		Colour calcol =  (tick_colour_->automatic()) ? *line_colour_ : *tick_colour_;
		tick->setColour(calcol);
		tick->setThickness(tick_thickness_);
		axis.push_back(tick);
	}

}

void HorizontalAxis::tick(HorizontalAxisVisitor& axis)
{
	if ( !tick_ )
		return;

	double y1, y2;
	axis.tick(y1, y2, magCompare(tick_position_ , "out"));

	const Transformation& transformation = axis.transformation();

	for (AxisItems::const_iterator x = items_.begin(); x != items_.end(); ++x)
	{
		if ( !(*x)->isTick() ) continue;
		if ( !transformation.inX((*x)->position()) ) continue;
		Polyline* tick = new Polyline();
		double px = transformation.x((*x)->position());
		tick->push_back(PaperPoint(px, y1));
		tick->push_back(PaperPoint(px, y2));
		Colour calcol =  (tick_colour_->automatic()) ? *line_colour_ : *tick_colour_;
		tick->setColour(calcol);
		tick->setThickness(tick_thickness_);
		axis.push_back(tick);
	}

}
void HorizontalAxis::line(TopAxisVisitor& out) const
{
	if (!line_)
		return;
	PaperPoint from(out.minX(), out.minY());
	PaperPoint to(out.maxX(), out.minY());

	Polyline* axis =  new Polyline();

	axis->setColour(*line_colour_);
	axis->setThickness(line_thickness_);
	axis->setLineStyle(line_style_);
	axis->push_back(from);
	axis->push_back(to);
	out.push_back(axis);
}

void HorizontalAxis::line(BottomAxisVisitor& out) const
{
	if (!line_)
		return;
	PaperPoint from(out.minX(), out.maxY());
	PaperPoint to(out.maxX(), out.maxY());

	Polyline* axis = new Polyline();

	axis->setColour(*line_colour_);
	axis->setThickness(line_thickness_);
	axis->setLineStyle(line_style_);
	axis->push_back(from);
	axis->push_back(to);
	out.push_back(axis);
}





void VerticalAxis::line(LeftAxisVisitor& out) const
{
	if (!line_)
		return;
	PaperPoint from(out.maxX(), out.minY());
	PaperPoint to(out.maxX(), out.maxY());

	Polyline* axis = new Polyline();

	axis->setColour(*line_colour_);
	axis->setThickness(line_thickness_);
	axis->setLineStyle(line_style_);
	axis->push_back(from);
	axis->push_back(to);
	out.push_back(axis);
}

void VerticalAxis::line(RightAxisVisitor& out) const
{
	if (!line_)
		return;
	PaperPoint from(out.minX(), out.minY());
	PaperPoint to(out.minX(), out.maxY());

	Polyline* axis = new Polyline();

	axis->setColour(*line_colour_);
	axis->setThickness(line_thickness_);
	axis->setLineStyle(line_style_);
	axis->push_back(from);
	axis->push_back(to);
	out.push_back(axis);
}

void HorizontalAxis::label(HorizontalAxisVisitor& axis)
{
	if ( !label_ )
		return;
	Justification justification =  axis.justificationTickLabel(label_orientation_);
	const Transformation& transformation = axis.transformation();
	double angle = ( magCompare(label_orientation_, "horizontal") ) ? 0 : -1.57;
	int count = -1;
	map<int, double> positions;
	double y1, y2;
	axis.tick(y1, y2, magCompare(tick_position_, "out"));
	positions[-1] = y1;
	title_position_ = y1;
	for (AxisItems::const_iterator x = items_.begin(); x != items_.end(); ++x)
	{
		    if ( !transformation.inX((*x)->position()) ) continue;
			if ( (*x)->isLabel() == false)
				continue;
        	if ( (*x)->isFirst() && !label_first_ ) continue;
        	if ( (*x)->isLast() && !label_last_ ) continue;
        	string label = createLabel(**x);
          if ( label.empty() ) continue;
        	 count++;
        	if ( count % label_frequency_  ) continue;

		double height = ((*x)->height() == DBL_MIN || (*x)->height() == 0) ? label_height_ : (*x)->height();
		double pos = axis.offsetTickLabel(height, (*x)->level());

		map<int, double>::iterator p = positions.find((*x)->level());

		if ( p !=  positions.end() ) {
			pos = p->second;

		}
		else {
			 p =  positions.find((*x)->level()-1);
			 pos = axis.offsetTickLabel(height, p->second);
			 positions[(*x)->level()] = pos;

			 title_position_ = pos;
		}

		PaperPoint point(transformation.x((*x)->position()), pos);

		bool out = false;
		if ( magCompare(label_position_, "inter_tick") ) {
			AxisItems::const_iterator next = x;
			next++;
			while ( next != items_.end() && !(*next)->isLabel() )
				next++;
			out = true;
			if ( next != items_.end() && (*x)->level() == (*next)->level() ) {
				//string nlabel = createLabel(**next);
				const double x1 = transformation.x((*x)->position());
				const double x2 = transformation.x((*next)->position());
				point = PaperPoint((x2+x1)/2, pos);
				out = (!transformation.inX((x2+x1)/2)) ;
			}
		}


		if ( out )
			continue;
		Text* text = new Text();

		Colour newcolour;
		if ((*x)->colour() != "undef" )
			newcolour =  Colour((*x)->colour());
		else
			newcolour =  (label_colour_->automatic()) ? *line_colour_ : *label_colour_;


		MagFont font(label_font_);
		font.colour(newcolour);
		font.style(label_font_style_);
	    font.size( height );
	    (*x)->setFont(font);
	    text->setFont(font);

	    text->setText(label);
	    text->setJustification(justification);
	    text->setVerticalAlign(MBOTTOM);
	    text->setAngle(angle);

	    text->push_back(point);
	    axis.push_back(text);
	 }
}




void VerticalAxis::label(VerticalAxisVisitor& axis)
{
	if ( !label_ )
		return;
	Justification justification =  axis.justificationTickLabel(label_orientation_);
	const Transformation& transformation = axis.transformation();

	int count = -1;
	map<int, double> positions;
	double x1, x2;
	axis.tick(x1, x2, magCompare(tick_position_, "out"));
	x1 = axis.offsetTickLabel(0.025, x1);
	positions[-1] = x1;
	title_position_ = x1;



	PaperPoint point;

	for (AxisItems::const_iterator y = items_.begin(); y != items_.end(); ++y)
	{
		if ( (*y)->isLabel() == false)
			continue;

		string label = createLabel(**y);

		if ( !transformation.inY((*y)->position()) ) continue;

		if ( (*y)->isFirst()   && !label_first_ ) continue;
		if ( (*y)->isLast() && !label_last_ ) continue;


		if ( label.empty() ) continue;
		count++;
		if ( count % label_frequency_  ) continue;


		double height = ((*y)->height() == DBL_MIN || (*y)->height() == 0 ) ? label_height_ : (*y)->height();
		double pos;
		map<int, double>::iterator p = positions.find((*y)->level());

		if ( p !=  positions.end() ) {
			pos = p->second - axis.offsetTickLabel(height, 0);;
		}
		else {
			p =  positions.find((*y)->level()-1);
			pos =  p->second;
			double newpos = axis.offsetTickLabel(height, p->second);
			positions[(*y)->level()] = newpos;

			title_position_ = newpos;
		}
        PaperPoint point(pos, transformation.y((*y)->position()));

		bool out = false;


		if ( magCompare(label_position_, "inter_tick") ) {
			AxisItems::const_iterator next = y;
			next++;
			while ( next != items_.end() && !(*next)->isLabel() )
				next++;
			out = true;
			if (next != items_.end() && (*y)->level() == (*next)->level()) {
        			const double y1 = transformation.y((*y)->position());
        			const double y2 = transformation.y((*next)->position());
        			point = PaperPoint(pos, (y2+y1)/2);
					out = !transformation.inY((y2+y1)/2);
			}
		}


		if ( out)
			continue;
		Text* text = new Text();
		text->setJustification(justification);
		Colour newcolour =  (label_colour_->automatic()) ? *line_colour_ : *label_colour_;

		MagFont font(label_font_);
		font.colour(newcolour);
		font.style(label_font_style_);
		font.size(height);
		(*y)->setFont(font);
		text->setFont(font);
		text->setText(label);
		text->setJustification(justification);
		text->setVerticalAlign(MHALF);
		text->push_back(point);
		axis.push_back(text);
	}
}

string Axis::number(const AxisItem& in)
{
	return in.label();
}

string Axis::labellist(const AxisItem& in)
{
	if ( label_labels_.empty() )
		return in.label();



	if ( currentLabel_ < label_labels_.size() ) {
		string label = label_labels_[currentLabel_];
		currentLabel_++;

		return label;
	}
	else
		return label_labels_.empty() ? in.label() : label_labels_.back();

}

string Axis::latitude(const AxisItem& item)
{
	ostringstream lat;
	string ns = "&#176;";
	if ( item.position() < 0 ) ns += "S";
	if ( item.position() >= 0 ) ns += "N";
	float y = float(maground(abs(item.position())*100)) / 100;
	lat <<  y << ns;
	return lat.str();
}

string Axis::longitude(const AxisItem& item)
{

		ostringstream lon;
		string ew = "&#176;";
		if (  item.position() < 0 ) ew += "W";
		if (  item.position() >= 0 ) ew += "E";
		float x = float(maground(abs( item.position())*100)) / 100;
		lon << x << ew;
		return lon.str();

}
string Axis::basic(const AxisItem& item)
{
		return item.label();
}

string Axis::createLabel(const AxisItem& in)
{

	MagLog::dev() << "Axis::createLabel " << in << " with method : " << label_type_ << endl;

	map<string,  LabelHelper >::iterator helper = labelHelpers_.find(lowerCase(label_type_));
	if ( helper != labelHelpers_.end() )
		return (this->*helper->second)(in);
	else {
		MagLog::warning() << "Could not the method " << label_type_ << " to setup axis labels..." << endl;
		return in.label();
	}



}

void HorizontalAxis::title(HorizontalAxisVisitor& out)
{
	if ( !title_ )
		return;
	Text* text = new Text();

	MagFont font(title_font_, title_font_style_, title_height_);
	font.colour((title_colour_->automatic()) ? *line_colour_ : *title_colour_);
	text->setVerticalAlign(out.textAlignment("horizontal"));
	text->setFont(font);
	text->setText(title_text_);

	double x = (out.minX() + out.maxX())/2;
	text->push_back(PaperPoint(x, out.offsetTickLabel(title_height_, title_position_)));

	out.push_back(text);
}

void VerticalAxis::title(VerticalAxisVisitor& out)
{
	if ( !title_ )
		return;
	out.frameIt();
	double angle = out.angleTitle();
	double x = title_position_;

	Text* text = new Text();
	MagFont font(title_font_, title_font_style_, title_height_);
	font.colour((title_colour_->automatic()) ? *line_colour_ : *title_colour_);
	text->setAngle(( title_orientation_ == "horizontal" ) ? 0 : angle);
	text->setFont(font);
	text->setText(title_text_);

    double y = (out.minY() + out.maxY())/2;
	text->push_back(PaperPoint(x, y));

	out.push_back(text);
}

void HorizontalAxis::grid(DrawingVisitor& out) const
{
	if ( !grid_ )
		return;

	double bottom = out.minY();
	double top = out.maxY();
	double pos;

	const Transformation& transformation = out.transformation();
	for (AxisItems::const_iterator x = items_.begin(); x != items_.end(); ++x)
    {
    	if ( !(*x)->isGrid() ) continue;
    	pos = (*x)->position();
    	if ( !transformation.inX(pos) ) continue;
		Polyline* grid = new Polyline();
		grid->push_back(PaperPoint(transformation.x(pos), bottom));
		grid->push_back(PaperPoint(transformation.x(pos), top));
		if ( pos == grid_reference_level_  ) {
			Colour colour = !grid_reference_colour_->automatic() ? *grid_reference_colour_ : *grid_colour_;
			grid->setColour(colour);
			grid->setLineStyle(grid_reference_style_);
			grid->setThickness(grid_reference_thickness_);
		}
		else {
			grid->setColour(*grid_colour_);
			grid->setLineStyle(grid_style_);
			grid->setThickness(grid_thickness_);
		}

		out.push_back(grid);
    }
}

void VerticalAxis::grid(DrawingVisitor& out) const
{
	if ( !grid_ )
		return;
	double left = out.minX();
	double right =  out.maxX();
	double pos;
	const Transformation& transformation = out.transformation();
	for (AxisItems::const_iterator y = items_.begin(); y != items_.end(); ++y)
    {
    	if (!(*y)->isGrid() ) continue;
    	pos = (*y)->position();
    	if ( !transformation.inY(pos) ) continue;
    	Polyline* grid = new Polyline();
		grid->push_back(PaperPoint(left, transformation.y(pos)));
		grid->push_back(PaperPoint(right, transformation.y(pos)));
		if ( pos == grid_reference_level_  ) {
				Colour colour = !grid_reference_colour_->automatic() ? *grid_reference_colour_ : *grid_colour_;
				grid->setColour(colour);
				grid->setLineStyle(grid_reference_style_);
				grid->setThickness(grid_reference_thickness_);
		}
		else {
				grid->setColour(*grid_colour_);
				grid->setLineStyle(grid_style_);
				grid->setThickness(grid_thickness_);
		}
		out.push_back(grid);
		 }
}

void VerticalAxis::minortick(VerticalAxisVisitor& axis)
{
	if ( !minor_tick_ )
		return;
	double x1, x2;
	axis.minortick(x1, x2, magCompare(tick_position_, "out"));

	const Transformation& projection = axis.transformation();

	for (AxisItems::const_iterator y = items_.begin(); y != items_.end(); ++y)
    {

	        if ( !(*y)->isMinorTick() ) continue;
	        if ( !projection.inY((*y)->position()) ) continue;
	        Polyline* tick = new Polyline();
	        tick->push_back(PaperPoint(x1, (*y)->position()));
	        tick->push_back(PaperPoint(x2, (*y)->position()));
	        Colour calcol =  (minor_tick_colour_->automatic()) ? *tick_colour_ : *minor_tick_colour_;
	        tick->setColour(calcol);
	        tick->setThickness(minor_tick_thickness_);
	        axis.push_back(tick);
    }

}

void HorizontalAxis::minortick(HorizontalAxisVisitor& axis)
{
	if ( !minor_tick_ )
	return;
	double y1, y2;
	axis.minortick(y1, y2, magCompare(tick_position_, "out") );
	const Transformation& projection = axis.transformation();
	for (AxisItems::const_iterator x = items_.begin(); x != items_.end(); ++x)
    {

	        if ( !(*x)->isMinorTick() ) continue;
	        if ( !projection.inX((*x)->position()) ) continue;

	        Polyline* tick = new Polyline();

	        tick->push_back(PaperPoint((*x)->position(), y1));
	        tick->push_back(PaperPoint((*x)->position(), y2));
	        Colour calcol =  (minor_tick_colour_->automatic()) ? *tick_colour_ : *minor_tick_colour_;
	        tick->setColour(calcol);
	        tick->setThickness(minor_tick_thickness_);
	        axis.push_back(tick);
    }
}


void  HorizontalAxis::tip(TopAxisVisitor& out) const
{

}

void  HorizontalAxis::tip(BottomAxisVisitor& out) const
{
	if ( !tip_ )
		return;
	Text* text = new Text();

	MagFont font;
	font.size(tip_height_);
	font.colour((tip_colour_->automatic()) ? *label_colour_ : *tip_colour_);
	text->setFont(font);
	text->setText(tip_text_);
	text->setVerticalAlign(MTOP);

	double x = out.maxX() - (out.maxX() -  out.minX())*0.05;

	text->push_back(PaperPoint(x, title_position_));

	out.push_back(text);
}

void  VerticalAxis::tip(LeftAxisVisitor& out) const
{
	if (!tip_)
		return;
	double angle = out.angleTip();
	const double x = title_position_;

	Text* text = new Text();
	MagFont font;
	font.size(tip_height_);
	font.colour((tip_colour_->automatic()) ? *label_colour_ : *tip_colour_);
	text->setFont(font);
	text->setText(tip_text_);

	text->setAngle(( orientation_ == "horizontal" ) ? 0 : angle);

	double y  = out.maxY() - (out.maxY() -  out.minY())*0.05;;


	text->push_back(PaperPoint(x, y));

	out.push_back(text);
}

void  VerticalAxis::tip(RightAxisVisitor& out) const
{

}



