// plot4.cpp  
// Copyright (c) Menno Rubingh 2016.  Web: [http://rubinghsoftware.de]
//
// MR Mar 2016.
// 
// Print to stdout a crude ASCII-art plot of y as a function of x,
// as defined by the Reverse Polish expession specified as a command-line argument.
// 
// The plot ranges over x = 0.0 to x = 1.0, and the plotted y range is [0.0, 1.0].
// 
//---
// Uses:
//    rpinterpreter.h      -- RPInterpreter.
//    rpinterpreter.a      -- Static library containing RPInterpreter implementation.
//
//    llgp_between.h       -- BETWEEN_II().
//
//---
// Build:
//    g++  -Wall -ggdb  -o plot4  plot4.cpp  rpinterpreter.a  -lm 
//


#include "rpinterpreter.h"  //RPInterpreter.

#include <stdio.h>
#include <assert.h>

#include <math.h>           //round(), isnan(), isinf(), signbit().
#include <limits.h>         //INT_MIN, INT_MAX.



//-------------------------------------------------------------------------------------
// Print individual line of the plot.
//-------------------------------------------------------------------------------------

// This is a class with only class functions and class constants.
// Objects of this class are never instantiated.
// I'm using a class and not a namespace, because the class allows elements
// in it to be private.
class AsciiPlotter
{
	// Number of columns of the plot.
	static int const NWID = 65;
	//static int const NWID = 51;

	
	// For given y, return the column number where to print the marker.
	// If beyond the printing column range, then return a number < 0 (if below), 
	// or > NWID (if above).
	static
	int yToCol( 
		double           iY,
		double           iYMin,  //} Plot range for y.
		double           iYMax ) //}
	{
		assert( iYMin < iYMax );
		assert( !isnan(iY) );
	
		double f = (iY-iYMin)/(iYMax-iYMin);
		double colDbl = round( (double)NWID * f );
		//DEBUG printf( "[f=%+10.3e d=%+10.3e c=%+10d] ",f, colDbl, (int)colDbl );

		// Numerical range of double is outside the signed int numerical range,
		// so need to check the signed int range before can cast to (int).
		// These int range checks also handle the case that colDbl == +- Inf,
		// which are values that also do not pass through the (int) cast correctly.
		if ( colDbl <= (double)INT_MIN ) { return -10;    /*Arbitrary value < 0   */ }
		if ( colDbl >= (double)INT_MAX ) { return NWID+10;/*Arbitrary value > NWID*/ }

		return (int)colDbl;
	}

public:
	static
	void printLine(
		double           iY,
		double           iYMin,  //} Plot range for y.
		double           iYMax ) //}
	{
		int colY = yToCol( iY, iYMin, iYMax );

		printf( "%c ", ( (colY < 0) ? '<' : ' ' ) );

		for ( int k = 0; k <= NWID; k++ )
		{
			char c = ' ';
			if ( k == 0    ) { c = '|'; }
			if ( k == NWID ) { c = '|'; }
		
			if ( k == colY ) { c = '*'; }

			printf( "%c", c );
		}

		printf( " %c", ( (colY > NWID ) ? '>' : ' ' ) );
	}
};



//-------------------------------------------------------------------------------------
// For one x value, evaluate the expression and print the plot line.
//-------------------------------------------------------------------------------------

void evalAndPlotOneX( 
	RPInterpreter *  uRP, 
	double           iX,     //Input x value.
	double           iYMin,  //} Plot range for y.
	double           iYMax ) //}
{
	// Set variable 'x', clear the other vars to zero.
	uRP->clearVars();
	uRP->putVar( 'x', iX );

	// Evaluate.
	uRP->evaluate();

	// Get the output y.
	double yOut;
	bool f = uRP->getVar( 'y', &yOut );

	// Print the plot line for this x.
	printf( "x=%+10.3e ", iX );
	if ( !f )
	{
		printf( "EDOM " );
	}
	else
	{
		printf( "y=%+10.3e  ", yOut );
		AsciiPlotter::printLine( yOut, iYMin, iYMax );
	}

	printf( "\n" );
}



//-------------------------------------------------------------------------------------
// MAIN:
// - Read RP expression string from cmd line;
// - Loop through a range of x values;
//   for each x value, evaluate expression to compute y, and plot the y value.
//-------------------------------------------------------------------------------------

void usage()
{
	fprintf( stderr, 
	  "Usage: plot4 'RPEXPRESSION'\n" 
	  "Note: Put RPEXPRESSION between single quotes to prevent shell expansion.\n" 
	);

	fprintf( stderr, "\n%s\n", 
		RPInterpreter::helptxt() );
}

int main( int argc, char ** argv )
{
	if ( argc < 2 ) { usage(); return -1; }
	char const * arg_expr = argv[1];

	printf( "expr = [%s]\n", arg_expr );



	// Parse the expression.

	RPInterpreter rp;

	if ( ! rp.scanFromString( arg_expr ) ) { return -1; }
	rp.dump( stdout );



	// Loop through a range of x values, and plot.

	double xMin    = 0.0;
	double xMax    = 1.0;
	double xStep   = 0.025;
	double epsilon = xStep/100.0;

	for ( double x = xMin; x <= (xMax+epsilon); x += xStep )
	{
		evalAndPlotOneX( &rp, x, 0.0, 1.0 );
	}



	return 0;
}