/*
 This file is part of TALER
 (C) 2019 GNUnet e.V.
 TALER is free software; you can redistribute it and/or modify it under the
 terms of the GNU General Public License as published by the Free Software
 Foundation; either version 3, or (at your option) any later version.
 TALER is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 You should have received a copy of the GNU General Public License along with
 TALER; see the file COPYING.  If not, see 
 */
/**
 * Check if we are running under nodejs.
 */
const isNode =
  typeof process !== "undefined" &&
  typeof process.release !== "undefined" &&
  process.release.name === "node";
export enum LogLevel {
  Trace = "trace",
  Message = "message",
  Info = "info",
  Warn = "warn",
  Error = "error",
  None = "none",
}
let globalLogLevel = LogLevel.Info;
const byTagLogLevel: Record = {};
export function getGlobalLogLevel(): string {
  return globalLogLevel;
}
export function setGlobalLogLevelFromString(logLevelStr: string): void {
  globalLogLevel = getLevelForString(logLevelStr);
}
export function setLogLevelFromString(tag: string, logLevelStr: string): void {
  byTagLogLevel[tag] = getLevelForString(logLevelStr);
}
function getLevelForString(logLevelStr: string): LogLevel {
  switch (logLevelStr.toLowerCase()) {
    case "trace":
      return LogLevel.Trace;
    case "info":
      return LogLevel.Info;
    case "warn":
    case "warning":
      return LogLevel.Warn;
    case "error":
      return LogLevel.Error;
    case "none":
      return LogLevel.None;
    default:
      if (isNode) {
        process.stderr.write(`Invalid log level, defaulting to WARNING\n`);
      } else {
        console.warn(`Invalid log level, defaulting to WARNING`);
      }
      return LogLevel.Warn;
  }
}
function writeNodeLog(
  message: any,
  tag: string,
  level: string,
  args: any[],
): void {
  try {
    let msg = `${new Date().toISOString()} ${tag} ${level} ${message}`;
    if (args.length != 0) {
      msg += ` ${JSON.stringify(args, undefined, 2)}\n`;
    } else {
      msg += `\n`;
    }
    process.stderr.write(msg);
  } catch (e) {
    // This can happen when we're trying to log something that doesn't want to be
    // converted to a string.
    let msg = `${new Date().toISOString()} (logger) FATAL `;
    if (e instanceof Error) {
      msg += `failed to write log: ${e.message}\n`;
    } else {
      msg += "failed to write log\n";
    }
    process.stderr.write(msg);
  }
}
/**
 * Logger that writes to stderr when running under node,
 * and uses the corresponding console.* method to log in the browser.
 */
export class Logger {
  constructor(private tag: string) { }
  shouldLogTrace(): boolean {
    const level = byTagLogLevel[this.tag] ?? globalLogLevel;
    switch (level) {
      case LogLevel.Trace:
        return true;
      case LogLevel.Message:
      case LogLevel.Info:
      case LogLevel.Warn:
      case LogLevel.Error:
      case LogLevel.None:
        return false;
    }
  }
  shouldLogInfo(): boolean {
    const level = byTagLogLevel[this.tag] ?? globalLogLevel;
    switch (level) {
      case LogLevel.Trace:
      case LogLevel.Message:
      case LogLevel.Info:
        return true;
      case LogLevel.Warn:
      case LogLevel.Error:
      case LogLevel.None:
        return false;
    }
  }
  shouldLogWarn(): boolean {
    const level = byTagLogLevel[this.tag] ?? globalLogLevel;
    switch (level) {
      case LogLevel.Trace:
      case LogLevel.Message:
      case LogLevel.Info:
      case LogLevel.Warn:
        return true;
      case LogLevel.Error:
      case LogLevel.None:
        return false;
    }
  }
  shouldLogError(): boolean {
    const level = byTagLogLevel[this.tag] ?? globalLogLevel;
    switch (level) {
      case LogLevel.Trace:
      case LogLevel.Message:
      case LogLevel.Info:
      case LogLevel.Warn:
      case LogLevel.Error:
        return true;
      case LogLevel.None:
        return false;
    }
  }
  info(message: string, ...args: any[]): void {
    if (!this.shouldLogInfo()) {
      return;
    }
    if (isNode) {
      writeNodeLog(message, this.tag, "INFO", args);
    } else {
      console.info(
        `${new Date().toISOString()} ${this.tag} INFO ` + message,
        ...args,
      );
    }
  }
  warn(message: string, ...args: any[]): void {
    if (!this.shouldLogWarn()) {
      return;
    }
    if (isNode) {
      writeNodeLog(message, this.tag, "WARN", args);
    } else {
      console.warn(
        `${new Date().toISOString()} ${this.tag} INFO ` + message,
        ...args,
      );
    }
  }
  error(message: string, ...args: any[]): void {
    if (!this.shouldLogError()) {
      return;
    }
    if (isNode) {
      writeNodeLog(message, this.tag, "ERROR", args);
    } else {
      console.info(
        `${new Date().toISOString()} ${this.tag} ERROR ` + message,
        ...args,
      );
    }
  }
  trace(message: string, ...args: any[]): void {
    if (!this.shouldLogTrace()) {
      return;
    }
    if (isNode) {
      writeNodeLog(message, this.tag, "TRACE", args);
    } else {
      console.info(
        `${new Date().toISOString()} ${this.tag} TRACE ` + message,
        ...args,
      );
    }
  }
  reportBreak(): void {
    if (!this.shouldLogError()) {
      return;
    }
    const location = new Error("programming error");
    this.error(`assertion failed: ${location.stack}`);
  }
}