// fltrppm4.cpp
// Copyright (c) Menno Rubingh 2016.  Web: [http://rubinghsoftware.de]
//
// MR Mar 2016.
//
// Change the value of each pixel in a "P6" PPM file (each pixel independently 
// of the other pixels), according to the Reverse Polish expression specified as
// a command line argument.
// The input PPM image is read from stdin, the output PPM image is written to
// stdout.  The size of the image remains unchanged. 
//
//---
// Design notes:
//
//  - The individual operations (+ - * ...) in the RP expression do not clip, but
//    the value of the r,g,b output variable is clipped to the interval [0.0, 1.0]
//    after reading the output variable from the RP interpreter.
//
//  - Checking for domain errors during expression evaluation is done in two ways:
//    1) The whole series of evaluate() calls for ALL pixels in the image is bracketed
//       between one single feclearexcept() ... fetestexcept() pair, checking for a
//       domain error for any of the pixels in the whole image.
//    2) Selectable by command-line option, optionally also check for domain errors 
//       every time an r,g,b output variable value is read.  This costs a (very) small 
//       (negligibly small) time for every pixel, but preempts any partial nonsense 
//       data being written to the output PPM file.
//
//---
// Uses:
//    rpinterpreter.h,  } -- Class RPInterpreter.
//    rpinterpreter.a   }
//    llgp_between.h      -- BETWEEN_II().
//
//---
// Build:
//    g++  -Wall -ggdb  -o fltrppm4  fltrppm4.cpp  rpinterpreter.a  -lm
//


#include "rpinterpreter.h"   //RPInterpreter.

#include "llgp_between.h"    //BETWEEN_II().
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>          //exit().

#include <fenv.h>            //feclearexcept(), fetestexcept().



//--------------------------------------------------------------------------
// Application-specific datatypes.
//--------------------------------------------------------------------------

// 8-bit unsigned integer.
typedef unsigned char byte_t;


// Indices in an array of RGB values.
enum colorcoord_t
{
        CC_R = 0, CC_G, CC_B
};


// RGB pixel value, each elem unsigned 8-bit.
struct byte3_t
{
	byte_t a[3];

	byte3_t( void ) { a[0] = 0; a[1] = 0; a[2] = 0; }
};

// RGB pixel value, each elem double in [0,1].
struct double3_t
{
	double a[3];

	double3_t( void ) { a[0] = 0.0; a[1] = 0.0; a[2] = 0.0; }
};



//--------------------------------------------------------------------------
// Scanning text from input file.
//--------------------------------------------------------------------------

bool char_isWS( char iC )
{
	return ( (iC==' ') || (iC=='\t') || (iC=='\n') );
}

int skipWSAndComment( FILE * iF )
{
	int c;
	while ( c = fgetc(iF), c != EOF )
	{
		if ( char_isWS( c ) )
		{ 
			/*empty*/
		}
		else if ( c == '#' ) //Comment line
		{ 
			while ( c = fgetc(iF), c != EOF && c != '\n' ) { ; } 
		}
		else
		{
			break; //LEAVE LOOP.
		}
	}
	return c;
}

bool scanDecInt( 
	FILE *  iF, 
	int *   uC, //in & out
	int *   oVal )
{
	bool seenOne = 0;
	int  val = 0;
	while ( BETWEEN_II( '0', *uC , '9' ) )
	{
		seenOne = 1;
		val *= 10;
		val += *uC - '0';
		*uC = fgetc(iF);
	}
	*oVal = val;
	return seenOne;
}



//--------------------------------------------------------------------------
// Reading and writing pixel values from/to PPM "P6" file.
//--------------------------------------------------------------------------

bool getPixel_byte3( 
	FILE *     iF,
	byte3_t *  oPixelByte )
{
	if ( fread( oPixelByte, 3, 1, iF ) != 1 ) { return false; }
	return true;
}
bool putPixel_byte3( 
	FILE *           oF,
	byte3_t const *  iPixelByte )
{
	if ( fwrite( iPixelByte, 3, 1, oF ) != 1 ) { return false; }
	return true;
}



double byteToDouble( byte_t i )
{
	return (double)i / 255.0;
}
byte_t doubleToByte( double i )
{
	return (byte_t)( (255.0 * i) + 0.49999 );
}

void byte3ToDouble3( 
	byte3_t const *  in,
	double3_t *      out )
{
	for ( int k = 0; k < 3; k++ )
	{
		out->a[k] = byteToDouble( in->a[k] ); 
	}
}
void double3ToByte3(
	double3_t const *  in,
	byte3_t *          out )
{
	for ( int k = 0; k < 3; k++ )
	{
		out->a[k] = doubleToByte( in->a[k] ); 
	}
}


bool getPixel_double3(
	FILE *       iF,
	double3_t *  oPixelDouble )
{
	byte3_t pixelByte;
	if ( ! getPixel_byte3( iF, &pixelByte ) ) { return false; }

	byte3ToDouble3( &pixelByte, oPixelDouble ); 

	return true;
}
bool putPixel_double3( 
	FILE *             oF,
	double3_t const *  iPixelDouble )
{
	byte3_t pixelByte;
	double3ToByte3( iPixelDouble, &pixelByte );

	return putPixel_byte3( oF, &pixelByte );
}



//--------------------------------------------------------------------------
// Program abortion on error 
// (crude but suffices for a small program like this).
//--------------------------------------------------------------------------

#include <stdarg.h>

void fatal( 
	const char * iFmt,
	... )
{
	fprintf( stderr, "fltrppm4: fatal: " );

	va_list ap;
        va_start( ap, iFmt );

	vfprintf( stderr, iFmt, ap );

	va_end( ap );

	fprintf( stderr, "\n" );
	exit( -1 );
}



//--------------------------------------------------------------------------
// Filter one pixel.
//--------------------------------------------------------------------------

// Clip a double to the interval [0.0, 1.0].
double clip( double iVal )
{
	if ( iVal < 0.0 ) { return 0.0; }
	if ( iVal > 1.0 ) { return 1.0; }
	return iVal;
}

void filterPixel(
	RPInterpreter *    uRP,  
	int                iX,         //} Pixel coordinates (for diagnostic message).
	int                iY,         //}
	double3_t const *  iPixel,
	double3_t *        oPixel,
	bool               iChkEvery ) //Check for domain error at every pixel.
{
	// Set variables.
	uRP->clearVars();
	uRP->putVar( 'r', iPixel->a[CC_R] );
	uRP->putVar( 'g', iPixel->a[CC_G] );
	uRP->putVar( 'b', iPixel->a[CC_B] );

	// Evaluate expression.
	uRP->evaluate(); 

	// Get output variable values.
	double rOut;
	double gOut;
	double bOut;
	bool f = 
		uRP->getVar( 'r', &rOut ) &&
		uRP->getVar( 'g', &gOut ) &&
		uRP->getVar( 'b', &bOut );
	if ( iChkEvery && !f ) 
	{
		fatal( "Pixel x=%d y=%d: Runtime DomainError\n", iX, iY );
	}

	// Set output RGB values.
	oPixel->a[CC_R] = clip( rOut );
	oPixel->a[CC_G] = clip( gOut );
	oPixel->a[CC_B] = clip( bOut );
}



//--------------------------------------------------------------------------
// Filter PPM file.
// Input  = stdio FILE.
// output = stdio FILE.
// On error, call 'fatal()' to abort the program.
//--------------------------------------------------------------------------

// Parameters:
//    uRP = RPInterpreter object, containing the parsed expression.
//    iF  = Input file.
//    oF  = Output file.
void filterPPM(
	RPInterpreter *  uRP,  
	FILE *           iF,
	FILE *           oF,
	bool             iChkEvery ) //Check for domain error at every pixel.
{
	//
	// Read/write the PPM file prologue.
	//

	int c1;
	int c2;
	if ( c1 = fgetc( iF ), c1 == EOF )
		{ fatal( "fgetc c1" ); }
	if ( c2 = fgetc( iF ), c2 == EOF )
		{ fatal( "fgetc c1" ); }
	if ( ! ( c1 == 'P' && c2 == '6' ) )
		{ fatal( "mnum != 'P6'" ); }

	int c;
	int wid;
	int hih;
	int maxclr;

	c = skipWSAndComment( iF );
	if ( c == EOF ) { fatal( "EOF before width" ); }
	if ( ! scanDecInt( iF, &c, &wid ) ) { fatal( "error scanning width" ); }

	c = skipWSAndComment( iF );
	if ( c == EOF ) { fatal( "EOF before height" ); }
	if ( ! scanDecInt( iF, &c, &hih ) ) { fatal( "error scanning height" ); }

	c = skipWSAndComment( iF );
	if ( c == EOF ) { fatal( "EOF before max-color" ); }
	if ( ! scanDecInt( iF, &c, &maxclr ) ) { fatal( "error scanning maxcolor" ); }

	fprintf( oF, "P6 %u %u %u\n", wid, hih, maxclr );

	if ( maxclr != 255 )
		{ fatal( "This program handles only PPM files with maxcolor == 255" ); }


	//
	// Filter the pixels.
	//

	for ( int y = 0; y < hih; y++ )
	{
		for ( int x = 0; x < wid; x++ )
		{
			double3_t pixelIn;
			if ( ! getPixel_double3( iF, &pixelIn ) )
				{ fatal( "Missing pixel data in file" ); }

			double3_t pixelOut;
			filterPixel( uRP, x, y, &pixelIn, &pixelOut, iChkEvery );

			putPixel_double3( oF, &pixelOut );
		}//x
	}//y
}



//--------------------------------------------------------------------------
// MAIN.
//--------------------------------------------------------------------------

#include <string.h>  //strcmp().

void usage()
{
	fprintf( stderr, 
		"Usage: fltrppm4 'EXPRESSION' [options]\n" 
		"Options: \n"
		"  -v   Verbose.\n"
		"  -d   Check for domain error at EVERY pixel.\n"
		"\n"
		"Note: Enclose EXPRESSION in single quotes to prevent shell expansion.\n"
		"\n" 
	);

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


int main( int argc, char ** argv )
{
	// 
	// Cmd line args.
	// 

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

	bool arg_v        = false;
	bool arg_chkEvery = false;

	for ( int iOpt = 2; iOpt < argc; iOpt++ )
	{
		if      ( strcmp( argv[iOpt], "-v" ) == 0 ) { arg_v        = true; }
		else if ( strcmp( argv[iOpt], "-d" ) == 0 ) { arg_chkEvery = true; }
		else { usage(); return -1; }
	}



	// 
	// Create RPInterpreter object,
	// scan the expression string.
	// 

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

	if ( arg_v )
	{
		rp.dump( stderr );
	}

	
	
	//
	// Filter the PPM file.
	//

	feclearexcept( FE_ALL_EXCEPT );

	filterPPM( &rp, stdin, stdout, arg_chkEvery );

	if ( fetestexcept( FE_INVALID ) )
	{
		fatal( "Runtime DomainError\n" );
	}



	return 0;
}