/*
 * 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.sampling;

import org.apache.commons.math.ode.DerivativeException;

/**
 * This class wraps an object implementing {@link FixedStepHandler} into a
 * {@link StepHandler}.
 * 
 * <p>
 * This wrapper allows to use fixed step handlers with general integrators which
 * cannot guaranty their integration steps will remain constant and therefore
 * only accept general step handlers.
 * </p>
 *
 * <p>
 * The stepsize used is selected at construction time. The
 * {@link FixedStepHandler#handleStep handleStep} method of the underlying
 * {@link FixedStepHandler} object is called at the beginning time of the
 * integration t0 and also at times t0+h, t0+2h, ... If the integration range is
 * an integer multiple of the stepsize, then the last point handled will be the
 * endpoint of the integration tend, if not, the last point will belong to the
 * interval [tend - h ; tend].
 * </p>
 *
 * <p>
 * There is no constraint on the integrator, it can use any timestep it needs
 * (time steps longer or shorter than the fixed time step and non-integer ratios
 * are all allowed).
 * </p>
 *
 * @see StepHandler
 * @see FixedStepHandler
 * @version $Revision$ $Date$
 * @since 1.2
 */

public class StepNormalizer implements StepHandler {

	/** Fixed time step. */
	private double h;

	/** Underlying step handler. */
	private final FixedStepHandler handler;

	/** Last step time. */
	private double lastTime;

	/** Last State vector. */
	private double[] lastState;

	/** Last Derivatives vector. */
	private double[] lastDerivatives;

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

	/**
	 * Simple constructor.
	 * 
	 * @param h
	 *            fixed time step (sign is not used)
	 * @param handler
	 *            fixed time step handler to wrap
	 */
	public StepNormalizer(final double h, final FixedStepHandler handler) {
		this.h = Math.abs(h);
		this.handler = handler;
		reset();
	}

	/**
	 * Determines whether this handler needs dense output. This handler needs
	 * dense output in order to provide data at regularly spaced steps
	 * regardless of the steps the integrator uses, so this method always
	 * returns true.
	 * 
	 * @return always true
	 */
	public boolean requiresDenseOutput() {
		return true;
	}

	/**
	 * Reset the step handler. Initialize the internal data as required before
	 * the first step is handled.
	 */
	public void reset() {
		lastTime = Double.NaN;
		lastState = null;
		lastDerivatives = null;
		forward = true;
	}

	/**
	 * Handle the last accepted step
	 * 
	 * @param interpolator
	 *            interpolator for the last accepted step. For efficiency
	 *            purposes, the various integrators reuse the same object on
	 *            each call, so if the instance wants to keep it across all
	 *            calls (for example to provide at the end of the integration a
	 *            continuous model valid throughout the integration range), it
	 *            should build a local copy using the clone method and store
	 *            this copy.
	 * @param isLast
	 *            true if the step is the last one
	 * @throws DerivativeException
	 *             this exception is propagated to the caller if the underlying
	 *             user function triggers one
	 */
	public void handleStep(final StepInterpolator interpolator, final boolean isLast) throws DerivativeException {

		if (lastState == null) {

			lastTime = interpolator.getPreviousTime();
			interpolator.setInterpolatedTime(lastTime);
			lastState = interpolator.getInterpolatedState().clone();
			lastDerivatives = interpolator.getInterpolatedDerivatives().clone();

			// take the integration direction into account
			forward = (interpolator.getCurrentTime() >= lastTime);
			if (!forward) {
				h = -h;
			}

		}

		double nextTime = lastTime + h;
		boolean nextInStep = forward ^ (nextTime > interpolator.getCurrentTime());
		while (nextInStep) {

			// output the stored previous step
			handler.handleStep(lastTime, lastState, lastDerivatives, false);

			// store the next step
			lastTime = nextTime;
			interpolator.setInterpolatedTime(lastTime);
			System.arraycopy(interpolator.getInterpolatedState(), 0, lastState, 0, lastState.length);
			System.arraycopy(interpolator.getInterpolatedDerivatives(), 0, lastDerivatives, 0, lastDerivatives.length);

			nextTime += h;
			nextInStep = forward ^ (nextTime > interpolator.getCurrentTime());

		}

		if (isLast) {
			// there will be no more steps,
			// the stored one should be flagged as being the last
			handler.handleStep(lastTime, lastState, lastDerivatives, true);
		}

	}

}
