#include "config.h"
#include "L.h"


template <class ttype, class ttype2>
int compare_accuracy(ttype a, ttype2 b){
    Double x=abs(a-b)/(abs(a)+abs(b));
    if(x<tolerance) return DIGITS;
    return -Int(log(x)* 0.4342944819032518); // 0.4342944819032518 = 1/log(10)
}

template <class ttype>
int compare_to_zero(ttype a){
    Double x=abs(a);
    return -Int(log(x)* 0.4342944819032518); // 0.4342944819032518 = 1/log(10)
}

// A convenience function that compares the actual accuracy against
// the expected accuracy and outputs "PASS" or "FAIL". The result
// of this function is meant to be logical OR'd with the current
// global "result" variable, which starts at zero and, should it
// ever become one, remains one.
int process_result(int actual_accuracy, int expected_accuracy) {
   if (actual_accuracy < expected_accuracy) {
      cout << "FAIL" << endl;
      return 1;
    }
    else {
      cout << "PASS" << endl;
      return 0;
    }
}

//a simple program illustrating a few features of the L_function class.

int main (int argc, char *argv[]) {

    // The overall result of these checks. Zero (shell success) if
    // they all pass, or one otherwise.
    int result = 0;

    // A temporary variable to hold the accuracy (as reported by
    // compare_accuracy) of the current test.
    int actual_accuracy;

    // The expected accuracy of the current test. The test "passes" if
    // the expected accuracy is less than or equal to the actual
    // accuracy. All of these were "computed" by looking at the output
    // on my machine and more or less assuming that they're reasonable.
    //
    // TODO: we should probably use a variable multiplier here that we
    // can adjust when using double-double or quad-double precision.
    // Using double-double appears to roughly double the expected
    // accuracy; but in any case, for now, this is just what's necessary
    // for the tests to pass with plain old double precision.
    int expected_accuracy;

#if PRECISION_MULTIPLE
    // Speed up MPFR computations by using fewer digits. This choice
    // relies on the fact that expected_accuracy never goes above 15.
    DIGITS = 18;
#endif

    initialize_globals(); //initialize global variables. This *must* be called.


    Double x;
    Complex s;

    L_function<int> zeta; //default L-function is the Riemann zeta function

    L_function<int> L4; //will be assigned below to be L(s,chi_{-4}),
                        //chi{-4} being the real quadratic character mod 4

    L_function<Complex> L5; //will be assigned below to be L(s,chi),
                            //with chi being a complex character mod 5


    // ==================== Initialize the L-functions ======================
    //
    // one drawback of arrays- the index starts at 0 rather than 1 so
    // each array below is declared to be one entry larger than it is.
    // I prefer this so that referring to the array elements is more
    // straightforward, for example coeff[1] refers to the first
    // Dirichlet cefficient rather than coeff[0]. But to make up for
    // this, we insert a bogus entry (such as 0) at the start of each
    // array.
    //

     // The Dirichlet coefficients, periodic of period 4.
    int coeff_L4[] = {0, 1, 0, -1, 0};

    // The gamma factor Gamma(gamma s + lambda) is Gamma(s/2+1/2)
    Double gamma_L4[] = {0, 0.5};

    Complex lambda_L4[] = {Double(0), Double(0.5)}; // the lambda
    Complex pole_L4[] = {Double(0)}; // no pole
    Complex residue_L4[] = {Double(0)}; // no residue


    // "L4" is the name of the L-function
    //  1 - what_type, 1 stands for periodic Dirichlet coefficients
    //  4 - N_terms, number of Dirichlet coefficients given
    //  coeff_L4  - array of Dirichlet coefficients
    //  4 - period (0 if coeffs are not periodic)
    //  sqrt(4/Pi) - the Q^s that appears in the functional equation
    //  1 - sign of the functional equation
    //  1 - number of gamma factors of the form Gamma(gamma s + lambda), gamma = .5 or 1
    //  gamma_L4  - array of gamma's (each gamma is .5 or 1)
    //  lambda_L4  - array of lambda's (given as complex numbers)
    //  0 - number of poles. Typically there won't be any poles.
    //  pole_L4 - array of poles, in this case none
    //  residue_L4 - array of residues, in this case none
    //
    //  Note: one could call the constructor without the last three
    //  arguments when number of poles is zero, as in:
    //
    //  L4 = L_function<int>("L4",1,4,coeff_L4,4,sqrt(4/Pi),1,1,gamma_L4,lambda_L4);
    //
    L4 = L_function<int>("L4", 1, 4, coeff_L4, 4, sqrt(4/Pi), Double(1), 1, gamma_L4, lambda_L4, 0, pole_L4, residue_L4);

    Complex coeff_L5[] = {Double(0),Double(1),I,-I,Double(-1),Double(0)};

    Complex gauss_sum = Double(0);
    for (int n=1; n <= 4; n++) {
      gauss_sum += coeff_L5[n]*exp(n*2*I*Pi/5);
    }


    // "L5" is the name of the L-function
    //  1 - what_type, 1 stands for periodic Dirichlet coefficients
    //  5 - N_terms, number of Dirichlet coefficients given
    //  coeff_L5  - array of Dirichlet coefficients
    //  5 - period (0 if coeffs are not periodic)
    //  sqrt(5/Pi), the Q^s that appears in the functional equation
    //  gauss_sum/sqrt(5) - omega of the functional equation
    //  1 - number of gamma factors of the form Gamma(gamma s + lambda), gamma = .5 or 1
    //  gamma_L4  - L5 has same gamma factor as L4
    //  lambda_L4  - ditto
    L5 = L_function<Complex>("L5",1,5,coeff_L5,5,sqrt(Double(5)/Pi),gauss_sum/(I*sqrt(Double(5))),1,gamma_L4,lambda_L4);


    x = zeta.initialize_gram(0.);
    for(int n=1; n <= 100; n++) {
        x=zeta.next_gram(x);
    }

    x = L5.initialize_gram(0.);
    for(int n=1; n <= 100; n++) {
        x=L5.next_gram(x);
    }

    cout << "Testing basic functions and constants..." << endl;

    actual_accuracy = compare_accuracy(lcalc_cos(Pi),-1);
    expected_accuracy = 13;
    cout << "  lcalc_cos(Pi) agrees with -1 to: "
	 << actual_accuracy
	 << " digits... ";
    result |= process_result(actual_accuracy, expected_accuracy);


    actual_accuracy = compare_accuracy(lcalc_cos(2)*lcalc_cos(2)+lcalc_sin(2)*lcalc_sin(2),1);
    expected_accuracy = 13;
    cout << "  lcalc_cos(2)^2 +lcalc_sin(2)^2 agrees with 1 to: "
	 << actual_accuracy
	 << " digits... ";
    result |= process_result(actual_accuracy, expected_accuracy);


    actual_accuracy = compare_accuracy(GAMMA(Double(2)),1);
    expected_accuracy = 13;
    cout << "  GAMMA(2) agrees with 1 to: "
	 << actual_accuracy
	 << " digits... ";
    result |= process_result(actual_accuracy, expected_accuracy);



    Double tmp = 0;
    for(int j=1; j <= 1000000; j++) {
      tmp += lcalc_cos(twoPi*j/1000000);
    }
    actual_accuracy = compare_to_zero(tmp);
    expected_accuracy = 9;
    cout << "  Sum of lcalc_cos(2*pi*j/1000000) agrees with 0 to: "
         << actual_accuracy
	 << " digits... ";
    result |= process_result(actual_accuracy, expected_accuracy);




    // Test the incomplete gamma function routines by comparing the
    // continued fraction with Temme's asymptotics near the transition
    // zone.
    Complex z,w;
    z=1+100*I; w = z+9;
    expected_accuracy = 13;
    actual_accuracy = compare_accuracy(cfrac_GAMMA(z,w) ,Q(z,w)*GAMMA(z,w));
    cout << "  cfrac_GAMMA(1+100*I, 10+100*I) agrees with temme to: "
	 << actual_accuracy
	 << " digits... ";
    result |= process_result(actual_accuracy, expected_accuracy);

    z=1+1000*I; w = z+9;
    expected_accuracy = 12;
    actual_accuracy = compare_accuracy(cfrac_GAMMA(z,w) ,Q(z,w)*GAMMA(z,w));
    cout << "  cfrac_GAMMA(1+1000*I, 10+1000*I) agrees with temme to: "
         << actual_accuracy
	 << " digits... ";
    result |= process_result(actual_accuracy, expected_accuracy);

    z=1+10000*I; w = z+9;
    expected_accuracy = 11;
    actual_accuracy = compare_accuracy(cfrac_GAMMA(z,w) ,Q(z,w)*GAMMA(z,w));
    cout << "  cfrac_GAMMA(1+10000*I, 10+10000*I) agrees with temme to: "
	 << actual_accuracy
	 << " digits... ";
    result |= process_result(actual_accuracy, expected_accuracy);

    z=1+100000*I; w = z+9;
    expected_accuracy = 10;
    actual_accuracy = compare_accuracy(cfrac_GAMMA(z,w) ,Q(z,w)*GAMMA(z,w));
    cout << "  cfrac_GAMMA(1+100000*I, 10+10000*I) agrees with temme to: "
	 << actual_accuracy
	 << " digits... ";
    result |= process_result(actual_accuracy, expected_accuracy);

    cout << "Testing Dirichlet L-functions..." << endl;
    expected_accuracy = 15;
    actual_accuracy = compare_accuracy(L4.value((Double)1),Pi/4);
    cout << "  L(1,chi_{-4})  agrees with Pi/4 to: "
         << actual_accuracy
	 <<  " digits... ";
    result |= process_result(actual_accuracy, expected_accuracy);


    x=str_to_Double("-1.46035450880958681288949915251529801246722933101258149054288608782553052947450062527641937546335681951449637467986952958389234371035889426181923283975");

#if HAVE_LIBPARI
    cout << "Testing an elliptic curve L-function..." << endl;

    char a1[2]; strcpy(a1,"0");
    char a2[2]; strcpy(a2,"0");
    char a3[2]; strcpy(a3,"0");
    char a4[2]; strcpy(a4,"4");
    char a6[2]; strcpy(a6,"0");

    // The last argument is to prevent pari from giving its interrupt
    // signal when its elliptic curve a_p algorithm is called and
    // interrupted with ctrl-c.
    pari_init_opts(400000000,2,INIT_DFTm);

    L_function<Double> L32(a1,a2,a3,a4,a6,10000);

    expected_accuracy = 13;

    // The formula given comes from computing the real period and applying bsd.
    // See Dave Husemoller, Elliptic Curves, Prop 6.2, page 185.
    actual_accuracy = compare_accuracy(L32.value(Double(1)/Double(2)),GAMMA(Double(1)/Double(2))*GAMMA(Double(1)/Double(4))/GAMMA(Double(3)/Double(4))/Double(8));

    cout << "  L_{32A}(1/2)  agrees with GAMMA(1/2)GAMMA(1/4)/GAMMA(3/4)/8 to: "
	 << actual_accuracy
	 <<  " digits... ";
    result |= process_result(actual_accuracy, expected_accuracy);
#endif

    cout << "Testing the zeta function..." << endl;
    x = str_to_Double("-1.46035450880958681288949915251529801246722933101258149054288608782553052947450062527641937546335681951449637467986952958389234371035889426181923283975");
    expected_accuracy = 13;
    actual_accuracy = compare_accuracy(zeta.value(Double(0.5)), x);
    cout << "  zeta.value(.5) agrees with Maple to: "
	 << actual_accuracy
	 << " digits... ";
    result |= process_result(actual_accuracy, expected_accuracy);

    Complex L1, L2;
    Double t=1;
    cout << "  Comparing gamma sum to Riemann sum for zeta(s)..." << endl;
    // As "s" and "t" vary, I see between 10 and 13 digits of agreement.
    expected_accuracy = 10;

    // Perform fewer loops if the computations are slower but more precise.
    Complex s_delta = Double(0.5);

#if PRECISION_DOUBLE_DOUBLE || PRECISION_MULTIPLE
    s_delta = 0.75;
#elif PRECISION_QUAD_DOUBLE
    s_delta = 1.0;
#endif

    do {
        s = -3.5 + I*t;
        do {
            s += s_delta;
            L1 = zeta.value(s,0,"pure","Gamma sum");
            L2 = zeta.value(s,0,"pure","Riemann sum");
            setprecision(10);
	    actual_accuracy = compare_accuracy(L1,L2);
            cout << "    s=" << s << " agrees to: "
		 << actual_accuracy
		 <<  " digits... ";
	    result |= process_result(actual_accuracy, expected_accuracy);

        } while(real(s)<3.5);
        t *= 10;
    } while(t < 1e3);


    do_blfi=false;
    cout << "  Comparing Riemann-Siegel to gamma sum for zeta(1/2 + i*t)..."
	 << endl;

    // These expected accuracies differ too much to just use the most
    // permissive one for all of the checks.
    int expected_accuracies[] = {10, 10, 9, 8, 6, 6, 4};
    for (int exponent = 0; exponent <= 6; exponent++) {
        t = std::pow(10,exponent+4);
        s = Complex(.5,t);
        L1 = zeta.value(s,0,"pure","Gamma sum");
        L2 = zeta.value(s,0,"pure");
	expected_accuracy = expected_accuracies[exponent];
	actual_accuracy = compare_accuracy(L1,L2);
        cout << "    t=" << t << " agrees to: "
	     << actual_accuracy
	     <<  " digits... ";
	result |= process_result(actual_accuracy, expected_accuracy);
    };



    t=1e6; Double t2;
    input_mean_spacing = 0.1;
    cout << "  Comparing Riemann-Siegel to band-limited interpolation for zeta(1/2 + i*t)..." << endl;

    // Perform fewer loops if the computations are slower but more precise.
    Double tmax = 1e10;
    int nodes = 10000;

#if PRECISION_DOUBLE_DOUBLE || PRECISION_MULTIPLE
    nodes = 1000;
    tmax = 1e9;
#elif PRECISION_QUAD_DOUBLE
    nodes = 100;
    tmax = 1e7;
#endif
    do {
        t2 = t;
        L1 = L2 = 0;
        do_blfi = false;
        for(int j=1; j <= nodes; j++){
            t2 += input_mean_spacing;
            L1 += zeta.value(Complex(.5,t2),0,"pure");
        }
        t2 = t;
        do_blfi=true;
        for(int j=1; j<= nodes; j++){
            t2 += input_mean_spacing;
            L2 += zeta.value(Complex(.5,t2),0,"pure");
        }
	actual_accuracy = compare_accuracy(L1,L2);
	expected_accuracy = 15; // verified up to t=1e11
        cout << "    (t,L1,L2)=(" << t << "," << L1 << "," << L2 << ") "
	     << "agrees to: "
	     << actual_accuracy
	     <<  " digits... ";
	result |= process_result(actual_accuracy, expected_accuracy);
        t*=10;
    } while(t < tmax); // anything more takes too long

    return result;
}
