// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'simulation.dart';
import 'tolerance.dart';
import 'utils.dart';

/// Base class for composite simulations.
///
/// Concrete subclasses must implement the [currentSimulation] getter, the
/// [currentIntervalOffset] getter, and the [step] function to select the
/// appropriate simulation at a given time interval. This class implements the
/// [x], [dx], and [isDone] functions by calling the [step] method if necessary
/// and then deferring to the [currentSimulation]'s methods with a time offset
/// by [currentIntervalOffset].
///
/// The tolerance of this simulation is pushed to the simulations that are used
/// by this group as they become active. This mean simulations should not be
/// shared among different groups that are active at the same time.
abstract class SimulationGroup extends Simulation {

  /// The currently active simulation.
  ///
  /// This getter should return the same value until [step] is called and
  /// returns true.
  Simulation get currentSimulation;

  /// The time offset applied to the currently active simulation when deferring
  /// [x], [dx], and [isDone] to it.
  double get currentIntervalOffset;

  /// Called when a significant change in the interval is detected. Subclasses
  /// must decide if the current simulation must be switched (or updated).
  ///
  /// Must return true if the simulation was switched in this step, otherwise
  /// false.
  ///
  /// If this function returns true, then [currentSimulation] must start
  /// returning a new value.
  bool step(double time);

  double _lastStep = -1.0;
  void _stepIfNecessary(double time) {
    if (nearEqual(_lastStep, time, Tolerance.defaultTolerance.time))
      return;

    _lastStep = time;
    if (step(time))
      currentSimulation.tolerance = tolerance;
  }

  @override
  double x(double time) {
    _stepIfNecessary(time);
    return currentSimulation.x(time - currentIntervalOffset);
  }

  @override
  double dx(double time) {
    _stepIfNecessary(time);
    return currentSimulation.dx(time - currentIntervalOffset);
  }

  @override
  bool isDone(double time) {
    _stepIfNecessary(time);
    return currentSimulation.isDone(time - currentIntervalOffset);
  }

  @override
  set tolerance(Tolerance t) {
    currentSimulation.tolerance = t;
    super.tolerance = t;
  }
}
