#ifndef UTILS_NUMERICAL_H
#define UTILS_NUMERICAL_H

#include "Utils.h"
#include <vector>
#include <cmath>
using namespace std;

// someuseful definitions
const double MAX_DOUBLE_VAL = 1.0e100;
const double MAX_NEG_DOUBLE_VAL = -1.0*MAX_DOUBLE_VAL;
const double MIN_POS_VAL = 1.0e-40;

// Some matrix utilities

//template<class T> 
//T MatrixPermanent(const vector<T>& A, int n); // expects n by n matrix encoded as vector 
inline int* dec2binarr(long n, int dim)
{
    // note: res[dim] will save the sum res[0]+...+res[dim-1]
    int* res = (int*)calloc(dim + 1, sizeof(int));   
    int pos = dim - 1;

    // note: this will crash if dim < log_2(n)...
    while (n > 0)
    {
        res[pos] = n % 2;
        res[dim] += res[pos];
        n = n / 2; // integer division        
        pos--;
    }

    return res;
}

template<class T> 
T MatrixPermanent(const vector<T>& A, int n)
{
//cout << "MatrixPermanent: n = " << n << endl;
	// expects n by n matrix encoded as vector 
    T sum = 0;
    T rowsumprod, rowsum;
    //int* chi = new int[n + 1];    
	int *chi;
    double C = (double)pow((double)2, n); 

    // loop all 2^n submatrices of A
    for (int k = 1; k < C; k++)
    {
//cout << "k = " << k << endl;
        rowsumprod = 1;
        chi = dec2binarr(k, n); // characteristic vector        

        // loop columns of submatrix #k
        for (int m = 0; m < n; m++)
        {
//cout << "m = " << m << endl;
            rowsum = 0;

            // loop rows and compute rowsum
            for (int p = 0; p < n; p++)
			{
//cout << "p = " << p << endl;
				YW_ASSERT_INFO( m*n+p <(int)A.size(), "array out of bound" );
                rowsum += chi[p] * A[m * n + p];
			}
            // update product of rowsums
            rowsumprod *= rowsum;    
        
            // (optional -- use for sparse matrices)
            // if (rowsumprod == 0) break;    
        }        
        
        sum += (T)pow((double)-1, n - chi[n]) * rowsumprod;        
		free(chi);
    }    

	//delete [] chi;

    return sum;
}

// compute the product
template<class T>
T CalcProductOfVec(const vector<T>& A)
{
    YW_ASSERT_INFO(A.size() > 0, "Must have at least one item");
    T res = A[0];
    for( int i=1; i<(int)A.size(); ++i )
    {
        res *= A[i];
    }
    return res;
}

// estimate normal distribution: mean and stdev
template<class T>
void EstMeanStdDev(const vector<T>& A, double &valMean, double &valStd)
{
    YW_ASSERT_INFO(A.size() > 0, "Must have at least one item");
    double sumAll = A[0];
    for( int i=1; i<(int)A.size(); ++i )
    {
        sumAll += A[i];
    }
    valMean = sumAll/A.size();
    // calculate std err
    double svalDiff = 0.0;
    for( int i=1; i<(int)A.size(); ++i )
    {
        double diff = A[i] - valMean;
        svalDiff += diff*diff;
    }
    valStd = sqrt( svalDiff/A.size() );
}

double ConvToZScore(double val, double valMean, double valStd);


// useful algorithms like Brent's method
class NumericalAlgoUtils
{
public:
    virtual ~NumericalAlgoUtils() {}
	virtual double EvaluateAt(double pt, void *pParam) = 0;
	double Func1DMinBrent( double ax, double bx, double cx, double tol, double *xmin );
	virtual bool IsSignificantlyLarge(double v1, double v2) const;
    static bool IsLikeliSignificantlyLargeThresNum(double valLikeli1, double valLikeli2, int numItems, double thres);
};

// statistics related
double CalcApproxCDFStdNormal(double val);
double CalcBinomialProb(double p, int n, int k);

// other numerical related methods
double RoundDoubleValTo(double val, int numFractionDigits);
int GetCeilingPowerOf(int val, int base);
int RoundToInt(double val);
bool IsConvergedWithin(double valCurr, double valPre, double maxDiffFrac);
void NormalizeVec(vector<double> &vecDoubles);
void NormalizeVecByMax(vector<double> &vecDoubles);
double CalcSumOfSquareError( const vector<double> &vecDoubles1, const vector<double> &vecDoubles2 );
double CalcFactorial(int n);
double CalcNumNChooseKDouble(int n, int k);

// segment a list of numbers into some segments s.t. the sum of squares from segment mean is minimized
class UtilsSegmentNumbers
{
public:
    UtilsSegmentNumbers(const std::vector<double> &listInputNumsIn);
    void SetThresRedPct(double pct) { thresReducedErrorPct = pct; }
    void FindBestSegments( std::vector<std::pair<int,int> > &listSegs) const;
    
private:
    void Init();
    double FindBestSegsFor(int numSegs, std::vector<std::pair<int,int> > &listSegs) const;
    double GetSegSum(int i, int j) const;       // i <= j, [i,j] is a segment
    int GetSize() const { return listInputNums.size(); }
    
    const std::vector<double> &listInputNums;
    std::vector<double> listRunningSums;
    double thresReducedErrorPct;
};

#endif
