#include <InterViews/color.h>
#include <InterViews/background.h>
#include <InterViews/font.h>
#include <InterViews/label.h>
#include <InterViews/layout.h>
#include <InterViews/page.h>
#include <InterViews/rule.h>
#include <InterViews/canvas.h>
#include <InterViews/window.h>
#include <InterViews/geometry.h>
#include <IV-look/kit.h>
#include <stream.h>
#include <math.h>
#include <string.h>
#include "plot_lim.h"
#include "iv_graph.h"
#include "iv_plot.h"
#include "cgidbg.h"
#include "mkstr.h"

/*
 * Axis - position tick marks as on axis
 */

#include "iv_axis.h"
#include <stdio.h>
#include <float.h>



static const CharactersInNumber = 10 ;

static int limit_large(double& val)
{
	static double sq_large = 0 ;
	if (sq_large == 0.) sq_large = sqrt(DBL_MAX*.1);
	int neg = val < 0 ;
	if (fabs(val) > sq_large) {
		val = sq_large ;
		if (neg) val = -val ;
		return 1 ;
	}
	return 0 ;
}

static int limit_small(double& val)
{
	static double sq_small = 0 ;
	if (sq_small == 0.) sq_small = sqrt(DBL_MIN*10);
	int neg = val < 0 ;
	if (fabs(val) < sq_small) {
		val = sq_small ;
		if (neg) val = -val ;
		return 1 ;
	}
	return 0 ;
}

static const char * GetNumberString(double Diff, double Val)
{
	static char StrBuf[64+CharactersInNumber+1];
	Val *= 1.+1.e-15 ;  // avoid truncation 
	const char * Format = "";
	int Precision = -1 ;
	limit_small(Diff);
	limit_large(Val);
	if (fabs(Val/Diff) < 1.e-6 ) strcpy(StrBuf,"0") ;
	else {
		double Log10 = 0. ;
		// if (Diff > very_small) Log10 = log10(Diff) ;
		Log10 = log10(fabs(Val));
		if (Log10 <  CharactersInNumber-1 && Log10 > -3) {
			// %f format
			Format = "%-9.*f" ;
			if ( Log10 > 4.) Precision = 0 ;
			else Precision = (int) (4. - Log10) ;
		} else {
			/* %e format */
			Format = "%-9.*e" ;
			Precision = 3 ;
			if (fabs(Val) > .999e10) Precision-- ;
		}
		sprintf(StrBuf,Format,Precision,Val);
		/* strcpy(StrBuf,form(Format,Precision,Val)); */
		for (char * SetNull = StrBuf; *SetNull; SetNull++)
			if (*SetNull == ' ') *SetNull = '\0' ;
		if (strlen(StrBuf) > CharactersInNumber) {
			TheLog << "Numer label too big ****** : " << 
					StrBuf << "\n" ;
			StrBuf[CharactersInNumber]= '\0' ;
		}
	}
	return StrBuf ;
}

static const char * RightJustifyNumber(const char * Source)
{
	static char Result[CharactersInNumber+1] ;
	int Length = strlen(Source);
	// if (Length > CharactersInNumber - 2) return Source ;
	int Start = (CharactersInNumber - Length) ;
	for (int i = 0 ; i < Start ; i++) Result[i] = ' ' ;
	strcpy(Result+Start,Source);
	return Result ;
}

static const char * CenterNumber(const char * Source)
{
	static char Result[CharactersInNumber+1] ;
	int Length = strlen(Source);
	// if (Length > CharactersInNumber - 2) return Source ;
	int Start = (CharactersInNumber - Length + 1) >> 1 ;
	for (int i = 0 ; i < Start ; i++) Result[i] = ' ' ;
	strcpy(Result+Start,Source);
	for (i = strlen(Result) ; i < CharactersInNumber ; i++) 
		Result[i] = ' ' ;
	Result[CharactersInNumber] = '\0' ;
	return Result ;
}


static double plot_increment(double scaled_size)
// scaled_size is ratio of total plot range to largest power of 10 <= this range
// Value returned is the multiplier of this power of 10 that is the
// increment between ticks.
{
	if (scaled_size < 1. || scaled_size >= 10.) DbgError("plot_increment",
		"invalid parameter");
	if (scaled_size > 5.) return 1. ;
	if (scaled_size > 2.5) return .5 ;
	if (scaled_size > 2.0) return .25 ;
	return .2 ;
}

static int number_ticks(double begin, double end, double& size, double& start)
{
	// LogOut << "number_ticks(" << begin << "," << end << ")\n" ;
	size = start = 0.0 ;
	if (begin > end) return 0 ;
	int fail = 0 ;
	if (fabs(begin) > very_big) fail = 1 ;
	if (fabs(end) > very_big) fail = 1 ;
	if (fail) return 0 ;
	double half_diff = (end * .5) - (begin * .5) ;
	if (half_diff > very_big*.5) return 0 ;
	double diff = end - begin ;
	if (diff < very_small) {
		start = begin ;
		return 0 ;
	}
	// LogOut << "diff = " << diff << "\n" ;
	double log_diff = log10(diff);
	int sign = 1 ;
	if (log_diff < 0) {
		log_diff = -log_diff + 1 ;
		sign = -1 ;
	}
	double power_10 = (int32) (log_diff + 1.e-8);
	// LogOut<<"log_diff = " << log_diff << ", power_10 = " << power_10 << "\n";
	double factor = pow(10, power_10);
	if (factor < 0x7fffffff) factor = (int32) (factor * (1. + 1.e-12));
	if (sign < 0) factor = 1./factor ;
	double scaled_size = diff/factor ;
	if (scaled_size < 1. && scaled_size > .9999999) scaled_size = 1 ;
/*
 *	LogOut << "scaled_size = " << scaled_size << ", factor = " << factor <<
 *		"\n" ;
 */

	size = plot_increment(scaled_size);
	// LogOut << "plot_increment = " << size << "\n" ;
	size *= factor ;

	// LogOut << "size = " << size << "\n" ;
	double pos_begin = fabs(begin);
	int Return = 1 ;
	if (pos_begin < size * 0x7fffffff) {
		int32 mult = 0;
		if (size > 0.0) mult = (int32) (pos_begin/size) ;

		// LogOut << "mult = " << mult << ", size = " << size << "\n" ;

		if (begin < 0.0) {
			start = -mult * size ;
			while (start > begin) start -= size ;
			start += size ;
		} else {
			start = mult*size ;
			while (start < begin) start += size ;
		}
		// LogOut << "start = " << start << "\n" ;
		Return += (int) (1.e-8 + (end - start)/size);
	}
/*
 *	LogOut << "Return = " << Return << ", size = " << size << ", start = "
 *		<< start << "\n" ;
 */
	if (Return > 20) Return = 20 ;
	return Return ;
}

char** make_ticks(int count,double start, double increment, int is_y)
{
/*
 *	LogOut << "make_ticks(" << count << ", " << start << "," << increment
 *		<< ", " << is_y << ")\n" ;
 */
	char** strings = new char* [count];
	double diff = start + count *increment ;
	for (int i = 0; i < count; ++i) {
/*
 *		LogOut << i << ": diff = " << diff << ",  val = " <<
 *			start+i*increment << "\n" ;
 */
		const char * str = GetNumberString(diff,start+i*increment);
		if (is_y) str = RightJustifyNumber(str);
		strings[i] = Concatenate(str);
	}
	return strings ;
}

void TickMarks::clear()
{
	if (!strings) return ;
	for (int i = 0 ; i < num; i++) delete strings[i];
	delete strings ;
}

TickMarks::TickMarks()
{
	strings=0;
	num=0;
	first = increment = 0. ;
}

TickMarks::~TickMarks()
{
	clear();
}

int TickMarks::max_length() const
{
	if (!strings) return 0 ;
	int max = 0 ;
	int length ;
	for (int i = 0 ; i < num; i++) if (max < (length=strlen(strings[i])))
		max = length ;
	return max ;
}

void TickMarks::set(int n, double f, double inc, int is_y)
{
	// LogOut << "TickMarks::set(" << n << ", " << f << ", " << is_y << ")\n" ;
	num = n ;
	first = f ;
	increment = inc ;
	strings = make_ticks(num, first, increment, is_y);
	// dump();
}

void TickMarks::dump() const
{
	TheLog << "TickMarks::dump: " << num ;
	if (!num) {
		TheLog << "\n" ;
		return ;
	}
	TheLog << " from " << first << " step " << increment << "\n" ;
	for (int i = 0 ; i < num ; i++) TheLog << i << " : " << strings[i] <<
		"\n" ;
}

Axis::Axis(Graph * gr, int y_flag):
	Background(nil,WidgetKit::instance()->background()),
	is_y(y_flag),
	canvas(0),
	ticks(new TickMarks)
{
	// LogOut << "Axis ctor\n" ;
	_first = new double ;
	_last = new double ;
	*_first = very_big ; *_last = -very_big ;
	graph = gr ;

	char_font = graph->font();
	FontBoundingBox bbox ;
	char_font->font_bbox(bbox);
	char_width = bbox.width() ;
	char_height = .5 *(3 + bbox.font_descent() + bbox.font_ascent()) ;
	// LogOut << "char_height = " << char_height << "\n" ;
	char_inc = char_width * 1.15 ;
	// LogOut << "char_width = " << char_width << "\n" ;

	char_color= 0;
	// LogOut << "Axis ctor exit\n" ;
}

Axis::~Axis ()
{
	delete _first ;
	delete _last ;
	delete ticks ;
	if (char_color) Resource::unref(char_color);
}

void Axis::do_damage()
{
	if (!canvas) return ;
	// Extension temp;
	// temp.set(canvas,_a);
	// canvas->damage(temp);
	canvas->damage(_a.left(),_a.bottom(), _a.right(),_a.top());
/*
 *	LogOut << "Axis::do_damage, is_y = " << is_y << ", l = " << _a.left()
 *		<< ", b = " << _a.bottom()
 *		<< ", r = " << _a.right() << ", t = " << _a.top() << "\n";
 */
	graph->plot()->do_damage();
}

void Axis::allocate (Canvas* c, const Allocation& a, Extension& ext) {
	_a = a;
	canvas = c ;
	Background::allocate(c,a,ext);
	if (!char_color) {
		char_color = Color::lookup(graph->window()->display(),"black") ;
		Resource::ref(char_color);
	}
	if (!char_color) DbgError("Axis::allocate","no color");
}

void Axis::write(const char * str, Coord x, Coord y) const
{
	// LogOut << "Axis::write(\"" << str << "\", " << x << ", " << y << ")\n" ;
	if (!char_color) DbgError("Axis::write","no color");
	Coord base = x ;
	for (const char * c = str; *c; c++, base+= char_inc)
		canvas->character(char_font,*c,char_width,char_color,base,y);
	// LogOut << "Axis::write exit\n" ;
}

const TickMarks& Axis::tick_marks() const
{
	// LogOut<<"Axis::tick_marks(), f = "<<*_first<<", l = " << *_last << "\n" ;
	double first, last ;
	if (is_y) {
		if (!graph->allow_rescale_y()) return *ticks ;
		first = graph->y_min();
		last  = graph->y_max();
	} else {
		if (!graph->allow_rescale_x()) return *ticks ;
		first = graph->x_min();
		last  = graph->x_max();
	}
	// LogOut << "first = " << first << ", last = " << last << "\n" ;
	if (first == *_first && last == *_last && ticks->strings) return *ticks;
	ticks->clear();

	*_first = first;
	*_last = last;
	double size,start ;
	int num = number_ticks(*_first,*_last,size,start);
	if (!num) num = 1 ;
	ticks->set(num,start,size,is_y);
	return *ticks ;
}

int XAxis::tick_positioning(Coord& base,Coord& inc) const
{
	// LogOut << "XAxis::tick_positioning(" << base << ", " << inc << ")\n" ;
	tick_marks();
	const Allocation* plot_alloc = graph->plot_allocation();
	if (!plot_alloc) return 0 ;
	Coord small = plot_alloc->left();
	Coord large = plot_alloc->right();
	// LogOut << "small = " << small << ", large = " << large << "\n" ;
	double full_diff = *_last - *_first ;
	if (full_diff <= 0.0) {
		if (*_first == very_big || *_last == -very_big) return 0 ;
		base = (small + large) * .5 ;
		inc = 0 ;
		inc = 0.0 ;
		return 1 ;
	}
	// LogOut << "full_diff = " << full_diff << "\n" ;
	Coord diff = large - small ;
	// LogOut << "diff = " << diff << "\n" ;
	base = small + diff * (ticks->first - *_first) /full_diff ;
	// LogOut << "base = " << base << "\n" ;
	inc = diff * ticks->increment /full_diff ;
/*
 *	LogOut << "diff = " << diff << ", base = " << base << ", inc = " << inc
 *		<< "\n" ;
 */
	return ticks->num ;
}

void XAxis::write_ticks() const
{
	// LogOut << "XAxis::write_ticks, canvas = " << (void *) canvas << "\n" ;
	if (!canvas) return ;
	Coord ypos = _a.bottom() + 5. ;
	const Allocation* plot_alloc = graph->plot_allocation();
	if (!plot_alloc) return  ;
	Coord limit = plot_alloc->right();
	Coord width = limit - plot_alloc->left();
	Coord base,inc ;
	if (!tick_positioning(base,inc)) {
		// LogOut << "no tick_positioning\n" ;
		return ;
	}
	int max_length = ticks->max_length();
	// LogOut << "width = " << width << ", char_inc = " << char_inc << "\n" ; 
	// LogOut << "max_length = " << max_length << "\n" ;
	Coord space_per_label = max_length * (char_inc+1) ;
/*
 *	LogOut << "space_per_label = " << space_per_label << ", num = " <<
 *		ticks->num << "\n" ;
 */

	int skip = 1 ;
	skip = (int) (ticks->num * space_per_label/width) + 1;
	for (int i = 0; i < ticks->num ; i+= skip) {
		Coord label_width = strlen(ticks->strings[i]) * char_inc ;
		Coord xpos = inc * i + base - label_width*.5 + 4. ;
		if (xpos + label_width <= limit)
			write(ticks->strings[i], xpos, ypos);
	}
}

int YAxis::tick_positioning(Coord& base,Coord& inc) const
{
	// LogOut << "YAxis::tick_positioning(" << base << ", " << inc << ")\n" ;
	tick_marks();
	const Allocation* plot_alloc = graph->plot_allocation();
	if (!plot_alloc) return 0 ;
	Coord small = plot_alloc->bottom();
	Coord large = plot_alloc->top();
	double full_diff = *_last - *_first ;
	if (full_diff <= 0.0) {
		if (*_first == very_big || *_last == -very_big) return 0 ;
		base = (small + large) * .5 ;
		inc = 0 ;
		return 1 ;
	}
	Coord diff = large - small ;
	base = small + diff * (ticks->first - *_first) /full_diff ;
	inc = diff * ticks->increment /full_diff ;
/*
 *	LogOut << "diff = " << diff << ", base = " << base << ", inc = " << inc
 *		<< "\n" ;
 */
	return ticks->num ;
}

void YAxis::write_ticks() const
{
	// LogOut << "YAxis::write_ticks() canvas = " << (void *) canvas << "\n" ;
	if (!canvas) return ;
	Coord xpos = _a.left() + 10.0 ;
/*
 *	LogOut << "YAxis::write_ticks(," << ticks->increment << ", " <<
 *		ticks->first << ", " << ticks->num << ")\n" ;
 */
	Coord base,inc ;
	if (!tick_positioning(base,inc)) {
		// LogOut << "no tick positioning\n" ;
		return ;
	}
	const Allocation* plot_alloc = graph->plot_allocation();
	if (!plot_alloc) return  ;
	Coord top = plot_alloc->top();
	Coord bottom = plot_alloc->bottom();
	for (int i = 0; i < ticks->num ; i++) {
		Coord ypos = inc * i + base - char_height *.5 ;
		if (ypos + char_height > top) ypos = top - char_height ;
		if (ypos  < bottom) ypos = bottom ;
		write(ticks->strings[i], xpos, ypos);
	}
}

void Axis::draw(Canvas*c , const Allocation& a) const 
{
	// LogOut << "Axis::draw, y = " << is_y << "\n" ;
	if (is_y) {
		// Background::draw(c,a);
		graph->x_axis().draw(c,a);
		// LogOut << "Axis::draw, y exit\n" ;
		return ;
	}
	write_ticks();
	graph->y_axis().write_ticks();
	// LogOut << "Axis::draw\n" ;
}


YAxis::YAxis (Graph * gr) : Axis(gr,1) {
}

YAxis::~YAxis () { }

static void req_display(Requirement &r, const char * name)
{
	TheLog << "Req " << name << ": stretch = " << r.stretch() <<
		", shrink = " << r.shrink() << ", align = " << r.alignment() 
		<< ", natural = " << r.natural() << "\n" ;
}

void YAxis::request (Requisition& r) const {
	MonoGlyph::request(r);
	Requirement& rx = r.x_requirement();
	// req_display(rx,"x for Y");
	Requirement& ry = r.y_requirement();
	// req_display(ry,"y for Y");

	rx.natural(70.);
	rx.stretch(0.0);
	rx.shrink(0.0);
	rx.alignment(0.0);

	

	ry.stretch(fil);
	ry.shrink(fil);
	ry.alignment(0);

	// req_display(rx,"x for Yf");
	// req_display(ry,"y for Yf");
}


Coord YAxis::length() const
{
	return _a.top() - _a.bottom();
}


XAxis::XAxis (Graph *gr) : Axis(gr,0) {
}

XAxis::~XAxis () { }


void XAxis::request (Requisition& r) const {
	MonoGlyph::request(r);
	Requirement& rx = r.x_requirement();
	// req_display(rx,"x for X");
	Requirement& ry = r.y_requirement();
	// req_display(ry,"y for X");

	rx.stretch(fil);
	rx.shrink(fil);
	rx.alignment(1.0);

	// ry.natural(0.); // added to make axises symmetruc
	ry.natural(20.); 
	ry.stretch(0.0);
	ry.shrink(0.0);
	ry.alignment(.2);



	// req_display(rx,"x for Xf");
	// req_display(ry,"y for Xf");
}

Coord XAxis::length() const
{
	return _a.right() - _a.left();
}


void Axis::check_change()
{
	// LogOut << "Axis::check_change\n" ;
	double s_first = *_first ;
	double s_last = *_last ;
	tick_marks();
	if (s_first != *_first || s_last != *_last) {
		graph->mark(-1);
		graph->do_damage();
		
		// LogOut << "doing damage\n" ;
	}
}

void XAxis::redraw()
{
	// LogOut <<"calling graph->redraw_x(), graph = " << (void*) graph << "\n" ;
	graph->redraw_x();
}

void YAxis::redraw()
{
	// LogOut<<"calling graph->redraw_y(), graph = " << (void*) graph << "\n" ;
	graph->redraw_y();
}

int XAxis::check_redraw()
{
	// LogOut << "XAxis::check_redraw()\n" ;
	if (!graph->allow_rescale_x()) return 0 ;
	double check_min = graph->x_min();
	double check_max = graph->x_max();
/*
 *	LogOut << "min = " << check_min << ", " << *_first << ", max = " <<
 *			check_max << "," << *_last << "\n" ;
 */
	if (check_min == *_first && check_max == *_last) return 0 ;
	// graph->redraw_x();
	graph->mark(-1);
	graph->do_damage();
	return 1 ;
}

int YAxis::check_redraw()
{
	// LogOut << "YAxis::check_redraw()\n" ;
	if (!graph->allow_rescale_y()) return 0 ;
	double check_min = graph->y_min();
	double check_max = graph->y_max();
/*
 *	LogOut << "min = " << check_min << ", " << *_first << ", max = " <<
 *			check_max << "," << *_last << "\n" ;
 */
	if (check_min == *_first && check_max == *_last) return 0 ;
	// graph->redraw_y();
	// graph->redraw();
	graph->mark(-1);
	graph->do_damage();
	return 1 ;
}

void XAxis::do_redraw() const
{
	graph->redraw_x();
	write_ticks();
}

void YAxis::do_redraw() const
{
	graph->redraw_y();
	write_ticks();
}


