/*
    Theseus - maximum likelihood superpositioning of macromolecular structures

    Copyright (C) 2004-2010 Douglas L. Theobald

    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.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the:

    Free Software Foundation, Inc.,
    59 Temple Place, Suite 330,
    Boston, MA  02111-1307  USA

    -/_|:|_|_\-
*/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#ifdef   __linux__
  #include <getopt.h>
#endif
#include <ctype.h>
#include <math.h>
#include <float.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <time.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_sort.h>
#include <gsl/gsl_statistics_double.h>

#include "distfit_local.h"

#define VERSION "0.90"
#define DISTNUM 23

/* array of function names */
char
dist_name[DISTNUM][32] = 
{
	"Normal",      "Laplace",   "EVD",        "Logistic",
	"Cauchy",      "Uniform",   "Weibull", 
	"Gamma",       "ChisqrGen",  "Lognormal",
	"Invgamma",    "Invgauss",  "Recinvgauss", "Pareto",
	"Exponential", "Invchisqr", "Chisqr",      "Chi",
	"Rayleigh",
	"Maxwell",     "Beta",      "BetaSym", "BetaPrime"     //"Binomial"
};

/* array of function pointers */
DistFit
distfit_func[DISTNUM] = 
{
	&normal_fit,    &laplace_fit,   &EVD_fit,         &logistic_fit,
	&cauchy_fit,    &uniform_fit,   &weibull_fit, 
	&gamma_fit,     &chisqrgen_fit,   &lognormal_fit,
	&invgamma_fit,  &invgauss_fit,  &recinvgauss_fit, &pareto_fit,
	&exp_fit,       &invchisqr_fit, &chisqr_fit,      &chi_fit,
	&rayleigh_fit,
	&maxwell_fit,   &beta_fit,      &betasym_fit,  &betaprime_fit   //&binomial_fit
};

/* array of function pointers */
DistDev
distdev_func[DISTNUM] = 
{
	&normal_dev,    &laplace_dev,   &EVD_dev,         &logistic_dev,
	&cauchy_dev,    &uniform_dev,   &weibull_dev,
	&gamma_dev,     &chisqrgen_dev, &lognormal_dev,
	&invgamma_dev,  &invgauss_dev,  &recinvgauss_dev, &pareto_dev,
	&exp_dev,       &invchisqr_dev, &chisqr_dev,      &chi_dev,
	&rayleigh_dev,
	&maxwell_dev,   &beta_dev,      &betasym_dev,     &betaprime_dev //&binomial_dev
};

DistPDF
distpdf_func[DISTNUM] = 
{
	&normal_pdf,    &laplace_pdf,   &EVD_pdf,         &logistic_pdf,
	&cauchy_pdf,    &uniform_pdf,   &weibull_pdf,
	&gamma_pdf,     &chisqrgen_pdf,   &lognormal_pdf,
	&invgamma_pdf,  &invgauss_pdf,  &recinvgauss_pdf, &pareto_pdf,
	&exp_pdf,       &invchisqr_pdf, &chisqr_pdf,      &chi_pdf,
	&rayleigh_pdf,
	&maxwell_pdf,   &beta_pdf,      &betasym_pdf,     &betaprime_pdf //&binomial_pdf
};

DistCDF
distcdf_func[DISTNUM] = 
{
	&normal_cdf,    &laplace_cdf,   &EVD_cdf,         &logistic_cdf,
	&cauchy_cdf,    &uniform_cdf,   &weibull_cdf,
	&gamma_cdf,     &chisqrgen_cdf,   &lognormal_cdf,
	&invgamma_cdf,  &invgauss_cdf,  &recinvgauss_cdf, &pareto_cdf,
	&exp_cdf,       &invchisqr_cdf, &chisqr_cdf,      &chi_cdf,
	&rayleigh_cdf,
	&maxwell_cdf,   &beta_cdf,      &betasym_cdf,     &betaprime_cdf //&binomial_cdf
};

DistSDF
distsdf_func[DISTNUM] = 
{
	&normal_sdf,    &laplace_sdf,   &EVD_sdf,         &logistic_sdf,
	&cauchy_sdf,    &uniform_sdf,   &weibull_sdf,
	&gamma_sdf,     &chisqrgen_sdf,   &lognormal_sdf,
	&invgamma_sdf,  &invgauss_sdf,  &recinvgauss_sdf, &pareto_sdf,
	&exp_sdf,       &invchisqr_sdf, &chisqr_sdf,      &chi_sdf,
	&rayleigh_sdf,
	&maxwell_sdf,   &beta_sdf,      &betasym_sdf,     &betaprime_sdf //&binomial_sdf
};

DistInt
distint_func[DISTNUM] = 
{
	&normal_int,    &laplace_int,   &EVD_int,         &logistic_int,
	&cauchy_int,    &uniform_int,   &weibull_int,
	&gamma_int,     &chisqrgen_int,   &lognormal_int,
	&invgamma_int,  &invgauss_int,  &recinvgauss_int, &pareto_int,
	&exp_int,       &invchisqr_int, &chisqr_int,      &chi_int,
	&rayleigh_int,
	&maxwell_int,   &beta_int,      &betasym_int,     &betaprime_int //&binomial_int
};

int
dist_pnum[DISTNUM] = 
{
	2, 2, 2, 2,
	2, 2, 2,
	2, 2, 2,
	2, 2, 2, 2,
	1, 1, 1, 1,
	1,
	1, 2, 1, 2 //2
};


int
dist_nonneg[DISTNUM] = 
{
	0, 0, 0, 0,
	0, 0, 1,
	1, 1, 0,
	0, 0, 0, 0,
	1, 0, 1, 1,
	1,
	1, 0, 0, 1 //1
};


int
dist_posdef[DISTNUM] = 
{
	0, 0, 0, 0,
	0, 0, 0,
	0, 0, 1,
	1, 1, 1, 1,
	0, 1, 0, 0,
	0,
	0, 0, 0, 0 //0
};

int
dist_beta[DISTNUM] = 
{
	0, 0, 0, 0,
	0, 0, 0,
	0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0,
	0, 1, 1, 0 //0
};

DFParams        DfParams;
DFParams       *dfparams = &DfParams;
DistStats     **diststats = NULL;
int distsort[DISTNUM];
double *pdfparams = NULL;
double *cdfparams = NULL;
double *sdfparams = NULL;
double *intparams = NULL;


double
FindMode(const double *x, const int len)
{
    int     i;
    int     i_beg, i_end, i_min, intsize;
    double  range, range_min, mode;


    i_min = i_beg = 0;
    i_end = len -1;
    range_min = 0.0;

    intsize = (i_end - i_beg) / 2.0;

    while (intsize > 1)
    {
        for (i = i_beg; i < i_end - intsize; ++i)
        {
            range = x[i + intsize] - x[i];

            if (i == i_beg)
            {
                i_min = i_beg;
                range_min = range;
            }
            else if (range < range_min)
            {
                i_min = i;
                range_min = range;
            }
        }

        i_beg = i_min;
        i_end = i_min + intsize;
        intsize = (i_end - i_beg) / 2.0;
    }

    if (intsize == 1)
        mode = (x[i_end] + x[i_beg]) / 2.0;
    else
        mode = x[i_beg];

    return(mode);
}


void
CalcDistStats(const double *array, const int len)
{
    double        *x = malloc(len * sizeof(double));
    double         pstats[10];

    memcpy(x, array, len * sizeof(double));

    gsl_sort(x, 1, len);
    pstats[0] = gsl_stats_mean(x, 1, len);
    pstats[1] = gsl_stats_sd_with_fixed_mean(x, 1, len, pstats[0]);
    pstats[2] = gsl_stats_skew_m_sd(x, 1, len, pstats[0], pstats[1]);
    pstats[3] = gsl_stats_kurtosis_m_sd(x, 1, len, pstats[0], pstats[1]);
    pstats[4] = gsl_stats_median_from_sorted_data(x, 1, len);
    pstats[5] = gsl_stats_quantile_from_sorted_data(x, 1, len, 0.025);
    pstats[6] = gsl_stats_quantile_from_sorted_data(x, 1, len, 0.975);
    pstats[7] = gsl_stats_quantile_from_sorted_data(x, 1, len, 0.05);
    pstats[8] = gsl_stats_quantile_from_sorted_data(x, 1, len, 0.95);
    pstats[9] = FindMode(x, len);
    
    printf("\nmode approximation: %g\n", pstats[9]);

    printf("\n       ave (+/-       SD)    skew    kurt     median ( 95%% credible interval ) ( 90%% credible interval )");
    printf("\n--------------------------------------------------------------------------------------------------------");

    printf("\n% 10.2f (+/- %8.2f) % 7.2f % 7.2f % 10.2f (% 10.2f - % 10.2f) (% 10.2f - % 10.2f)\n\n", 
           pstats[0], pstats[1], pstats[2], pstats[3], pstats[4], pstats[5], pstats[6], pstats[7], pstats[8]);

    free(x);
}


int
ParseCmdLine(int *argc, char **argv[])
{
    int             option;

    /* get the options */
    while ((option = getopt (*argc, *argv, "b:c:C:d:D:hI:m:n:op:P:s:S:t:vz")) != -1)
    {
        switch (option)
        {
            case 'b':
                dfparams->bootstrap = (int) strtol(optarg, NULL, 10);
                break;

            case 'c':
                dfparams->col = (int) strtol(optarg, NULL, 10) - 1;

                if (dfparams->col > 15)
                {
                    Usage();
                    exit(EXIT_FAILURE);
                }
                break;

            case 'C':
                cdfparams = calloc(3, sizeof(double));
                sscanf(optarg, "%lf:%lf:%lf", &cdfparams[0], &cdfparams[1], &cdfparams[2]);
                break;

            case 'd':
                if (isdigit(optarg[0]))
                {
                    dfparams->distindex = (int) strtol(optarg, NULL, 10);
                    if (dfparams->distindex < 0 || dfparams->distindex >= DISTNUM)
                    {
                        printf("\n  Bad -d \"%s\" \n", optarg);
                        Usage();
                        exit(EXIT_FAILURE);
                    }
                }
                else
                {
                    strtolower(optarg);

                    if      (strncmp(optarg, "normal", 4) == 0)
                        dfparams->distindex = 0;
                    else if (strncmp(optarg, "laplace", 4) == 0)
                        dfparams->distindex = 1;
                    else if (strncmp(optarg, "evd", 3) == 0)
                        dfparams->distindex = 2;
                    else if (strncmp(optarg, "logistic", 4) == 0)
                        dfparams->distindex = 3;
                    else if (strncmp(optarg, "cauchy", 4) == 0)
                        dfparams->distindex = 4;
                    else if (strncmp(optarg, "uniform", 4) == 0)
                        dfparams->distindex = 5;
                    else if (strncmp(optarg, "weibull", 4) == 0)
                        dfparams->distindex = 6;
                    else if (strncmp(optarg, "gamma", 4) == 0)
                        dfparams->distindex = 7;
                    else if (strncmp(optarg, "chisqrgen", 7) == 0)
                        dfparams->distindex = 8;
                    else if (strncmp(optarg, "lognormal", 4) == 0)
                        dfparams->distindex = 9;
                    else if (strncmp(optarg, "invgamma", 5) == 0)
                        dfparams->distindex = 10;
                    else if (strncmp(optarg, "invgauss", 5) == 0)
                        dfparams->distindex = 11;
                    else if (strncmp(optarg, "recinvgauss", 3) == 0)
                        dfparams->distindex = 12;
                    else if (strncmp(optarg, "pareto", 4) == 0)
                        dfparams->distindex = 13;
                    else if (strncmp(optarg, "exponential", 4) == 0)
                        dfparams->distindex = 14;
                    else if (strncmp(optarg, "invchisqr", 4) == 0)
                        dfparams->distindex = 15;
                    else if (strncmp(optarg, "chisqr", 4) == 0)
                        dfparams->distindex = 16;
                    else if (strncmp(optarg, "chi", 4) == 0)
                        dfparams->distindex = 17;
                    else if (strncmp(optarg, "rayleigh", 3) == 0)
                        dfparams->distindex = 18;
                    else if (strncmp(optarg, "maxwell", 3) == 0)
                        dfparams->distindex = 19;
                    else if (strncmp(optarg, "beta", 4) == 0)
                        dfparams->distindex = 20;
                    else if (strncmp(optarg, "betasym", 4) == 0)
                        dfparams->distindex = 21;
                    else if (strncmp(optarg, "betaprime", 4) == 0)
                        dfparams->distindex = 22;
                    else
                    {
                        printf("\n  Bad -d \"%s\" \n", optarg);
                        Usage();
                        exit(EXIT_FAILURE);
                    }
                }
                break;

            case 'D':
                dfparams->distdev = (int) strtol(optarg, NULL, 10);
                break;

            case 'h':
                dfparams->histo = 1;
                break;

            case 'I':
                intparams = calloc(4, sizeof(double));
                sscanf(optarg, "%lf:%lf:%lf:%lf", &intparams[0], &intparams[1], &intparams[2], &intparams[3]);
                break;

            case 'm':
                dfparams->mix = (int) strtol(optarg, NULL, 10);
                break;

            case 'n':
                dfparams->randnum = (int) strtol(optarg, NULL, 10);
                break;

            case 'p':
                sscanf(optarg, "%lf:%lf", &dfparams->p1, &dfparams->p2);
                break;

            case 'P':
                pdfparams = calloc(3, sizeof(double));
                sscanf(optarg, "%lf:%lf:%lf", &pdfparams[0], &pdfparams[1], &pdfparams[2]);
                break;

            case 's':
                dfparams->seed = (int) strtol(optarg, NULL, 10);
                break;

            case 'S':
                sdfparams = calloc(3, sizeof(double));
                sscanf(optarg, "%lf:%lf:%lf", &sdfparams[0], &sdfparams[1], &sdfparams[2]);
                break;

            case 't':
                dfparams->transform = (int) strtol(optarg, NULL, 10);

                if (dfparams->transform == 0)
                    dfparams->transform = 6;

                break;

            case 'v':
                Version();
                exit(EXIT_SUCCESS);
                break;

            case 'o':
                dfparams->fixones = 1;
                break;

            case 'z':
                dfparams->fixzeros = 1;
                break;

            default:
                fprintf(stderr, "\n Bad option '-%c' \n\n", optopt);
                Usage();
                exit(EXIT_FAILURE);
                break;
        }
    }

    *argv += optind; /* now argv is set with first arg = argv[0] */

    return(*argc - optind); /* number of nonoption args */
}


int
main(int argc, char *argv[])
{
    int             narguments, length, transformed;
    int             i;
    double         *array = NULL, *freq = NULL, *warray = NULL;
    double          ave, median, adev, mdev, sdev, var, skew, kurt, hrange, lrange;

    setrlimit(RLIMIT_CORE, 0);

    dfparams->distnum = 1;
    dfparams->isbeta = 1;
    dfparams->isneg = 0;
    dfparams->distindex = -1;
    dfparams->bootstrap = 1;
    dfparams->histo = 0;
    dfparams->transform = 1;
    dfparams->col = 0;
    dfparams->seed = -1;
    dfparams->p1 = 1.0;
    dfparams->p2 = 1.0;
    dfparams->distdev = -1;
    dfparams->randnum = 100;
    dfparams->mix = 0;
    dfparams->fixzeros = 0;
    dfparams->fixones = 0;

    //printf("\n###### %e\n", normal_sdf(5.0, 0.0, 1.0));

    if(argc == 1)
    {
        Usage();
        exit(EXIT_FAILURE);
    }

    narguments = ParseCmdLine(&argc, &argv);

    if (dfparams->seed == -1)
        dfparams->seed = (unsigned long) time(NULL);

    if (pdfparams != NULL)
    {
        if (dfparams->distindex == -1)
            dfparams->distindex = 0;

        printf("\n PDF %s(%f,%f,%f):  %e \n\n",
               dist_name[dfparams->distindex],
               pdfparams[0], pdfparams[1], pdfparams[2],
               distpdf_func[dfparams->distindex](pdfparams[0], pdfparams[1], pdfparams[2]));

        free(pdfparams);
        exit(EXIT_SUCCESS);
    }
    else if (cdfparams != NULL)
    {
        if (dfparams->distindex == -1)
            dfparams->distindex = 0;

        printf("\n CDF %s(%f,%f,%f):  %e \n\n",
               dist_name[dfparams->distindex],
               cdfparams[0], cdfparams[1], cdfparams[2],
               distcdf_func[dfparams->distindex](cdfparams[0], cdfparams[1], cdfparams[2]));

        free(cdfparams);
        exit(EXIT_SUCCESS);
    }
    else if (sdfparams != NULL)
    {
        if (dfparams->distindex == -1)
            dfparams->distindex = 0;

        printf("\n SDF %s(%f,%f,%f):  %e \n\n",
               dist_name[dfparams->distindex],
               sdfparams[0], sdfparams[1], sdfparams[2],
               distsdf_func[dfparams->distindex](sdfparams[0], sdfparams[1], sdfparams[2]));

        free(sdfparams);
        exit(EXIT_SUCCESS);
    }
    else if (intparams != NULL)
    {
        if (dfparams->distindex == -1)
            dfparams->distindex = 0;

        printf("\n Int %s(%f,%f,%f,%f):  %e \n\n",
               dist_name[dfparams->distindex],
               intparams[0], intparams[1], intparams[2], intparams[3],
               distint_func[dfparams->distindex](intparams[0], intparams[1], intparams[2], intparams[3]));

        free(intparams);
        exit(EXIT_SUCCESS);
    }
    else if (dfparams->distdev > -1)
    {
        PrintRandDist("tmpdist.txt", distdev_func[dfparams->distdev], dfparams->p1, dfparams->p2, dfparams->randnum, dfparams->seed);
        exit(EXIT_SUCCESS);
    }

    if (narguments == 1)
    {
        if (dfparams->histo == 0)
        {
            /* get data values from file */
            array = getvals(argv[0], &length, dfparams->col);
            dfparams->length = length;

            if (length < 4)
            {
                fprintf(stderr,
                        "\n You really can't fit anything with %d data points! \n",
                        length);
                Usage();
                exit(EXIT_FAILURE);
            }

            if (dfparams->fixzeros > 0)
                for (i = 0; i < length; ++i)
                    if (array[i] <= 0.0)
                        array[i] = DBL_EPSILON;

            if (dfparams->fixones > 0)
                for (i = 0; i < length; ++i)
                    if (array[i] == 1.0)
                        array[i] = 1.0 - DBL_EPSILON;

            warray = calloc((length+1), sizeof(double));

            for (i = 0; i < length; ++i)
                warray[i] = array[i];
        }
        else
        {
            /* data is arranged as a histogram */
            FILE                *listfile = NULL;
            int                  ch;

            listfile = fopen(argv[0], "r");
            if (listfile == NULL)
            {
                fprintf(stderr,
                        "\n ERROR69: cannot open first file \"%s\" \n",
                        argv[0]);
                exit(EXIT_FAILURE);
            }

            length = 0;
            while(1)
            {
                ch = getc(listfile);
        
                if (ch == EOF || ch == '\n')
                    ++length;
        
                if (ch == EOF)
                    break;
            }

            array =  calloc((length+1), sizeof(double));
            warray = calloc((length+1), sizeof(double));
            freq =   calloc((length+1), sizeof(double));
            length = gethistvals(listfile, array, freq, length);
            dfparams->length = length;

            for (i = 0; i < length; ++i)
                warray[i] = array[i];

            fclose(listfile);
        }
    }
    else
    {
        fprintf(stderr, "\n ERROR00: Distribution file unspecified \n\n");
        Usage();
        exit(EXIT_FAILURE);
    }

    if (dfparams->distindex > -1)
        dfparams->distnum = 1;
    else
        dfparams->distnum = DISTNUM;

    diststats = malloc((dfparams->distnum) * sizeof(DistStats *));
    for(i = 0; i < dfparams->distnum; ++i)
        diststats[i] = malloc(sizeof(DistStats));

    if (dfparams->distindex == -1)
    {
        for(i = 0; i < dfparams->distnum; ++i)
        {
            strcpy(diststats[i]->dist_name, dist_name[i]);
            diststats[i]->distfit_func = distfit_func[i];
        }
    }
    else
    {
        strcpy(diststats[0]->dist_name, dist_name[dfparams->distindex]);
        diststats[0]->distfit_func = distfit_func[dfparams->distindex];
    }

    //PrintRandDist("tmpdist.txt", distdev_func[dfparams->distdev], dfparams->p1, dfparams->p2, dfparams->randnum, dfparams->seed);

    moments(array, length, &ave, &median, &adev, &mdev, &sdev, &var, &skew, &kurt, &hrange, &lrange);

    printf("\n\nData      average      median     ave dev     med dev     std dev    variance    skewness    kurtosis\n");
    printf(  "----  ----------- ----------- ----------- ----------- ----------- ----------- ----------- -----------\n");
    printf("%-5d % 11.3e % 11.3e % 11.3e % 11.3e % 11.3e % 11.3e % 11.3e % 11.3e\n",
           length, ave, median, adev, mdev, sdev, var, skew, kurt);
    printf("\n         range \n");
    printf("-------------------------- \n");
    printf("% 11.3e to % 11.3e\n\n", lrange, hrange);

    CalcDistStats(array, length);

    if (dfparams->mix > 0)
    {
        double     *mean = malloc(dfparams->mix * sizeof(double));
        double     *var = malloc(dfparams->mix * sizeof(double));
        double     *mixp = malloc(dfparams->mix * sizeof(double));
        double      omni_logL, mix_chi2, logL;

        omni_logL = DistFitMix(normal_pdf, normal_fit_w, normal_init_mix_params,
                               array, length, dfparams->mix, mean, var, mixp);

        for(i = 0; i < dfparams->mix; ++i)
            printf("  \n Mix %3d:  mean: % 11.3e var: % 11.3e MixP: % 9.6f",
                   i+1, mean[i], var[i], mixp[i]);

        mix_chi2 = chi_sqr_adapt_mix(array, length, 0, &logL, mean, var,
                                     mixp, dfparams->mix,
                                     normal_pdf, normal_lnpdf, normal_int);

        printf("\nlogL:% 11.3f n:%5d AIC:% 11.3f chi^2:% 9.6f\n\n",
               omni_logL, length, omni_logL - 3 * dfparams->mix + 1, mix_chi2);

        free(mean);
        free(var);
        free(mixp);
    }

    /* do the bootstrap if so desired */
    if (dfparams->distnum == 1 && dfparams->bootstrap > 1)
    {
        BootFit(diststats, dfparams->distindex, array, length, dfparams);
    }
    else if(dfparams->distnum == 1)
    {
        SingleDistFit(distfit_func, dfparams->distindex, dist_name[dfparams->distindex], diststats[0], array, length, dfparams);
    }
    else
    {
        for (i = 0; i < dfparams->transform; ++i)
        {
            transformed = TransformData(i, array, warray, length, dfparams->distnum);

            if (transformed == 0)
                continue;

            TestData(array, length);

            if (dfparams->histo == 1)
            {
                MultiHistFit(array, freq, length);
            }
            else
            {
                /* fit the data to each distribution */
                MultiDistFit(distfit_func, dist_name, diststats, dfparams->distnum, array, length, dfparams);

                /* sort the results by AIC, best fits at top (most positive AIC) */
                InsortDiststats(diststats, dfparams->distnum);

                /* Calcualte Akaike weights and Bayesian posterior probabilities */
                CalcAkaike(diststats, dfparams->distnum);

                /* print out the final results */
                PrintDistStats(diststats, dfparams->distnum);
            }
        }
    }

    free(array);
    free(warray);
    if (dfparams->histo == 1)
        free(freq);
    for(i = 0; i < dfparams->distnum; ++i)
        free(diststats[i]);
    free(diststats);

    return (EXIT_SUCCESS);
}


void
TestData(const double *data, const int length)
{
    int             i;

	dfparams->isbeta = 1;
	dfparams->isneg = 0;
	dfparams->iszero = 0;
	for (i = 0; i < length; ++i)
	{
		if (data[i] > 1.0)
		{
			dfparams->isbeta = 0;
			break;
		}
	}

	for (i = 0; i < length; ++i)
	{
		if (data[i] == 0.0)
		{
			dfparams->isbeta = 0;
			dfparams->iszero = 1;
			break;
		}
	}

	for (i = 0; i < length; ++i)
	{
	    if (data[i] < 0.0)
		{
			dfparams->isbeta = 0;
			dfparams->isneg = 1;
			break;
		}
	}
}


int
TestDistVsData(const int ndist)
{
    if (dist_beta[ndist] == 1 && dfparams->isbeta == 0)
        return(0);
    else if (dist_posdef[ndist] == 1 && dfparams->isneg == 1)
        return(0);
    else if (dist_nonneg[ndist] == 1 && dfparams->isneg == 1)
        return(0);
    else if (dist_posdef[ndist] == 1 && dfparams->iszero == 1)
        return(0);
    else if (dist_nonneg[ndist] == 1 && dfparams->iszero == 1) /* should return 1, but there are problems fitting data with zeros */
        return(0);
    else
        return(1);
}


void
PrintRandDist(char *fname, double (*dist_dev)(double param1, double param2, const gsl_rng *r2),
              double param1, double param2, int ndata, const unsigned long seed)
{
    int                    i;
    double                 *array = NULL;
    char                   argstring[64];
    FILE                   *tmpfile;
    const gsl_rng_type     *T = NULL;
    gsl_rng                *r2 = NULL;

    gsl_rng_env_setup();
    gsl_rng_default_seed = seed;
    T = gsl_rng_ranlxs2;
    r2 = gsl_rng_alloc(T);

    array = malloc(ndata * sizeof(double));

    for (i = 0; i < ndata; ++i)
        array[i] = dist_dev(param1, param2, r2);
        /* array[i] = uniform_dev(0, 1) * sin(uniform_dev(0, 1)); */

    tmpfile = fopen(fname, "w");

    sprintf(&argstring[0], "%% .%de\n", DBL_DIG+1);

    for (i = 0; i < ndata; ++i)
        fprintf(tmpfile, argstring, array[i]);

    fflush(NULL);

    fclose(tmpfile);
    free(array);
    gsl_rng_free(r2);
}


int
TransformData(int transform, double *array, double *warray, int length, int distnum)
{
    int             j;

    switch(transform)
    {
        case 0:
            printf(" raw data:\n");

            for (j = 0; j < length; ++j)
                array[j] = warray[j];
            break;

        case 1:
            printf(" data^2:\n");

            for (j = 0; j < length; ++j)
                array[j] = mysquare(warray[j]);
            break;

        case 2:
            printf(" exp(data):\n");

            for (j = 0; j < length; ++j)
                array[j] = exp(warray[j]);
            break;

        case 3:
            printf(" cos(data):\n");

            for (j = 0; j < length; ++j)
                array[j] = cos(warray[j]);
            break;

        case 4:
            for (j = 0; j < length; ++j)
            {
                if (warray[j] <= 0.0)
                    return(0);
                else
                    array[j] = sqrt(warray[j]);
            }
            printf(" sqrt(data):\n");
            break;

        case 5:
            for (j = 0; j < length; ++j)
            {
                if (warray[j] <= 0.0)
                    return(0);
                else
                    array[j] = log(warray[j]);
            }

            printf(" log(data):\n");
            break;
    }

    fflush(NULL);

    return(1);
}


void
MultiHistFit(double *array, double *freq, int length)
{
    double          param1, param2, chisq, logL;

    chisq = gamma_histfit(array, freq, length, &param1, &param2, &logL);
    printf(" gamma         >>> %11.3e %11.3e %11.3e %11.5f \n", param1, param2, chisq, logL/length);
    fflush(NULL);

    chisq = weibull_histfit(array, freq, length, &param1, &param2, &logL);
    printf(" weibull       >>> %11.3e %11.3e %11.3e %11.5f \n", param1, param2, chisq, logL/length);
    fflush(NULL);

    chisq = exp_histfit(array, freq, length, &param1, &param2, &logL);
    param2 = 0.0;
    printf(" exp           >>> %11.3e %11.3e %11.3e %11.5f \n", param1, param2, chisq, logL/length);
    fflush(NULL);
}


void
BootFit(DistStats **diststats, int distindex, double *array, int length, DFParams *dfparams)
{
    int             i, j, k, m, bootstrap = dfparams->bootstrap;
    double         *jackarray = NULL, *p = NULL;
    unsigned int   *weights = NULL;
    double          param1, param2, chisq, logL, AIC, BIC;
    double          param1_2, param2_2, logL_2, AIC_2, BIC_2;
    const gsl_rng_type     *T = NULL;
    gsl_rng                *r2 = NULL;

    T = gsl_rng_ranlxs2;
    r2 = gsl_rng_alloc(T);

    weights = calloc(length, sizeof(unsigned int));
    p = calloc(length, sizeof(double));
    jackarray = calloc(length, sizeof(double));

    for (i = 0; i < length; ++i)
        p[i] = 1.0 / length;

    param1_2 = param2_2 = logL_2 = AIC_2 = BIC_2 = 0.0;
    for (i = 0; i < dfparams->bootstrap; ++i)
    {
        if (i > 0)
            //multinomial_eqdev(length, (1.0 / length), weights, length);
            gsl_ran_multinomial (r2, length, length, p, weights);

        j = k = 0;
        while(j < length && k < length)
        {
            for (m = 0; m < weights[k]; ++m, ++j)
                jackarray[j] = array[k];

            ++k;
        }

        param1 = param2 = chisq = logL = AIC = BIC = 0.0;
        chisq = diststats[0]->distfit_func(jackarray, length, &param1, &param2, &logL);

        if (j < 13)
        {
            AIC = logL - ((2.0 * length) / (length - 3.0));
            BIC = logL - 2.0 * log(length);
        }
        else
        {
            AIC = logL - (length / (length - 2.0));
            BIC = logL - log(length);
        }

        diststats[0]->param1 += param1;
        diststats[0]->param2 += param2;
        diststats[0]->chisq += chisq;
        diststats[0]->logL += logL;
        diststats[0]->logLper += (logL / (double) length);
        diststats[0]->AIC += AIC;
        diststats[0]->BIC += BIC;

        param1_2 += mysquare(param1);
        param2_2 += mysquare(param2);
        logL_2 += mysquare(logL);
        AIC_2 += mysquare(AIC);
        BIC_2 += mysquare(BIC);
    }

    /* method of moments determination of variances and std devs (1st and 2nd raw sample moments) */
    param1_2 = sqrt(param1_2/ (double) bootstrap - mysquare(diststats[0]->param1/ (double) bootstrap)) ;
    param2_2 = sqrt(param2_2 / (double) bootstrap - mysquare(diststats[0]->param2 / (double) bootstrap));
    logL_2 = sqrt(logL_2 / (double) bootstrap - mysquare(diststats[0]->logL / (double) bootstrap));
    AIC_2 = sqrt(AIC_2 / (double) bootstrap - mysquare(diststats[0]->AIC / (double) bootstrap));
    BIC_2 = sqrt(BIC_2 / (double) bootstrap - mysquare(diststats[0]->BIC / (double) bootstrap));

    diststats[0]->param1 /= (double) bootstrap;
    diststats[0]->param2 /= (double) bootstrap;
    diststats[0]->chisq /= (double) bootstrap;
    diststats[0]->logL /= (double) bootstrap;
    diststats[0]->logLper /= (double) bootstrap;
    diststats[0]->AIC /= (double) bootstrap;
    diststats[0]->BIC /= (double) bootstrap;

    printf(" seed = %lu \n", dfparams->seed);
    fflush(NULL);
    PrintDistStats(diststats, 1);

    diststats[0]->param1 = param1_2;
    diststats[0]->param2 = param2_2;
    diststats[0]->chisq = 0.0;
    diststats[0]->logL = logL_2;
    diststats[0]->logLper = logL_2 / (double) length;
    diststats[0]->AIC = AIC_2;
    diststats[0]->BIC = BIC_2;
    strcpy(diststats[0]->dist_name, "SD");

    PrintDistSD(diststats);

    free(weights);
    free(jackarray);
    free(p);
    gsl_rng_free(r2);
}


void
SingleDistFit(DistFit *distfit_func, int distindex, char *dist_name,
              DistStats *diststats, double *array, int length, DFParams *dfparams)
{
    double         param1, param2, chisq, logLper, logL, AIC, BIC;

	param1 = param2 = chisq = logLper = logL = AIC = BIC = 0.0;
	strcpy(diststats->dist_name, dist_name);

	if (TestDistVsData(distindex) == 0)
		return;

	chisq = distfit_func[distindex](array, length, &param1, &param2, &logL);
	logLper = logL / (double) length;

	if (chisq == -1.0)
		diststats->AIC = -DBL_MAX;

	if (dist_pnum[distindex] == 2)
	{
		AIC = logL - ((2.0 * length) / (length - 3.0));
		BIC = logL - 2.0 * log(length);
	}
	else if (dist_pnum[distindex] == 1)
	{
		AIC = logL - (length / (length - 2.0));
		BIC = logL - log(length);
	}

	if (fabs(chisq) > 1e6)
		chisq = DBL_MAX;

	if (logLper < -1e7)
		logLper = -DBL_MAX;

	if (logL < -1e11)
		logL = -DBL_MAX;

	if (AIC < -1e11)
		AIC = -DBL_MAX;

	if (BIC < -1e11)
		BIC = -DBL_MAX;

	diststats->param1 = param1;
	diststats->param2 = param2;
	diststats->chisq = chisq;
	diststats->logL = logL;
	diststats->logLper = logLper;
	diststats->AIC = AIC;
	diststats->BIC = BIC;
}


void
MultiDistFit(DistFit *distfit_func, char dist_name[][32],
             DistStats **diststats, int distnum, double *array, int length, DFParams *dfparams)
{
    int            j;
    double         param1, param2, chisq, logLper, logL, AIC, BIC;

    for (j = 0; j < distnum; ++j)
    {
        param1 = param2 = chisq = logLper = logL = AIC = BIC = 0.0;
        strcpy(diststats[j]->dist_name, dist_name[j]);

        if (TestDistVsData(j) == 0)
            continue;

/*         printf("\n Fitting %s ...\n", dist_name[j]); */
/*         fflush(NULL); */

        chisq = distfit_func[j](array, length, &param1, &param2, &logL);
        logLper = logL / (double) length;

        if (dist_pnum[j] == 2)
        {
            AIC = logL - ((2.0 * length) / (length - 3.0));
            BIC = logL - 2.0 * log(length);
        }
        else if (dist_pnum[j] == 1)
        {
            AIC = logL - (length / (length - 2.0));
            BIC = logL - log(length);
        }

        if (logLper < -1e7)
            logLper = -DBL_MAX;

        if (logL < -1e11)
            logL = -DBL_MAX;

        if (AIC < -1e11)
            AIC = -DBL_MAX;

        if (BIC < -1e11)
            BIC = -DBL_MAX;

        diststats[j]->param1 = param1;
        diststats[j]->param2 = param2;
        diststats[j]->chisq = chisq;
        diststats[j]->logL = logL;
        diststats[j]->logLper = logLper;
        diststats[j]->AIC = AIC;
        diststats[j]->BIC = BIC;

        if (!isfinite(diststats[j]->AIC))
            diststats[j]->AIC = -DBL_MAX;

        if (!isfinite(diststats[j]->BIC))
            diststats[j]->BIC = -DBL_MAX;

        if (!isfinite(diststats[j]->logL))
            diststats[j]->logL = -DBL_MAX;

        if (!isfinite(diststats[j]->logLper))
            diststats[j]->logLper = -DBL_MAX;

        if (!isfinite(diststats[j]->chisq))
            diststats[j]->chisq = DBL_MAX;

        if (chisq == -1.0)
            diststats[j]->AIC = -DBL_MAX;
    }
}


void
CalcAkaike(DistStats **diststats, int distnum)
{
    double         wtsum, largest;
    int            i;

    largest = -DBL_MAX;
    for(i = 0; i < distnum; ++i)
    {
        if (TestDistVsData(i) == 0)
            continue;

        if (largest < diststats[i]->AIC)
            largest = diststats[i]->AIC;
    }

    wtsum = 0.0;
    for(i = 0; i < distnum; ++i)
    {
        if (TestDistVsData(i) == 0)
            continue;

/* 
        if(diststats[i]->AIC == -DBL_MAX ||
           diststats[i]->chisq < 0.0)
            continue;
 */

        diststats[i]->akaikewt = exp(diststats[i]->AIC - largest);
        
        //printf("\nweight[%d]:%e %e %e", i, diststats[i]->akaikewt, largest, diststats[i]->AIC);
        
        if (!isfinite(diststats[i]->akaikewt))
            diststats[i]->akaikewt = 0.0;
        
        wtsum += diststats[i]->akaikewt;
    }

    for(i = 0; i < distnum; ++i)
    {
 
        if (TestDistVsData(i) == 0)
            continue;
/*
        if(diststats[i]->AIC == -DBL_MAX || diststats[i]->chisq < 0.0)
            diststats[i]->bayesprob = 0.0;
        else
 */

            diststats[i]->bayesprob = diststats[i]->akaikewt / wtsum;
    }
}


void
PrintDistStats(DistStats **diststats, int distnum)
{
    int            i, idist, tst;

    printf(" Distribution      param1      param2     chi^2     logL/num           logL            AIC            BIC  bayes \n");
    printf(" ------------ ----------- ----------- --------- ------------ -------------- -------------- -------------- ------ \n");
    fflush(NULL);

    for(idist = 0; idist < distnum; ++idist)
    {
        i = distsort[idist];

        tst = TestDistVsData(i);
        if (tst == 0)
            continue;

        if(diststats[i]->AIC == -DBL_MAX ||
           diststats[i]->chisq < 0.0)
            continue;

        if (fabs(diststats[i]->chisq) > 1e6)
            diststats[i]->chisq = DBL_MAX;

        if (diststats[i]->logLper < -1e7)
            diststats[i]->logLper = -DBL_MAX;

        if (diststats[i]->logL < -1e11)
            diststats[i]->logL = -DBL_MAX;

        if (diststats[i]->AIC < -1e11)
            diststats[i]->AIC = -DBL_MAX;

        if (fabs(diststats[i]->chisq) < 1e6)
        {
            printf(" %-12s %11.3e %11.3e %9.2f %12.3f %14.1f %14.1f %14.1f %6.3f\n",
                   diststats[i]->dist_name,
                   diststats[i]->param1, diststats[i]->param2,
                   diststats[i]->chisq,  diststats[i]->logLper,
                   diststats[i]->logL,   diststats[i]->AIC, diststats[i]->BIC,
                   diststats[i]->bayesprob);
        }
        else
        {
            printf(" %-12s %11.3e %11.3e %9.9s %12.3f %14.1f %14.1f %14.1f %6.3f\n",
                   diststats[i]->dist_name,
                   diststats[i]->param1, diststats[i]->param2,
                   "BIG",  diststats[i]->logLper,
                   diststats[i]->logL,   diststats[i]->AIC, diststats[i]->BIC,
                   diststats[i]->bayesprob);
        }

        fflush(NULL);
    }

    printf("\n");
    fflush(NULL);
}


void
PrintDistSD(DistStats **diststats)
{
    printf(" %-12s %11.3e %11.3e           %12.3f %14.1f %14.1f %14.1f ",
           diststats[0]->dist_name,
           diststats[0]->param1, diststats[0]->param2,
           diststats[0]->logLper, diststats[0]->logL,
           diststats[0]->AIC,   diststats[0]->BIC);
    fflush(NULL);

    printf("\n");
    fflush(NULL);
}


/* big to little, reverse of the usual sort */
void
InsortDiststats_old(DistStats **diststats, int distnum)
{
    int            i, j;
    DistStats       *temp;

    for (i = 0; i < distnum; ++i)
        distsort[i] = i;

    for (i = 1; i < distnum; ++i)
    {
        j = i;
        temp = diststats[j];

        while ((j > 0) && (diststats[j-1]->AIC < temp->AIC))
        {
            diststats[j] = diststats[j-1];
            j--;
        }

        diststats[j] = temp;
    }
}


void
InsortDiststats(DistStats **diststats, int distnum)
{
    int             i, j, tmpj;
    double          tempAIC;

    for (i = 0; i < distnum; ++i)
        distsort[i] = i;

    for (i = 1; i < distnum; ++i)
    {
        j = i;
        tempAIC = diststats[distsort[j]]->AIC;
        tmpj = distsort[j];

        while ((j > 0) && (diststats[distsort[j-1]]->AIC < tempAIC))
        {
            distsort[j] = distsort[j-1];
            j--;
        }

        distsort[j] = tmpj;
    }

/*     for (i = 0; i < distnum; ++i) */
/*         printf("\n %3d %3d %e", i, distsort[i], diststats[distsort[i]]->AIC); */
/*  */
/*     exit(0); */
}


void
printlist(double *list, int length)
{
    int            i;
    for (i=0; i < length; ++i)
        printf("\n %7.4e", list[i]);
    putchar('\n');
    fflush(NULL);
}


void
Usage(void)
{
    fputc('\n', stderr);
    fprintf(stderr, "distfit v. %s\n", VERSION);
    fprintf(stderr, "Maximum Likelihood fitting of statistical distributions to data \n\n");
    fprintf(stderr, "Usage: \n");
    fprintf(stderr, "distfit [-bcdDnpCIPSstv] <distfile> \n\n");
    fprintf(stderr, "distfile should contain values in columns separated by whitespace \n\n");
    fprintf(stderr, "  -b {# bootstrap replicates} -- # of bootstrap replicates \n");
    fprintf(stderr, "  -c {column #} -- which data column to read (max = 16) \n");
    fprintf(stderr, "  -d {distribution} -- to fit a specific distribution, use w/ -bCIPS \n");
    fprintf(stderr, "     {0}  Normal (def), (1)  Laplace,   (2)  EVD,      (3)  Logistic, \n");
    fprintf(stderr, "     (4)  Cauchy,       (5)  Uniform,   (6)  Weibull,  (7)  Gamma, \n");
    fprintf(stderr, "     (8)  ChiSqrGen,    (9)  Lognormal, (10) InvGamma, (11) InvGauss, \n");
    fprintf(stderr, "     (12) RecInvGauss,  (13) Pareto,    (14) Exp,      (15) InvChiSqr, \n");
    fprintf(stderr, "     (16) ChiSqr,       (17) Chi,       (18) Rayleigh  (19) Maxwell,  \n"); 
    fprintf(stderr, "     (20) Beta,         (21) BetaSymm,  (22) BetaPrime \n");
    fprintf(stderr, "  -D {distribution} -- distribution to simulate, use w/ -np \n");
    fprintf(stderr, "  -m {# of mixtures} -- fit a normal mixture distribution \n");
    fprintf(stderr, "  -n {# replicates} -- number of variates to simulate \n");
    fprintf(stderr, "  -p {p1:p2} -- two parameters for a specified distribution (ran # gen) \n");
    fprintf(stderr, "  -C {x:p1:p2} -- Calculate CDF for x and params p1 and p2 \n");
    fprintf(stderr, "     (e.g., loc and scale) for distribution specified with -d \n");
    fprintf(stderr, "  -I {x:y:p1:p2} -- Integrate PDF betwee x and y, params p1 and p2 \n");
    fprintf(stderr, "  -P {x:p1:p2} -- Calculate PDF for x and params p1 and p2 \n");
    fprintf(stderr, "  -S {x:p1:p2} -- Calculate SDF for x and params p1 and p2 \n");
    fprintf(stderr, "  -s {seed} -- specify a random seed, must be an integer \n");
    fprintf(stderr, "  -t -- do a series of simple data transformations and refit \n");
    fprintf(stderr, "  -z -- \"fix\" zeros by setting to DBL_EPSILON (%e) \n", DBL_EPSILON);
    fprintf(stderr, "  -o -- \"fix\" ones by setting to 1.0 - DBL_EPSILON \n");
    fprintf(stderr, "  -v -- version \n\n");
    fflush(NULL);
}


void
Version(void)
{
    fprintf(stderr,
            "\n  \'distfit\' version %s compiled on %s %s\n  by user %s with machine \"%s\" \n",
            VERSION, __DATE__, __TIME__, getenv("USER"), getenv("HOST"));
    fflush(NULL);
}


int
gethistvals(FILE *listfile, double *data, double *freq, int length)
{
    int            i;
    char           line[512];

    rewind(listfile);
    i = 0;
    while(fgets(line, 512, listfile) != NULL && i < length)
    {
        sscanf(line, "%le %le", &data[i], &freq[i]);
        ++i;
    }

    length = i;

    data[length] = freq[length] = DBL_MAX; /* sentinel required for quicksort */

/*     for (i = 0; i < length; ++i) */
/*         printf("\n%4d %lf %lf", i, data[i], freq[i]); */

    return(length);
}


double
*getvals(char *listfile_name, int *length, int column)
{
    int            i, numscanned, numvals;
    FILE          *listfile = NULL;
    double        *array = NULL;
    char           columns[16][512];
    char           line[512], *comment;
    int            ch;

    listfile = fopen(listfile_name, "r");
    if (listfile == NULL)
    {
        fprintf(stderr,
                "\n ERROR69: cannot open first file \"%s\" \n",
                listfile_name);
        exit(EXIT_FAILURE);
    }

    *length = 0;
    while(1)
    {
        ch = getc(listfile);

        if (ch == EOF || ch == '\n')
            ++(*length);

        if (ch == EOF)
            break;
    }

    array = calloc((*length + 1), sizeof(double));

    rewind(listfile);

    i = 0;
    while(fgets(line, 512, listfile) != NULL && i < *length)
    {
        if (line[0] == '#')
        {
            --(*length);
            continue;
        }

        comment = strchr(line, '#');

        if (comment != NULL)
            *comment = '\0';

/*         numscanned = sscanf(line, "%le %le %le %le %le %le %le %le %le %le %le %le %le %le %le %le", */
/*                             &columns[0], &columns[1], &columns[2],  &columns[3], */
/*                             &columns[4], &columns[5], &columns[6],  &columns[7], */
/*                             &columns[8], &columns[9], &columns[10], &columns[11], */
/*                             &columns[12], &columns[13], &columns[14], &columns[15]); */

        numscanned = sscanf(line, "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s",
                            &columns[0][0], &columns[1][0], &columns[2][0],  &columns[3][0],
                            &columns[4][0], &columns[5][0], &columns[6][0],  &columns[7][0],
                            &columns[8][0], &columns[9][0], &columns[10][0], &columns[11][0],
                            &columns[12][0], &columns[13][0], &columns[14][0], &columns[15][0]);

        numvals = sscanf(&columns[column][0], "%le", &array[i]);

        //array[i] = columns[column];
        /* printf("\n %d \n", numscanned); */

        if(numvals < 1 || !isfinite(array[i]) || array[i] == DBL_MAX || array[i] == -DBL_MAX || numscanned <= column)
        {
            --(*length);
            continue;
        }
        else
        {
            ++i;
        }
    }

    *length = i;

    array[*length] = DBL_MAX; /* sentinel required for quicksort */

    fclose(listfile);

    return(array);
}


/* The following functions are used for mixture model analysis */
static void
CalcProbs(DistPDF distpdf, const double *x, double **probs, const int n,
          const double *mixp, const int mixn, const double *mean,
          const double *var)
{
    int         i, j;
    double      sump;

    for (i = 0; i < n; ++i)
    {
        sump = 0.0;
        for (j = 0; j < mixn; ++j)
        {
            probs[j][i] = mixp[j] * distpdf(x[i], mean[j], var[j]);
            sump += probs[j][i];
        }

        for (j = 0; j < mixn; ++j)
            probs[j][i] /= sump;
    }

/*     for (i = 0; i < n; ++i) */
/*         printf("\n%3d %f", i, probs[0][i]); */
}


static void
CalcMixProbs(double **probs, const int n, double *mixp, const int mixn)
{
    int         i, j;

    for (j = 0; j < mixn; ++j)
    {
        mixp[j] = 0.0;
		for (i = 0; i < n; ++i)
			mixp[j] += probs[j][i];
			
        mixp[j] /= n;
    }
}


static void
InitMixProbs(double **probs, double *mixp, const int n, const int mixn)
{
    int         i, j;
    double      sum;

    for (i = 0; i < mixn; ++i)
    {
        probs[i] = malloc(n * sizeof(double));
        memsetd(probs[i], 1.0 / mixn, n);
        mixp[i] = 1.0 / mixn;
    }

    for (i = 0; i < n; ++i)
    {
        sum = 0.0;
        for (j = 0; j < mixn; ++j)
            sum += probs[j][i];

        for (j = 0; j < mixn; ++j)
            probs[j][i] /= sum;
    }
}


static double
CalcOmniLogL(DistPDF distpdf, const double *x, const int n, const int mixn,
             double *mean, double *var, double *mixp)
{
    int             i, j;
    double          omni_logL, sum;

    omni_logL = 0.0;
    for (i = 0; i < n; ++i)
    {
        sum = 0.0;
        for (j = 0; j < mixn; ++j)
            sum += mixp[j] * distpdf(x[i], mean[j], var[j]);

        omni_logL += log(sum);
    }

    return(omni_logL);
}


double
DistFitMix(DistPDF distpdf, DistFitW distfitw, InitMixParams distinit,
           const double *x, const int n, const int mixn,
           double *param1, double *param2, double *mixp)
{
    double          omni_logL, logL;
    double        **probs = malloc(mixn * sizeof(double *));
    double         *oldprobs = malloc(n * sizeof(double));
    int             i, j, maxiters = 50000;
    double          tol = 1e-9;

    InitMixProbs(probs, mixp, n, mixn);
    distinit(x, n, mixn, param1, param2);

    for (i = 0; i < maxiters; ++i)
    {
        memcpy(oldprobs, probs[0], n * sizeof(double));

        for (j = 0; j < mixn; ++j)
            printf("%5d:%-2d % f % f % f\n", i, j, param1[j], param2[j], mixp[j]);

		CalcProbs(distpdf, x, probs, n, mixp, mixn, param1, param2);
		CalcMixProbs(probs, n, mixp, mixn);

        for (j = 0; j < mixn; ++j)
            distfitw(x, n, probs[j], &param1[j], &param2[j], &logL);

        if (VecEq(oldprobs, probs[0], n, tol) == 1 && i > 4)
            break;
    }

    for (i = 0; i < n; ++i)
    {
        printf("\n## %3d", i+1);
        for (j = 0; j < mixn; ++j)
            printf(" % f", probs[j][i]);
        printf("   % f", x[i]);
    }

    putchar('\n');

    omni_logL = CalcOmniLogL(distpdf, x, n, mixn, param1, param2, mixp);

    for (i = 0; i < mixn; ++i)
        free(probs[i]);
    free(probs);
    free(oldprobs);

    return(omni_logL);
}
