defmodule ExChalk do
  @moduledoc """
  Provides a simple interface to text color and styling.

  ExChalk exposes three APIs: colors, background colors, and modifiers.

  ## Colors

  `ExChalk.color_name(text)` will color `text` with `color_name` color.

  Supported colors are:

  - `black`
  - `red`
  - `green`
  - `yellow`
  - `blue`
  - `magenta`
  - `cyan`
  - `white`
  - `gray`

  Example: `ExChalk.red("Hello!")`

  ## Background colors

  `ExChalk.bg_color_name(text)` will color `text` with `bg_color_name`
  background color.

  Supported background colors are:

  - `bg_black`
  - `bg_red`
  - `bg_green`
  - `bg_yellow`
  - `bg_blue`
  - `bg_magenta`
  - `bg_cyan`
  - `bg_white`

  Example: `ExChalk.bg_red("Hello!")`

  ## Modifiers

  `ExChalk.modifier(text)` will style `text` with a given `modifier`.

  Supported modifiers are:

  - `reset`
  - `bold`
  - `dim`
  - `italic`
  - `underline`
  - `inverse`
  - `hidden`
  - `strikethrough`

  Example: `ExChalk.italic("Hello!")`
  """

  @color_reset "\e[39m"
  @bg_reset "\e[49m"

  alias IO.ANSI

  @colors [
    black: ANSI.color(0),
    red: ANSI.color(1),
    green: ANSI.color(2),
    yellow: ANSI.color(3),
    blue: ANSI.color(4),
    magenta: ANSI.color(5),
    cyan: ANSI.color(6),
    white: ANSI.color(7),
    gray: "\e[90m",
    grey: "\e[90m"
  ]

  @bg_colors [
    black: ANSI.color_background(0),
    red: ANSI.color_background(1),
    green: ANSI.color_background(2),
    yellow: ANSI.color_background(3),
    blue: ANSI.color_background(4),
    magenta: ANSI.color_background(5),
    cyan: ANSI.color_background(6),
    white: ANSI.color_background(7)
  ]

  @modifiers [
    reset: {ANSI.reset(), ANSI.reset()},
    bold: {ANSI.bright(), "\e[22m"},
    dim: {ANSI.faint(), "\e[22m"},
    italic: {ANSI.italic(), "\e[23m"},
    underline: {ANSI.underline(), "\e[24m"},
    inverse: {"\e[7m", "\e[27m"},
    hidden: {"\e[8m", "\e[28m"},
    strikethrough: {"\e[9m", "\e[29m"}
  ]

  for {name, code} <- @colors do
    def unquote(:"#{name}")(str) when is_list(str) do
      [prev_start, prev, prev_end] = str

      [prev_start, [unquote(code), prev, @color_reset], prev_end]
    end

    def unquote(:"#{name}")(str) do
      [unquote(code), str, @color_reset]
    end
  end

  for {name, code} <- @bg_colors do
    def unquote(:"bg_#{name}")(str) when is_list(str) do
      [prev_start, prev, prev_end] = str

      [prev_start, [unquote(code), prev, @bg_reset], prev_end]
    end

    def unquote(:"bg_#{name}")(str) do
      [unquote(code), str, @bg_reset]
    end
  end

  for {name, {start, reset}} <- @modifiers do
    def unquote(:"#{name}")(str) when is_list(str) do
      [prev_start, prev, prev_end] = str

      [prev_start, [unquote(start), prev, unquote(reset)], prev_end]
    end

    def unquote(:"#{name}")(str) do
      [unquote(start), str, unquote(reset)]
    end
  end

  @doc """
  Converts the given IO list to a string
  """
  @spec to_str(iodata) :: binary
  def to_str(list), do: IO.iodata_to_binary(list)
end
