require 'paint/version'  unless defined? Paint::VERSION
require 'paint/shortcuts'unless defined? Paint::SHORTCUTS
require 'paint/util'

module Paint
  autoload :RGB_COLORS, 'paint/rgb_colors'
  autoload :RGB_COLORS_ANSI, 'paint/rgb_colors_ansi'

  # Important purpose
  NOTHING = "\033[0m"

  # Basic colors (often, the color differs when using the bright effect)
  # Final color will be 30 + value for foreground and 40 + value for background
  ANSI_COLORS = {
    :black   => 0,
    :red     => 1,
    :green   => 2,
    :yellow  => 3,
    :blue    => 4,
    :magenta => 5,
    :cyan    => 6,
    :white   => 7,
    :default => 9,
  }

  # Terminal effects - most of them are not supported ;)
  # See http://en.wikipedia.org/wiki/ANSI_escape_code
  ANSI_EFFECTS = {
    :reset         => 0,  :nothing         => 0,  # usually supported
    :bright        => 1,  :bold            => 1,  # usually supported
    :faint         => 2,
    :italic        => 3,
    :underline     => 4,                          # usually supported
    :blink         => 5,  :slow_blink      => 5,
    :rapid_blink   => 6,
    :inverse       => 7,  :swap            => 7,  # usually supported
    :conceal       => 8,  :hide            => 9,
    :default_font  => 10,
    :font0 => 10, :font1 => 11, :font2 => 12, :font3 => 13, :font4 => 14,
    :font5 => 15, :font6 => 16, :font7 => 17, :font8 => 18, :font9 => 19,
    :fraktur       => 20,
    :bright_off    => 21, :bold_off        => 21, :double_underline => 21,
    :clean         => 22,
    :italic_off    => 23, :fraktur_off     => 23,
    :underline_off => 24,
    :blink_off     => 25,
    :inverse_off   => 26, :positive        => 26,
    :conceal_off   => 27, :show            => 27, :reveal           => 27,
    :crossed_off   => 29, :crossed_out_off => 29,
    :frame         => 51,
    :encircle      => 52,
    :overline      => 53,
    :frame_off     => 54, :encircle_off    => 54,
    :overline_off  => 55,
  }

  # cache
  ANSI_COLORS_FOREGROUND = {
    :black   => '30',
    :red     => '31',
    :green   => '32',
    :yellow  => '33',
    :blue    => '34',
    :magenta => '35',
    :cyan    => '36',
    :white   => '37',
    :default => '39',
  }

  # cache
  ANSI_COLORS_BACKGROUND = {
    :black   => '40',
    :red     => '41',
    :green   => '42',
    :yellow  => '43',
    :blue    => '44',
    :magenta => '45',
    :cyan    => '46',
    :white   => '47',
    :default => '49',
  }

  class << self
    # Takes a string and color options and colorizes the string
    # See README.rdoc for details
    def [](string, *options)
      return string.to_s if mode.zero? || options.empty?
      
      if options.size == 1 && !options.first.respond_to?(:to_ary)
        options = options.first
      end
      
      cache[options] + string.to_s + NOTHING
    end

    # Sometimes, you only need the color
    # Used by []
    def color(*options)
      return '' if mode.zero? || options.empty?
      mix = []
      color_seen = false
      colors = ANSI_COLORS_FOREGROUND

      options.each{ |option|
        case option
        when Symbol
          if color = colors[option]
            mix << color
            color_seen = :set
          elsif ANSI_EFFECTS.key?(option)
            mix << effect(option)
          elsif option == :random
            mix << random(color_seen)
            color_seen = :set
          else
            raise ArgumentError, "Unknown color or effect: #{ option }"
          end

        when Array
          if option.size == 3 && option.all?{ |n| n.is_a? Numeric }
            mix << rgb(*(option + [color_seen])) # 1.8 workaround
            color_seen = :set
          else
            raise ArgumentError, "Array argument must contain 3 numerals"
          end

        when ::String
          if option =~ /^#?(?:[0-9a-f]{3}){1,2}$/i
            mix << hex(option, color_seen)
            color_seen = :set
          else
            mix << rgb_name(option, color_seen)
            color_seen = :set
          end

        when Numeric
          integer = option.to_i
          color_seen = :set if (30..49).include?(integer)
          mix << integer

        when nil
          color_seen = :set
        
        else
          raise ArgumentError, "Invalid argument: #{ option.inspect }"

        end
        
        if color_seen == :set
          colors = ANSI_COLORS_BACKGROUND
          color_seen = true
        end
      }

      wrap(*mix)
    end

    # This variable influences the color code generation
    # Currently supported values:
    # * 256    - 256 colors
    # * 16     - only ansi colors and bright effect
    # * 8      - only ansi colors
    # * 0      - no colorization!
    def mode() @mode ||= detect_mode end
    def mode=(val) cache.clear; @mode = val end

    # Adds ansi sequence
    def wrap(*ansi_codes)
      "\033[" + ansi_codes*";" + "m"
    end

    # Creates simple ansi color by looking it up on Paint::ANSI_COLORS
    def simple(color_name, background = false)
      (background ? 40 : 30) + ANSI_COLORS[color_name]
    end

    # Creates a 256-compatible color from rgb values
    def rgb(red, green, blue, background = false)
      if mode == 8 || mode == 16
        "#{background ? 4 : 3}#{rgb_like_value(red, green, blue, mode == 16)}"
      else
        "#{background ? 48 : 38}#{rgb_value(red, green, blue)}"
      end
    end

    # Creates 256-compatible color from a html-like color string
    def hex(string, background = false)
      string.tr! '#',''
      rgb(
       *(if string.size == 6
          # string.chars.each_cons(2).map{ |hex_color| hex_color.join.to_i(16) }
          [string[0,2].to_i(16), string[2,2].to_i(16), string[4,2].to_i(16)]
        else
          string.chars.map{ |hex_color_half| (hex_color_half*2).to_i(16) }
        end + [background]) # 1.8 workaround
      )
    end

    # Creates a 256-color from a name found in Paint::RGB_COLORS (based on rgb.txt)
    def rgb_name(color_name, background = false)
      if color_code = RGB_COLORS[color_name]
        rgb(*(color_code + [background] )) # 1.8 workaround
      end
    end

    # Creates a random ansi color
    def random(background = false)
      (background ? 40 : 30) + rand(8)
    end

    # Creates the specified effect by looking it up in Paint::ANSI_EFFECTS
    def effect(effect_name)
      ANSI_EFFECTS[effect_name]
    end

    private
    
    def cache
      @cache ||= Hash.new { |h, k| h[k] = color(*k) }
    end
 
    # Returns nearest supported 256-color an rgb value, without fore-/background information
    # Inspired by the rainbow gem
    def rgb_value(red, green, blue)
      gray_possible = true
      sep = 42.5

      while gray_possible
        if red < sep || green < sep || blue < sep
          gray = red < sep && green < sep && blue < sep
          gray_possible = false
        end
        sep += 42.5
      end

      if gray
        ";5;#{ 232 + ((red.to_f + green.to_f + blue.to_f)/33).round }"
      else # rgb
        ";5;#{ [16, *[red, green, blue].zip([36, 6, 1]).map{ |color, mod|
          (6 * (color.to_f / 256)).to_i * mod
        }].inject(:+) }"
      end
    end

    # Returns ansi color matching an rgb value, without fore-/background information
    # See http://mail.python.org/pipermail/python-list/2008-December/1150496.html
    def rgb_like_value(red, green, blue, use_bright = false)
      color_pool =  RGB_COLORS_ANSI.values
      color_pool += RGB_COLORS_ANSI_BRIGHT.values if use_bright

      ansi_color_rgb = color_pool.min_by{ |col| distance([red, green, blue],col) }
      key_method = RUBY_VERSION < "1.9" ? :index : :key
      if ansi_color = RGB_COLORS_ANSI.send(key_method, ansi_color_rgb)
        ANSI_COLORS[ansi_color]
      else
        ansi_color = RGB_COLORS_ANSI_BRIGHT.send(key_method, ansi_color_rgb)
        "#{ANSI_COLORS[ansi_color]};1"
      end
    end

    def distance(rgb1, rgb2)
      rgb1.zip(rgb2).inject(0){ |acc, (cur1, cur2)|
        acc + (cur1 - cur2)**2
      }
    end
  end
end

# J-_-L
