/*
 * complex operation program copyright (C) 2009 H.Niwa
 */

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <errno.h>
#include <math.h>

#include <string>
#include <cmath>
#include <complex>

#include "syserr.h"

#include "bin_node.h"
#include "gc.h"
#include "var.h"
#include "pred.h"
#include "context.h"
#include "unify.h"
#include "builtin.h"
#include "sysmodule.h"
#include "expression.h"
#include "let.h"
#include "module.h"
#include "help.h"

#include "complx.h"

extern void PushStack(Context* cx, Node* goals, Node* md, Node* env);
extern int PopStack(Context* cx, Node* &goals, Node* &md, Node* &env);

int CReal(Context* cx, Node* goalscar);
int CImage(Context* cx, Node* goalscar);

int CAbs(Context* cx, Node* goalscar);
int CArg(Context* cx, Node* goalscar);
int CNorm(Context* cx, Node* goalscar);
int CConj(Context* cx, Node* goalscar);
int CPolar(Context* cx, Node* goalscar);

int CSin(Context* cx, Node* goalscar);
int CCos(Context* cx, Node* goalscar);
int CTan(Context* cx, Node* goalscar);

int CASin(Context* cx, Node* goalscar);
int CACos(Context* cx, Node* goalscar);
int CATan(Context* cx, Node* goalscar);

int CSinh(Context* cx, Node* goalscar);
int CCosh(Context* cx, Node* goalscar);
int CTanh(Context* cx, Node* goalscar);

int CASinh(Context* cx, Node* goalscar);
int CACosh(Context* cx, Node* goalscar);
int CATanh(Context* cx, Node* goalscar);

int CLog(Context* cx, Node* goalscar);
int CLog10(Context* cx, Node* goalscar);

int CPow(Context* cx, Node* goalscar);
int CSqrt(Context* cx, Node* goalscar);

int CExp(Context* cx, Node* goalscar);
int CExp2(Context* cx, Node* goalscar);
int CExp10(Context* cx, Node* goalscar);




int CReal(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	long double f;

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toFloat(f))) {
		return 0;
	}

	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(f));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}


int CImage(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	long double f;

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toImage(f))) {
		return 0;
	}

	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(f));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}



int CAbs(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(abs(c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}


int CArg(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(arg(c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}


int CNorm(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(norm(c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}


int CConj(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(conj(c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;

}


int CPolar(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 3) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag1 = goalscar->Cdr()->Cdr()->Car()->Val();
	Node* ag2 = goalscar->Cdr()->Cdr()->Cdr()->Car()->Val();
	long double	f1, f2;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag1->kind() != ATOM) || (!((Atom*)ag1)->toFloat(f1))) {
		return 0;
	}
	if ((ag2->kind() != ATOM) || (!((Atom*)ag2)->toFloat(f2))) {
		return 0;
	}
	if (f1 < 0) {
		return 0;
	}

	std::complex<long double> p =
		std::complex<long double>(f1*cos(f2),f1*sin(f2));

	Node* env = Nil->Cons(Nil);

	SetEnv(env, v);
	((Undef*)v)->Set(mka(p));
	PushStack(cx, Nil, Nil, env);
		
	return 1;

}



int CSin(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(sin(c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}


int CCos(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(cos(c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;

}


int CTan(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(tan(c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;

}



int CASin(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c, z, i(0,1), n1(1,0);

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(z))) {
		return 0;
	}

	c = - i * log(i*z+sqrt(-z*z+n1));
		
	Node* env = Nil->Cons(Nil);

	SetEnv(env, v);
	((Undef*)v)->Set(mka(c));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}


int CACos(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c, z, i(0,1), n1(1,0);

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(z))) {
		return 0;
	}
	
	c = -i* log(z+sqrt(z*z-n1));

	Node* env = Nil->Cons(Nil);

	SetEnv(env, v);
	((Undef*)v)->Set(mka(c));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}


int CATan(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c, z, i(0,1), n1(1,0), n2(2,0);

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(z))) {
		return 0;
	}
	
	c = -i*log((i*z+n1)/(-i*z+n1))/n2;

	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(c));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}



int CSinh(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(sinh(c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}


int CCosh(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(cosh(c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}


int CTanh(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(tanh(c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;

}



int CASinh(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c, z, i(0,1), n1(1,0);

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(z))) {
		return 0;
	}

	c = log(z+sqrt(z*z+n1));
		
	Node* env = Nil->Cons(Nil);

	SetEnv(env, v);
	((Undef*)v)->Set(mka(c));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}


int CACosh(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();

	std::complex<long double> c, z, i(0,1), n1(1,0);

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(z))) {
		return 0;
	}

	c = log(z + sqrt(z*z - n1)); 

	Node* env = Nil->Cons(Nil);

	SetEnv(env, v);
	((Undef*)v)->Set(mka(c));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}


int CATanh(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c, z, i(0,1), n1(1,0), n2(2,0);

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(z))) {
		return 0;
	}
	
	c = log((n1+z)/(n1-z))/n2;

	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(c));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}



int CLog(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(log(c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;

}


int CLog10(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(log10(c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;
}



int CPow(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 3) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag1 = goalscar->Cdr()->Cdr()->Car()->Val();
	Node* ag2 = goalscar->Cdr()->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c1, c2;

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag1->kind() != ATOM) || (!((Atom*)ag1)->toComplex(c1))) {
		return 0;
	}
	if ((ag2->kind() != ATOM) || (!((Atom*)ag2)->toComplex(c2))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(pow(c1, c2)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;

}


int CSqrt(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(sqrt(c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;

}



int CExp(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c;
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(exp(c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;

}


int CExp2(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c, c2(2.0, 0.0);
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(pow(c2, c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;

}

int CExp10(Context* cx, Node* goalscar)
{
	if (ListLength(goalscar->Cdr()) != 2) {
		return 0;
	}

	Node* v = goalscar->Cdr()->Car()->Val();
	Node* ag = goalscar->Cdr()->Cdr()->Car()->Val();
	std::complex<long double>	c, c10(10.0, 0.0);
	

	if (v->kind() != UNDEF) {
		return 0;
	}
	
	if ((ag->kind() != ATOM) || (!((Atom*)ag)->toComplex(c))) {
		return 0;
	}
	
	Node* env = Nil->Cons(Nil);


	SetEnv(env, v);
	((Undef*)v)->Set(mka(pow(c10, c)));
	PushStack(cx, Nil, Nil, env);
		
	return 1;

}

