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

/// Performs an action and returns either the result of the action or a [Future]
/// that evaluates to the result.
typedef dynamic Action();

/// Performs [action] repeatedly until it either succeeds or [timeout] limit is
/// reached.
///
/// When the retry time out, the last seen error and stack trace are returned in
/// an error [Future].
Future<dynamic> retry(Action action, Duration timeout,
    Duration pauseBetweenRetries) async {
  assert(action != null);
  assert(timeout != null);
  assert(pauseBetweenRetries != null);

  Stopwatch sw = stopwatchFactory()..start();
  dynamic result = null;
  dynamic lastError = null;
  dynamic lastStackTrace = null;
  bool success = false;

  while(!success && sw.elapsed < timeout) {
    try {
      result = await action();
      success = true;
    } catch(error, stackTrace) {
      lastError = error;
      lastStackTrace = stackTrace;
      if (sw.elapsed < timeout) {
        await new Future<Null>.delayed(pauseBetweenRetries);
      }
    }
  }

  if (success)
    return result;
  else
    return new Future.error(lastError, lastStackTrace);
}

/// A function that produces a [Stopwatch].
typedef Stopwatch StopwatchFactory();

/// Restores [stopwatchFactory] to the default implementation.
void restoreStopwatchFactory() {
  stopwatchFactory = () => new Stopwatch();
}

/// Used by [retry] as a source of [Stopwatch] implementation.
StopwatchFactory stopwatchFactory = () => new Stopwatch();