/*  trinculo, Flexible tools for multinomial association
    Copyright (C) 2014  Luke Jostins

    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 3 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.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.*/

#include <iostream>
#include <math.h>
#include <stdlib.h>

using namespace std;

// These functions calculate the CDF of the chi-sq distribution using regularized (incomplete) gamma functions

double partialgamma1(double x, double a, double lgamma, double relTol = 1e-6){
	//regularized gamma function P(a,x)
  double c, d, k;
  c = 1/a;
  d = c;
  k = a;
  while (fabs(c)/fabs(d) > relTol){
    k += 1;
    c *= x/k;
    d += c;
  }
  return(d*exp(-x + a*log(x) - lgamma));
}

double partialgamma2(double x, double a, double lgamma, double relTol = 1e-6){
	//regularized gamma function Q(x,a) = 1 - P(a,x)

double tiny = 1e-30;

  double a_j, b_j, C_j, D_j, f, f_change;
  int j;

  j = 1;  
  C_j = 1/tiny;
  b_j = x + 1 - a;
  D_j = 1/b_j;
  f = D_j;
  f_change = 0;

  while (fabs(1 - f_change) > relTol){
	  b_j += 2;
    a_j = -j*(j - a);
    C_j = b_j + a_j/C_j;
    D_j = b_j + a_j*D_j;
    if (C_j < tiny) C_j = tiny;
    if (D_j < tiny) D_j = tiny;
    D_j = 1/D_j;
    f_change = C_j*D_j;
    f *= f_change;
    j++;
  }

  return(f*exp(-x + a*log(x) - lgamma));
}

double pchisq(double x, int df){
	//calculate the CDF of the chisq distribution, using regularized gamma functons
  double gamma;
  
  gamma = lgamma( 0.5*((double)df) );
  if (x < df + 1) return(1 - partialgamma1(0.5*x,0.5*((double) df),gamma));
  else return(partialgamma2(0.5*x,0.5*((double) df),gamma));
}
