require 'vedeu/support/coercions'

module Vedeu

  # Convert a CSS/HTML colour string into a terminal escape sequence.
  #
  # If provided with an empty value or a string it cannot convert, it will
  # return an empty string.
  #
  # When provided with a named colour, uses the terminal's value for that
  # colour. If a theme is being used with the terminal, which overrides the
  # defaults, then the theme's colour will be used. The recognised names are:
  # :black, :red, :green, :yellow, :blue, :magenta, :cyan, :white, :default.
  #
  # When a number between 0 and 255 is provided, Vedeu will use the terminal
  # colour corresponding with that colour.
  #
  # Finally, when provided a CSS/HTML colour string e.g. '#ff0000', Vedeu will
  # translate that to the 8-bit escape sequence or if you have a capable
  # terminal and the `VEDEU_TERM=xterm-truecolor` environment variable set,
  # a 24-bit representation.
  #
  # @todo More documentation required (create a fancy chart!)
  class Translator

    include Vedeu::Coercions

    # @!attribute [r] colour
    # @return [String]
    attr_reader :colour
    alias_method :value, :colour

    # Convert a CSS/HTML colour string into a terminal escape sequence.
    #
    # @param colour [Fixnum|String|Symbol]
    # @return [String]
    def self.escape_sequence(colour = '')
      new(colour).escape_sequence
    end

    # Return a new instance of Vedeu::Translator.
    #
    # @param colour [Fixnum|String|Symbol]
    # @return [Translator]
    def initialize(colour = '')
      @colour = colour
    end

    # @return [String]
    # @see Vedeu::Translator
    def escape_sequence
      if no_colour?
        ''

      elsif registered?(colour)
        retrieve(colour)

      elsif rgb?
        rgb

      elsif numbered?
        numbered

      elsif named?
        named

      else
        ''

      end
    end
    alias_method :to_s, :escape_sequence

    # @return [String]
    def to_html
      if rgb?
        colour

      else
        ''

      end
    end

    private

    # Subclasses implement this method.
    #
    # @param colour [String]
    # @return [String]
    def retrieve(colour)
      ''
    end

    # Subclasses implement this method.
    #
    # @param colour [String]
    # @return [FalseClass]
    def registered?(colour)
      false
    end

    # @return [Boolean]
    def no_colour?
      colour.nil? || colour.to_s.empty?
    end

    # @return [Boolean]
    def named?
      colour.is_a?(Symbol) && valid_name?
    end

    # Returns an escape sequence for a named background colour.
    #
    # @note
    #   Valid names can be found at {Vedeu::Esc#codes}
    #
    # @return [String]
    def named
      ["\e[", named_codes, 'm'].join
    end

    # Returns a boolean indicating whether the colour provided is a valid named
    # colour.
    #
    # @return [Boolean]
    def valid_name?
      Vedeu::Esc.codes.keys.include?(colour)
    end

    # Returns a boolean indicating whether the colour provided is a terminal
    # numbered colour.
    #
    # @return [Boolean]
    def numbered?
      colour.is_a?(Fixnum) && valid_range?
    end

    # Returns an escape sequence.
    #
    # @return [String]
    def numbered
      [numbered_prefix, css_to_numbered, 'm'].join
    end

    # Returns a boolean indicated whether the colour is a valid HTML/CSS colour.
    #
    # @return [Boolean]
    def rgb?
      !!(colour =~ /^#([A-Fa-f0-9]{6})$/)
    end

    # Returns an escape sequence.
    #
    # @return [String]
    def rgb
      if Vedeu::Configuration.colour_mode == 16_777_216
        register(colour, sprintf(rgb_prefix, *css_to_rgb))

      else
        numbered

      end
    end

    # Returns a boolean indicating whether the numbered colour is within the
    # range of valid terminal numbered colours.
    #
    # @return [Boolean]
    def valid_range?
      colour >= 0 && colour <= 255
    end

    # Returns a collection of converted HTML/CSS octets as their decimal
    # equivalents.
    #
    # @example
    #   colour = '#aadd55'
    #   css_to_rgb # => [170, 221, 85]
    #
    # @return [Array]
    def css_to_rgb
      [
        colour[1..2].to_i(16),
        colour[3..4].to_i(16),
        colour[5..6].to_i(16)
      ]
    end

    # @return [Fixnum]
    def css_to_numbered
      if rgb?
        [16, red, green, blue].inject(:+)

      elsif numbered?
        colour

      end
    end

    # Takes the red component of {#css_to_rgb} and converts to the correct value
    # for setting the terminal red value.
    #
    # @return [Fixnum]
    def red
      (css_to_rgb[0] / 51) * 36
    end

    # Takes the green component of {#css_to_rgb} and converts to the correct
    # value for setting the terminal green value.
    #
    # @return [Fixnum]
    def green
      (css_to_rgb[1] / 51) * 6
    end

    # Takes the blue component of {#css_to_rgb} and converts to the correct
    # value for setting the terminal blue value.
    #
    # @return [Fixnum]
    def blue
      (css_to_rgb[2] / 51) * 1
    end

    # @raise [NotImplemented] Subclasses of this class must implement this
    #   method.
    # @return [Exception]
    def named_codes
      fail NotImplemented, 'Subclasses implement this.'
    end

    # @raise [NotImplemented] Subclasses of this class must implement this
    #   method.
    # @return [NotImplemented]
    def numbered_prefix
      fail NotImplemented, 'Subclasses implement this.'
    end

    # @raise [NotImplemented] Subclasses of this class must implement this
    #   method.
    # @return [NotImplemented]
    def rgb_prefix
      fail NotImplemented, 'Subclasses implement this.'
    end

  end # Translator

end # Vedeu