/*
 * MLOptimizer.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.inference.ml;

import dr.inference.loggers.Logger;
import dr.inference.markovchain.MarkovChain;
import dr.inference.markovchain.MarkovChainListener;
import dr.inference.model.Likelihood;
import dr.inference.model.Model;
import dr.inference.operators.OperatorSchedule;
import dr.util.Identifiable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * A heuristic optimizer that uses the MCMC framework.
 *
 * @author Andrew Rambaut
 *
 * @version $Id: MLOptimizer.java,v 1.5 2006/06/13 03:50:54 alexei Exp $
 */
public class MLOptimizer implements Runnable, Identifiable {

	/** the likelihood function */
	private final Likelihood likelihood;

	/** the operator schedule */
	private OperatorSchedule schedule;

	private final Logger[] loggers;

	int chainLength;

	private String id = null;

	/**
	 * Constructor
	 * @param chainLength the chain length
	 * @param schedule operator schedule to be used in chain.
	 */
	public MLOptimizer(String id,
		int chainLength,
		Likelihood likelihood,
		OperatorSchedule schedule,
		Logger[] loggers) {

		this.id = id;

		mc = new MarkovChain(null, likelihood, schedule, new GreatDelugeCriterion(0.2), 2000, 1, MarkovChain.EVALUATION_TEST_THRESHOLD, false);
        //mc = new MarkovChain(null, likelihood, schedule, new HillClimbingCriterion(), false);

		this.chainLength = chainLength;
		this.likelihood = likelihood;
		this.loggers = loggers;

		setOperatorSchedule(schedule);

		//initialize transients
		currentState = 0;
	}

	public void run() {
		chain();
	}

	/**
	 * This method actually intiates the MCMC analysis.
	 * the site patterns have been dropped from the site model
	 * to reduce the footprint of the sample.
	 */
	public void chain() {
        currentState = 0;

		if (loggers != null) {
            for (Logger logger : loggers) {
                logger.startLogging();
            }
        }

		timer.start();

		mc.reset();
		timer.start();

			mc.addMarkovChainListener(chainListener);
			mc.runChain(getChainLength(), true/*, 0*/);
			mc.removeMarkovChainListener(chainListener);

		timer.stop();
	}

	/** @return the likelihood function. */
	public Likelihood getLikelihood() { return likelihood; }

	/** @return the timer. */
	public dr.util.Timer getTimer() { return timer; }

	/** set the operator schedule used in this MCMC analysis.
	 * MUST be called before chain!
	 */
	public void setOperatorSchedule(OperatorSchedule sched) {
		this.schedule = sched;
	}

	/** @return the operator schedule used in this MCMC analysis. */
	public OperatorSchedule getOperatorSchedule() { return schedule; }

	/** @return the length of this analysis.*/
	public final int getChainLength() { return chainLength; }

	// TRANSIENT PUBLIC METHODS *****************************************

	/** @return the current state of the MCMC analysis. */
	public final long getCurrentState() { return currentState; }

	/** @return the progress (0 to 1) of the MCMC analysis. */
	public final double getProgress() {
		return (double)currentState / chainLength;
	}

    private final MarkovChainListener chainListener = new MarkovChainListener() {
	// for receiving messages from subordinate MarkovChain

        /**
         * Called to update the current model keepEvery states.
         */
        public void currentState(long state, Model currentModel) {

            currentState = state;

            if (loggers != null) {
                for (Logger logger : loggers) {
                    logger.log(state);
                }
            }
        }

        /** Called when a new new best posterior state is found. */
        public void bestState(long state, Model bestModel) {
            currentState = state;
        }

        /** Called when a new new best likelihood state is found. */
        public void bestLklModel(long state, Model bestModel) {
            currentState = state;
        }

        /** cleans up when the chain finishes (possibly early). */
        public void finished(long chainLength) {
            currentState = chainLength;

            if (loggers != null) {
                for (Logger logger : loggers) {
                    logger.log(currentState);
                    logger.stopLogging();
                }
            }

           /* if (false) {
                NumberFormatter formatter = new NumberFormatter(8);

                System.out.println();
                System.out.println("Operator analysis");
                for (int i =0; i < schedule.getOperatorCount(); i++) {

                    MCMCOperator op = schedule.getOperator(i);
                    double acceptanceProb = MCMCOperator.Utils.getAcceptanceProbability(op);
                    System.out.println(formatter.formatToFieldWidth(op.getOperatorName(), 30) + "\t" + formatter.formatDecimal(op.getMeanDeviation(), 2) + "\t" + formatter.formatDecimal(acceptanceProb, 4));
                }
                System.out.println();
            }
*/
        }
    };

	/**
	 * Creates a DOM element that represents this MCMC analysis.
	 */
	public Element createElement(Document d) {
		throw new RuntimeException("Not implemented!");
	}

	public String getId() { return id; }

	public void setId(String id) { this.id = id; }

	// PRIVATE TRANSIENTS

	private long currentState;

    private final dr.util.Timer timer = new dr.util.Timer();

	/** this markov chain does most of the work. */
	private MarkovChain mc = null;
}

