// 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/artifacts.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/compile.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/tester/flutter_tester.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';

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

void main() {
  MemoryFileSystem fs;

  setUp(() {
    fs = 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 = FlutterTesterApp.fromCurrentDirectory();
      expect(app.name, 'my_project');
      expect(app.packagesFile.path, fs.path.join(projectPath, '.packages'));
    }, overrides: <Type, Generator>{
      FileSystem: () => fs,
    });
  });

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

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

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

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

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

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

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

    setUp(() {
      device = 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, isNot(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;

      MockArtifacts mockArtifacts;
      MockKernelCompiler mockKernelCompiler;
      MockProcessManager mockProcessManager;
      MockProcess mockProcess;

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

      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 = MockProcessManager();
        mockProcessManager.processFactory =
            (List<String> commands) => mockProcess;

        mockArtifacts = MockArtifacts();
        final String artifactPath = fs.path.join(flutterRoot, 'artifact');
        fs.file(artifactPath).createSync(recursive: true);
        when(mockArtifacts.getArtifactPath(any)).thenReturn(artifactPath);

        mockKernelCompiler = MockKernelCompiler();
      });

      testUsingContext('not debug', () async {
        final LaunchResult result = await device.startApp(null,
            mainPath: mainPath,
            debuggingOptions: 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: 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 = MockProcess(
            stdout: Stream<List<int>>.fromIterable(<List<int>>[
          '''
Observatory listening on $observatoryUri
Hello!
'''
              .codeUnits,
        ]));

        when(mockKernelCompiler.compile(
          sdkRoot: anyNamed('sdkRoot'),
          incrementalCompilerByteStorePath: anyNamed('incrementalCompilerByteStorePath'),
          mainPath: anyNamed('mainPath'),
          outputFilePath: anyNamed('outputFilePath'),
          depFilePath: anyNamed('depFilePath'),
          trackWidgetCreation: anyNamed('trackWidgetCreation'),
          extraFrontEndOptions: anyNamed('extraFrontEndOptions'),
          fileSystemRoots: anyNamed('fileSystemRoots'),
          fileSystemScheme: anyNamed('fileSystemScheme'),
          packagesPath: anyNamed('packagesPath'),
        )).thenAnswer((_) async {
          fs.file('$mainPath.dill').createSync(recursive: true);
          return CompilerOutput('$mainPath.dill', 0, <Uri>[]);
        });

        final LaunchResult result = await device.startApp(null,
            mainPath: mainPath,
            debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null)));
        expect(result.started, isTrue);
        expect(result.observatoryUri, observatoryUri);
        expect(logLines.last, 'Hello!');
      }, overrides: startOverrides);
    });
  });
}

class MockArtifacts extends Mock implements Artifacts {}
class MockKernelCompiler extends Mock implements KernelCompiler {}
class FakeKernelCompilerFactory implements KernelCompilerFactory {
  FakeKernelCompilerFactory(this.kernelCompiler);

  final KernelCompiler kernelCompiler;

  @override
  Future<KernelCompiler> create(FlutterProject flutterProject) async {
    return kernelCompiler;
  }
}