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

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
import 'package:test/test.dart';

final List<String> results = <String>[];

Set<TestRoute> routes = new HashSet<TestRoute>();

class TestRoute extends Route<String> {
  TestRoute(this.name);
  final String name;

  @override
  List<OverlayEntry> get overlayEntries => _entries;

  List<OverlayEntry> _entries = <OverlayEntry>[];

  void log(String s) {
    results.add('$name: $s');
  }

  @override
  void install(OverlayEntry insertionPoint) {
    log('install');
    OverlayEntry entry = new OverlayEntry(
      builder: (BuildContext context) => new Container(),
      opaque: true
    );
    _entries.add(entry);
    navigator.overlay?.insert(entry, above: insertionPoint);
    routes.add(this);
  }

  @override
  void didPush() {
    log('didPush');
  }

  @override
  void didReplace(TestRoute oldRoute) {
    log('didReplace ${oldRoute.name}');
  }

  @override
  bool didPop(String result) {
    log('didPop $result');
    bool returnValue;
    if (returnValue = super.didPop(result))
      dispose();
    return returnValue;
  }

  @override
  void didPopNext(TestRoute nextRoute) {
    log('didPopNext ${nextRoute.name}');
  }

  @override
  void didChangeNext(TestRoute nextRoute) {
    log('didChangeNext ${nextRoute?.name}');
  }

  @override
  void dispose() {
    log('dispose');
    _entries.forEach((OverlayEntry entry) { entry.remove(); });
    _entries.clear();
    routes.remove(this);
  }

}

void runNavigatorTest(
  WidgetTester tester,
  NavigatorState host,
  NavigatorTransactionCallback test,
  List<String> expectations
) {
  expect(host, isNotNull);
  host.openTransaction(test);
  expect(results, equals(expectations));
  results.clear();
  tester.pump();
}

void main() {
  test('Route management - push, replace, pop', () {
    testWidgets((WidgetTester tester) {
      GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
      tester.pumpWidget(new Navigator(
        key: navigatorKey,
        onGenerateRoute: (_) => new TestRoute('initial')
      ));
      NavigatorState host = navigatorKey.currentState;
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
        },
        [
          'initial: install',
          'initial: didPush',
          'initial: didChangeNext null',
        ]
      );
      TestRoute second;
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.push(second = new TestRoute('second'));
        },
        [
          'second: install',
          'second: didPush',
          'second: didChangeNext null',
          'initial: didChangeNext second',
        ]
      );
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.push(new TestRoute('third'));
        },
        [
          'third: install',
          'third: didPush',
          'third: didChangeNext null',
          'second: didChangeNext third',
        ]
      );
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.replace(oldRoute: second, newRoute: new TestRoute('two'));
        },
        [
          'two: install',
          'two: didReplace second',
          'two: didChangeNext third',
          'initial: didChangeNext two',
          'second: dispose',
        ]
      );
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.pop('hello');
        },
        [
          'third: didPop hello',
          'third: dispose',
          'two: didPopNext third',
        ]
      );
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.pop('good bye');
        },
        [
          'two: didPop good bye',
          'two: dispose',
          'initial: didPopNext two',
        ]
      );
      tester.pumpWidget(new Container());
      expect(results, equals(['initial: dispose']));
      expect(routes.isEmpty, isTrue);
      results.clear();
    });
  });

  test('Route management - push, remove, pop', () {
    testWidgets((WidgetTester tester) {
      GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
      tester.pumpWidget(new Navigator(
        key: navigatorKey,
        onGenerateRoute: (_) => new TestRoute('first')
      ));
      NavigatorState host = navigatorKey.currentState;
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
        },
        [
          'first: install',
          'first: didPush',
          'first: didChangeNext null',
        ]
      );
      TestRoute second;
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.push(second = new TestRoute('second'));
        },
        [
          'second: install',
          'second: didPush',
          'second: didChangeNext null',
          'first: didChangeNext second',
        ]
      );
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.push(new TestRoute('third'));
        },
        [
          'third: install',
          'third: didPush',
          'third: didChangeNext null',
          'second: didChangeNext third',
        ]
      );
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.removeRouteBefore(second);
        },
        [
          'first: dispose',
        ]
      );
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.pop('good bye');
        },
        [
          'third: didPop good bye',
          'third: dispose',
          'second: didPopNext third',
        ]
      );
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.push(new TestRoute('three'));
        },
        [
          'three: install',
          'three: didPush',
          'three: didChangeNext null',
          'second: didChangeNext three',
        ]
      );
      TestRoute four;
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.push(four = new TestRoute('four'));
        },
        [
          'four: install',
          'four: didPush',
          'four: didChangeNext null',
          'three: didChangeNext four',
        ]
      );
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.removeRouteBefore(four);
        },
        [
          'second: didChangeNext four',
          'three: dispose',
        ]
      );
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.pop('the end');
        },
        [
          'four: didPop the end',
          'four: dispose',
          'second: didPopNext four',
        ]
      );
      tester.pumpWidget(new Container());
      expect(results, equals(['second: dispose']));
      expect(routes.isEmpty, isTrue);
      results.clear();
    });
  });

  test('Route management - push, replace, popUntil', () {
    testWidgets((WidgetTester tester) {
      GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
      tester.pumpWidget(new Navigator(
        key: navigatorKey,
        onGenerateRoute: (_) => new TestRoute('A')
      ));
      NavigatorState host = navigatorKey.currentState;
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
        },
        [
          'A: install',
          'A: didPush',
          'A: didChangeNext null',
        ]
      );
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.push(new TestRoute('B'));
        },
        [
          'B: install',
          'B: didPush',
          'B: didChangeNext null',
          'A: didChangeNext B',
        ]
      );
      TestRoute routeC;
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.push(routeC = new TestRoute('C'));
        },
        [
          'C: install',
          'C: didPush',
          'C: didChangeNext null',
          'B: didChangeNext C',
        ]
      );
      TestRoute routeB;
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.replaceRouteBefore(anchorRoute: routeC, newRoute: routeB = new TestRoute('b'));
        },
        [
          'b: install',
          'b: didReplace B',
          'b: didChangeNext C',
          'A: didChangeNext b',
          'B: dispose',
        ]
      );
      runNavigatorTest(
        tester,
        host,
        (NavigatorTransaction transaction) {
          transaction.popUntil(routeB);
        },
        [
          'C: didPop null',
          'C: dispose',
          'b: didPopNext C',
        ]
      );
      tester.pumpWidget(new Container());
      expect(results, equals(['A: dispose', 'b: dispose']));
      expect(routes.isEmpty, isTrue);
      results.clear();
    });
  });
}
