// 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 'package:archive/archive.dart';

import '../application_package.dart';
import '../base/file_system.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../cmake.dart';
import '../cmake_project.dart';
import '../globals.dart' as globals;

abstract class WindowsApp extends ApplicationPackage {
  WindowsApp({required String projectBundleId}) : super(id: projectBundleId);

  /// Creates a new [WindowsApp] from a windows sub project.
  factory WindowsApp.fromWindowsProject(WindowsProject project) {
    return BuildableWindowsApp(
      project: project,
    );
  }

  /// Creates a new [WindowsApp] from an existing executable or a zip archive.
  ///
  /// `applicationBinary` is the path to the executable or the zipped archive.
  static WindowsApp? fromPrebuiltApp(FileSystemEntity applicationBinary) {
    if (!applicationBinary.existsSync()) {
      globals.printError('File "${applicationBinary.path}" does not exist.');
      return null;
    }

    if (applicationBinary.path.endsWith('.exe')) {
      return PrebuiltWindowsApp(
        executable: applicationBinary.path,
        applicationPackage: applicationBinary,
      );
    }

    if (!applicationBinary.path.endsWith('.zip')) {
      // Unknown file type
      globals.printError('Unknown windows application type.');
      return null;
    }

    // Try to unpack as a zip.
    final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_app.');
    try {
      globals.os.unzip(globals.fs.file(applicationBinary), tempDir);
    } on ArchiveException {
      globals.printError('Invalid prebuilt Windows app. Unable to extract from archive.');
      return null;
    }
    final List<FileSystemEntity> exeFilesFound = <FileSystemEntity>[];
    for (final FileSystemEntity file in tempDir.listSync()) {
      if (file.basename.endsWith('.exe')) {
        exeFilesFound.add(file);
      }
    }

    if (exeFilesFound.isEmpty) {
      globals.printError('Cannot find .exe files in the zip archive.');
      return null;
    }

    if (exeFilesFound.length > 1) {
      globals.printError('Archive "${applicationBinary.path}" contains more than one .exe files.');
      return null;
    }

    return PrebuiltWindowsApp(
      executable: exeFilesFound.single.path,
      applicationPackage: applicationBinary,
    );
  }

  @override
  String get displayName => id;

  String executable(BuildMode buildMode);
}

class PrebuiltWindowsApp extends WindowsApp implements PrebuiltApplicationPackage {
  PrebuiltWindowsApp({
    required String executable,
    required this.applicationPackage,
  }) : _executable = executable,
       super(projectBundleId: executable);

  final String _executable;

  @override
  String executable(BuildMode buildMode) => _executable;

  @override
  String get name => _executable;

  @override
  final FileSystemEntity applicationPackage;
}

class BuildableWindowsApp extends WindowsApp {
  BuildableWindowsApp({
    required this.project,
  }) : super(projectBundleId: project.parent.manifest.appName);

  final WindowsProject project;

  @override
  String executable(BuildMode buildMode) {
    final String? binaryName = getCmakeExecutableName(project);
    return globals.fs.path.join(
        getWindowsBuildDirectory(),
        'runner',
        sentenceCase(getNameForBuildMode(buildMode)),
        '$binaryName.exe',
    );
  }

  @override
  String get name => project.parent.manifest.appName;
}