// Copyright 2015 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 'dart:async';

import 'package:newton/newton.dart';

import 'package:sky/src/animation/animated_simulation.dart';

/// A simulation that linearly varies from [begin] to [end] over [duration]
class TweenSimulation extends Simulation {
  final double _durationInSeconds;

  /// The initial value of the simulation
  final double begin;

  /// The terminal value of the simulation
  final double end;

  TweenSimulation(Duration duration, this.begin, this.end) :
      _durationInSeconds = duration.inMicroseconds / Duration.MICROSECONDS_PER_SECOND {
    assert(_durationInSeconds > 0.0);
    assert(begin != null && begin >= 0.0 && begin <= 1.0);
    assert(end != null && end >= 0.0 && end <= 1.0);
  }

  double x(double timeInSeconds) {
    assert(timeInSeconds >= 0.0);
    final double t = timeInSeconds / _durationInSeconds;
    return t >= 1.0 ? end : begin + (end - begin) * t;
  }

  double dx(double timeInSeconds) => 1.0;

  bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds;
}

/// A timeline for an animation
class Timeline {
  Timeline(Function onTick) {
    _animation = new AnimatedSimulation(onTick);
  }

  AnimatedSimulation _animation;

  /// The current value of the timeline
  double get value => _animation.value.clamp(0.0, 1.0);
  void set value(double newValue) {
    assert(newValue != null && newValue >= 0.0 && newValue <= 1.0);
    assert(!isAnimating);
    _animation.value = newValue;
  }

  /// Whether the timeline is currently animating
  bool get isAnimating => _animation.isAnimating;

  Future _start({
    Duration duration,
    double begin: 0.0,
    double end: 1.0
  }) {
    assert(!_animation.isAnimating);
    assert(duration > Duration.ZERO);
    return _animation.start(new TweenSimulation(duration, begin, end));
  }

  /// Animate value of the timeline to the given target over the given duration
  ///
  /// Returns a future that resolves when the timeline stops animating,
  /// typically when the timeline arives at the target value.
  Future animateTo(double target, { Duration duration }) {
    assert(duration > Duration.ZERO);
    return _start(duration: duration, begin: value, end: target);
  }

  /// Stop animating the timeline
  void stop() {
    _animation.stop();
  }

  // Gives the given simulation control over the timeline
  Future fling(Simulation simulation) {
    stop();
    return _animation.start(simulation);
  }
}