/*

   Copyright (C) 2001,2002,2003,2004 Michael Rubinstein

   This file is part of the L-function package L.

   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
   of the License, 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.

   Check the License for details. You should have received a copy of it, along
   with the package; see the file 'COPYING'. If not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

*/

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



// ZETA FUNCTION
Complex Zeta(Complex s, const char *return_type) {

  Complex L_value;

  L_value= siegel(s);


  //this returns Zeta(s)
  if (!strcmp(return_type,"pure"))
      return L_value;

  //returns Zeta(s) rotated to be real on critical line
  else if (!strcmp(return_type,"rotated pure"))
      return L_value*exp(I*(imag(log_GAMMA(s/2)) - (imag(s)/2)*log(Pi)));

  return L_value;

}


// RIEMANN-SIEGEL ZETA EVALUATION
Complex siegel(Complex s) {

    Double theta, result = 0;
    Double errorTerms;
    Double tau,p, t;
    int N, n;

    Double z;


    tau = sqrt(abs(imag(s))/(2*Pi));
    N = Int(floor(tau));
    p = tau - N;

    t = imag(s);

    theta = imag(log_GAMMA(s/2)) - (imag(s)/2)*log(Pi);


    if (my_verbose>1) cout <<"Main sum is " << N << " terms long" << endl;
    two_inverse_sqrt(N); LOG(N); //call to extend two_inverse_SQUARE_ROOT and LG tables
                                 //ahead of time as far as needed. More efficient this way

    if(N>7000){ //parallelization only makes sense if the length is substantial enough to make up for overhead
        #pragma omp parallel for reduction(+: result)
        for (n = N; n >0; n--){
          #ifndef PRECISION_DOUBLE
              __builtin_prefetch(&two_inverse_SQUARE_ROOT[n-1],0,0);
              __builtin_prefetch(&LG[n-1],0,0);
          #endif
          result += two_inverse_SQUARE_ROOT[n]*lcalc_cos(theta-t*LG[n]);
        }
    }
    else{
        for (int n = N; n >0; n--){ //checking n>0 is faster than checking n<=N
          #ifndef PRECISION_DOUBLE
              __builtin_prefetch(&two_inverse_SQUARE_ROOT[n-1],0,0);
              __builtin_prefetch(&LG[n-1],0,0);
          #endif
          result += two_inverse_SQUARE_ROOT[n]*lcalc_cos(theta-t*LG[n]);
        }
    }


    z=p-.5;

    max_n=N;


    errorTerms=rs_remainder_terms(z,tau);
    errorTerms*=pow(tau,-Double(1)/2)*(1-2*((N-1)%2));

    result += errorTerms;


    // result is Z.  Now we rotate to get zeta
    return (result* exp(-I*theta));



}


Double rs_remainder_terms(Double z, Double tau){

    Double remainder=0;
    Double one_over_tau=1./tau,r=1.,C;


    Double zz[144];
    zz[0]=1;
    for(int n=1;n<=143;n++)
        zz[n]=zz[n-1]*z; //stores powers of z;

    int j=0,n;
    do{
        C=0.;
        int shift = j%2;

        /*
        Break this into 4 blocks and check size of terms after each block
        Compromise between checking size after each term and not doing any checking
        */
        for(n=0;n<=20;n++){
            C+=rs_remainder[j][n]*zz[2*n+shift];
        }
        if(my_norm(rs_remainder[j][n]*zz[2*n+shift])>tolerance_sqrd){
            for(n=21;n<=35;n++){
                C+=rs_remainder[j][n]*zz[2*n+shift];
            }
            if(my_norm(rs_remainder[j][n]*zz[2*n+shift])>tolerance_sqrd){
                for(n=36;n<=54;n++){
                    C+=rs_remainder[j][n]*zz[2*n+shift];
                }
                if(my_norm(rs_remainder[j][n]*zz[2*n+shift])>tolerance_sqrd){
                    for(n=55;n<=71;n++){
                        C+=rs_remainder[j][n]*zz[2*n+shift];
                    }
                }
            }
        }

        remainder+=C*r;

        r*=one_over_tau;
        j++;
    }while(r>tolerance&&j<=39);

    return remainder;
}

