const { Console } = require("console");
const Colors = require("./Colors");
const { inspect } = require("util");
const Timestamp = require("../../util/Timestamp");

/**
 * Komada's console class, extends NodeJS Console class.
 *
 */
class KomadaConsole extends Console {

  /**
   * Constructs our KomadaConsole instance
   * @param {boolean}  [stdout=process.stdout] The location of standard output. Must be a writable stream.
   * @param {boolean}  [stderr=process.stderr] The location of standrad error outputt. Must be a writable stream.
   * @param {boolean} [colors={}] The colors for this console instance.
   * @param {boolean}  [timestamps=false] Whether or not Timestamps should be enabled.
   */
  constructor({ stdout, stderr, useColor, colors = {}, timestamps = true }) {
    super(stdout, stderr);
    /**
     * The standard output stream for this console, defaulted to process.stderr.
     * @name KomadaConsole#stdout
     * @type {WritableStream}
     */
    Object.defineProperty(this, "stdout", { value: stdout });

    /**
     * The standard error output stream for this console, defaulted to process.stderr.
     * @name KomadaConsole#stderr
     * @type {WritableStream}
     */
    Object.defineProperty(this, "stderr", { value: stderr });

    /**
     * Whether or not timestamps should be enabled for this console.
     * @type {boolean}
     */
    this.timestamps = timestamps;

    /**
     * Whether or not this console should use colors.
     * @type {boolean}
     */
    this.useColors = typeof useColor === "undefined" ? this.stdout.isTTY || false : useColor;

    /**
     * The colors for this console.
     * @name KomadaConsole#colors
     * @type  {boolean|Colors}
     */
    this.colors = {
      debug: colors.debug || { message: { background: null, text: null, style: null }, time: { background: null, text: "lightmagenta", style: null } },
      error: colors.error || { message: { background: null, text: null, style: null }, time: { background: "red", text: null, style: null } },
      log: colors.log || { message: { background: null, text: null, style: null }, time: { background: null, text: "lightblue", style: null } },
      verbose: colors.verbose || { message: { background: null, text: "gray", style: null }, time: { background: null, text: "gray", style: null } },
      warn: colors.warn || { message: { background: null, text: null, style: null }, time: { background: "lightyellow", text: "black", style: null } },
      wtf: colors.wtf || { message: { background: "red", text: null, style: ["bold", "underline"] }, time: { background: "red", text: null, style: ["bold", "underline"] } },
    };
  }

  /**
   * @memberof KomadaConsole
   * @typedef {object} Colors - Time is for the timestamp of the log, message is for the actual output.
   * @property {ColorObjects} debug An object containing a message and time color object.
   * @property {ColorObjects} error An object containing a message and time color object.
   * @property {ColorObjects} log An object containing a message and time color object.
   * @property {ColorObjects} verbose An object containing a message and time color object.
   * @property {ColorObjects} warn An object containing a message and time color object.
   * @property {ColorObjects} wtf An object containing a message and time Color Object.
   */

  /**
   * @memberof KomadaConsole
   * @typedef {object} ColorObjects
   * @property {MessageObject} message A message object containing colors and styles.
   * @property {TimeObject} time A time object containing colors and styles.
   */

  /**
   * @memberof KomadaConsole
   * @typedef {object} MessageObject
   * @property {BackgroundColorTypes} background The background color. Can be a basic string like "red", a hex string, or a RGB array.
   * @property {TextColorTypes} text The text color. Can be a basic string like "red", a hex string, or a RGB array.
   * @property {StyleTypes} style A style string from StyleTypes.
   */

  /**
    * @memberof KomadaConsole
    * @typedef {object} TimeObject
    * @property {BackgroundColorTypes} background The background color. Can be a basic string like "red", a hex string, or a RGB array.
    * @property {TextColorTypes} text The text color. Can be a basic string like "red", a hex string, a RGB array, or HSL array.
    * @property {StyleTypes} style A style string from StyleTypes.
    */

  /**
    * @memberof KomadaConsole
    * @typedef {*} TextColorTypes - All the valid color types.
    * @property {string} black
    * @property {string} red
    * @property {string} green
    * @property {string} yellow
    * @property {string} blue
    * @property {string} magenta
    * @property {string} cyan
    * @property {string} gray
    * @property {string} grey
    * @property {string} lightgray
    * @property {string} lightgrey
    * @property {string} lightred
    * @property {string} lightgreen
    * @property {string} lightyellow
    * @property {string} lightblue
    * @property {string} lightmagenta
    * @property {string} lightcyan
    * @property {string} white
    * @property {string} #008000 green
    * @property {Array} [255,0,0] red
    * @property {Array} [229,50%,50%] blue
    */

  /**
    * @memberof KomadaConsole
    * @typedef {*} BackgroundColorTypes - One of these strings, HexStrings, RGB, or HSL are valid types.
    * @property {string} black
    * @property {string} red
    * @property {string} green
    * @property {string} blue
    * @property {string} magenta
    * @property {string} cyan
    * @property {string} gray
    * @property {string} grey
    * @property {string} lightgray
    * @property {string} lightgrey
    * @property {string} lightred
    * @property {string} lightgreen
    * @property {string} lightyellow
    * @property {string} lightblue
    * @property {string} lightmagenta
    * @property {string} lightcyan
    * @property {string} white
    * @property {string} #008000 green
    * @property {Array} [255,0,0] red
    * @property {Array} [229,50%,50%] blue
    */

  /**
    * @memberof KomadaConsole
    * @typedef {*} StyleTypes
    * @property {string} normal
    * @property {string} bold
    * @property {string} dim
    * @property {string} italic
    * @property {string} underline
    * @property {string} inverse
    * @property {string} hidden
    * @property {string} strikethrough
    */

  /**
   * Logs everything to the console/writable stream.
   * @param {*} stuff The stuff we want to print.
   * @param {string} [type="log"] The type of log, particularly useful for coloring.
   */
  log(stuff, type = "log") {
    stuff = KomadaConsole.flatten(stuff, this.useColors);
    const message = this.colors ? this.colors[type.toLowerCase()].message : {};
    const time = this.colors ? this.colors[type.toLowerCase()].time : {};
    const timestamp = this.timestamps ? `${this.timestamp(`[${Timestamp.format(new Date())}]`, time)} ` : "";
    if (this[`_${type}`]) {
      this[`_${type}`](stuff.split("\n").map(str => `${timestamp}${this.messages(str, message)}`).join("\n"));
    } else {
      super.log(stuff.split("\n").map(str => `${timestamp}${this.messages(str, message)}`).join("\n"));
    }
  }

  /**
   * Print something to console as a simple log.
   * @param {*} stuff The stuff to log
   */
  _log(stuff) {
    super.log(stuff);
  }

  /**
   * Print something to console as an error.
   * @param {*} stuff The stuff to log
   */
  _error(stuff) {
    super.error(stuff);
  }

  /**
   * Print something to console as a "WTF" error.
   * @param {*} stuff The stuff to log
   */
  _wtf(stuff) {
    super.error(stuff);
  }

  /**
   * Print something to console as a debug log.
   * @param {*} stuff The stuff to log
   */
  _debug(stuff) {
    super.log(stuff);
  }

  /**
   * Print something to console as a verbose log.
   * @param {*} stuff The stuff to log
   */
  _verbose(stuff) {
    super.log(stuff);
  }

  /**
   * Print something to console as a warning.
   * @param {*} stuff The stuff to log
   */
  _warn(stuff) {
    super.log(stuff);
  }

  timestamp(timestamp, time) {
    if (!this.useColors) return timestamp;
    return `${Colors.format(timestamp, time)} `;
  }

  messages(string, message) {
    if (!this.useColors) return string;
    return Colors.format(string, message);
  }

  /**
   * Flattens our data into a readable string.
   * @param {*} data Some data to flatten
   * @param {boolean} useColors Whether or not the inspection should color the output
   * @return {string}
   */
  static flatten(data, useColors) {
    data = data.stack || data.message || data;
    if (typeof data === "object" && typeof data !== "string" && !Array.isArray(data)) data = inspect(data, { depth: 0, colors: useColors });
    if (Array.isArray(data)) data = data.join("\n");
    return data;
  }

}

module.exports = KomadaConsole;