/* Copyright 2009-2013 David Hadka
 *
 * This file is part of the MOEA Framework.
 *
 * The MOEA Framework 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 3 of the License, or (at your
 * option) any later version.
 *
 * The MOEA Framework 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 the MOEA Framework.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.moeaframework.analysis.collector;

import java.io.NotSerializableException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.moeaframework.algorithm.PeriodicAction;
import org.moeaframework.core.Algorithm;

/**
 * Decorates an algorithm to periodically collect information about its runtime
 * behavior.  The {@code NFE} field is automatically recorded by this class.
 */
public class InstrumentedAlgorithm extends PeriodicAction {
	
	/**
	 * The accumulator to which all recorded information is stored.
	 */
	private final Accumulator accumulator;
	
	/**
	 * The collectors responsible for recording the necessary information.
	 */
	private final List<Collector> collectors;

	/**
	 * Decorates the specified algorithm to periodically collect information
	 * about its runtime behavior.
	 * 
	 * @param algorithm the algorithm to decorate
	 * @param frequency the frequency, in evaluations, that data is collected
	 */
	public InstrumentedAlgorithm(Algorithm algorithm, int frequency) {
		super(algorithm, frequency, FrequencyType.EVALUATIONS);
		
		accumulator = new Accumulator();
		collectors = new ArrayList<Collector>();
	}
	
	/**
	 * Adds a collector to this instrumented algorithm.  The collector should
	 * have already been attached to the algorithm.
	 * 
	 * @param collector the collector
	 */
	public void addCollector(Collector collector) {
		collectors.add(collector);
	}
	
	/**
	 * Returns the accumulator to which all recorded information is stored.
	 * 
	 * @return the accumulator to which all recorded information is stored
	 */
	public Accumulator getAccumulator() {
		return accumulator;
	}

	@Override
	public void doAction() {
		accumulator.add("NFE", algorithm.getNumberOfEvaluations());
		
		for (Collector collector : collectors) {
			collector.collect(accumulator);
		}
	}
	
	/**
	 * Proxy for serializing and deserializing the state of an
	 * {@code InstrumentedAlgorithm} instance. This proxy supports saving
	 * the underlying algorithm state and the {@code accumulator}.
	 */
	private static class InstrumentedAlgorithmState implements
	Serializable {

		private static final long serialVersionUID = -313598408729472790L;

		/**
		 * The state of the underlying algorithm.
		 */
		private final Serializable algorithmState;
		
		/**
		 * The {@code accumulator} from the {@code InstrumentedAlgorithm}
		 * instance.
		 */
		private final Accumulator accumulator;

		/**
		 * Constructs a proxy for storing the state of an
		 * {@code InstrumentedAlgorithm} instance.
		 * 
		 * @param algorithmState the state of the underlying algorithm
		 * @param accumulator the {@code accumulator} from the
		 *        {@code InstrumentedAlgorithm} instance
		 */
		public InstrumentedAlgorithmState(Serializable algorithmState,
				Accumulator accumulator) {
			super();
			this.algorithmState = algorithmState;
			this.accumulator = accumulator;
		}

		/**
		 * Returns the underlying algorithm state.
		 * 
		 * @return the underlying algorithm state
		 */
		public Serializable getAlgorithmState() {
			return algorithmState;
		}

		/**
		 * Returns the {@code accumulator} from the
		 * {@code InstrumentedAlgorithm} instance.
		 * 
		 * @return the {@code accumulator} from the
		 *         {@code InstrumentedAlgorithm} instance
		 */
		public Accumulator getAccumulator() {
			return accumulator;
		}
		
	}

	@Override
	public Serializable getState() throws NotSerializableException {
		return new InstrumentedAlgorithmState(super.getState(),
				accumulator);
	}

	@Override
	public void setState(Object objState) throws NotSerializableException {
		InstrumentedAlgorithmState state =
				(InstrumentedAlgorithmState)objState;
		
		super.setState(state.getAlgorithmState());
		
		//copy the stored accumulator contents to this accumulator
		Accumulator storedAccumulator = state.getAccumulator();
		
		for (String key : storedAccumulator.keySet()) {
			for (int i=0; i<storedAccumulator.size(key); i++) {
				accumulator.add(key, storedAccumulator.get(key, i));
			}
		}
	}
	
}
