#include <math.h>
#include "mpfitAST.h"
#include "math_utils.h"
#include "gsl/gsl_sf_erf.h"
#include "gsl/gsl_sf_bessel.h"

std::map<std::string, double> mpfit_vars;
double mpfit_quick_vars[256];
mpfitASTNode *mpfitAST;
dpStringList mpfit_sorted_variables;

std::map<std::string, int> mpfit_functions1, mpfit_functions2, mpfit_functions3;

void initialize_mpfit_functions(void) {
    if (mpfit_functions1.size() > 0) return;
    mpfit_functions1["sin"] = 1;
    mpfit_functions1["cos"] = 2;
    mpfit_functions1["tan"] = 3;
    mpfit_functions1["sinh"] = 4;
    mpfit_functions1["cosh"] = 5;
    mpfit_functions1["tanh"] = 6;
    mpfit_functions1["asin"] = 7;
    mpfit_functions1["acos"] = 8;
    mpfit_functions1["atan"] = 9;
    mpfit_functions2["atan"] = 9;
    mpfit_functions1["asinh"] = 10;
    mpfit_functions1["acosh"] = 11;
    mpfit_functions1["atanh"] = 12;
    mpfit_functions1["exp"] = 13;
    mpfit_functions1["log"] = 14;
    mpfit_functions1["ln"] = 15;
    mpfit_functions1["sqrt"] = 16;
    mpfit_functions1["erf"] = 17;
    mpfit_functions3["bessel"] = 18;
    mpfit_functions1["int"] = 19;
    mpfit_functions1["round"] = 20;
    mpfit_functions1["frac"] = 21;
    mpfit_functions1["abs"] = 22;
    mpfit_functions2["gauss"] = 54;
}

std::map<std::string, int> mpfitAST_dummy;

double mpfitunaryMinusNode::evaluate() {
  double value = exp->evaluate();
	return -value;
}

mpfitvariableNode::mpfitvariableNode(std::string value) : id(value) {
    first_character = value[0];
    if (value.size() == 1) {
        onechar = true;
    } else {
        onechar = false;
    }
    if (!(onechar && (first_character == 'x'))) {
        if (mpfitAST_dummy.count(id) == 0) {
            mpfit_sorted_variables.append(id);
            mpfitAST_dummy[id] = 1;
        }
    }
}

double mpfitvariableNode::evaluate() {
    if (onechar) return mpfit_quick_vars[first_character];
    else return mpfit_vars[id];
}

mpfitfunctionNode::mpfitfunctionNode(std::string func, mpfitASTNode *exp) : arg(exp) {
    std::string f = func.substr(0, func.size() - 1);
    if (mpfit_functions1.count(f) != 1) {
        mpfiterror(("function " + func + " does not take 3 parameters").c_str());
    } else {
        id = mpfit_functions1.at(f);
    }
}

double mpfitfunctionNode::evaluate() {
    double arg1 = arg->evaluate();
    switch(id) {
    case 1: return sin(arg1); break;
    case 2: return cos(arg1); break;
    case 3: return tan(arg1); break;
    case 4: return sinh(arg1); break;
    case 5: return cosh(arg1); break;
    case 6: return tanh(arg1); break;
    case 7: return asin(arg1); break;
    case 8: return acos(arg1); break;
    case 9: return atan(arg1); break;
    case 10: return asinh(arg1); break;
    case 11: return acosh(arg1); break;
    case 12: return atanh(arg1); break;
    case 13: return exp(arg1); break;
    case 14: return log(arg1) / log(10.); break;
    case 15: return log(arg1); break;
    case 16: return sqrt(arg1); break;
    case 17: return gsl_sf_erf(arg1); break;
//    case 18: return bessel(arg1); break;
    case 19: return (double)(int)arg1; break;
    case 20: return (double)nint(arg1); break;
    case 21: return fabs(arg1 - (int)arg1); break;
    case 22: return fabs(arg1); break;
    default: return arg1; break;
	}
}

mpfitfunctionNode2::mpfitfunctionNode2(std::string func, mpfitASTNode *exp, mpfitASTNode *exp2) : arg1(exp), arg2(exp2) {
    std::string f = func.substr(0, func.size() - 1);
    if (mpfit_functions2.count(f) != 1) {
        mpfiterror(("function " + func + " does not take 2 parameters").c_str());
    } else {
        id = mpfit_functions2.at(f);
    }
}

double mpfitfunctionNode2::evaluate() {
    double a1 = arg1->evaluate();
    double a2 = arg2->evaluate();
    switch(id) {
    case 9: return atan2(a1, a2); break;
    case 54: return exp(-4.0*log(2.0)*pow(a1/a2,2.0)); break;

    default: return a1; break;
    }
}

mpfitfunctionNode3::mpfitfunctionNode3(std::string func, mpfitASTNode *exp, mpfitASTNode *exp2, mpfitASTNode *exp3) : arg1(exp), arg2(exp2), arg3(exp3) {
    std::string f = func.substr(0, func.size() - 1);
    if (mpfit_functions3.count(f) != 1) {
        mpfiterror(("function " + func + " does not take 3 parameters").c_str());
    } else {
        id = mpfit_functions3.at(f);
    }
}

double mpfitfunctionNode3::evaluate() {
    double a1 = arg1->evaluate();
    double a2 = arg2->evaluate();
    double a3 = arg3->evaluate();
    switch(id) {
    case 18: {
        int order, kind;
        kind = (int)a2;
        order = (int)a3;
        if ((order < 0) || (kind < 1) || (kind > 2)) {
            mpfiterror("Bessel: illegal argument\n");
        } else switch (kind) {
            case 1: switch (order) {
                case 0: return gsl_sf_bessel_J0(a1); break;
                case 1: return gsl_sf_bessel_J1(a1); break;
                default: return gsl_sf_bessel_Jn(order, a1); break;
            }
                break;
            case 2: switch (order) {
                case 0: return gsl_sf_bessel_Y0(a1); break;
                case 1: return gsl_sf_bessel_Y1(a1); break;
                default: return gsl_sf_bessel_Yn(order, a1); break;
            }
                break;
            default: mpfiterror("Bessel: illegal argument\n"); break;
        }
    }
    default: return a3; break;
    }
}

double mpfitplusNode::evaluate() {
  double leftvalue = left->evaluate();
	double rightvalue = right->evaluate();
	
	return leftvalue + rightvalue;
}

double mpfitminusNode::evaluate() {
  double leftvalue = left->evaluate();
	double rightvalue = right->evaluate();
	
	return leftvalue - rightvalue;
}

double mpfitmultiplyNode::evaluate() {
  double leftvalue = left->evaluate();
	double rightvalue = right->evaluate();
	
	return leftvalue * rightvalue;
}

double mpfitdivideNode::evaluate() {
  double leftvalue = left->evaluate();
	double rightvalue = right->evaluate();
	
	return leftvalue / rightvalue;
}

double mpfitpowerNode::evaluate() {
  double leftvalue = left->evaluate();
	double rightvalue = right->evaluate();
	
	return pow(leftvalue, rightvalue);
}

