// joycub - an interpeter for the programming language Joy
// 
// (c) 2004-2006 Sergey Zubkov 
// $Id: builtins_math.cc,v 1.2 2006/02/27 18:44:01 cubbi Exp $
#include <vector>
#include <sstream>
#include <fstream>
#include <cmath>
#include <ctime>
#include "program.h"
#include "builtins.h"
#include "exceptions.h"
#include "float.h"
#include "set.h"
#include "char.h"
#include "string.h"
#include "bool.h"
#include "op.h"
#include "symtab.h"

static char RCSID[] ="$Id: builtins_math.cc,v 1.2 2006/02/27 18:44:01 cubbi Exp $";

template <class T>
int sign(T n) {
	if(n<0) return -1;
	if(n>0) return 1;
	return 0;
}
//sign      :  N1  ->  N2
//Integer N2 is the sign (-1 or 0 or +1) of integer N1,
//or float N2 is the sign (-1.0 or 0.0 or 1.0) of float N1.
void op_sign(JC_Stack & STACK) {
	STACK.assert_depth(1,"sign");
	JC_objref* ref_n = STACK.pop();
   	// result is INT or CHAR
	switch(ref_n->get_type()) {
		case INT:
		case CHAR:
			STACK.push_back(new JC_objref(new JC_int(
				sign(ref_n->to_int()))));
			break;
		default: // FLOAT and the errors
			STACK.push_back(new JC_objref(new JC_int(
				sign(ref_n->to_double()))));
			break;
	}
	delete ref_n;
}


// OPERATOR
// +      :  M I  ->  N
// Numeric N is the result of adding integer I to numeric M.
// Also supports float
void op_plus(JC_Stack & STACK) {
	STACK.assert_depth(2,"+");
	JC_objref* ref_i = STACK.pop();
	JC_objref* ref_m = STACK.pop();
   	// result is INT or CHAR
	if( (ref_m->get_type()==INT || ref_m->get_type()==CHAR)
	   	&&
	   	(ref_i->get_type()==INT || ref_i->get_type()==CHAR) )  {
			if(ref_m->get_type()==CHAR ||  ref_i->get_type()==CHAR) {
				STACK.push_back(new JC_objref(new JC_char(
						ref_m->to_int() + ref_i->to_int())));
			} else {
				STACK.push_back(new JC_objref(new JC_int(
						ref_m->to_int() + ref_i->to_int())));
			}
	} else { // result is FLOAT
		STACK.push_back(new JC_objref(new JC_float(
			ref_m->to_double() + ref_i->to_double())));
	}
	delete ref_m; delete ref_i;
}

// OPERATOR
// -      :  M I  ->  N
// Numeric N is the result of subtracting integer I from numeric M.
// Also supports float.
void op_minus(JC_Stack & STACK) {
	STACK.assert_depth(2,"-");
	JC_objref* ref_i = STACK.pop();
	JC_objref* ref_m = STACK.pop();
	if( (ref_m->get_type()==INT || ref_m->get_type()==CHAR)
	   	&&
	   	(ref_i->get_type()==INT || ref_i->get_type()==CHAR) )  {
			if(ref_m->get_type()==CHAR ||  ref_i->get_type()==CHAR) {
				STACK.push_back(new JC_objref(new JC_char(
						ref_m->to_int() - ref_i->to_int())));
			} else {
				STACK.push_back(new JC_objref(new JC_int(
						ref_m->to_int() - ref_i->to_int())));
			}
	} else { // result is FLOAT
		STACK.push_back(new JC_objref(new JC_float(
			ref_m->to_double() - ref_i->to_double())));
	}
	delete ref_m; delete ref_i;
}

// rem      :  I J  ->  K
// Integer K is the remainder of dividing I by J.
// Also supports float.
void op_rem(JC_Stack & STACK) {
	STACK.assert_depth(2,"rem");
	JC_objref* ref_j = STACK.pop();
	JC_objref* ref_i = STACK.pop();
	if( (ref_j->get_type()==INT || ref_j->get_type()==CHAR)
	   	&&
	   	(ref_i->get_type()==INT || ref_i->get_type()==CHAR) )  {
		STACK.push_back(new JC_objref(new JC_int(
			ref_i->to_int() % ref_j->to_int())));
	} else { // result is FLOAT or throw type error
		double q = ref_i->to_double()/ref_j->to_double();
		double q2 =  ref_j->to_double()*(int)q;
		STACK.push_back(new JC_objref(new JC_float(
			ref_i->to_double() - q2)));
	}
	delete ref_i; delete ref_j;
}
// div      :  I J  ->  K L
// Integers K and L are the quotient and remainder of dividing I by J.
void op_div(JC_Stack & STACK) {
	STACK.assert_depth(2,"div");
	JC_objref* ref_j = STACK.pop();
	JC_objref* ref_i = STACK.pop();
	STACK.push_back(new JC_objref(new JC_int(
		ref_i->to_int() / ref_j->to_int())));
	STACK.push_back(new JC_objref(new JC_int(
		ref_i->to_int() % ref_j->to_int())));
	delete ref_i; delete ref_j;
}


// OPERATOR
// *      :  I J  ->  K
// Integer K is the product of integers I and J.
// Also supports float.
void op_mult(JC_Stack & STACK) {
	STACK.assert_depth(2,"*");
	JC_objref* ref_i = STACK.pop();
	JC_objref* ref_m = STACK.pop();
	if( (ref_m->get_type()==INT || ref_m->get_type()==CHAR)
	   	&&
	   	(ref_i->get_type()==INT || ref_i->get_type()==CHAR) )  {
		STACK.push_back(new JC_objref(new JC_int(
			ref_m->to_int() * ref_i->to_int())));
	} else { // result is FLOAT or throw type error
		STACK.push_back(new JC_objref(new JC_float(
			ref_m->to_double() * ref_i->to_double())));
	}
	delete ref_m; delete ref_i;
}

// OPERATOR
// /      :  I J  ->  K
// Integer K is the (rounded) ratio of integers I and J.  Also supports float.
void op_divide(JC_Stack & STACK) {
	STACK.assert_depth(2,"/");
	int i = STACK.size()-1; // last element's index
	JC_objref* ref_m = STACK[i-1];
	JC_objref* ref_i = STACK[i];
	STACK.pop_back();
	STACK.pop_back();
	if(ref_m->get_type()==INT && ref_i->get_type()==INT)  { // result is INT
		STACK.push_back(new JC_objref(new JC_int(
			ref_m->to_int() / ref_i->to_int())));
	} else { // result is FLOAT or throw type error
		STACK.push_back(new JC_objref(new JC_float(
			ref_m->to_double() / ref_i->to_double())));
	}
	delete ref_m;
	delete ref_i;
}

// max      :  N1 N2  ->  N
// N is the maximum of numeric values N1 and N2.  Also supports float.
void op_max(JC_Stack & STACK) {
	STACK.assert_depth(2,"max");
	JC_objref* ref_m = STACK.pop();
	JC_objref* ref_i = STACK.pop();
	if(ref_m->get_type()==INT && ref_i->get_type()==INT)  { // result is INT
		STACK.push_back(new JC_objref(new JC_int(
			max(ref_m->to_int() , ref_i->to_int()))));
	} else { // result is FLOAT or throw type error
		STACK.push_back(new JC_objref(new JC_float(
			max(ref_m->to_double() , ref_i->to_double()))));
	}
	delete ref_m; delete ref_i;
}
// min      :  N1 N2  ->  N
// N is the minimum of numeric values N1 and N2.  Also supports float.
void op_min(JC_Stack & STACK) {
	STACK.assert_depth(2,"min");
	JC_objref* ref_m = STACK.pop();
	JC_objref* ref_i = STACK.pop();
	if(ref_m->get_type()==INT && ref_i->get_type()==INT)  { // result is INT
		STACK.push_back(new JC_objref(new JC_int(
			min(ref_m->to_int() , ref_i->to_int()))));
	} else { // result is FLOAT or throw type error
		STACK.push_back(new JC_objref(new JC_float(
			min(ref_m->to_double() , ref_i->to_double()))));
	}
	delete ref_m; delete ref_i;
}

//maxint      :  ->  maxint
//Pushes largest integer (platform dependent). Typically it is 32 bits.
void op_maxint(JC_Stack & STACK) {
	STACK.push_back(new JC_objref(new JC_int( ~(1<<(8*sizeof(int)-1))  )));
}

// sqrt      :  F  ->  G
// G is the square root of F.
void op_sqrt(JC_Stack & STACK) {
	STACK.assert_depth(1,"sqrt");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(sqrt(ref_f->to_double()))));
	delete ref_f;
}

// pow      :  F G  ->  H
// H is F raised to the Gth power.
void op_pow(JC_Stack & STACK) {
	STACK.assert_depth(2,"pow");
	JC_objref* ref_g = STACK.pop();
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			pow(ref_f->to_double(), ref_g->to_double()))));
	delete ref_f; delete ref_g;
}

// sin      :  F  ->  G
// G is the sine of F.
void op_sin(JC_Stack & STACK) {
	STACK.assert_depth(1,"sin");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			sin(ref_f->to_double()))));
	delete ref_f;
}

// sinh      :  F  ->  G
// G is the hyperbolic sine of F.
void op_sinh(JC_Stack & STACK) {
	STACK.assert_depth(1,"sinh");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			sinh(ref_f->to_double()))));
	delete ref_f;
}

//tan      :  F  ->  G
//G is the tangent of F.
void op_tan(JC_Stack & STACK) {
	STACK.assert_depth(1,"tan");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			tan(ref_f->to_double()))));
	delete ref_f;
}

//tanh      :  F  ->  G
//G is the hyperbolic tangent of F.
void op_tanh(JC_Stack & STACK) {
	STACK.assert_depth(1,"tanh");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			tanh(ref_f->to_double()))));
	delete ref_f;
}

//cos      :  F  ->  G
//G is the cosine of F.
void op_cos(JC_Stack & STACK) {
	STACK.assert_depth(1,"cos");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			cos(ref_f->to_double()))));
	delete ref_f;
}

//cosh      :  F  ->  G
//G is the hyperbolic cosine of F.
void op_cosh(JC_Stack & STACK) {
	STACK.assert_depth(1,"cosh");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			cosh(ref_f->to_double()))));
	delete ref_f;
}

// acos      :  F  ->  G
// G is the arc cosine of F.
void op_acos(JC_Stack & STACK) {
	STACK.assert_depth(1,"acos");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			acos(ref_f->to_double()))));
	delete ref_f;
}

// asin      :  F  ->  G
// G is the arc sine of F.
void op_asin(JC_Stack & STACK) {
	STACK.assert_depth(1,"asin");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			asin(ref_f->to_double()))));
	delete ref_f;
}

// atan      :  F  ->  G
// G is the arc tangent of F.
void op_atan(JC_Stack & STACK) {
	STACK.assert_depth(1,"atan");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			atan(ref_f->to_double()))));
	delete ref_f;
}

// atan2      :  F G  ->  H
// H is the arc tangent of F / G.
void op_atan2(JC_Stack & STACK) {
	STACK.assert_depth(2,"atan2");
	JC_objref* ref_g = STACK.pop();
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			atan2(ref_f->to_double(), ref_g->to_double()))));
	delete ref_f; delete ref_g;
}


// trunc      :  F  ->  I
// I is an integer equal to the float F truncated toward zero.
void op_trunc(JC_Stack & STACK) {
	STACK.assert_depth(1,"trunc");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_int(
			(int)trunc(ref_f->to_double()))));
	delete ref_f;
}

// rand      :    -> I
// I is a random integer.
void op_rand(JC_Stack & STACK) {
	STACK.push_back(new JC_objref(new JC_int( rand() )));
}

// srand      :  I  ->
// Sets the random integer seed to integer I.
void op_srand(JC_Stack & STACK) {
	STACK.assert_depth(1,"srand");
	JC_objref* ref_i=STACK.pop();
	srand(ref_i->to_int());
	delete ref_i;
}


// ceil      :  F  ->  G
// G is the float ceiling of F.
void op_ceil(JC_Stack & STACK) {
	STACK.assert_depth(1,"ceil");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			ceil(ref_f->to_double()))));
	delete ref_f;
}

//exp      :  F  ->  G
//G is e (2.718281828...) raised to the Fth power.
void op_exp(JC_Stack & STACK) {
	STACK.assert_depth(1,"exp");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			exp(ref_f->to_double()))));
	delete ref_f;
}

//floor      :  F  ->  G
//G is the floor of F.
void op_floor(JC_Stack & STACK) {
	STACK.assert_depth(1,"floor");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			floor(ref_f->to_double()))));
	delete ref_f;
}

//frexp      :  F  ->  G I
//G is the mantissa and I is the exponent of F.
//Unless F = 0, 0.5 <= abs(G) < 1.0.
void op_frexp(JC_Stack & STACK) {
	STACK.assert_depth(1,"frexp");
	JC_objref* ref_f = STACK.pop();
	int i;
	double g = frexp(ref_f->to_double(), &i);
	STACK.push_back(new JC_objref(new JC_float(g)));
	STACK.push_back(new JC_objref(new JC_int(i)));
	delete ref_f;
}

//ldexp      :  F I  -> G
//G is F times 2 to the Ith power.
void op_ldexp(JC_Stack & STACK) {
	STACK.assert_depth(2,"ldexp");
	JC_objref* ref_i = STACK.pop();
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			ldexp(ref_f->to_double(), ref_i->to_int()))));
	delete ref_f; delete ref_i;
}

//log      :  F  ->  G
//G is the natural logarithm of F.
void op_log(JC_Stack & STACK) {
	STACK.assert_depth(1,"log");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			log(ref_f->to_double()))));
	delete ref_f;
}

//log10      :  F  ->  G
//G is the common logarithm of F.
void op_log10(JC_Stack & STACK) {
	STACK.assert_depth(1,"log10");
	JC_objref* ref_f = STACK.pop();
	STACK.push_back(new JC_objref(new JC_float(
			log10(ref_f->to_double()))));
	delete ref_f;
}

//modf      :  F  ->  G H
//G is the fractional part and H is the integer part
void op_modf(JC_Stack & STACK) {
	STACK.assert_depth(1,"modf");
	JC_objref* ref_f = STACK.pop();
	double h;
	double g = modf(ref_f->to_double(), &h);
	STACK.push_back(new JC_objref(new JC_float(g)));
	STACK.push_back(new JC_objref(new JC_float(h)));
	delete ref_f;
}


