// Copyright 2017 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:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:test/test.dart';

import '../src/context.dart';
import '../src/mocks.dart';

void main() {
  group('AppContext', () {
    test('error', () async {
      final BufferLogger mockLogger = new BufferLogger();
      final VerboseLogger verboseLogger = new VerboseLogger(mockLogger);
      verboseLogger.supportsColor = false;

      verboseLogger.printStatus('Hey Hey Hey Hey');
      verboseLogger.printTrace('Oooh, I do I do I do');
      verboseLogger.printError('Helpless!');

      expect(mockLogger.statusText, matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms|       )\] Hey Hey Hey Hey\n'
                                            r'\[ (?: {0,2}\+[0-9]{1,3} ms|       )\] Oooh, I do I do I do\n$'));
      expect(mockLogger.traceText, '');
      expect(mockLogger.errorText, matches(r'^\[ (?: {0,2}\+[0-9]{1,3} ms|       )\] Helpless!\n$'));
    });
  });

  group('Spinners', () {
    MockStdio mockStdio;
    AnsiSpinner ansiSpinner;
    AnsiStatus ansiStatus;
    int called;
    final RegExp secondDigits = new RegExp(r'[^\b]\b\b\b\b\b[0-9]+[.][0-9]+(?:s|ms)');

    setUp(() {
      mockStdio = new MockStdio();
      ansiSpinner = new AnsiSpinner();
      called = 0;
      ansiStatus = new AnsiStatus('Hello world', true, () => called++, 20);
    });

    List<String> outputLines() => mockStdio.writtenToStdout.join('').split('\n');

    Future<void> doWhile(bool doThis()) async {
      return Future.doWhile(() async {
        // Future.doWhile() isn't enough by itself, because the VM never gets
        // around to scheduling the other tasks for some reason.
        await new Future<void>.delayed(const Duration(milliseconds: 0));
        return doThis();
      });
    }

    testUsingContext('AnsiSpinner works', () async {
      ansiSpinner.start();
      await doWhile(() => ansiSpinner.ticks < 10);
      List<String> lines = outputLines();
      expect(lines[0], startsWith(' \b-\b\\\b|\b/\b-\b\\\b|\b/'));
      expect(lines[0].endsWith('\n'), isFalse);
      expect(lines.length, equals(1));
      ansiSpinner.stop();
      lines = outputLines();
      expect(lines[0], endsWith('\b \b'));
      expect(lines.length, equals(1));

      // Verify that stopping multiple times doesn't clear multiple times.
      ansiSpinner.stop();
      lines = outputLines();
      expect(lines[0].endsWith('\b \b '), isFalse);
      expect(lines.length, equals(1));
      ansiSpinner.cancel();
      lines = outputLines();
      expect(lines[0].endsWith('\b \b '), isFalse);
      expect(lines.length, equals(1));
    }, overrides: <Type, Generator>{Stdio: () => mockStdio});

    testUsingContext('AnsiStatus works when cancelled', () async {
      ansiStatus.start();
      await doWhile(() => ansiStatus.ticks < 10);
      List<String> lines = outputLines();
      expect(lines[0], startsWith('Hello world               \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
      expect(lines[0].endsWith('\n'), isFalse);
      expect(lines.length, equals(1));
      ansiStatus.cancel();
      lines = outputLines();
      expect(lines[0], endsWith('\b \b'));
      expect(lines.length, equals(2));
      expect(called, equals(1));
      ansiStatus.cancel();
      lines = outputLines();
      expect(lines[0].endsWith('\b \b\b \b'), isFalse);
      expect(lines.length, equals(2));
      expect(called, equals(1));
      ansiStatus.stop();
      lines = outputLines();
      expect(lines[0].endsWith('\b \b\b \b'), isFalse);
      expect(lines.length, equals(2));
      expect(called, equals(1));
    }, overrides: <Type, Generator>{Stdio: () => mockStdio});

    testUsingContext('AnsiStatus works when stopped', () async {
      ansiStatus.start();
      await doWhile(() => ansiStatus.ticks < 10);
      List<String> lines = outputLines();
      expect(lines[0], startsWith('Hello world               \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
      expect(lines.length, equals(1));

      // Verify a stop prints the time.
      ansiStatus.stop();
      lines = outputLines();
      List<Match> matches = secondDigits.allMatches(lines[0]).toList();
      expect(matches, isNotNull);
      expect(matches, hasLength(1));
      Match  match = matches.first;
      expect(lines[0], endsWith(match.group(0)));
      final String initialTime = match.group(0);
      expect(called, equals(1));
      expect(lines.length, equals(2));
      expect(lines[1], equals(''));

      // Verify stopping more than once generates no additional output.
      ansiStatus.stop();
      lines = outputLines();
      matches = secondDigits.allMatches(lines[0]).toList();
      expect(matches, hasLength(1));
      match = matches.first;
      expect(lines[0], endsWith(initialTime));
      expect(called, equals(1));
      expect(lines.length, equals(2));
      expect(lines[1], equals(''));
    }, overrides: <Type, Generator>{Stdio: () => mockStdio});

    testUsingContext('AnsiStatus works when cancelled', () async {
      ansiStatus.start();
      await doWhile(() => ansiStatus.ticks < 10);
      List<String> lines = outputLines();
      expect(lines[0], startsWith('Hello world               \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
      expect(lines.length, equals(1));

      // Verify a cancel does _not_ print the time and prints a newline.
      ansiStatus.cancel();
      lines = outputLines();
      List<Match> matches = secondDigits.allMatches(lines[0]).toList();
      expect(matches, isEmpty);
      expect(lines[0], endsWith('\b \b'));
      expect(called, equals(1));
      // TODO(jcollins-g): Consider having status objects print the newline
      // when canceled, or never printing a newline at all.
      expect(lines.length, equals(2));

      // Verifying calling stop after cancel doesn't print anything weird.
      ansiStatus.stop();
      lines = outputLines();
      matches = secondDigits.allMatches(lines[0]).toList();
      expect(matches, isEmpty);
      expect(lines[0], endsWith('\b \b'));
      expect(called, equals(1));
      expect(lines[0], isNot(endsWith('\b \b\b \b')));
      expect(lines.length, equals(2));
    }, overrides: <Type, Generator>{Stdio: () => mockStdio});
  });
}