#include "axis.h"
#include "frame.h"
#include <cmath>
#include <cstdio>
#include "exc.H"
#include "warning.h"
#include <algorithm>

namespace blop
{

    void axis::operator=(const axis &) {cerr<<"this shouldn't be called"<<endl; exit(1);}

    color axis::default_axiscolor_(0,0,0);

    bool axis::default_symmetric_range_ = false;
    bool axis::default_draw_tics_ = true;
    void axis::default_draw_tics(bool f) {default_draw_tics_ = f;}

    void axis::default_symmetric_range(bool s)
    {
        axis::default_symmetric_range_ = s;
    }

    length &axis::default_ticlength_()
    {
        static length l = 2*MM;
        return l;
    }

    length &axis::default_minor_ticlength_()
    {
        static length l = MM;
        return l;
    }

    void axis::default_ticlength(const length &l)
    {
        default_ticlength_() = l;
    }

    void axis::default_minor_ticlength(const length &l)
    {
        default_minor_ticlength_() = l;
    }

    axis &axis::transform(axis *orig,
			  const function &f, const function &finverse, bool own_tic_calc)
    {
	transformed_axis_ = orig;
	transform_ = f;
	transform_inverse_ = finverse;
	own_tic_calculation_ = own_tic_calc;
	return *this;
    }

    axis &axis::axiscolor(const color &c)
    {
	axiscolor_ = c;
	title_.textcolor(c);
	scalelabel_.textcolor(c);
	modified_ = true;
	return *this;
    }

    const color &axis::axiscolor() const {return axiscolor_;}

    void axis::default_axiscolor(const color &c) {default_axiscolor_ = c;}

    axis &axis::autoextend_min(bool f)
    {
	if(transformed_axis_)
	{
	    transformed_axis_->autoextend_min(f);
	}
	if(f == true) autoextend_min_ = 2;
	else autoextend_min_ = -2;
	return *this;
    }

    axis &axis::autoextend_max(bool f)
    {
	if(transformed_axis_)
	{
	    transformed_axis_->autoextend_max(f);
	}
	if(f == true) autoextend_max_ = 2;
	else autoextend_max_ = -2;
	return *this;
    }

    axis &axis::autoextend(bool f)
    {
	if(transformed_axis_)
	{
	    transformed_axis_->autoextend(f);
	}
	if(f == true) autoextend_min_ = autoextend_max_ = 2;
	else autoextend_min_ = autoextend_max_ = -2;
	return *this;
    }

    axis &axis::autoextend_min_soft(bool f)
    {
	if(transformed_axis_)
	{
	    transformed_axis_->autoextend_min_soft(f);
	}
	if(autoextend_min_ != 2 && autoextend_min_ != -2)
	{
	    if(f == true) autoextend_min_ = 1;
	    else autoextend_min_ = -1;
	}
	return *this;
    }

    axis &axis::autoextend_max_soft(bool f)
    {
	if(transformed_axis_)
	{
	    transformed_axis_->autoextend_max_soft(f);
	}
	if(autoextend_max_ != 2 && autoextend_max_ != -2)
	{
	    if(f == true) autoextend_max_ = 1;
	    else autoextend_max_ = -1;
	}
	return *this;
    }

    void axis::prepare_for_draw()
    {
	if(print_me_ < 1) return;
	if(draw_tics_) ticlength_.register_me();
	if(draw_minor_tics_) minor_ticlength_.register_me();
	beg_.register_me();
	end_.register_me();
	pos_.register_me();
	bl1_.register_me();
	bl2_.register_me();
	if(title_.text().str() != "") title_.prepare_for_draw();
	scalelabel_.prepare_for_draw();
	if(!cuts_.empty())
	{
	    cut_gap_.register_me();
	    cut_x1_.register_me();
	    cut_x2_.register_me();
	    cut_x3_.register_me();
	    cut_x4_.register_me();
	    cut_y1_.register_me();
	    cut_y2_.register_me();
	    cut_y3_.register_me();
	    cut_y4_.register_me();
	}
    }


    void axis::pos(const length &p,
		   const length &l2,const length &l3)
    {
	pos_ = p;
	beg_ = l2;
	end_ = l3;
    }

    void axis::pos(const length &p)
    {
	pos_ = p;
	pos_changed_ = true;
    }

    axis::axis(int id)  : id_(id), tics(this)
    {
	pos_changed_ = false;
	title_autoset_ = false;
	transformed_axis_ = 0;
	cut_gap_ = default_cut_gap_();
	cut_x1_ = -0.5*!cut_gap_-EX;
	cut_x2_ = -0.5*!cut_gap_+EX;
	cut_x3_ =  0.5*!cut_gap_-EX;
	cut_x4_ =  0.5*!cut_gap_+EX;
	cut_y1_ = -2*EX;
	cut_y2_ = -1*EX;
	cut_y3_ = EX;
	cut_y4_ = 2*EX;

	unit_value_  = 1;
	unit_symbol_ = "";
	unit_format_ = "";

	axiscolor_ = default_axiscolor_;
	scale_ = unset;
	scalelabel_.off();
	scalelabel_.textcolor(axiscolor_);
	if(id_ == x1)
	{
	    title_.angle(0);
	    title_.y(!pos_ - !title_axis_sep_,sym::top);
	    title_.x(0.5*(!beg_+!end_),sym::center);
	}
	if(id_ == x2)
	{
	    title_.angle(0);
	    title_.y(!pos_ + !title_axis_sep_,sym::bottom);
	    title_.x(0.5*(!beg_+!end_),sym::center);
	}
	if(id_ == y1)
	{
	    title_.angle(90*unit::deg);
	    title_.x(!pos_ - !title_axis_sep_,sym::right);
	    title_.y(0.5*(!beg_+!end_),sym::center);
	}
	if(id_ == y2)
	{
	    title_.angle(90*unit::deg);
	    title_.x(!pos_ + !title_axis_sep_,sym::left);
	    title_.y(0.5*(!beg_+!end_),sym::center);
	}

	logscale_ = false;

	title_.text("");
	title_.textcolor(default_axiscolor_);

	tics_format_ = minor_tics_format_ = "%g";

	ticlength_ = default_ticlength_();
	minor_ticlength_ = default_minor_ticlength_();

	draw_tics_ = default_draw_tics_;
	draw_minor_tics_ = false;
        n_minor_tics_ = 0;

	min_ = unset;
	max_ = unset;
	min_fixed_ = false;
	max_fixed_ = false;

	tic_start_ = tic_incr_ = tic_end_ = unset;
	tic_start_fixed_ = tic_incr_fixed_ = tic_end_fixed_ = false;

	labelsep_ = EX;
	titlesep_ = EX;
	label_maxsize_ = 0;
	label_maxdist_ = 0;
	title_axis_sep_ = !label_maxdist_ + !titlesep_;
	script_size_ = 0;

	autoextend_min_ = autoextend_max_ = 1;

	if(id != x1 && id != x2 && id != y1 && id != y2)
	{
	    warning::print("Bad id","axis::axis()");
	    exit(1);
	}
        
        symmetric_range_ = default_symmetric_range_;
    }

    axis::axis(const axis &o) : tics(this)
    {
	unit_value_  = o.unit_value_;
	unit_symbol_ = o.unit_symbol_;
	unit_format_ = o.unit_format_;
	
	pos_changed_ = o.pos_changed_;
	title_autoset_ = o.title_autoset_;
	transformed_axis_ = 0;
	cut_gap_ = o.cut_gap_;
	cut_x1_ = o.cut_x1_;
	cut_x2_ = o.cut_x2_;
	cut_x3_ = o.cut_x3_;
	cut_x4_ = o.cut_x4_;
	cut_y1_ = o.cut_y1_;
	cut_y2_ = o.cut_y2_;
	cut_y3_ = o.cut_y3_;
	cut_y4_ = o.cut_y4_;

	scale_ = o.scale_;
	id_ = o.id_;
	logscale_          = o.logscale_;

	title_             = o.title_;

	tics_              = o.tics_;
	minor_tics_        = o.minor_tics_;
	tics_format_       = o.tics_format_;
	minor_tics_format_ = o.minor_tics_format_;
	ticlength_         = o.ticlength_;
	minor_ticlength_   = o.minor_ticlength_;
	draw_tics_         = o.draw_tics_;
	draw_minor_tics_   = o.draw_minor_tics_;

	min_ = o.min_;
	max_ = o.max_;
	min_fixed_ = o.min_fixed_;
	max_fixed_ = o.max_fixed_;

	tic_start_ = o.tic_start_;
	tic_incr_  = o.tic_incr_;
	tic_end_   = o.tic_end_;
	tic_start_fixed_ = o.tic_start_fixed_;
	tic_incr_fixed_  = o.tic_incr_fixed_;
	tic_end_fixed_   = o.tic_end_fixed_;
	user_tics_       = o.user_tics_;

	cerr<<"The length copying should be checked"<<endl;
	labelsep_ = o.labelsep_; 
	titlesep_ = o.titlesep_;
	label_maxsize_ = o.label_maxsize_;
	label_maxdist_ = o.label_maxdist_;
	title_axis_sep_ = !label_maxdist_ + titlesep_;

	autoextend_min_ = o.autoextend_min_;
	autoextend_max_ = o.autoextend_max_;
    }

    axis::~axis() {}

    axis &axis::min(double a)
    {
	min_ = a;
	if(a == unset) min_fixed_ = false;
	else min_fixed_ = true;
	if(transformed_axis_)
	{
            if(a == unset) transformed_axis_->min(unset);
	    else transformed_axis_->min(transform_inverse_(a).dbl());
	}
	modified_ = true;
	return *this;
    }

    axis &axis::max(double a)
    {
	max_ = a;
	if(a == unset) max_fixed_ = false;
	else max_fixed_ = true;
	if(transformed_axis_)
	{
            if(a == unset) transformed_axis_->max(unset);
	    else transformed_axis_->max(transform_inverse_(a).dbl());
	}
	modified_ = true;
	return *this;
    }

    axis &axis::range(double a,double b)
    {
	min(a);
        max(b);
	modified_ = true;
	return *this; 
    }

    void axis::calculate_lengths()
    {
	if(tics_.empty())
	{
	    label_maxsize_ = 0;
	    label_maxdist_ = 0;
	}
	else
	{
	    vector<var> l;
	    for(unsigned int i=0; i<tics_.size(); ++i)
	    {
		l.push_back(tics_[i].label());
	    }
	    
	    if(id_ == x1 || id_ == x2)
	    {
		label_maxsize_ = maxlheight(l);
	    }
	    else
	    {
		label_maxsize_ =  maxwidth(l);
	    }
	    label_maxdist_ = !labelsep_ + !label_maxsize_;
	}

	if(id_ == x1 || id_ == y1)
	{
	    if(tics_.empty()) bl1_ = !pos_;
	    else bl1_ = !pos_ - !labelsep_;

	    bl2_ = !pos_ - !label_maxdist_;
	    bl3_ = !pos_ - !title_axis_sep_;
	    if(id_ == x1) bl4_ = !bl3_ - !title_.height();
	    else bl4_ = !bl3_ - !title_.width();
	}
	else
	{
	    if(tics_.empty()) bl1_ = !pos_;
	    else bl1_ = !pos_ + !labelsep_;

	    bl2_ = !pos_ + !label_maxdist_;
	    bl3_ = !pos_ + !title_axis_sep_;
	    if(id_ == x2) bl4_ = !bl3_ + !title_.height();
	    if(id_ == x2) bl4_ = !bl3_ + !title_.width();
	}

	if(title_.text().str() == "")
	{
	    script_size_ = !label_maxdist_;
	}
	else
	{
	    if(id_ == x1 || id_ == x2)
	    {
		script_size_ = !title_axis_sep_ + title_.height();
	    }
	    else
	    {
		script_size_ = !title_axis_sep_ + title_.width();
	    }
	}
    }

    label *axis::titlebox()
    {
	return &title_;
    }

    axis &axis::title(const var &s, bool autoset)
    {
	title_autoset_ = autoset;
	title_.text(s.str());
	if(id_ == x1 || id_ == x2) title_.angle(0);
	else title_.angle(90*unit::deg);
	if(unit_symbol_ != "" && unit_format_ != "")
	{
	    var f = replace(var("%s"),var(unit_symbol_),var(unit_format_));
	    title_.text(title_.text() & " " & f);
	}
	calculate_lengths();
	modified_ = true;
	return *this;
    }

    var axis::title() const
    {
	return title_.text();
    }

    axis &axis::clear()
    {
	transformed_axis_ = 0;

	user_tics_.clear();

	draw_tics_ = true;

	logscale_ = false;
	title_.text("");
	tics_.clear();
	minor_tics_.clear();

	min_fixed_ = max_fixed_ = false;
	min_ = max_ = unset;
	tic_start_ = tic_incr_ = tic_end_ = unset;
	tic_start_fixed_ = false;
	tic_incr_fixed_ = false;
	tic_end_fixed_ = false;
	return *this;
    }

    axis &axis::extend_range(double a)
    {
	if(a == unset) return *this;
	if(transformed_axis_)
	{
	    transformed_axis_->extend_range(transform_inverse_(a).dbl());
	}

	if(a == unset) return *this;
	if(min_ == unset) min_ = a;
	else
	{
	    if(! min_fixed_)
	    {
		if(a < min_) min_ = a;
	    }
	}
	if(max_ == unset) max_ = a;
	else
	{
	    if(! max_fixed_)
	    {
		if(max_ < a) max_ = a;
	    }
	}
	return *this;
    }

    axis &axis::logscale(bool l)
    {
	if(transformed_axis_)
	{
	    warning::print("Can not set logscale for a transformed axis");
	    return *this;
	}
	logscale_ = l;
	if(logscale_ == true && min_ != unset && min_ <= 0) min(unset);
	modified_ = true;
	if(l) draw_minor_tics_ = true;
	return *this;
    }

    axis &axis::clear_autosettings()
    {
	if(!min_fixed_) min_ = unset;
	if(!max_fixed_) max_ = unset;
	if(!tic_start_fixed_) tic_start_ = unset;
	if(!tic_incr_fixed_)  tic_incr_  = unset;
	if(!tic_end_fixed_)   tic_end_   = unset;
	tics_.clear();
	if(title_autoset_) title("");
	minor_tics_.clear();
	return *this;
    }

    void axis::print_cuts(terminal *t)
    {
	if(cuts_.empty()) return;

	t->set_color(axiscolor_);

	vector<terminal::coord> cuts1, cuts2;
	if(id_ == x1 || id_ == x2)
	{
	    cuts1.push_back(terminal::coord(cut_x1_.termspecific_id(),
					    cut_y1_.termspecific_id()));
	    cuts1.push_back(terminal::coord(cut_x2_.termspecific_id(),
					    cut_y2_.termspecific_id()));
	    cuts1.push_back(terminal::coord(cut_x1_.termspecific_id(),
					    cut_y3_.termspecific_id()));
	    cuts1.push_back(terminal::coord(cut_x2_.termspecific_id(),
					    cut_y4_.termspecific_id()));

	    cuts2.push_back(terminal::coord(cut_x3_.termspecific_id(),
					    cut_y1_.termspecific_id()));
	    cuts2.push_back(terminal::coord(cut_x4_.termspecific_id(),
					    cut_y2_.termspecific_id()));
	    cuts2.push_back(terminal::coord(cut_x3_.termspecific_id(),
					    cut_y3_.termspecific_id()));
	    cuts2.push_back(terminal::coord(cut_x4_.termspecific_id(),
					    cut_y4_.termspecific_id()));

	}
	else
	{
	    cuts1.push_back(terminal::coord(cut_y1_.termspecific_id(),
					    cut_x1_.termspecific_id()));
	    cuts1.push_back(terminal::coord(cut_y2_.termspecific_id(),
					    cut_x2_.termspecific_id()));
	    cuts1.push_back(terminal::coord(cut_y3_.termspecific_id(),
					    cut_x1_.termspecific_id()));
	    cuts1.push_back(terminal::coord(cut_y4_.termspecific_id(),
					    cut_x2_.termspecific_id()));

	    cuts2.push_back(terminal::coord(cut_y1_.termspecific_id(),
					    cut_x3_.termspecific_id()));
	    cuts2.push_back(terminal::coord(cut_y2_.termspecific_id(),
					    cut_x4_.termspecific_id()));
	    cuts2.push_back(terminal::coord(cut_y3_.termspecific_id(),
					    cut_x3_.termspecific_id()));
	    cuts2.push_back(terminal::coord(cut_y4_.termspecific_id(),
					    cut_x4_.termspecific_id()));
	}

	for(unsigned int i=0; i<cuts_.size(); ++i)
	{
	    double norm = map_point(cuts_[i].second); //*(1+1e-6));
	    if(norm == unset) continue;

	    int cutpos_id = t->lincombi(1-norm,beg_.termspecific_id(),
					norm, end_.termspecific_id());
	    if(id_ == x1 || id_ == x2)
	    {
		t->translate(cutpos_id,pos_.termspecific_id());
	    }
	    else
	    {
		t->translate(pos_.termspecific_id(),cutpos_id);
	    }
	    t->draw_lines(cuts1);
	    t->draw_lines(cuts2);
	    t->reset_transformation();
	}
    }

    void axis::print_tics(terminal *t,const length &p1, const length &p2,
			  const length &p3,bool mirror)
    {
	if(!draw_tics_ && !draw_minor_tics_) return;

	t->set_color(axiscolor_);

	int coord_id = t->newlength();

	if(draw_tics_)
	{
	    terminal::id ticlength_id = ticlength_.termspecific_id();
	    if( ((id_ == x1 || id_ == y1) && mirror==true) ||
		((id_ == x2 || id_ == y2) && mirror==false) )
	    {
		ticlength_id = t->lincombi(-1,ticlength_id);
	    }

	    for(vector<tic>::size_type i=0; i<tics_.size(); ++i)
	    {
		double norm = map_point(tics_[i].value());

		// skip those points, for which map_point returns unset
		// (for example those which fall into a cut
		if(norm == unset) continue;

		t->overwrite(coord_id,
			     1-norm,p1.termspecific_id(),
			     norm,  p2.termspecific_id());
		terminal::coord start;
		vector<terminal::id> dx,dy;
		if(id_ == x1 || id_ == x2)
		{
		    start.x = coord_id;
		    start.y = p3.termspecific_id();
		    dx.push_back(ZERO.termspecific_id());
		    dy.push_back(ticlength_id);
		}
		else
		{
		    start.x = p3.termspecific_id();
		    start.y = coord_id;
		    dx.push_back(ticlength_id);
		    dy.push_back(ZERO.termspecific_id());
		}
		t->draw_rlines(start,dx,dy);
	    }
	}

	if(draw_minor_tics_)
	{
	    terminal::id minor_ticlength_id = minor_ticlength_.termspecific_id();
	    if( ((id_ == x1 || id_ == y1) && mirror==true) ||
		((id_ == x2 || id_ == y2) && mirror==false) )
	    {
		minor_ticlength_id = t->lincombi(-1,minor_ticlength_id);
	    }

	    for(vector<tic>::size_type i=0; i<minor_tics_.size(); ++i)
	    {
		double norm = map_point(minor_tics_[i].value());
		if(norm == unset) continue;
		t->overwrite(coord_id,
			     1-norm,p1.termspecific_id(),
			     norm,  p2.termspecific_id());
		terminal::coord start;
		vector<terminal::id> dx,dy;
		if(id_ == x1 || id_ == x2)
		{
		    start.x = coord_id;
		    start.y = p3.termspecific_id();
		    dx.push_back(ZERO.termspecific_id());
		    dy.push_back(minor_ticlength_id);
		}
		else
		{
		    start.x = p3.termspecific_id();
		    start.y = coord_id;
		    dx.push_back(minor_ticlength_id);
		    dy.push_back(ZERO.termspecific_id());
		}
		t->draw_rlines(start,dx,dy);
	    }
	}
    }

    void axis::print_line(terminal *term)
    {
	term->set_color(axiscolor_);

	vector<terminal::coord> c;
	if(id_ == y1 || id_ == y2)
	{
	    c.push_back(terminal::coord(pos_.termspecific_id(),beg_.termspecific_id()));
	    c.push_back(terminal::coord(pos_.termspecific_id(),end_.termspecific_id()));
	}
	else
	{
	    c.push_back(terminal::coord(beg_.termspecific_id(),pos_.termspecific_id()));
	    c.push_back(terminal::coord(end_.termspecific_id(),pos_.termspecific_id()));
	}
	term->draw_lines(c);
    }

    void axis::print_title(terminal *term)
    {
	if(title_.text().str() != "")
	{
	    term->set_color(axiscolor_);
	    title_.print(term);
	}
    }

    void axis::print_ticlabels(terminal *term)
    {
	term->set_color(axiscolor_);
	scalelabel_.print(term);

	int coord_id = term->newlength();
	for(vector<tic>::size_type i=0; i<tics_.size(); ++i)
	{
	    double norm = map_point(tics_[i].value());
	    if(norm == unset) continue;
	    term->overwrite(coord_id,
			    1-norm,beg_.termspecific_id(),
			    norm,end_.termspecific_id());

	    // draw the tic label
	    int xalign = sym::left,yalign = sym::bottom;
	    terminal::id xpos,ypos;

	    //if(id_ == x1)  // default! overwrite later if needed
	    {
		xpos.length_id = coord_id;
		xalign = sym::center;
		ypos = bl2_.termspecific_id();
		yalign = sym::base;
	    }
	    if(id_ == x2)
	    {
		xpos.length_id = coord_id;
		xalign = sym::center;
		ypos = bl1_.termspecific_id();
		yalign = sym::bottom;
	    }
	    if(id_ == y1)
	    {
		xpos = bl1_.termspecific_id();
		xalign = sym::right;
		ypos.length_id = coord_id;
		yalign = sym::center;
	    }
	    if(id_ == y2)
	    {
		xpos = bl1_.termspecific_id();
		xalign = sym::left;
		ypos.length_id = coord_id;
		yalign = sym::center;
	    }
	    term->draw_text(terminal::coord(xpos,ypos),tics_[i].label().str(),
			    xalign,yalign,0);

	}
    }    

    void axis::print(terminal *term)
    {
	if(print_me_ < 2) return;
	term->open_layer(layer_);
	print_title(term);
	print_line(term);
	print_ticlabels(term);
	print_tics(term, beg_, end_, pos_);
	print_cuts(term);
	term->close_layer(layer_);
    }

    void axis::print(terminal *term, bool printline)
    {
	if(print_me_ < 2) return;
	print_title(term);
	if(printline) print_line(term);
	print_ticlabels(term);
	print_tics(term, beg_, end_, pos_);
	print_cuts(term);
    }

    double axis::map_point(double p)
    {
	if(transformed_axis_)
	{
	    return transformed_axis_->map_point(transform_inverse_(p).dbl());
	}

	if(p == unset || isnan(p) || isinf(p)) return unset;
	for(unsigned int i=0; i<cuts_.size(); ++i)
	{
	    if(cuts_[i].first < p && p < cuts_[i].second) return unset;
	}

	double effective_range = max_ - min_;
	double effective_value = p - min_;

	if(logscale_)
	{
	    effective_range = ::log10(max_) - ::log10(min_);
	    effective_value = ::log10(p)    - ::log10(min_);
	}

	for(unsigned int i=0; i<cuts_.size(); ++i)
	{
	    if(cuts_[i].first < max_ && min_ < cuts_[i].second)
	    {
		double l = std::max(cuts_[i].first,min_);
		double h = std::min(cuts_[i].second,max_);
		if(!logscale_) effective_range -= h-l;
		else effective_range -= ::log10(h)-::log10(l);
	    }

	    if(cuts_[i].first < p && min_ < cuts_[i].second)
	    {
		double l = std::max(cuts_[i].first,min_);
		double h = std::min(cuts_[i].second,p);
		if(!logscale_) effective_value -= h-l;
		else effective_value -= ::log10(h)-::log10(l);
	    }
	}

	double mapped = effective_value / effective_range;

	if(mapped == unset || isnan(mapped) || isinf(mapped)) return unset;
	return mapped;
    }

    axis &axis::calculate_tics()
    {
	if(!draw_tics_) return *this;
	tics_.clear();

	// if this is a transformed axis, synchronize the range
	// with the original axis
	if(transformed_axis_)
	{
	    transformed_axis_->calculate_tics();
	    min_ = transform_(transformed_axis_->min_).dbl();
	    max_ = transform_(transformed_axis_->max_).dbl();

	    // if it should not itself calculate its tics, then copy from the original,
	    // and return
	    if(!own_tic_calculation_)
	    {
		char s[1000];
		for(unsigned int i=0; i<transformed_axis_->tics_.size(); ++i)
		{
		    const double v = transform_(transformed_axis_->tics_[i].value()).dbl();
		    sprintf(s,tics_format_.c_str(),v);
		    tics_.push_back(tic(v,s));
		}
		calculate_lengths();
		return *this;
	    }
	}

	if(min_ == unset || max_ == unset)
	{
	    return *this;
	}
	
	if(max_ <= min_)
	{
	    if(id_ == x1) warning::print("Zero x1-range","axis::calculate_tics()");
	    if(id_ == x2) warning::print("Zero x2-range","axis::calculate_tics()");
	    if(id_ == y1) warning::print("Zero y1-range","axis::calculate_tics()");
	    if(id_ == y2) warning::print("Zero y2-range","axis::calculate_tics()");
	    if(!min_fixed_) min_ -= 1;
	    if(!max_fixed_) max_ += 1;
	}


	// make tic calculation if the user requested it (tic_start_!=unset ||
	// tic_end_!=unset || tic_incr_!=unset),
	// or if no user_tics have been specified
	if(tic_start_ != unset || tic_end_  != unset || tic_incr_  != unset ||
	   user_tics_.empty())
	   
	{
	    tic scaletic;
	    scaletic.value(scale_);
	    blop::calculate_tics(min_,
				 min_fixed_ || (transformed_axis_!=0),
				 max_,
				 max_fixed_ || (transformed_axis_!=0),
				 tic_incr_,
				 tic_incr_fixed_,     // stepfixed
				 (tic_start_fixed_?tic_start_:unset),
				 (tic_end_fixed_  ?tic_end_  :unset),
				 scaletic,
				 unit_value_,
				 //(unit_str_!=""?unit::value(unit_):1.0),
				 cuts_,
				 logscale_,
				 logscale_, // normalform tics
				 true,      // normalform scale
				 tics_,
				 tics_format_.c_str(),
                                 "%g",
                                 5,
                                 symmetric_range_);
	    scalelabel_.text(var("$\\times$") & scaletic.label());

	    if(draw_minor_tics_ && cuts_.empty())
	    {
                if(logscale_)
                {
                    if(!tics_.empty())
                    {
                        double scale = 1;
                        if(scaletic.value() != unset) scale = scaletic.value();
                        int    expint = (int)round(::log10(tics_[0].value()*scale*unit_value_)-1);
                        for(int m=2; m<10 && m*::pow(10.0,expint)<tics_[0].value()*scale*unit_value_; ++m)
                        {
                            double v = m*::pow(10.0,expint);
                            if(v<min_) continue;
                            minor_tics_.push_back(tic(v,""));
                        }
                    }
                    for(unsigned int i=0; i<tics_.size(); ++i)
                    {
                        double scale = 1;
                        if(scaletic.value() != unset) scale = scaletic.value();

                        const double start = tics_[i].value()/(scale*unit_value_);
                        const double stop  = (i<tics_.size()-1?tics_[i+1].value()/(scale*unit_value_):max_/(scale*unit_value_));

                        //double expdbl = ::log10(start);
                        //int    expint = (int)(0.1+::round(expdbl));
                        const double    expdbl = ::log10(start);
                        const int    expint = (int)(round(expdbl));
                        const int    mant   = int(::pow(10.0, expdbl-expint)+0.01);

                        for(int m=mant+1; m<10 && m*::pow(10.0,expint)<stop; ++m)
                        {
                            minor_tics_.push_back(tic(m*::pow(10.0,expint)*scale*unit_value_,""));
                        }
                    }
                }
                else
                {
                    if(tics_.size() >= 2)
                    {
                        // check if they are equidistant
                        bool equidistant = true;
                        for(unsigned int i=2; i<tics_.size(); ++i)
                        {
                            if( ((tics_[i].value()-tics_[i-1].value())-(tics_[1].value()-tics_[0].value()))/(tics_[1].value()-tics_[0].value()) > 0.001)
                            {
                                equidistant = false;
                                break;
                            }
                        }
                        if(equidistant)
                        {
                            int n_minor_tic_intervals = n_minor_tics_;
                            if(n_minor_tic_intervals == 0)
                            {
                                n_minor_tic_intervals = 10;
                                int shown_tics = 0;
                                for(unsigned int i=0; i<tics_.size(); ++i)
                                {
                                    if(min_ <= tics_[i].value() && tics_[i].value() <= max_) ++shown_tics;
                                }
                                if(shown_tics > 5) n_minor_tic_intervals = 5;
                                if(shown_tics > 6) n_minor_tic_intervals = 4;
                                if(shown_tics > 12) n_minor_tic_intervals = 2;
                            }

                            for(unsigned int i=0; i<tics_.size()-1; ++i)
                            {
                                const double t1 = tics_[i].value();
                                const double t2 = tics_[i+1].value();
                                const double delta = (t2-t1)/n_minor_tic_intervals;
                                for(int m=1; m<n_minor_tic_intervals; ++m)
                                {
                                    minor_tics_.push_back(tic(t1+m*delta,""));
                                }
                            }
                            {
                                const double delta = (tics_[1].value()-tics_[0].value())/n_minor_tic_intervals;
                                for(double m=tics_[0].value()-delta; m>min_; m-=delta)
                                {
                                    minor_tics_.push_back(tic(m,""));
                                }

                            }
                            {
                                const double delta = (tics_.back().value()-tics_[tics_.size()-2].value())/n_minor_tic_intervals;
                                for(double m=tics_.back().value()+delta; m<max_; m+=delta)
                                {
                                    minor_tics_.push_back(tic(m,""));
                                }

                            }
                        }
                        else
                        {
                            cerr<<"Non-equidistant tics, can not calculate minor tics"<<endl;
                        }
                    }
                }
	    }
	}

	// add the user-tics
	if(!user_tics_.empty())
	{
	    for(unsigned int i=0; i<user_tics_.size(); ++i)
	    {
		tics_.push_back(user_tics_[i]);
	    }
	}

	for(unsigned int i=0; i<tics_.size(); ++i)
	{
	    extend_range(tics_[i].value());
	}
	calculate_lengths();

	return *this;
    }

    axis::tic_setter &axis::tic_setter::operator()()
    {
	axis_->user_tics_.clear();
	axis_->modified_ = true;
	operator()(unset,unset,unset);
	return *this;
    }

    // Set the tic incrementation (i.e. stepsize)
    axis::tic_setter &axis::tic_setter::operator()(var i)
    {
	axis_->draw_tics_ = true;
	axis_->tic_incr_ = i.dbl();
	if(i != unset) axis_->tic_incr_fixed_ = true;
	else axis_->tic_incr_fixed_ = false;
	axis_->modified_ = true;
	return *this;
    }

    // only set the increment and limits for the tics. do not create
    // user-tics, since start and end values might optionally be
    // 'unset', in which case they will be calculated from the
    // actual range of the axis. 
    axis::tic_setter &axis::tic_setter::operator()(var incr,var start,var end)
    {
	axis_->draw_tics_ = true;

	axis_->tic_start_ = start.dbl();
	if(start.dbl() != unset) axis_->tic_start_fixed_ = true;
	else axis_->tic_start_fixed_ = false;

	axis_->tic_incr_ = incr.dbl();
	if(incr.dbl() != unset) axis_->tic_incr_fixed_ = true;
	else axis_->tic_incr_fixed_ = false;

	axis_->tic_end_ = end.dbl();
	if(end.dbl() != unset) axis_->tic_end_fixed_ = true;
	else axis_->tic_end_fixed_ = false;

	axis_->modified_ = true;
	return *this;
    }

    // Set a single tic at value 'v', having label 'l'
    axis::tic_setter &axis::tic_setter::operator()(var v, var l)
    {
	// First check if at this value the user has already set
	// a label. Change this, if so.
	for(unsigned int i=0; i<axis_->user_tics_.size(); ++i)
	{
	    if(axis_->user_tics_[i].value() == v.dbl() &&
	       axis_->user_tics_[i].label().str() == l.str()) return *this;
	}

	// otherwise add this value/label pair to user_tics_
	axis_->user_tics_.push_back(tic(v.dbl(),l));
	axis_->modified_ = true;
	return *this;
    }

    axis &axis::titleaxissep(const length &l)
    {
	title_axis_sep_ = l;
	modified_ = true;
	return *this;
    }

    axis &axis::unit(const var &u, const var &format)
    {
	// First, remove any previous unit indication from the title
	if(unit_format_ != "" && title_.text().str() != "" && unit_symbol_ != "")
	{
	    string unit_indicator = replace(var("%s"),var(unit_symbol_),var(unit_format_));
	    string t = title_.text().str();
	    string::size_type pos = t.rfind(unit_indicator);
	    if(pos == t.size()-unit_indicator.size())
	    {
		t.erase(pos);
		title_.text(t);
	    }
	}

	if(!find_unit_or_cons(u,&unit_value_,&unit_symbol_))
	{
	    unit_value_  = 1;
	    unit_symbol_ = "";
	    unit_format_ = "";
	    return *this;
	}

	unit_format_ = format.str();
	if(unit_symbol_ != "" && unit_format_ != "")
	{
	    var f = replace(var("%s"),var(unit_symbol_),var(unit_format_));
	    title_.text(title_.text() & " " & f);
	}
	
	return *this;
    }

    axis &axis::scale(double s)
    {
	if(transformed_axis_)
	{
	    warning::print("Can not (yet) set scale for a transformed axis");
	    return *this;
	}
	modified_ = true;
	const double mind = 0.0000001;
	if(s == unset || ::fabs(s-1) < mind)
	{
	    scalelabel_.off();
	    scale_ = 1;
	    return *this;
	}
	scalelabel_.on();
	double exponent = ::log10(s);
	if(::fabs(::floor(exponent)-exponent) < mind)
	{
	    scale_ = ::pow(10,::floor(exponent));
	}
	else
	{
	    scale_ = s;
	}
	if(id_ == x1 || id_ == x2)
	{
	    scalelabel_.y(pos_).yalign(sym::center).x(end_ + EX);
	}
	else
	{
	    scalelabel_.x(pos_).xalign(sym::center).y(end_ + EX);
	}
	return *this;
    }

    double axis::scale() const
    {
	return scale_;
    }

    void axis::default_cut_gap(length l)
    {
	default_cut_gap_() = l;
    }

    length &axis::default_cut_gap_()
    {
	static length l = length::base_id_t(terminal::EX);
	return l;
    }

    axis &axis::cut_gap(length l)
    {
	modified_ = true;
	cut_gap_ = l;
	return *this;
    }

    const length &axis::cut_gap() const { return cut_gap_; }

    axis &axis::cut()
    {
	if(transformed_axis_)
	{
	    warning::print("Can not yet define cuts for a transformed axis");
	    return *this;
	}
	modified_ = true;
	cuts_.clear();
	return *this;
    }

    axis &axis::cut(double low, double high)
    {
	modified_ = true;
	if(low >= high)
	{
	    warning::print(var("Axis-cut low value (") & low & ") is bigger than high value (" & high & ")",
			   "axis::cut(double low, double high)");
	    return *this;
	}

	if(cuts_.empty())
	{
	    cuts_.push_back(pair<double,double>(low,high));
	    return *this;
	}

	for(unsigned int i=0; i<cuts_.size(); )
	{
	    // if no overlap, do nothing
	    if(cuts_[i].second < low || cuts_[i].first > high)
	    {
		++i;
		continue;
	    }

	    low = std::min(low,cuts_[i].first);
	    high = std::max(high,cuts_[i].second);
	    for(unsigned int j=i+1; j<cuts_.size(); ++j)
	    {
		cuts_[j-1].first = cuts_[j].first;
		cuts_[j-1].second = cuts_[j].second;
	    }
	    cuts_.pop_back();
	}

	// and finally insert it into the correct place

/*
  unsigned int i=0; 
  for(; i<cuts_.size() && high < cuts_[i].first; ++i);
  cuts_.insert(cuts_.begin()+i, pair<double,double>(low,high));
*/

	for(unsigned int i=0; i<cuts_.size(); ++i)
	{
	    if(high < cuts_[i].first)
	    {
		cuts_.insert(cuts_.begin()+i, pair<double,double>(low,high));
		return *this;
	    }
	}
	// if no insertion, so we got here, put it to the end
	cuts_.push_back(pair<double,double>(low,high));
	return *this;
    }



    // ---------- tic_setter - global -------------------------------------------------

    tic_setter::tic_setter(int a) : axis_(a) {}

    axis *tic_setter::get_axis()
    {
	switch(axis_)
	{
	case axis::x1 : return frame::current().x1axis();
	case axis::x2 : return frame::current().x2axis();
	case axis::y1 : return frame::current().y1axis();
	case axis::y2 : return frame::current().y2axis();
	default: return 0;
	}
	return 0;
    }

    tic_setter &tic_setter::operator()()
    {
	get_axis()->tics();
	return *this;
    }

    tic_setter &tic_setter::operator()(var a)
    {
	get_axis()->tics(a);
	return *this;
    }

    tic_setter &tic_setter::operator()(var a,var b)
    {
	get_axis()->tics(a,b);
	return *this;
    }

    tic_setter &tic_setter::operator()(var a,var b,var c)
    {
	get_axis()->tics(a,b,c);
	return *this;
    }

}