// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
// for details. 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:collection/collection.dart';

import 'config.dart';
import 'os.dart';
import 'syntax.g.dart';

/// The configuration for a C toolchain inside [CodeConfig.cCompiler].
final class CCompilerConfig {
  /// Path to a C compiler.
  final Uri compiler;

  /// Path to a native linker.
  final Uri linker;

  /// Path to a native archiver.
  final Uri archiver;

  final WindowsCCompilerConfig? _windows;

  /// Configuration provided when [CodeConfig.targetOS] is [OS.windows].
  WindowsCCompilerConfig get windows => switch (_windows) {
    null => throw StateError(
      'Cannot access windows if CodeConfig.targetOS is not Windows',
    ),
    final c => c,
  };

  /// Constructs a new [CCompilerConfig] based on the given toolchain tools.
  CCompilerConfig({
    required this.archiver,
    required this.compiler,
    required this.linker,
    WindowsCCompilerConfig? windows,
  }) : _windows = windows;

  @override
  bool operator ==(Object other) {
    if (other is! CCompilerConfig) {
      return false;
    }
    if (other.archiver != archiver) return false;
    if (other.compiler != compiler) return false;
    if (other.linker != linker) return false;
    if (other._windows?.developerCommandPrompt?.script !=
        _windows?.developerCommandPrompt?.script) {
      return false;
    }
    if (!const ListEquality<String>().equals(
      other._windows?.developerCommandPrompt?.arguments,
      _windows?.developerCommandPrompt?.arguments,
    )) {
      return false;
    }
    return true;
  }

  @override
  int get hashCode => Object.hash(
    archiver,
    compiler,
    linker,
    _windows?.developerCommandPrompt?.script,
    const ListEquality<String>().hash(
      _windows?.developerCommandPrompt?.arguments,
    ),
  );
}

/// The configuration provided in [CCompilerConfig.windows] when
/// [CodeConfig.targetOS] is [OS.windows].
final class WindowsCCompilerConfig {
  /// The configuration for the Windows Developer Command Prompt.
  final DeveloperCommandPrompt? developerCommandPrompt;

  /// Constructs a new [WindowsCCompilerConfig].
  WindowsCCompilerConfig({this.developerCommandPrompt});
}

/// The configuration for the Windows Developer Command Prompt in
/// [WindowsCCompilerConfig.developerCommandPrompt].
///
/// Sets up the environment variables for [CCompilerConfig.compiler],
/// [CCompilerConfig.linker], and [CCompilerConfig.archiver] on Windows.
///
/// Specific to [CodeConfig.targetArchitecture].
final class DeveloperCommandPrompt {
  /// The path to the batch script that sets up the environment variables.
  ///
  /// Must be invoked with [arguments].
  ///
  /// This path is typically something such as:
  /// - `C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat`
  /// - `C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\vsdevcmd\ext\vcvars.bat`
  /// - `C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat`
  /// - `C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsamd64_x86.bat`
  ///
  /// Typically run with `%comspec% /k` on Windows to have a terminal with the
  /// right environment variables for subsequent commands.
  final Uri script;

  /// Arguments to invoke [script] with.
  ///
  /// This is empty if [script] already contains [CodeConfig.targetArchitecture]
  /// in its name, and contains the [CodeConfig.targetArchitecture] if the
  /// script name does not.
  final List<String> arguments;

  /// Constructs a new [DeveloperCommandPrompt].
  DeveloperCommandPrompt({required this.script, required this.arguments});
}

/// Extension methods for [CCompilerConfig] to convert to and from the syntax
/// model.
extension CCompilerConfigSyntaxExtension on CCompilerConfig {
  /// Converts this [CCompilerConfig] to its corresponding
  /// [CCompilerConfigSyntax].
  CCompilerConfigSyntax toSyntax() => CCompilerConfigSyntax(
    ar: archiver,
    cc: compiler,
    ld: linker,
    windows: _windows?.toSyntax(),
  );

  /// Converts a [CCompilerConfigSyntax] to its corresponding [CCompilerConfig].
  static CCompilerConfig fromSyntax(CCompilerConfigSyntax cCompiler) =>
      CCompilerConfig(
        archiver: cCompiler.ar,
        compiler: cCompiler.cc,
        linker: cCompiler.ld,
        windows: switch (cCompiler.windows) {
          null => null,
          final windows => WindowsCCompilerConfigSyntaxExtension.fromSyntax(
            windows,
          ),
        },
      );
}

/// Extension methods for [WindowsCCompilerConfig] to convert to and from the
/// syntax model.
extension WindowsCCompilerConfigSyntaxExtension on WindowsCCompilerConfig {
  /// Converts this [WindowsCCompilerConfig] to its corresponding
  /// [WindowsSyntax].
  WindowsSyntax toSyntax() =>
      WindowsSyntax(developerCommandPrompt: developerCommandPrompt?.toSyntax());

  /// Converts a [WindowsSyntax] to its corresponding [WindowsCCompilerConfig].
  static WindowsCCompilerConfig fromSyntax(WindowsSyntax windows) =>
      WindowsCCompilerConfig(
        developerCommandPrompt: switch (windows.developerCommandPrompt) {
          null => null,
          final dcp => DeveloperCommandPromptSyntaxExtension.fromSyntax(dcp),
        },
      );
}

/// Extension methods for [DeveloperCommandPrompt] to convert to and from the
/// syntax model.
extension DeveloperCommandPromptSyntaxExtension on DeveloperCommandPrompt {
  /// Converts this [DeveloperCommandPrompt] to its corresponding
  /// [DeveloperCommandPromptSyntax].
  DeveloperCommandPromptSyntax toSyntax() =>
      DeveloperCommandPromptSyntax(script: script, arguments: arguments);

  /// Converts a [DeveloperCommandPromptSyntax] to its corresponding
  /// [DeveloperCommandPrompt].
  static DeveloperCommandPrompt fromSyntax(DeveloperCommandPromptSyntax dcp) =>
      DeveloperCommandPrompt(script: dcp.script, arguments: dcp.arguments);
}
