// Copyright 2014 The Flutter 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:io';

import 'package:args/args.dart';
import 'package:flutter_devicelab/common.dart';

import '../framework/adb.dart';
import '../framework/task_result.dart';
import '../framework/utils.dart';

/// [Task] for defining build-test separation.
///
/// Using this [Task] allows DeviceLab capacity to only be spent on the [test].
abstract class BuildTestTask {
  BuildTestTask(this.args, {this.workingDirectory, this.runFlutterClean = true,}) {
    final ArgResults argResults = argParser.parse(args);
    applicationBinaryPath = argResults[kApplicationBinaryPathOption] as String;
    buildOnly = argResults[kBuildOnlyFlag] as bool;
    testOnly = argResults[kTestOnlyFlag] as bool;

  }

  static const String kApplicationBinaryPathOption = 'application-binary-path';
  static const String kBuildOnlyFlag = 'build';
  static const String kTestOnlyFlag = 'test';

  final ArgParser argParser = ArgParser()
    ..addOption(kApplicationBinaryPathOption)
    ..addFlag(kBuildOnlyFlag)
    ..addFlag(kTestOnlyFlag);

  /// Args passed from the test runner via "--task-arg".
  final List<String> args;

  /// If true, skip [test].
  bool buildOnly = false;

  /// If true, skip [build].
  bool testOnly = false;

  /// Whether to run `flutter clean` before building the application under test.
  final bool runFlutterClean;

  /// Path to a built application to use in [test].
  ///
  /// If not given, will default to child's expected location.
  String applicationBinaryPath;

  /// Where the test artifacts are stored, such as performance results.
  final Directory workingDirectory;

  /// Run Flutter build to create [applicationBinaryPath].
  Future<void> build() async {
    await inDirectory<void>(workingDirectory, () async {
      if (runFlutterClean) {
        section('FLUTTER CLEAN');
        await flutter('clean');
      }
      section('BUILDING APPLICATION');
      await flutter('build', options: getBuildArgs(deviceOperatingSystem));
    });

  }

  /// Run Flutter drive test from [getTestArgs] against the application under test on the device.
  ///
  /// This assumes that [applicationBinaryPath] exists.
  Future<TaskResult> test() async {
    final Device device = await devices.workingDevice;
    await device.unlock();
    await inDirectory<void>(workingDirectory, () async {
      section('DRIVE START');
      await flutter('drive', options: getTestArgs(deviceOperatingSystem, device.deviceId));
    });

    return parseTaskResult();
  }

  /// Args passed to flutter build to build the application under test.
  List<String> getBuildArgs(DeviceOperatingSystem deviceOperatingSystem) => throw UnimplementedError('getBuildArgs is not implemented');

  /// Args passed to flutter drive to test the built application.
  List<String> getTestArgs(DeviceOperatingSystem deviceOperatingSystem, String deviceId) => throw UnimplementedError('getTestArgs is not implemented');

  /// Logic to construct [TaskResult] from this test's results.
  Future<TaskResult> parseTaskResult() => throw UnimplementedError('parseTaskResult is not implemented');

  /// Path to the built application under test.
  ///
  /// Tasks can override to support default values. Otherwise, it will default
  /// to needing to be passed as an argument in the test runner.
  String getApplicationBinaryPath() => applicationBinaryPath;

  /// Run this task.
  ///
  /// Throws [Exception] when unnecessary arguments are passed.
  Future<TaskResult> call() async {
    if (buildOnly && testOnly) {
      throw Exception('Both build and test should not be passed. Pass only one.');
    }

    if (buildOnly && applicationBinaryPath != null) {
      throw Exception('Application binary path is only used for tests');
    }

    if (!testOnly) {
      unawaited(build());
    }

    if (buildOnly) {
      return TaskResult.buildOnly();
    }

    return test();
  }
}
