/*
 * TreeWorkingPriorParsers.java
 *
 * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
 *
 * This file is part of BEAST.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership and licensing.
 *
 * BEAST is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 *  BEAST 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with BEAST; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

package dr.evomodelxml;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;

import jebl.math.Binomial;

import org.apache.commons.math.MathException;
import org.apache.commons.math.analysis.interpolation.LoessInterpolator;

import dr.evolution.io.Importer.ImportException;
import dr.evolution.io.TreeTrace;
import dr.evomodel.coalescent.CoalescentConstantLikelihood;
import dr.evomodel.coalescent.ExponentialProductLikelihood;
import dr.evomodel.coalescent.ExponentialProductPosteriorMeansLikelihood;
import dr.evomodel.coalescent.ExponentialProductSufficientStatisticsLikelihood;
import dr.evomodel.coalescent.GammaProductLikelihood;
import dr.evomodel.tree.ConditionalCladeFrequency;
import dr.evomodel.tree.TreeModel;
import dr.inference.distribution.ConditionalCladeProbability;
import dr.inference.distribution.MultivariateDistributionLikelihood;
import dr.inference.model.ConstantLikelihood;
import dr.inference.model.Likelihood;
import dr.inference.model.Statistic;
import dr.inference.trace.LogFileTraces;
import dr.inference.trace.TraceException;
import dr.math.distributions.MultivariateGammaDistribution;
import dr.math.functionEval.GammaFunction;
import dr.util.FileHelpers;
import dr.xml.AbstractXMLObjectParser;
import dr.xml.AttributeRule;
import dr.xml.ElementRule;
import dr.xml.XMLObject;
import dr.xml.XMLObjectParser;
import dr.xml.XMLParseException;
import dr.xml.XMLSyntaxRule;

/**
 * @author Guy Baele
 * @author Marc A. Suchard
 */
public class TreeWorkingPriorParsers {

    public final static boolean DEBUG = true;

    public static final String CONSTANT_TREE_TOPOLOGY_PRIOR = "constantTreeTopologyPrior";
    public static final String CONTEMPORANEOUS_COALESCENT_CONSTANT = "contemporaneousCoalescentConstantLikelihood";
    public static final String COALESCENT_CONSTANT_LIKELIHOOD = "coalescentConstantLikelihood";
    public static final String CONDITIONAL_CLADE_REFERENCE_PRIOR = "conditionalCladeProbability";
    public static final String COALESCENT_HEIGHTS_REFERENCE_PRIOR = "coalescentHeightsReferencePrior";
    public static final String PRODUCT_OF_EXPONENTIALS = "productOfExponentials";
    public static final String PRODUCT_OF_EXPONENTIALS_POSTERIOR_MEANS = "productOfExponentialsPosteriorMeans";
    public static final String PRODUCT_OF_EXPONENTIALS_POSTERIOR_MEANS_LOESS = "productOfExponentialsPosteriorMeansLoess";
    public static final String PRODUCT_OF_EXPONENTIALS_SUFFICIENT_STATISTICS = "productOfExponentialsSufficientStatistics";
    public static final String PRODUCT_OF_GAMMAS = "productOfGammas";
    public static final String CONSTANT_DECREASED_VARIANCE_PRIOR = "constantDecreasedVariancePrior";
    public final static String BURNIN = "burnin";
    public final static String EPSILON = "epsilon";
    public static final String PARAMETER_COLUMN = "parameterColumn";

    public static final double epsilon = 0.1;

    /**
     * 
     */
    public static XMLObjectParser COALESCENT_CONSTANT_LIKELIHOOD_PARSER = new AbstractXMLObjectParser () {

        public String getParserName() {
            return COALESCENT_CONSTANT_LIKELIHOOD;
        }

        public Object parseXMLObject(XMLObject xo) throws XMLParseException {

            //only use as test class, will require separate likelihood function
            /*Tree tree = (Tree) xo.getChild(Tree.class);
    		TreeIntervals intervals = new TreeIntervals(tree);
    		final int nIntervals = intervals.getIntervalCount();
    		System.err.println("Interval count: " + nIntervals);
    		double logPDF = 0.0;
            for (int i = 0; i < nIntervals; i++) {
            	System.err.println("Lineage count " + i + ": " + intervals.getLineageCount(i));
            	if (intervals.getLineageCount(i) > 2) {
            		logPDF += Math.log(Binomial.choose2(intervals.getLineageCount(i)));
            	}
            }
            System.err.println("logPDF = " + logPDF);
            return new ConstantLikelihood(-logPDF);*/

            TreeModel tree = (TreeModel) xo.getChild(TreeModel.class);
            //System.err.println(tree);
            return new CoalescentConstantLikelihood(tree);

        }

        public XMLSyntaxRule[] getSyntaxRules() {
            return rules;
        }

        private final XMLSyntaxRule[] rules = {
                new ElementRule(TreeModel.class)
        };

        public String getParserDescription() {
            return "Calculates the number of possible combinations of coalescent events.";
        }

        public Class getReturnType() {
            return Likelihood.class;
        }

    };

    /**
     * 
     */
    public static XMLObjectParser CONTEMPORANEOUS_COALESCENT_CONSTANT_PARSER = new AbstractXMLObjectParser () {

        public String getParserName() {
            return CONTEMPORANEOUS_COALESCENT_CONSTANT;
        }

        public Object parseXMLObject(XMLObject xo) throws XMLParseException {

            TreeModel treeModel = (TreeModel) xo.getChild(TreeModel.class);
            final int nTaxa = treeModel.getExternalNodeCount();
            double logPDF = 0.0;
            for (int i = nTaxa; i > 2; --i) {
                logPDF += Math.log(Binomial.choose2(i));
            }
            return new ConstantLikelihood(-logPDF);

        }

        public XMLSyntaxRule[] getSyntaxRules() {
            return rules;
        }

        private final XMLSyntaxRule[] rules = {
                new ElementRule(TreeModel.class)
        };

        public String getParserDescription() {
            return "Calculates the number of possible combinations of coalescent events.";
        }

        public Class getReturnType() {
            return Likelihood.class;
        }

    };

    /**
     * Parser that aims to mimic the behaviour of the GMRF Skyride for fixed log population sizes.
     */
    public static XMLObjectParser PRODUCT_OF_EXPONENTIALS_PARSER = new AbstractXMLObjectParser() {

        public String getParserName() {
            return PRODUCT_OF_EXPONENTIALS;
        }

        public Object parseXMLObject(XMLObject xo) throws XMLParseException {

            double logPopSize = xo.getDoubleAttribute("logPopSize");
            TreeModel treeModel = (TreeModel) xo.getChild(TreeModel.class);

            return new ExponentialProductLikelihood(treeModel, logPopSize);

        }

        public XMLSyntaxRule[] getSyntaxRules() {
            return rules;
        }

        private final XMLSyntaxRule[] rules = {
                AttributeRule.newDoubleRule("logPopSize"),
                new ElementRule(TreeModel.class)
        };

        public String getParserDescription() {
            return "Calculates a product of exponentials based on a (set of) fixed log population sizes.";
        }

        public Class getReturnType() {
            return Likelihood.class;
        }

    };

    /**
     * Parser that aims to use the posterior means of the time between coalescent events.
     */
    public static XMLObjectParser CONSTANT_DECREASED_VARIANCE_PRIOR_PARSER = new AbstractXMLObjectParser() {

        public String getParserName() {
            return CONSTANT_DECREASED_VARIANCE_PRIOR;
        }

        public Object parseXMLObject(XMLObject xo) throws XMLParseException {

            TreeModel treeModel = (TreeModel) xo.getChild(TreeModel.class);
            String fileName = xo.getStringAttribute(FileHelpers.FILE_NAME);

            try {

                File file = new File(fileName);
                String parent = file.getParent();

                if (!file.isAbsolute()) {
                    parent = System.getProperty("user.dir");
                }
                file = new File(parent, fileName);
                fileName = file.getAbsolutePath();

                String parameterName = xo.getStringAttribute(PARAMETER_COLUMN);
                int dimension = xo.getIntegerAttribute("dimension");

                LogFileTraces traces = new LogFileTraces(fileName, file);
                traces.loadTraces();
                long maxState = traces.getMaxState();

                // leaving the burnin attribute off will result in 10% being used
                long burnin = xo.getAttribute("burnin", maxState / 10);
                if (burnin < 0 || burnin >= maxState) {
                    burnin = maxState / 10;
                    System.out.println("WARNING: Burn-in larger than total number of states - using 10%");
                }
                traces.setBurnIn(burnin);

                int traceIndexParameter = -1;

                System.out.println("Looking for the following column:" + parameterName);
                for (int i = 0; i < traces.getTraceCount(); i++) {
                    String traceName = traces.getTraceName(i);
                    if (traceName.trim().equals(parameterName)) {
                        traceIndexParameter = i;
                        break;
                    }
                }
                System.out.println("Overview of traceIndexParameter:");
                if (traceIndexParameter == -1) {
                    throw new XMLParseException("Not all traces could be linked to the required columns.");
                } else {
                    System.out.println("  traceIndexParameter: " + traceIndexParameter);
                }

                Double[] parameterSamples = new Double[traces.getStateCount()];
                traces.getValues(traceIndexParameter).toArray(parameterSamples);

                //not necessary to work with flags, assume that we are using coalescentEventsStatistic
                double posteriorMean = 0.0;
                for (int i = 0; i < parameterSamples.length; i++) {
                    posteriorMean += parameterSamples[i];
                }
                posteriorMean /= ((double)parameterSamples.length);
                System.err.println("Variable column -> " + posteriorMean);
                //posteriorMean = Math.log(posteriorMean);
                //System.err.println("Log transformed: " + posteriorMean);

                //return new ExponentialProductPosteriorMeansLikelihood(treeModel, posteriorMeans);
                return new GammaProductLikelihood(treeModel, posteriorMean, dimension);

            } catch (FileNotFoundException fnfe) {
                throw new XMLParseException("File '" + fileName + "' can not be opened for " + getParserName() + " element.");
            } catch (java.io.IOException ioe) {
                throw new XMLParseException(ioe.getMessage());
            } catch (TraceException e) {
                throw new XMLParseException(e.getMessage());
            }

        }

        public XMLSyntaxRule[] getSyntaxRules() {
            return rules;
        }

        private final XMLSyntaxRule[] rules = {
                AttributeRule.newStringRule("fileName"),
                AttributeRule.newStringRule("parameterColumn"),
                AttributeRule.newIntegerRule("dimension"),
                new ElementRule(TreeModel.class)
        };

        public String getParserDescription() {
            return "Calculates a product of exponentials based on a set of posterior sample means.";
        }

        public Class getReturnType() {
            return Likelihood.class;
        }

    }; 

    /**
     * Parser that aims to use the posterior means of the time between coalescent events.
     */
    public static XMLObjectParser PRODUCT_OF_EXPONENTIALS_POSTERIOR_MEANS_PARSER = new AbstractXMLObjectParser() {

        public String getParserName() {
            return PRODUCT_OF_EXPONENTIALS_POSTERIOR_MEANS;
        }

        public Object parseXMLObject(XMLObject xo) throws XMLParseException {

            TreeModel treeModel = (TreeModel) xo.getChild(TreeModel.class);
            String fileName = xo.getStringAttribute(FileHelpers.FILE_NAME);

            try {

                File file = new File(fileName);
                String parent = file.getParent();

                if (!file.isAbsolute()) {
                    parent = System.getProperty("user.dir");
                }
                file = new File(parent, fileName);
                fileName = file.getAbsolutePath();

                String parameterName = xo.getStringAttribute(PARAMETER_COLUMN);
                int dimension = xo.getIntegerAttribute("dimension");

                LogFileTraces traces = new LogFileTraces(fileName, file);
                traces.loadTraces();
                long maxState = traces.getMaxState();

                // leaving the burnin attribute off will result in 10% being used
                long burnin = xo.getAttribute("burnin", maxState / 10);
                if (burnin < 0 || burnin >= maxState) {
                    burnin = maxState / 10;
                    System.out.println("WARNING: Burn-in larger than total number of states - using 10%");
                }
                traces.setBurnIn(burnin);

                int[] traceIndexParameter = new int[dimension];
                for (int i = 0; i < traceIndexParameter.length; i++) {
                    traceIndexParameter[i] = -1;
                }

                String[] columnNames = new String[dimension];
                for (int i = 1; i <= columnNames.length; i++) {
                    columnNames[i-1] = parameterName + i;
                }
                System.out.println("Looking for the following columns:");
                for (int i = 0; i < columnNames.length; i++) {
                    System.out.println("  " + columnNames[i]);
                }
                for (int i = 0; i < traces.getTraceCount(); i++) {
                    String traceName = traces.getTraceName(i);
                    for (int j = 0; j < columnNames.length; j++) {
                        if (traceName.trim().equals(columnNames[j])) {
                            traceIndexParameter[j] = i;
                            break;
                        }
                    }
                }
                System.out.println("Overview of traceIndexParameter:");
                for (int i = 0; i < traceIndexParameter.length; i++) {
                    if (traceIndexParameter[i] == -1) {
                        throw new XMLParseException("Not all traces could be linked to the required columns, problem with trace index: " + i + "; traceIndexParameter.length = " + traceIndexParameter.length);
                    }
                    System.out.println("  traceIndexParameter[" + i + "]: " + traceIndexParameter[i] );
                }

                Double[][] parameterSamples = new Double[dimension][traces.getStateCount()];
                for (int i = 0; i < dimension; i++) {
                    traces.getValues(traceIndexParameter[i]).toArray(parameterSamples[i]);
                }

                //not necessary to work with flags, assume that we are using coalescentEventsStatistic
                double[] posteriorMeans = new double[dimension];

                for (int i = 0; i < posteriorMeans.length; i++) {
                    //variable column
                    double mean = 0.0;
                    for (int j = 0; j < parameterSamples[i].length; j++) {
                        mean += parameterSamples[i][j];
                    }
                    mean /= ((double)parameterSamples[i].length);

                    //mean = 1.0/mean;

                    System.err.println("Variable column: " + i + " -> " + mean);
                    posteriorMeans[i] = Math.log(mean);
                }

                //Test: hard coding posterior means
                /*posteriorMeans[0] = 3.667;
				posteriorMeans[1] = 3.245;
				posteriorMeans[2] = 3.196;
				posteriorMeans[3] = 3.167;
				posteriorMeans[4] = 3.17;
				posteriorMeans[5] = 3.126;
				posteriorMeans[6] = 3.422;
				posteriorMeans[7] = 3.412;
				posteriorMeans[8] = 3.058;*/

                return new ExponentialProductPosteriorMeansLikelihood(treeModel, posteriorMeans);

            } catch (FileNotFoundException fnfe) {
                throw new XMLParseException("File '" + fileName + "' can not be opened for " + getParserName() + " element.");
            } catch (java.io.IOException ioe) {
                throw new XMLParseException(ioe.getMessage());
            } catch (TraceException e) {
                throw new XMLParseException(e.getMessage());
            }

        }

        public XMLSyntaxRule[] getSyntaxRules() {
            return rules;
        }

        private final XMLSyntaxRule[] rules = {
                AttributeRule.newStringRule("fileName"),
                AttributeRule.newStringRule("parameterColumn"),
                AttributeRule.newIntegerRule("dimension"),
                new ElementRule(TreeModel.class)
        };

        public String getParserDescription() {
            return "Calculates a product of exponentials based on a set of posterior sample means.";
        }

        public Class getReturnType() {
            return Likelihood.class;
        }

    }; 

    /**
     * Parser that aims to use the posterior means of the time between coalescent events.
     */
    public static XMLObjectParser PRODUCT_OF_EXPONENTIALS_POSTERIOR_MEANS_LOESS_PARSER = new AbstractXMLObjectParser() {

        public String getParserName() {
            return PRODUCT_OF_EXPONENTIALS_POSTERIOR_MEANS_LOESS;
        }

        public Object parseXMLObject(XMLObject xo) throws XMLParseException {

            TreeModel treeModel = (TreeModel) xo.getChild(TreeModel.class);
            String fileName = xo.getStringAttribute(FileHelpers.FILE_NAME);

            try {

                File file = new File(fileName);
                String parent = file.getParent();

                if (!file.isAbsolute()) {
                    parent = System.getProperty("user.dir");
                }
                file = new File(parent, fileName);
                fileName = file.getAbsolutePath();

                String parameterName = xo.getStringAttribute(PARAMETER_COLUMN);
                int dimension = xo.getIntegerAttribute("dimension");

                LogFileTraces traces = new LogFileTraces(fileName, file);
                traces.loadTraces();
                long maxState = traces.getMaxState();

                // leaving the burnin attribute off will result in 10% being used
                long burnin = xo.getAttribute("burnin", maxState / 10);
                if (burnin < 0 || burnin >= maxState) {
                    burnin = maxState / 10;
                    System.out.println("WARNING: Burn-in larger than total number of states - using 10%");
                    System.out.println("Burnin: " + burnin);
                }
                traces.setBurnIn(burnin);

                int[] traceIndexParameter = new int[dimension];
                for (int i = 0; i < traceIndexParameter.length; i++) {
                    traceIndexParameter[i] = -1;
                }

                String[] columnNames = new String[dimension];
                for (int i = 1; i <= columnNames.length; i++) {
                    columnNames[i-1] = parameterName + i;
                }
                System.out.println("Looking for the following columns:");
                for (int i = 0; i < columnNames.length; i++) {
                    System.out.println("  " + columnNames[i]);
                }
                for (int i = 0; i < traces.getTraceCount(); i++) {
                    String traceName = traces.getTraceName(i);
                    for (int j = 0; j < columnNames.length; j++) {
                        if (traceName.trim().equals(columnNames[j])) {
                            traceIndexParameter[j] = i;
                            break;
                        }
                    }
                }
                System.out.println("Overview of traceIndexParameter:");
                for (int i = 0; i < traceIndexParameter.length; i++) {
                    if (traceIndexParameter[i] == -1) {
                        throw new XMLParseException("Not all traces could be linked to the required columns, problem with trace index: " + i + "; traceIndexParameter.length = " + traceIndexParameter.length);
                    }
                    System.out.println("  traceIndexParameter[" + i + "]: " + traceIndexParameter[i] );
                }

                Double[][] parameterSamples = new Double[dimension][traces.getStateCount()];
                for (int i = 0; i < dimension; i++) {
                    traces.getValues(traceIndexParameter[i]).toArray(parameterSamples[i]);
                }

                //not necessary to work with flags, assume that we are using coalescentEventsStatistic
                double[] posteriorMeans = new double[dimension];

                for (int i = 0; i < posteriorMeans.length; i++) {
                    //variable column
                    double mean = 0.0;
                    for (int j = 0; j < parameterSamples[i].length; j++) {
                        mean += parameterSamples[i][j];
                    }
                    mean /= ((double)parameterSamples[i].length);

                    //mean = 1.0/mean;

                    System.err.println("Variable column: " + i + " -> " + mean);
                    posteriorMeans[i] = Math.log(mean);
                }

                try {
                    //Print log posterior means
                    System.err.println("Log Posterior Means:");
                    for (int i = 0; i < posteriorMeans.length; i++) {
                        System.err.println(posteriorMeans[i]);
                    }
                    //Call Loess interpolator here
                    LoessInterpolator loess = new LoessInterpolator(1.0, 2);
                    double[] xvalues = new double[posteriorMeans.length];
                    for (int i = 0; i < posteriorMeans.length; i++) {
                        xvalues[i] = i;
                    }

                    double[] loessOutput = loess.smooth(xvalues, posteriorMeans);
                    System.err.println("Loess output:");
                    for (int i = 0; i < loessOutput.length; i++) {
                        System.err.println(loessOutput[i]);
                    }

                    posteriorMeans = loessOutput;

                } catch (MathException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

                return new ExponentialProductPosteriorMeansLikelihood(treeModel, posteriorMeans);

                /*for (int i = 0; i < posteriorMeans.length; i++) {
					posteriorMeans[i] = Math.exp(posteriorMeans[i]);
				}
				return new GammaIntervalProductLikelihood(treeModel, 0.0, posteriorMeans, posteriorMeans);*/

            } catch (FileNotFoundException fnfe) {
                throw new XMLParseException("File '" + fileName + "' can not be opened for " + getParserName() + " element.");
            } catch (java.io.IOException ioe) {
                throw new XMLParseException(ioe.getMessage());
            } catch (TraceException e) {
                throw new XMLParseException(e.getMessage());
            }

        }

        public XMLSyntaxRule[] getSyntaxRules() {
            return rules;
        }

        private final XMLSyntaxRule[] rules = {
                AttributeRule.newStringRule("fileName"),
                AttributeRule.newStringRule("parameterColumn"),
                AttributeRule.newIntegerRule("dimension"),
                new ElementRule(TreeModel.class)
        };

        public String getParserDescription() {
            return "Calculates a product of exponentials based on a set of posterior sample means, undergoing loess smoothing.";
        }

        public Class getReturnType() {
            return Likelihood.class;
        }

    }; 

    /**
     * Parser that aims to use the posterior means of the time between coalescent events, using sufficient statistics.
     */
    public static XMLObjectParser PRODUCT_OF_EXPONENTIALS_SUFFICIENT_STATISTICS_PARSER = new AbstractXMLObjectParser() {

        public String getParserName() {
            return PRODUCT_OF_EXPONENTIALS_SUFFICIENT_STATISTICS;
        }

        public Object parseXMLObject(XMLObject xo) throws XMLParseException {

            TreeModel treeModel = (TreeModel) xo.getChild(TreeModel.class);
            String fileName = xo.getStringAttribute(FileHelpers.FILE_NAME);

            try {

                File file = new File(fileName);
                String parent = file.getParent();

                if (!file.isAbsolute()) {
                    parent = System.getProperty("user.dir");
                }
                file = new File(parent, fileName);
                fileName = file.getAbsolutePath();

                String parameterName = xo.getStringAttribute(PARAMETER_COLUMN);
                int dimension = xo.getIntegerAttribute("dimension");

                LogFileTraces traces = new LogFileTraces(fileName, file);
                traces.loadTraces();
                long maxState = traces.getMaxState();

                // leaving the burnin attribute off will result in 10% being used
                long burnin = xo.getAttribute("burnin", maxState / 10);
                if (burnin < 0 || burnin >= maxState) {
                    burnin = maxState / 10;
                    System.out.println("WARNING: Burn-in larger than total number of states - using 10%");
                }
                traces.setBurnIn(burnin);

                int[] traceIndexParameter = new int[dimension];
                for (int i = 0; i < traceIndexParameter.length; i++) {
                    traceIndexParameter[i] = -1;
                }

                String[] columnNames = new String[dimension];
                for (int i = 1; i <= columnNames.length; i++) {
                    columnNames[i-1] = parameterName + i;
                }
                System.out.println("Looking for the following columns:");
                for (int i = 0; i < columnNames.length; i++) {
                    System.out.println("  " + columnNames[i]);
                }
                for (int i = 0; i < traces.getTraceCount(); i++) {
                    String traceName = traces.getTraceName(i);
                    for (int j = 0; j < columnNames.length; j++) {
                        if (traceName.trim().equals(columnNames[j])) {
                            traceIndexParameter[j] = i;
                            break;
                        }
                    }
                }
                System.out.println("Overview of traceIndexParameter:");
                for (int i = 0; i < traceIndexParameter.length; i++) {
                    if (traceIndexParameter[i] == -1) {
                        throw new XMLParseException("Not all traces could be linked to the required columns.");
                    }
                    System.out.println("  traceIndexParameter[" + i + "]: " + traceIndexParameter[i] );
                }

                Double[][] parameterSamples = new Double[dimension][traces.getStateCount()];
                for (int i = 0; i < dimension; i++) {
                    traces.getValues(traceIndexParameter[i]).toArray(parameterSamples[i]);
                }

                //not necessary to work with flags, assume that we are using coalescentEventsStatistic
                double[] posteriorMeans = new double[dimension];

                for (int i = 0; i < posteriorMeans.length; i++) {
                    //variable column
                    double mean = 0.0;
                    for (int j = 0; j < parameterSamples[i].length; j++) {
                        mean += parameterSamples[i][j];
                    }
                    mean /= ((double)parameterSamples[i].length);
                    System.err.println("Variable column: " + i + " -> " + mean);
                    posteriorMeans[i] = Math.log(mean);
                }

                return new ExponentialProductSufficientStatisticsLikelihood(treeModel, posteriorMeans);

            } catch (FileNotFoundException fnfe) {
                throw new XMLParseException("File '" + fileName + "' can not be opened for " + getParserName() + " element.");
            } catch (java.io.IOException ioe) {
                throw new XMLParseException(ioe.getMessage());
            } catch (TraceException e) {
                throw new XMLParseException(e.getMessage());
            }

        }

        public XMLSyntaxRule[] getSyntaxRules() {
            return rules;
        }

        private final XMLSyntaxRule[] rules = {
                AttributeRule.newStringRule("fileName"),
                AttributeRule.newStringRule("parameterColumn"),
                AttributeRule.newIntegerRule("dimension"),
                new ElementRule(TreeModel.class)
        };

        public String getParserDescription() {
            return "Calculates a product of exponentials based on a set of posterior sample means, using sufficient statistics.";
        }

        public Class getReturnType() {
            return Likelihood.class;
        }

    }; 

    /**
     * Parser that forms a product of gamma distributions to decrease model testing variance 
     */
    public static XMLObjectParser PRODUCT_OF_GAMMAS_PARSER = new AbstractXMLObjectParser() {

        public String getParserName() {
            return PRODUCT_OF_GAMMAS;
        }

        public Object parseXMLObject(XMLObject xo) throws XMLParseException {

            TreeModel treeModel = (TreeModel) xo.getChild(TreeModel.class);
            double popSize = xo.getDoubleAttribute("popSize");
            String fileName = xo.getStringAttribute(FileHelpers.FILE_NAME);

            try {

                File file = new File(fileName);
                String parent = file.getParent();

                if (!file.isAbsolute()) {
                    parent = System.getProperty("user.dir");
                }
                file = new File(parent, fileName);
                fileName = file.getAbsolutePath();

                String parameterName = xo.getStringAttribute(PARAMETER_COLUMN);
                int dimension = xo.getIntegerAttribute("dimension");

                LogFileTraces traces = new LogFileTraces(fileName, file);
                traces.loadTraces();
                long maxState = traces.getMaxState();

                // leaving the burnin attribute off will result in 10% being used
                long burnin = xo.getAttribute("burnin", maxState / 10);
                if (burnin < 0 || burnin >= maxState) {
                    burnin = maxState / 10;
                    System.out.println("WARNING: Burn-in larger than total number of states - using 10%");
                }
                traces.setBurnIn(burnin);

                int[] traceIndexParameter = new int[dimension];
                for (int i = 0; i < traceIndexParameter.length; i++) {
                    traceIndexParameter[i] = -1;
                }

                String[] columnNames = new String[dimension];
                for (int i = 1; i <= columnNames.length; i++) {
                    columnNames[i-1] = parameterName + i;
                }
                System.out.println("Looking for the following columns:");
                for (int i = 0; i < columnNames.length; i++) {
                    System.out.println("  " + columnNames[i]);
                }
                for (int i = 0; i < traces.getTraceCount(); i++) {
                    String traceName = traces.getTraceName(i);
                    for (int j = 0; j < columnNames.length; j++) {
                        if (traceName.trim().equals(columnNames[j])) {
                            traceIndexParameter[j] = i;
                            break;
                        }
                    }
                }
                System.out.println("Overview of traceIndexParameter:");
                for (int i = 0; i < traceIndexParameter.length; i++) {
                    if (traceIndexParameter[i] == -1) {
                        throw new XMLParseException("Not all traces could be linked to the required columns.");
                    }
                    System.out.println("  traceIndexParameter[" + i + "]: " + traceIndexParameter[i] );
                }

                boolean[] flags = new boolean[dimension];
                for (int i = 0; i < dimension; i++) {
                    flags[i] = true;
                }

                //this is code to identify constant columns
                Double[][] parameterSamples = new Double[dimension][traces.getStateCount()];
                for (int i = 0; i < dimension; i++) {
                    traces.getValues(traceIndexParameter[i]).toArray(parameterSamples[i]);
                    double initial = parameterSamples[i][0];
                    boolean tempFlag = false;
                    for (int j = 0; j < parameterSamples[i].length; j++) {
                        if (parameterSamples[i][j] != initial) {
                            tempFlag = true;
                            break;
                        }
                    }
                    flags[i] = tempFlag;
                }

                //double[] shapes = new double[dimension];
                //double[] scales = new double[dimension];
                double[] means = new double[dimension];
                double[] variances = new double[dimension];

                for (int i = 0; i < flags.length; i++) {
                    if (flags[i]) {
                        //variable column
                        double mean = 0.0;
                        for (int j = 0; j < parameterSamples[i].length; j++) {
                            mean += parameterSamples[i][j];
                        }
                        mean /= ((double)parameterSamples[i].length);
                        means[i] = mean;
                        double variance = 0.0;
                        for (int j = 0; j < parameterSamples[i].length; j++) {
                            variance += Math.pow(parameterSamples[i][j] - mean, 2);
                        }
                        variance /= ((double)(parameterSamples[i].length-1));
                        variances[i] = variance;
                        //scales[i] = variance/mean;
                        //shapes[i] = mean/scales[i];
                        //System.err.println("Variable column: " + i + " -> " + shapes[i] + "   " + scales[i]);
                        System.err.println("Variable column: " + i + " -> " + means[i] + "   " + variances[i]);
                    } else {
                        //constant column
                        double mean = 0.0;
                        for (int j = 0; j < parameterSamples[i].length; j++) {
                            mean += parameterSamples[i][j];
                        }
                        mean /= ((double)parameterSamples[i].length);
                        means[i] = mean;
                        //variance will be 0.0, so add epsilon
                        variances[i] = 0.0;
                        variances[i] = epsilon;
                        //System.err.println("mean = " + mean + "   variance = " + variance);
                        //scales[i] = variance/mean;
                        //shapes[i] = mean/scales[i];
                        System.err.println("Constant column: " + i + " -> " + means[i] + "   " + variances[i]);
                    } 
                }

                //only provide the actual intervals, from the root downwards
                /*int newLength = 0;
				for (int i = 0; i < flags.length; i++) {
					if (means[i] == 0.0 && variances[i] == epsilon) {
						//do nothing at the moment
					} else {
						newLength++;
					}
				}
				System.err.println("Flags length = " + flags.length);
				System.err.println("New length = " + newLength);
				double[] newMeans = new double[newLength];
				double[] newVariances = new double[newLength];
				int counter = 0;
				for (int i = 0; i < flags.length; i++) {
					if (means[i] == 0.0 && variances[i] == epsilon) {
						//do nothing at the moment
					} else {
						newMeans[counter] = means[i];
						newVariances[counter] = variances[i];
						counter++;
					}
				}*/

                //return new GammaProductLikelihood(treeModel, popSize, newMeans, newVariances);
                //return new GammaIntervalProductLikelihood(treeModel, popSize, means, variances);
                //return new GammaIntervalProductLikelihood(treeModel, popSize, newMeans, newVariances);


                return new GammaProductLikelihood(treeModel, popSize, means, variances);


            } catch (FileNotFoundException fnfe) {
                throw new XMLParseException("File '" + fileName + "' can not be opened for " + getParserName() + " element.");
            } catch (java.io.IOException ioe) {
                throw new XMLParseException(ioe.getMessage());
            } catch (TraceException e) {
                throw new XMLParseException(e.getMessage());
            }

        }

        public XMLSyntaxRule[] getSyntaxRules() {
            return rules;
        }

        private final XMLSyntaxRule[] rules = {
                //AttributeRule.newDoubleRule("popSize"),
                AttributeRule.newStringRule("fileName"),
                AttributeRule.newStringRule("parameterColumn"),
                AttributeRule.newIntegerRule("dimension"),
                AttributeRule.newDoubleRule("popSize"),
                //new ElementRule(Parameter.class),
                new ElementRule(TreeModel.class)
        };

        public String getParserDescription() {
            return "Calculates a product of gamma distributions.";
        }

        public Class getReturnType() {
            return Likelihood.class;
        }

    };

    /**
     * A special parser that reads coalescent heights and builds reference priors for them.
     */
    public static XMLObjectParser COALESCENT_HEIGHTS_REFERENCE_PRIOR_PARSER = new AbstractXMLObjectParser() {

        public String getParserName() {
            return COALESCENT_HEIGHTS_REFERENCE_PRIOR;
        }

        public Object parseXMLObject(XMLObject xo) throws XMLParseException {

            String fileName = xo.getStringAttribute(FileHelpers.FILE_NAME);

            try {

                File file = new File(fileName);
                String parent = file.getParent();

                if (!file.isAbsolute()) {
                    parent = System.getProperty("user.dir");
                }
                file = new File(parent, fileName);
                fileName = file.getAbsolutePath();

                String parameterName = xo.getStringAttribute(PARAMETER_COLUMN);
                int dimension = xo.getIntegerAttribute("dimension");

                LogFileTraces traces = new LogFileTraces(fileName, file);
                traces.loadTraces();
                long maxState = traces.getMaxState();

                // leaving the burnin attribute off will result in 10% being used
                long burnin = xo.getAttribute("burnin", maxState / 10);
                if (burnin < 0 || burnin >= maxState) {
                    burnin = maxState / 10;
                    System.out.println("WARNING: Burn-in larger than total number of states - using 10%");
                }
                traces.setBurnIn(burnin);

                int[] traceIndexParameter = new int[dimension];
                for (int i = 0; i < traceIndexParameter.length; i++) {
                    traceIndexParameter[i] = -1;
                }

                String[] columnNames = new String[dimension];
                for (int i = 1; i <= columnNames.length; i++) {
                    columnNames[i-1] = parameterName + i;
                }
                System.out.println("Looking for the following columns:");
                for (int i = 0; i < columnNames.length; i++) {
                    System.out.println("  " + columnNames[i]);
                }
                for (int i = 0; i < traces.getTraceCount(); i++) {
                    String traceName = traces.getTraceName(i);
                    for (int j = 0; j < columnNames.length; j++) {
                        if (traceName.trim().equals(columnNames[j])) {
                            traceIndexParameter[j] = i;
                            break;
                        }
                    }
                }
                System.out.println("Overview of traceIndexParameter:");
                for (int i = 0; i < traceIndexParameter.length; i++) {
                    if (traceIndexParameter[i] == -1) {
                        throw new XMLParseException("Not all traces could be linked to the required columns.");
                    }
                    System.out.println("  traceIndexParameter[" + i + "]: " + traceIndexParameter[i] );
                }

                boolean[] flags = new boolean[dimension];
                for (int i = 0; i < dimension; i++) {
                    flags[i] = true;
                }

                Double[][] parameterSamples = new Double[dimension][traces.getStateCount()];
                for (int i = 0; i < dimension; i++) {
                    traces.getValues(traceIndexParameter[i]).toArray(parameterSamples[i]);
                    double initial = parameterSamples[i][0];
                    boolean tempFlag = false;
                    for (int j = 0; j < parameterSamples[i].length; j++) {
                        if (parameterSamples[i][j] != initial) {
                            tempFlag = true;
                            break;
                        }
                    }
                    flags[i] = tempFlag;
                }

                double[] shapes = new double[dimension];
                double[] scales = new double[dimension];

                for (int i = 0; i < flags.length; i++) {
                    if (flags[i]) {
                        double mean = 0.0;
                        for (int j = 0; j < parameterSamples[i].length; j++) {
                            mean += parameterSamples[i][j];
                        }
                        mean /= ((double)parameterSamples[i].length);
                        double variance = 0.0;
                        for (int j = 0; j < parameterSamples[i].length; j++) {
                            variance += Math.pow(parameterSamples[i][j] - mean, 2);
                        }
                        variance /= ((double)(parameterSamples[i].length-1));
                        scales[i] = variance/mean;
                        shapes[i] = mean/scales[i];
                        System.err.println("Variable column: " + i + " -> " + shapes[i] + "   " + scales[i]);
                    } /*else {
                    	   double mean = 0.0;
                           for (int j = 0; j < parameterSamples[i].length; j++) {
                               mean += parameterSamples[i][j];
                           }
                           mean /= ((double)parameterSamples[i].length);
                           //variance will be 0.0, so add epsilon
                           double variance = 0.000001;
                           System.err.println("mean = " + mean + "   variance = " + variance);
                           scales[i] = variance/mean;
                           shapes[i] = mean/scales[i];
                    	   System.err.println("Constant column: " + i + " -> " + shapes[i] + "   " + scales[i]);
                       } */
                }

                System.err.println("Columns to be evaluated:");
                for (int i = 0; i < flags.length; i++) {
                    if (flags[i]) {
                        System.err.println("Column " + i);
                    }
                }

                MultivariateGammaDistribution mvgd = new MultivariateGammaDistribution(shapes, scales, flags);

                MultivariateDistributionLikelihood likelihood = new MultivariateDistributionLikelihood(mvgd);
                for (int j = 0; j < xo.getChildCount(); j++) {
                    if (xo.getChild(j) instanceof Statistic) {
                        likelihood.addData((Statistic) xo.getChild(j));
                    } else {
                        throw new XMLParseException("illegal element in " + xo.getName() + " element");
                    }
                }

                return likelihood;

            } catch (FileNotFoundException fnfe) {
                throw new XMLParseException("File '" + fileName + "' can not be opened for " + getParserName() + " element.");
            } catch (java.io.IOException ioe) {
                throw new XMLParseException(ioe.getMessage());
            } catch (TraceException e) {
                throw new XMLParseException(e.getMessage());
            }

        }

        public XMLSyntaxRule[] getSyntaxRules() {
            return rules;
        }

        private final XMLSyntaxRule[] rules = {
                AttributeRule.newStringRule("fileName"),
                AttributeRule.newStringRule("parameterColumn"),
                AttributeRule.newIntegerRule("dimension"),
                //AttributeRule.newIntegerRule("burnin"),
                new ElementRule(Statistic.class, 1, Integer.MAX_VALUE)
        };

        public String getParserDescription() {
            return "Calculates the coalescent height probabilities based on a sample of coalescent heights.";
        }

        public Class getReturnType() {
            return MultivariateDistributionLikelihood.class;
        }

    };

    /**
     * A simple parser to provide a constant prior on the tree topology
     */

    public static XMLObjectParser CONSTANT_TREE_TOPOLOGY_PRIOR_PARSER = new AbstractXMLObjectParser() {

        public String getParserName() {
            return CONSTANT_TREE_TOPOLOGY_PRIOR;
        }

        public Object parseXMLObject(XMLObject xo) throws XMLParseException {

            TreeModel treeModel = (TreeModel) xo.getChild(TreeModel.class);
            final int nTaxa = treeModel.getExternalNodeCount();
            double combinations = GammaFunction.factorial(2*nTaxa-3)/(GammaFunction.factorial(nTaxa-2)*Math.pow(2.0,nTaxa-2));
            double logPDF = Math.log(1.0/combinations);
            return new ConstantLikelihood(logPDF);

        }

        public XMLSyntaxRule[] getSyntaxRules() {
            return rules;
        }

        private final XMLSyntaxRule[] rules = {
                new ElementRule(TreeModel.class)
        };

        public String getParserDescription() {
            return "Calculates the constant tree topology prior, i.e. 1 over the total number of rooted bifurcating trees.";
        }

        public Class getReturnType() {
            return Likelihood.class;
        }

    };

    /**
     * A special parser that reads a convenient short form of reference priors on trees.
     */
    public static XMLObjectParser CONDITIONAL_CLADE_REFERENCE_PRIOR_PARSER = new AbstractXMLObjectParser() {

        public String getParserName() {
            return CONDITIONAL_CLADE_REFERENCE_PRIOR;
        }

        public Object parseXMLObject(XMLObject xo) throws XMLParseException {

            //Coalescent.TREEPRIOR = true;

            TreeModel treeModel = (TreeModel) xo.getChild(TreeModel.class);

            String fileName = xo.getStringAttribute(FileHelpers.FILE_NAME);

            try {

                File file = new File(fileName);
                String name = file.getName();
                String parent = file.getParent();

                if (!file.isAbsolute()) {
                    parent = System.getProperty("user.dir");
                }
                file = new File(parent, fileName);
                fileName = file.getAbsolutePath();

                Reader reader = new FileReader(new File(parent, name));

                // the burn-in is used as the number of trees discarded
                int burnin = -1;
                if (xo.hasAttribute(BURNIN)) {
                    // leaving the burnin attribute off will result in 10% being used
                    burnin = xo.getIntegerAttribute(BURNIN);
                }

                // the epsilon value which represents the number of occurrences for every not observed clade
                double e = 1.0;
                if (xo.hasAttribute(EPSILON)) {
                    // leaving the epsilon attribute off will result in 1.0 being used
                    e = xo.getDoubleAttribute(EPSILON);
                }

                TreeTrace trace = TreeTrace.loadTreeTrace(reader);

                ConditionalCladeFrequency ccf = new ConditionalCladeFrequency(new TreeTrace[]{trace}, e, burnin, false);

                ConditionalCladeProbability ccp = new ConditionalCladeProbability(ccf, treeModel);

                return ccp;

            } catch (FileNotFoundException fnfe) {
                throw new XMLParseException("File '" + fileName + "' can not be opened for " + getParserName() + " element.");
            } catch (java.io.IOException ioe) {
                throw new XMLParseException(ioe.getMessage());
            } catch (ImportException ie) {
                throw new XMLParseException(ie.getMessage());
            }

            /*final int nTaxa = treeModel.getExternalNodeCount();
            double logPDF = 0.0;
            for (int i = nTaxa; i > 2; --i) {
                logPDF += Math.log(Binomial.choose2(i));
            }

            return new ConstantLikelihood(-logPDF);*/
        }

        public XMLSyntaxRule[] getSyntaxRules() {
            return rules;
        }

        private final XMLSyntaxRule[] rules = {
                AttributeRule.newStringRule("fileName"),
                AttributeRule.newIntegerRule(BURNIN),
                AttributeRule.newDoubleRule(EPSILON),
                new ElementRule(TreeModel.class)
        };

        public String getParserDescription() {
            return "Calculates the conditional clade probability of a tree based on a sample of tree space.";
        }

        public Class getReturnType() {
            return Likelihood.class;
        }
    };

}
