// 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:async";
import "dart:io";

import "package:path/path.dart" as path;

import "../runner/flutter_command_runner.dart";
import "../runner/flutter_command.dart";
import "../artifacts.dart";

class IOSCommand extends FlutterCommand {
  final String name = "ios";

  final String description = "Commands for creating and updating Flutter iOS projects";

  final bool requiresProjectRoot = true;

  IOSCommand() {
    argParser.addFlag('init', help: 'Initialize the Xcode project for building the iOS application');
  }

  static Uri _xcodeProjectUri(String revision) {
    String uriString = "https://storage.googleapis.com/flutter_infra/flutter/$revision/ios/FlutterXcode.zip";
    print("Downloading $uriString ...");
    return Uri.parse(uriString);
  }

  Future<List<int>> _fetchXcodeArchive() async {
    print("Fetching the Xcode project archive from the cloud...");

    HttpClient client = new HttpClient();

    HttpClientRequest request = await client.getUrl(_xcodeProjectUri(ArtifactStore.engineRevision));
    HttpClientResponse response = await request.close();

    if (response.statusCode != 200)
      throw new Exception(response.reasonPhrase);

    BytesBuilder bytesBuilder = new BytesBuilder(copy: false);
    await for (List<int> chunk in response)
      bytesBuilder.add(chunk);

    return bytesBuilder.takeBytes();
  }

  Future<bool> _inflateXcodeArchive(String directory, List<int> archiveBytes) async {
    print("Unzipping Xcode project to local directory...");

    if (archiveBytes.isEmpty)
      return false;

    // We cannot use ArchiveFile because this archive contains file that are exectuable
    // and there is currently no provision to modify file permissions during
    // or after creation. See https://github.com/dart-lang/sdk/issues/15078
    // So we depend on the platform to unzip the archive for us.

    Directory tempDir = await Directory.systemTemp.create();
    File tempFile = await new File(path.join(tempDir.path, "FlutterXcode.zip")).create();
    IOSink writeSink = tempFile.openWrite();
    writeSink.add(archiveBytes);
    await writeSink.close();

    ProcessResult result = await Process.run('/usr/bin/unzip', [tempFile.path, '-d', directory]);

    // Cleanup the temp directory after unzipping
    await Process.run('/bin/rm', ['rf', tempDir.path]);

    if (result.exitCode != 0)
      return false;

    Directory flutterDir = new Directory(path.join(directory, 'Flutter'));
    bool flutterDirExists = await flutterDir.exists();
    if (!flutterDirExists)
      return false;

    // Move contents of the Flutter directory one level up
    // There is no dart:io API to do this. See https://github.com/dart-lang/sdk/issues/8148

    bool moveFailed = false;
    for (FileSystemEntity file in flutterDir.listSync()) {
      ProcessResult result = await Process.run('/bin/mv', [file.path, directory]);
      moveFailed = result.exitCode != 0;
      if (moveFailed)
        break;
    }

    ProcessResult rmResult = await Process.run('/bin/rm', ["-rf", flutterDir.path]);

    return !moveFailed && rmResult.exitCode == 0;
  }

  Future<bool> _setupXcodeProjXcconfig(String filePath) async {
    StringBuffer localsBuffer = new StringBuffer();

    localsBuffer.writeln("// Generated. Do not edit or check into version control!!");
    localsBuffer.writeln("// Recreate using `flutter ios`");

    String flutterRoot = path.normalize(Platform.environment[kFlutterRootEnvironmentVariableName]);
    localsBuffer.writeln("FLUTTER_ROOT=$flutterRoot");

    // This holds because requiresProjectRoot is true for this command
    String applicationRoot = path.normalize(Directory.current.path);
    localsBuffer.writeln("FLUTTER_APPLICATION_PATH=$applicationRoot");

    String dartSDKPath = path.normalize(path.join(Platform.resolvedExecutable, "..", ".."));
    localsBuffer.writeln("DART_SDK_PATH=$dartSDKPath");

    File localsFile = new File(filePath);
    await localsFile.create(recursive: true);
    await localsFile.writeAsString(localsBuffer.toString());

    return true;
  }

  Future<int> _runInitCommand() async {
    // Step 1: Fetch the archive from the cloud
    String xcodeprojPath = path.join(Directory.current.path, "ios");
    List<int> archiveBytes = await _fetchXcodeArchive();

    // Step 2: Inflate the archive into the user project directory
    bool result = await _inflateXcodeArchive(xcodeprojPath, archiveBytes);
    if (!result) {
      print("Could not fetch the Xcode project from the cloud...");
      return -1;
    }

    // Step 3: Populate the Local.xcconfig with project specific paths
    result = await _setupXcodeProjXcconfig(path.join(xcodeprojPath, "Local.xcconfig"));
    if (!result) {
      print("Could not setup local properties file");
      return -1;
    }

    // Step 4: Launch Xcode and let the user edit plist, resources, provisioning, etc.
    print("Launching project in Xcode...");
    ProcessResult launch = await Process.run("/usr/bin/open", ["ios/FlutterApplication.xcodeproj"]);
    return launch.exitCode;
  }

  @override
  Future<int> runInProject() async {
    if (!Platform.isMacOS) {
      print("iOS specific commands may only be run on a Mac.");
      return -1;
    }

    if (argResults['init'])
      return await _runInitCommand();

    print("No flags specified...");
    return -1;
  }
}