SAWAxis = function(divDraw){
	this.divDraw_ = divDraw;
	this.sLineClass = "SAWGraph__Axis";
	this.sTextClass = "SAWGraph__Axis";
	this.aTicks_ = new Array();
	this.sTicks_ = new Array();
}

// minimum number of ticks in an axis -- SHOULD BE LARGER THAN 1!!!
SAWAxis.prototype.MIN_TICKS = 3;
SAWAxis.prototype.TICKLENGTH = 6;
SAWAxis.prototype.SMALLTICKLENGTH = 3;
SAWAxis.prototype.BSMALLTICKS = true;


SAWAxis.prototype.setBounds = function(fMin, fMax){
	this.fMin_ = fMin;
	this.fMax_ = fMax;
}


/**
Directly set the tick positions and texts, overwritting default configuration
*/
SAWAxis.prototype.setTicks = function(aTicks){
	this.BSMALLTICKS = false;
	for(vTick in aTicks){
		this.aTicks_.push(vTick);
		this.sTicks_.push(aTicks[vTick]);
	}
}

SAWAxis.prototype.setClasses = function(sLineClass, sTextClass){
	if(sLineClass) this.sLineClass = sLineClass;
	if(sTextClass) this.sTextClass = sTextClass;
}

// returns the number of ticks that an axis would have if it were to have tickstep iStep
SAWAxis.getNTicks_ = function(fMin, fMax, iStep){
	var iStart = Math.ceil(fMin/iStep),
		iStop = Math.floor(fMax/iStep),
		nSteps = iStop - iStart + 1;
	return nSteps;
}


// returns the optimal value for the tickstep, according to Axis.MIN_TICKS
SAWAxis.prototype.findAxisStep_ = function(){
	var fMin = this.fMin_, fMax = this.fMax_;
	if(this.MIN_TICKS<2) this.MIN_TICKS = 2;
	iStep = Math.pow(10,Math.floor(Math.log(fMax-fMin)/Math.log(10))-1);
	iSmallStep = 5*Math.pow(10,Math.floor(Math.log(fMax-fMin)/Math.log(10))-2);
	var nSteps = SAWAxis.getNTicks_(fMin, fMax, iStep);
	do{
		var iStepTry = iStep*5, nStepsTry = SAWAxis.getNTicks_(fMin, fMax, iStepTry);
		if(nStepsTry>=this.MIN_TICKS){
			iStep = iStepTry;
			iSmallStep *= 2;
			nSteps = nStepsTry;
		}
		else  break;
		
		iStepTry = iStep*2; nStepsTry = SAWAxis.getNTicks_(fMin, fMax, iStepTry);
		if(nStepsTry>=this.MIN_TICKS){
			iStep = iStepTry;
			iSmallStep *= 5;
			nSteps = nStepsTry;
		}
		else  break;
		
	}while(true);
	
	this.iStep_ = iStep;
	this.iSmallStep_ = iSmallStep;
}

// returns the array of tick values for an axis with tickstep iStep
SAWAxis.getTicks_ = function(fMin, fMax, iStep){
	var iStart = Math.ceil(fMin/iStep),
		iStop = Math.floor(fMax/iStep),
		nSteps = iStop - iStart + 1,
		aTicks = new Array();
	
	for(var i=iStart; i<=iStop; i++) aTicks[i-iStart] = iStep*i;
	
	return aTicks;
}

/**
	This function is called immediately before drawing, no options setting after this call take effect
*/
SAWAxis.prototype.computeAxisTicks_ = function(){
	var aTicks, sTicks;
	if(this.aTicks_.length == 0){
		this.findAxisStep_();
		aTicks = SAWAxis.getTicks_(this.fMin_, this.fMax_, this.iStep_);
		sTicks = new Array();
		for(var i =0; i < aTicks.length; i++){
			sTicks[i] = ''+aTicks[i];
		}
		
		this.aTicks_ = aTicks;
		this.sTicks_ = sTicks;
	}
}

SAWAxis.prototype.computeScaling_ = function(){
	this.alpha_ = (this.iScreenMax_ - this.iScreenMin_)/(this.fMax_ - this.fMin_);
	this.beta_ = this.iScreenMin_ - this.alpha_*this.fMin_;
}

SAWAxis.prototype.toScreen = function(f){
	return Math.round(this.alpha_ * f + this.beta_);
}

SAWAxis.prototype.toReal = function(ix){
	return ((ix - this.beta_)/this.alpha_);
}

SAWAxis.prototype.setScreenCoordinates_ = function(i4Min, i4Max){ // i4Min can be greater than i4Max
	this.iScreenMin_ = i4Min;
	this.iScreenMax_ = i4Max;
}

SAWAxis.prototype.draw = function(){
	alert("call of abstract method SAWAxis.prototype.draw");
}


///////////////////////////////////////////////
//   SAWXAxis
///////////////////////////////////////////////
SAWXAxis = function(divEl){
	this.base(divEl);
}
for (var m in SAWAxis.prototype) { 
	SAWXAxis.prototype[m] = SAWAxis.prototype[m]; 
} 
SAWXAxis.prototype.base = SAWAxis;
SAWXAxis.constructor = SAWXAxis;


// may be called before computing the scaling
SAWXAxis.prototype.computeBottomOffset_ = function(){
	var ilMax=0;
	for(var i=0; i<this.aTicks_.length; i++) {
		s=this.sTicks_[i];
		ilv = SAWDraw.getTextSize(this.divDraw_, s).height;
		if(ilv>ilMax) ilMax = ilv;
	}
	return ilMax+this.TICKLENGTH+1;
}

// should be called only after the scaling is computed
SAWXAxis.prototype.computeRightOffset_ = function(){
	if(this.alpha_)	{
		var vRet = 0;
		for(var iTick = this.aTicks_.length-1; iTick>=0; iTick--){
			var vTick = this.aTicks_[iTick];
			if(vTick>=this.fMin_ && vTick<this.fMax_){
				var ix = this.toScreen(vTick);
				ix += SAWDraw.getTextSize(this.divDraw_, this.sTicks_[iTick]).width / 2 + 1;
				vRet = Math.ceil(ix - this.iScreenMax_);
				break;
			}
		}
		return vRet;
	}
	else{
		alert('SAWXAxis.prototype.computeRightOffset_ called before computing scaling!');
	}
}

var ic=0;
SAWXAxis.prototype.draw = function(iy){
	ic++;
	ve = SAWDraw.hline(this.divDraw_, iy, this.iScreenMin_, this.iScreenMax_, this.sLineClass);
	var fMin = this.fMin_, fMax = this.fMax_;
	for(var iTick = 0; iTick<this.aTicks_.length; iTick++){
		var vTick = this.aTicks_[iTick];
		if(vTick>=fMin && vTick<fMax){
			SAWDraw.vline(this.divDraw_, this.toScreen(vTick), iy, iy + this.TICKLENGTH, this.sLineClass);
			SAWDraw.text(this.divDraw_, this.toScreen(vTick), iy + this.TICKLENGTH, this.sTicks_[iTick], 1, 2, this.sTextClass);
		}
	}
	if(this.BSMALLTICKS){
		aSmallTicks = SAWAxis.getTicks_(this.fMin_, this.fMax_, this.iSmallStep_);
		for(var iTick = 0; iTick<aSmallTicks.length; iTick++){
			SAWDraw.vline(this.divDraw_, this.toScreen(aSmallTicks[iTick]), iy, iy + this.SMALLTICKLENGTH, this.sLineClass);
		}
	}
}


///////////////////////////////////////////////
//   SAWYAxis
///////////////////////////////////////////////

SAWYAxis = function(divEl){
	this.base(divEl);
}
for (var m in SAWAxis.prototype) { 
	SAWYAxis.prototype[m] = SAWAxis.prototype[m]; 
} 
SAWYAxis.prototype.base = SAWAxis;
SAWYAxis.constructor = SAWYAxis;

// may be called before computing the scaling
SAWYAxis.prototype.computeLeftOffset_ = function(){
	var ilMax = 0;
	for(var i=0; i<this.aTicks_.length; i++){
		s=this.sTicks_[i];
		ilv = SAWDraw.getTextSize(this.divDraw_, s).width;
		if(ilv>ilMax) ilMax = ilv;
	}
	return ilMax+this.TICKLENGTH+1;
}

// should be called only after the scaling is computed
SAWYAxis.prototype.computeTopOffset_ = function(){
	if(this.alpha_)	{
		var vRet = 0;
		for(var iTick = this.aTicks_.length-1; iTick>=0; iTick--){
			var vTick = this.aTicks_[iTick];
			if(vTick>=this.fMin_ && vTick<=this.fMax_){
				var iy = this.toScreen(vTick);
				iy += SAWDraw.getTextSize(this.divDraw_, this.sTicks_[iTick]).height / 2 + 1;
				if(iy < this.iScreenMax_) vRet = Math.ceil(this.iScreenMax_ - iy);
				break;
			}
		}
		return vRet;
	}
	else{
		alert('SAWXAxis.prototype.computeTopOffset_ called before computing scaling!');
	}
}

SAWYAxis.prototype.draw = function(ix){
	SAWDraw.vline(this.divDraw_, ix, this.iScreenMax_, this.iScreenMin_, this.sLineClass);
	var fMin = this.fMin_, fMax = this.fMax_;
	for(var iTick = 0; iTick<this.aTicks_.length; iTick++){
		var vTick = this.aTicks_[iTick];
		if(vTick>=fMin && vTick<fMax){
			SAWDraw.hline(this.divDraw_, this.toScreen(vTick), ix - this.TICKLENGTH, ix, this.sLineClass);
			SAWDraw.text(this.divDraw_, ix - this.TICKLENGTH, this.toScreen(vTick), this.sTicks_[iTick], 2, 1, this.sTextClass);
		}
	}
	if(this.BSMALLTICKS){
		aSmallTicks = SAWAxis.getTicks_(this.fMin_, this.fMax_, this.iSmallStep_);
		for(var iTick = 0; iTick<aSmallTicks.length; iTick++){
			SAWDraw.hline(this.divDraw_, this.toScreen(aSmallTicks[iTick]), ix - this.SMALLTICKLENGTH, ix, this.sLineClass);
		}
	}
}



///////////////////////////////////////////////
//    SAWFrame
///////////////////////////////////////////////
SAWFrame = function(divDraw, divParent){
	this.divDraw_ = divDraw;
	this.divParent_ = divParent;
	this.xAxis_ = new SAWXAxis(divDraw);
	this.yAxis_ = new SAWYAxis(divDraw);
	
	this.sxTitle_ = '';
	this.syTitle_ = '';
	this.sBackgroundClass_ = null;
}
SAWFrame.prototype.BORDERWIDTH = 5;

SAWFrame.prototype.setAxisTitles = function(sx, sy){
	if(!sx) sx = ''; if(!sy) sy = '';
	this.sxTitle_ = sx;
	this.syTitle_ = sy;
}

SAWFrame.prototype.setBounds = function(xmin, xmax, ymin, ymax){
	this.xmin_ = xmin;
	this.xmax_ = xmax;
	this.ymin_ = ymin;
	this.ymax_ = ymax;
	this.xAxis_.setBounds(this.xmin_, this.xmax_);
	this.yAxis_.setBounds(this.ymin_, this.ymax_);
}

/**
This function is called immediately before drawing. No options can be set after this.
*/
SAWFrame.prototype.computeAxes_ = function(){
	var xAxis = this.xAxis_, yAxis = this.yAxis_;
	xAxis.computeAxisTicks_();
	yAxis.computeAxisTicks_();
	
	var iTitleX = 0, iTitleY = 0;
	if(this.sxTitle_.length>0){
		iTitleY = SAWDraw.getTextSize(this.divDraw_, this.sxTitle_).height+2;
	}
	if(this.syTitle_.length>0){
		iTitleX = SAWDraw.getTextSize(this.divDraw_, this.syTitle_).width+2;
	}
	
	var idy = xAxis.computeBottomOffset_(),
		idx = yAxis.computeLeftOffset_(),
		vDiv = this.divParent_,
		//vPos = SAWUtils.getElementAbsolutePosition(vDiv),
		//ix0 = vPos.left + iTitleX + idx + this.BORDERWIDTH,
		//iy0 = vPos.top + vDiv.offsetHeight - idy - this.BORDERWIDTH - iTitleY,
		//ix1 = vPos.left + vDiv.offsetWidth - this.BORDERWIDTH - 1,
		//iy1 = vPos.top + this.BORDERWIDTH;
		ix0 = iTitleX + idx + this.BORDERWIDTH,
		iy0 = vDiv.offsetHeight - idy - this.BORDERWIDTH - iTitleY,
		ix1 = vDiv.offsetWidth - this.BORDERWIDTH - 1,
		iy1 = this.BORDERWIDTH;
	this.ix0_ = ix0;
	this.iy0_ = iy0;
	this.ix1_ = ix1;
	this.iy1_ = iy1;
	xAxis.setScreenCoordinates_(ix0, ix1); xAxis.computeScaling_();
	yAxis.setScreenCoordinates_(iy0, iy1); yAxis.computeScaling_();
	
	var deltax2 = xAxis.computeRightOffset_();
	if(deltax2>0){
		this.ix1_ -= deltax2;
		xAxis.setScreenCoordinates_(this.ix0_, this.ix1_); xAxis.computeScaling_();
	}

	var deltay2 = yAxis.computeTopOffset_();
	if(deltay2>0){
		this.iy1_ += deltay2;
		yAxis.setScreenCoordinates_(this.iy0_, this.iy1_); yAxis.computeScaling_();
	}
}

SAWFrame.prototype.setClasses = function(sLineClass, sTextClass, sBackground){
	this.xAxis_.setClasses(sLineClass, sTextClass);
	this.yAxis_.setClasses(sLineClass, sTextClass);
	if(sBackground) this.sBackgroundClass_ = sBackgroundClass;
}

SAWFrame.prototype.getXAxis = function(){return this.xAxis_;}
SAWFrame.prototype.getYAxis = function(){return this.yAxis_;}

SAWFrame.prototype.draw = function(){
	if(this.sBackgroundClass_) this.divParent_.className = this.sBackgroundClass_;
	this.computeAxes_();
	var xAxis = this.xAxis_, yAxis = this.yAxis_;
	if(this.xmin_<0 && this.xmax_>0) SAWDraw.vline(this.divDraw_, xAxis.toScreen(0), this.iy1_, this.iy0_, yAxis.sLineClass);
	if(this.ymin_<0 && this.ymax_>0) SAWDraw.hline(this.divDraw_, yAxis.toScreen(0), this.ix0_, this.ix1_, xAxis.sLineClass);
	
	SAWDraw.hline(this.divDraw_, this.iy1_, this.ix0_, this.ix1_, xAxis.sLineClass);
	SAWDraw.vline(this.divDraw_, this.ix1_, this.iy1_, this.iy0_, yAxis.sLineClass);
	xAxis.draw(this.iy0_);
	yAxis.draw(this.ix0_);
	
	if(this.sxTitle_.length>0){
		//var iy0 = SAWUtils.getElementAbsolutePosition(this.divParent_).top *0 + this.divParent_.offsetHeight - this.BORDERWIDTH;
		var iy0 = this.divParent_.offsetHeight - this.BORDERWIDTH;
		SAWDraw.text(this.divDraw_, this.ix1_, iy0, this.sxTitle_, 2, 0, this.xAxis_.sTextClass);
	}
	if(this.syTitle_.length>0){
		//var ix0 = SAWUtils.getElementAbsolutePosition(this.divParent_).left + this.BORDERWIDTH;
		var ix0 = this.BORDERWIDTH;
		SAWDraw.text(this.divDraw_, ix0, this.iy1_, this.syTitle_, 0, 2, this.yAxis_.sTextClass);
	}
}
