using System;
using System.Collections.Generic;

namespace Unosquare.Swan {
  /// <summary>
  /// A console terminal helper to create nicer output and receive input from the user
  /// This class is thread-safe :).
  /// </summary>
  public static partial class Terminal {
    #region ReadKey

    /// <summary>
    /// Reads a key from the Terminal. This is the closest equivalent to Console.ReadKey.
    /// </summary>
    /// <param name="intercept">if set to <c>true</c> the pressed key will not be rendered to the output.</param>
    /// <param name="disableLocking">if set to <c>true</c> the output will continue to be shown.
    /// This is useful for services and daemons that are running as console applications and wait for a key to exit the program.</param>
    /// <returns>The console key information.</returns>
    public static ConsoleKeyInfo ReadKey(Boolean intercept, Boolean disableLocking = false) {
      if(IsConsolePresent == false) {
        return default;
      }

      if(disableLocking) {
        return Console.ReadKey(intercept);
      }

      lock(SyncLock) {
        Flush();
        InputDone.Reset();
        try {
          Console.CursorVisible = true;
          return Console.ReadKey(intercept);
        } finally {
          Console.CursorVisible = false;
          InputDone.Set();
        }
      }
    }

    /// <summary>
    /// Reads a key from the Terminal.
    /// </summary>
    /// <param name="prompt">The prompt.</param>
    /// <param name="preventEcho">if set to <c>true</c> [prevent echo].</param>
    /// <returns>The console key information.</returns>
    public static ConsoleKeyInfo ReadKey(this String prompt, Boolean preventEcho) {
      if(IsConsolePresent == false) {
        return default;
      }

      lock(SyncLock) {
        if(prompt != null) {
          ($" {(String.IsNullOrWhiteSpace(Settings.LoggingTimeFormat) ? String.Empty : DateTime.Now.ToString(Settings.LoggingTimeFormat) + " ")}" +
              $"{Settings.UserInputPrefix} << {prompt} ").Write(ConsoleColor.White);
        }

        ConsoleKeyInfo input = ReadKey(true);
        String echo = preventEcho ? String.Empty : input.Key.ToString();
        echo.WriteLine();
        return input;
      }
    }

    /// <summary>
    /// Reads a key from the terminal preventing the key from being echoed.
    /// </summary>
    /// <param name="prompt">The prompt.</param>
    /// <returns>A value that identifies the console key.</returns>
    public static ConsoleKeyInfo ReadKey(this String prompt) => prompt.ReadKey(true);

    #endregion

    #region Other Terminal Read Methods

    /// <summary>
    /// Reads a line of text from the console.
    /// </summary>
    /// <returns>The read line.</returns>
    public static String ReadLine() {
      if(IsConsolePresent == false) {
        return default;
      }

      lock(SyncLock) {
        Flush();
        InputDone.Reset();

        try {
          Console.CursorVisible = true;
          return Console.ReadLine();
        } finally {
          Console.CursorVisible = false;
          InputDone.Set();
        }
      }
    }

    /// <summary>
    /// Reads a number from the input. If unable to parse, it returns the default number.
    /// </summary>
    /// <param name="prompt">The prompt.</param>
    /// <param name="defaultNumber">The default number.</param>
    /// <returns>
    /// Conversions of string representation of a number to its 32-bit signed integer equivalent.
    /// </returns>
    public static Int32 ReadNumber(this String prompt, Int32 defaultNumber) {
      if(IsConsolePresent == false) {
        return defaultNumber;
      }

      lock(SyncLock) {
        $" {DateTime.Now:HH:mm:ss} USR << {prompt} (default is {defaultNumber}): ".Write(ConsoleColor.White);

        String input = ReadLine();
        return Int32.TryParse(input, out Int32 parsedInt) ? parsedInt : defaultNumber;
      }
    }

    /// <summary>
    /// Creates a table prompt where the user can enter an option based on the options dictionary provided.
    /// </summary>
    /// <param name="title">The title.</param>
    /// <param name="options">The options.</param>
    /// <param name="anyKeyOption">Any key option.</param>
    /// <returns>A value that identifies the console key that was pressed.</returns>
    public static ConsoleKeyInfo ReadPrompt(this String title, Dictionary<ConsoleKey, String> options, String anyKeyOption) {
      if(IsConsolePresent == false) {
        return default;
      }

      const ConsoleColor textColor = ConsoleColor.White;
      Int32 lineLength = Console.BufferWidth;
      Int32 lineAlign = -(lineLength - 2);
      String textFormat = "{0," + lineAlign + "}";

      // lock the output as an atomic operation
      lock(SyncLock) {
        { // Top border
          Table.TopLeft();
          Table.Horizontal(-lineAlign);
          Table.TopRight();
        }

        { // Title
          Table.Vertical();
          String titleText = String.Format(
              textFormat,
              String.IsNullOrWhiteSpace(title) ? " Select an option from the list below." : $" {title}");
          titleText.Write(textColor);
          Table.Vertical();
        }

        { // Title Bottom
          Table.LeftTee();
          Table.Horizontal(lineLength - 2);
          Table.RightTee();
        }

        // Options
        foreach(KeyValuePair<ConsoleKey, String> kvp in options) {
          Table.Vertical();
          String.Format(textFormat,
              $"    {"[ " + kvp.Key + " ]",-10}  {kvp.Value}").Write(textColor);
          Table.Vertical();
        }

        // Any Key Options
        if(String.IsNullOrWhiteSpace(anyKeyOption) == false) {
          Table.Vertical();
          String.Format(textFormat, " ").Write(ConsoleColor.Gray);
          Table.Vertical();

          Table.Vertical();
          String.Format(textFormat,
              $"    {" ",-10}  {anyKeyOption}").Write(ConsoleColor.Gray);
          Table.Vertical();
        }

        { // Input section
          Table.LeftTee();
          Table.Horizontal(lineLength - 2);
          Table.RightTee();

          Table.Vertical();
          String.Format(textFormat,
              Settings.UserOptionText).Write(ConsoleColor.Green);
          Table.Vertical();

          Table.BottomLeft();
          Table.Horizontal(lineLength - 2);
          Table.BottomRight();
        }
      }

      Int32 inputLeft = Settings.UserOptionText.Length + 3;

      SetCursorPosition(inputLeft, CursorTop - 2);
      ConsoleKeyInfo userInput = ReadKey(true);
      userInput.Key.ToString().Write(ConsoleColor.Gray);

      SetCursorPosition(0, CursorTop + 2);
      return userInput;
    }

    #endregion
  }
}