// Copyright (c) 2012, 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.

// TODO(ahe): Originally copied from sdk/pkg/compiler/lib/src/colors.dart,
// merge these two packages.
library colors;

import 'dart:convert' show jsonEncode;

import 'dart:io' show Platform, Process, ProcessResult, stderr, stdout;

/// ANSI/xterm termcap for setting default colors. Output from Unix
/// command-line program `tput op`.
const String DEFAULT_COLOR = "\x1b[39;49m";

/// ANSI/xterm termcap for setting black text color. Output from Unix
/// command-line program `tput setaf 0`.
const String BLACK_COLOR = "\x1b[30m";

/// ANSI/xterm termcap for setting red text color. Output from Unix
/// command-line program `tput setaf 1`.
const String RED_COLOR = "\x1b[31m";

/// ANSI/xterm termcap for setting green text color. Output from Unix
/// command-line program `tput setaf 2`.
const String GREEN_COLOR = "\x1b[32m";

/// ANSI/xterm termcap for setting yellow text color. Output from Unix
/// command-line program `tput setaf 3`.
const String YELLOW_COLOR = "\x1b[33m";

/// ANSI/xterm termcap for setting blue text color. Output from Unix
/// command-line program `tput setaf 4`.
const String BLUE_COLOR = "\x1b[34m";

/// ANSI/xterm termcap for setting magenta text color. Output from Unix
/// command-line program `tput setaf 5`.
const String MAGENTA_COLOR = "\x1b[35m";

/// ANSI/xterm termcap for setting cyan text color. Output from Unix
/// command-line program `tput setaf 6`.
const String CYAN_COLOR = "\x1b[36m";

/// ANSI/xterm termcap for setting white text color. Output from Unix
/// command-line program `tput setaf 7`.
const String WHITE_COLOR = "\x1b[37m";

/// All the above codes. This is used to compare the above codes to the
/// terminal's. Printing this string should have the same effect as just
/// printing [DEFAULT_COLOR].
const String ALL_CODES =
    BLACK_COLOR +
    RED_COLOR +
    GREEN_COLOR +
    YELLOW_COLOR +
    BLUE_COLOR +
    MAGENTA_COLOR +
    CYAN_COLOR +
    WHITE_COLOR +
    DEFAULT_COLOR;

const String TERMINAL_CAPABILITIES = """
colors
setaf 0
setaf 1
setaf 2
setaf 3
setaf 4
setaf 5
setaf 6
setaf 7
op
""";

/// Boolean value caching whether or not we should display ANSI colors.
///
/// If `null`, we haven't decided whether we should display ANSI colors or not.
bool? _enableColors;

/// Finds out whether we are displaying ANSI colors.
///
/// The first time this getter is invoked (either by a client or by an attempt
/// to use a color), it decides whether colors should be used based on the
/// logic in [_computeEnableColors] (unless a value has previously been set).
bool get enableColors => _enableColors ??= _computeEnableColors();

/// Allows the client to override the decision of whether to disable ANSI
/// colors.
void set enableColors(bool value) {
  _enableColors = value;
}

String wrap(String string, String color) {
  return enableColors ? "${color}$string${DEFAULT_COLOR}" : string;
}

String black(String string) => wrap(string, BLACK_COLOR);
String red(String string) => wrap(string, RED_COLOR);
String green(String string) => wrap(string, GREEN_COLOR);
String yellow(String string) => wrap(string, YELLOW_COLOR);
String blue(String string) => wrap(string, BLUE_COLOR);
String magenta(String string) => wrap(string, MAGENTA_COLOR);
String cyan(String string) => wrap(string, CYAN_COLOR);
String white(String string) => wrap(string, WHITE_COLOR);

/// Callback used by [_computeEnableColors] to report why it has or hasn't
/// chosen to use ANSI colors.
void Function(String) printEnableColorsReason = (_) {};

/// True if we should enable colors in output.
///
/// We enable colors when both `stdout` and `stderr` support ANSI escapes.
///
/// On non-Windows platforms, this functions checks the terminal capabilities,
/// on Windows we only enable colors if the VM getters are present and returned
/// `true`.
///
/// Note: do not call this method directly, as it is expensive to
/// compute. Instead, use [enableColors].
bool _computeEnableColors() {
  if (!stdout.supportsAnsiEscapes) {
    printEnableColorsReason(
      "Not enabling colors, stdout does not support ANSI colors.",
    );
    return false;
  }
  if (!stderr.supportsAnsiEscapes) {
    printEnableColorsReason(
      "Not enabling colors, stderr does not support ANSI colors.",
    );
    return false;
  }

  if (Platform.isWindows) {
    printEnableColorsReason("Enabling colors as OS is Windows.");
    return true;
  }

  // We have to check if the terminal actually supports colors. Currently,
  // to avoid linking the Dart VM with ncurses, ANSI escape support is reduced
  // to `Platform.environment['TERM'].contains("xterm")`.

  // The `-S` option of `tput` allows us to query multiple capabilities at
  // once.
  ProcessResult result = Process.runSync("/bin/sh", [
    "-c",
    "printf '%s' '$TERMINAL_CAPABILITIES' | tput -S",
  ]);

  if (result.exitCode != 0) {
    printEnableColorsReason("Not enabling colors, running tput failed.");
    return false;
  }

  List<String> lines = result.stdout.split("\n");

  if (lines.length != 2) {
    printEnableColorsReason(
      "Not enabling colors, unexpected output from tput: "
      "${jsonEncode(result.stdout)}.",
    );
    return false;
  }

  String numberOfColors = lines[0];
  if ((int.tryParse(numberOfColors) ?? -1) < 8) {
    printEnableColorsReason(
      "Not enabling colors, less than 8 colors supported: "
      "${jsonEncode(numberOfColors)}.",
    );
    return false;
  }

  String allCodes = lines[1].trim();
  if (ALL_CODES != allCodes) {
    printEnableColorsReason(
      "Not enabling colors, color codes don't match: "
      "${jsonEncode(ALL_CODES)} != ${jsonEncode(allCodes)}.",
    );
    return false;
  }

  printEnableColorsReason("Enabling colors.");
  return true;
}
