/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.math.ode.events;

import org.apache.commons.math.ConvergenceException;
import org.apache.commons.math.FunctionEvaluationException;
import org.apache.commons.math.analysis.UnivariateRealFunction;
import org.apache.commons.math.analysis.solvers.BrentSolver;
import org.apache.commons.math.ode.DerivativeException;
import org.apache.commons.math.ode.sampling.StepInterpolator;

/**
 * This class handles the state for one {@link EventHandler event handler}
 * during integration steps.
 *
 * <p>
 * Each time the integrator proposes a step, the event handler switching
 * function should be checked. This class handles the state of one handler
 * during one integration step, with references to the state at the end of the
 * preceding step. This information is used to decide if the handler should
 * trigger an event or not during the proposed step (and hence the step should
 * be reduced to ensure the event occurs at a bound rather than inside the
 * step).
 * </p>
 *
 * @version $Revision$ $Date$
 * @since 1.2
 */
public class EventState {

	/** Event handler. */
	private final EventHandler handler;

	/** Maximal time interval between events handler checks. */
	private final double maxCheckInterval;

	/** Convergence threshold for event localization. */
	private final double convergence;

	/** Upper limit in the iteration count for event localization. */
	private final int maxIterationCount;

	/** Time at the beginning of the step. */
	private double t0;

	/** Value of the events handler at the beginning of the step. */
	private double g0;

	/** Simulated sign of g0 (we cheat when crossing events). */
	private boolean g0Positive;

	/** Indicator of event expected during the step. */
	private boolean pendingEvent;

	/** Occurrence time of the pending event. */
	private double pendingEventTime;

	/** Occurrence time of the previous event. */
	private double previousEventTime;

	/** Integration direction. */
	private boolean forward;

	/**
	 * Variation direction around pending event. (this is considered with
	 * respect to the integration direction)
	 */
	private boolean increasing;

	/** Next action indicator. */
	private int nextAction;

	/**
	 * Simple constructor.
	 * 
	 * @param handler
	 *            event handler
	 * @param maxCheckInterval
	 *            maximal time interval between switching function checks (this
	 *            interval prevents missing sign changes in case the integration
	 *            steps becomes very large)
	 * @param convergence
	 *            convergence threshold in the event time search
	 * @param maxIterationCount
	 *            upper limit of the iteration count in the event time search
	 */
	public EventState(final EventHandler handler, final double maxCheckInterval, final double convergence,
			final int maxIterationCount) {
		this.handler = handler;
		this.maxCheckInterval = maxCheckInterval;
		this.convergence = Math.abs(convergence);
		this.maxIterationCount = maxIterationCount;

		// some dummy values ...
		t0 = Double.NaN;
		g0 = Double.NaN;
		g0Positive = true;
		pendingEvent = false;
		pendingEventTime = Double.NaN;
		previousEventTime = Double.NaN;
		increasing = true;
		nextAction = EventHandler.CONTINUE;

	}

	/**
	 * Get the underlying event handler.
	 * 
	 * @return underlying event handler
	 */
	public EventHandler getEventHandler() {
		return handler;
	}

	/**
	 * Get the maximal time interval between events handler checks.
	 * 
	 * @return maximal time interval between events handler checks
	 */
	public double getMaxCheckInterval() {
		return maxCheckInterval;
	}

	/**
	 * Get the convergence threshold for event localization.
	 * 
	 * @return convergence threshold for event localization
	 */
	public double getConvergence() {
		return convergence;
	}

	/**
	 * Get the upper limit in the iteration count for event localization.
	 * 
	 * @return upper limit in the iteration count for event localization
	 */
	public int getMaxIterationCount() {
		return maxIterationCount;
	}

	/**
	 * Reinitialize the beginning of the step.
	 * 
	 * @param t0
	 *            value of the independent <i>time</i> variable at the beginning
	 *            of the step
	 * @param y0
	 *            array containing the current value of the state vector at the
	 *            beginning of the step
	 * @exception EventException
	 *                if the event handler value cannot be evaluated at the
	 *                beginning of the step
	 */
	public void reinitializeBegin(final double t0, final double[] y0) throws EventException {
		this.t0 = t0;
		g0 = handler.g(t0, y0);
		g0Positive = (g0 >= 0);
	}

	/**
	 * Evaluate the impact of the proposed step on the event handler.
	 * 
	 * @param interpolator
	 *            step interpolator for the proposed step
	 * @return true if the event handler triggers an event before the end of the
	 *         proposed step (this implies the step should be rejected)
	 * @exception DerivativeException
	 *                if the interpolator fails to compute the switching
	 *                function somewhere within the step
	 * @exception EventException
	 *                if the switching function cannot be evaluated
	 * @exception ConvergenceException
	 *                if an event cannot be located
	 */
	public boolean evaluateStep(final StepInterpolator interpolator)
			throws DerivativeException, EventException, ConvergenceException {

		try {

			forward = interpolator.isForward();
			final double t1 = interpolator.getCurrentTime();
			final int n = Math.max(1, (int) Math.ceil(Math.abs(t1 - t0) / maxCheckInterval));
			final double h = (t1 - t0) / n;

			double ta = t0;
			double ga = g0;
			double tb = t0 + (interpolator.isForward() ? convergence : -convergence);
			for (int i = 0; i < n; ++i) {

				// evaluate handler value at the end of the substep
				tb += h;
				interpolator.setInterpolatedTime(tb);
				final double gb = handler.g(tb, interpolator.getInterpolatedState());

				// check events occurrence
				if (g0Positive ^ (gb >= 0)) {
					// there is a sign change: an event is expected during this
					// step

					// variation direction, with respect to the integration
					// direction
					increasing = (gb >= ga);

					final UnivariateRealFunction f = new UnivariateRealFunction() {
						public double value(final double t) throws FunctionEvaluationException {
							try {
								interpolator.setInterpolatedTime(t);
								return handler.g(t, interpolator.getInterpolatedState());
							} catch (DerivativeException e) {
								throw new FunctionEvaluationException(e, t);
							} catch (EventException e) {
								throw new FunctionEvaluationException(e, t);
							}
						}
					};
					final BrentSolver solver = new BrentSolver();
					solver.setAbsoluteAccuracy(convergence);
					solver.setMaximalIterationCount(maxIterationCount);
					double root;
					try {
						root = (ta <= tb) ? solver.solve(f, ta, tb) : solver.solve(f, tb, ta);
					} catch (IllegalArgumentException iae) {
						// the interval did not really bracket a root
						root = Double.NaN;
					}
					if (Double.isNaN(root) || ((Math.abs(root - ta) <= convergence)
							&& (Math.abs(root - previousEventTime) <= convergence))) {
						// we have either found nothing or found (again ?) a
						// past event, we simply ignore it
						ta = tb;
						ga = gb;
					} else if (Double.isNaN(previousEventTime) || (Math.abs(previousEventTime - root) > convergence)) {
						pendingEventTime = root;
						if (pendingEvent && (Math.abs(t1 - pendingEventTime) <= convergence)) {
							// we were already waiting for this event which was
							// found during a previous call for a step that was
							// rejected, this step must now be accepted since it
							// properly ends exactly at the event occurrence
							return false;
						}
						// either we were not waiting for the event or it has
						// moved in such a way the step cannot be accepted
						pendingEvent = true;
						return true;
					}

				} else {
					// no sign change: there is no event for now
					ta = tb;
					ga = gb;
				}

			}

			// no event during the whole step
			pendingEvent = false;
			pendingEventTime = Double.NaN;
			return false;

		} catch (FunctionEvaluationException e) {
			final Throwable cause = e.getCause();
			if ((cause != null) && (cause instanceof DerivativeException)) {
				throw (DerivativeException) cause;
			} else if ((cause != null) && (cause instanceof EventException)) {
				throw (EventException) cause;
			}
			throw new EventException(e);
		}

	}

	/**
	 * Get the occurrence time of the event triggered in the current step.
	 * 
	 * @return occurrence time of the event triggered in the current step.
	 */
	public double getEventTime() {
		return pendingEventTime;
	}

	/**
	 * Acknowledge the fact the step has been accepted by the integrator.
	 * 
	 * @param t
	 *            value of the independent <i>time</i> variable at the end of
	 *            the step
	 * @param y
	 *            array containing the current value of the state vector at the
	 *            end of the step
	 * @exception EventException
	 *                if the value of the event handler cannot be evaluated
	 */
	public void stepAccepted(final double t, final double[] y) throws EventException {

		t0 = t;
		g0 = handler.g(t, y);

		if (pendingEvent) {
			// force the sign to its value "just after the event"
			previousEventTime = t;
			g0Positive = increasing;
			nextAction = handler.eventOccurred(t, y, !(increasing ^ forward));
		} else {
			g0Positive = (g0 >= 0);
			nextAction = EventHandler.CONTINUE;
		}
	}

	/**
	 * Check if the integration should be stopped at the end of the current
	 * step.
	 * 
	 * @return true if the integration should be stopped
	 */
	public boolean stop() {
		return nextAction == EventHandler.STOP;
	}

	/**
	 * Let the event handler reset the state if it wants.
	 * 
	 * @param t
	 *            value of the independent <i>time</i> variable at the beginning
	 *            of the next step
	 * @param y
	 *            array were to put the desired state vector at the beginning of
	 *            the next step
	 * @return true if the integrator should reset the derivatives too
	 * @exception EventException
	 *                if the state cannot be reseted by the event handler
	 */
	public boolean reset(final double t, final double[] y) throws EventException {

		if (!pendingEvent) {
			return false;
		}

		if (nextAction == EventHandler.RESET_STATE) {
			handler.resetState(t, y);
		}
		pendingEvent = false;
		pendingEventTime = Double.NaN;

		return (nextAction == EventHandler.RESET_STATE) || (nextAction == EventHandler.RESET_DERIVATIVES);

	}

}
