// Copyright 2018 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:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/tester/flutter_tester.dart';
import 'package:process/process.dart';
import 'package:test/test.dart';

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

void main() {
  MemoryFileSystem fs;

  setUp(() {
    fs = new MemoryFileSystem();
  });

  group('FlutterTesterApp', () {
    testUsingContext('fromCurrentDirectory', () async {
      const String projectPath = '/home/my/projects/my_project';
      await fs.directory(projectPath).create(recursive: true);
      fs.currentDirectory = projectPath;

      final FlutterTesterApp app = new FlutterTesterApp.fromCurrentDirectory();
      expect(app.name, 'my_project');
      expect(app.packagePath, fs.path.join(projectPath, '.packages'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fs,
    });
  });

  group('FlutterTesterDevices', () {
    tearDown(() {
      FlutterTesterDevices.showFlutterTesterDevice = false;
    });

    testUsingContext('no device', () async {
      final FlutterTesterDevices discoverer = new FlutterTesterDevices();

      final List<Device> devices = await discoverer.devices;
      expect(devices, isEmpty);
    });

    testUsingContext('has device', () async {
      FlutterTesterDevices.showFlutterTesterDevice = true;
      final FlutterTesterDevices discoverer = new FlutterTesterDevices();

      final List<Device> devices = await discoverer.devices;
      expect(devices, hasLength(1));

      final Device device = devices.single;
      expect(device, const isInstanceOf<FlutterTesterDevice>());
      expect(device.id, 'flutter-tester');
    });
  });

  group('FlutterTesterDevice', () {
    FlutterTesterDevice device;
    List<String> logLines;

    setUp(() {
      device = new FlutterTesterDevice('flutter-tester');

      logLines = <String>[];
      device.getLogReader().logLines.listen(logLines.add);
    });

    testUsingContext('getters', () async {
      expect(device.id, 'flutter-tester');
      expect(await device.isLocalEmulator, isFalse);
      expect(device.name, 'Flutter test device');
      expect(device.portForwarder, isNull);
      expect(await device.targetPlatform, TargetPlatform.tester);

      expect(await device.installApp(null), isTrue);
      expect(await device.isAppInstalled(null), isFalse);
      expect(await device.isLatestBuildInstalled(null), isFalse);
      expect(await device.uninstallApp(null), isTrue);

      expect(device.isSupported(), isTrue);
    });

    group('startApp', () {
      String flutterRoot;
      String flutterTesterPath;

      String projectPath;
      String mainPath;

      MockProcessManager mockProcessManager;
      MockProcess mockProcess;

      final Map<Type, Generator> startOverrides = <Type, Generator>{
        Platform: () => new FakePlatform(operatingSystem: 'linux'),
        FileSystem: () => fs,
        Cache: () => new Cache(rootOverride: fs.directory(flutterRoot)),
        ProcessManager: () => mockProcessManager,
      };

      setUp(() {
        flutterRoot = fs.path.join('home', 'me', 'flutter');
        flutterTesterPath = fs.path.join(flutterRoot, 'bin', 'cache',
            'artifacts', 'engine', 'linux-x64', 'flutter_tester');

        final File flutterTesterFile = fs.file(flutterTesterPath);
        flutterTesterFile.parent.createSync(recursive: true);
        flutterTesterFile.writeAsBytesSync(const <int>[]);

        projectPath = fs.path.join('home', 'me', 'hello');
        mainPath = fs.path.join(projectPath, 'lin', 'main.dart');

        mockProcessManager = new MockProcessManager();
        mockProcessManager.processFactory =
            (List<String> commands) => mockProcess;
      });

      testUsingContext('not debug', () async {
        final LaunchResult result = await device.startApp(null,
            mainPath: mainPath,
            debuggingOptions: new DebuggingOptions.disabled(
                const BuildInfo(BuildMode.release, null)));
        expect(result.started, isFalse);
      }, overrides: startOverrides);

      testUsingContext('no flutter_tester', () async {
        fs.file(flutterTesterPath).deleteSync();
        expect(() async {
          await device.startApp(null,
              mainPath: mainPath,
              debuggingOptions: new DebuggingOptions.disabled(
                  const BuildInfo(BuildMode.debug, null)));
        }, throwsToolExit());
      }, overrides: startOverrides);

      testUsingContext('start', () async {
        final Uri observatoryUri = Uri.parse('http://127.0.0.1:6666/');
        mockProcess = new MockProcess(
            stdout: new Stream<List<int>>.fromIterable(<List<int>>[
          '''
Observatory listening on $observatoryUri
Hello!
'''
              .codeUnits
        ]));

        final LaunchResult result = await device.startApp(null,
            mainPath: mainPath,
            debuggingOptions: new DebuggingOptions.enabled(
                const BuildInfo(BuildMode.debug, null)));
        expect(result.started, isTrue);
        expect(result.observatoryUri, observatoryUri);

        expect(logLines.last, 'Hello!');
      }, overrides: startOverrides);
    });
  });
}