// 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 'binding.dart';

/// Signature for the [onTick] constructor argument of the [Ticker] class.
///
/// The argument is the time that the object had spent enabled so far
/// at the time of the callback being called.
typedef void TickerCallback(Duration elapsed);

/// Calls its callback once per animation frame.
///
/// When created, a ticker is initially disabled. Call [start] to
/// enable the ticker.
///
/// See also [SchedulerBinding.scheduleFrameCallback].
class Ticker {
  /// Creates a ticker that will call [onTick] once per frame while running.
  Ticker(TickerCallback onTick) : _onTick = onTick;

  final TickerCallback _onTick;

  Completer<Null> _completer;
  int _animationId;
  Duration _startTime;

  /// Whether this ticker has scheduled a call to call its callback
  /// on the next frame.
  bool get isTicking => _completer != null;

  /// Starts calling the ticker's callback once per animation frame.
  ///
  /// The returned future resolves once the ticker stops ticking.
  Future<Null> start() {
    assert(!isTicking);
    assert(_startTime == null);
    _completer = new Completer<Null>();
    _scheduleTick();
    return _completer.future;
  }

  /// Stops calling the ticker's callback.
  ///
  /// Causes the future returned by [start] to resolve.
  void stop() {
    if (!isTicking)
      return;

    _startTime = null;

    if (_animationId != null) {
      SchedulerBinding.instance.cancelFrameCallbackWithId(_animationId);
      _animationId = null;
    }

    // We take the _completer into a local variable so that isTicking is false
    // when we actually complete the future (isTicking uses _completer
    // to determine its state).
    Completer<Null> localCompleter = _completer;
    _completer = null;
    assert(!isTicking);
    localCompleter.complete();
  }

  void _tick(Duration timeStamp) {
    assert(isTicking);
    assert(_animationId != null);
    _animationId = null;

    if (_startTime == null)
      _startTime = timeStamp;

    _onTick(timeStamp - _startTime);

    // The onTick callback may have scheduled another tick already.
    if (isTicking && _animationId == null)
      _scheduleTick(rescheduling: true);
  }

  void _scheduleTick({ bool rescheduling: false }) {
    assert(isTicking);
    assert(_animationId == null);
    _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
  }
}