require 'locale'

module HTTPHeaders
  # RFC2616 Accept-Language field
  # (also used in RFC2798 'preferredLanguage' LDAP attribute type)
  class AcceptLanguage
    LANG_RANGE_Q_PATTERN="([a-z]{1,8}(-[a-z]{1,8})*|\\*)(;q=(1|0\.\d+))?"
    ACCEPT_LANG_PATTERN="#{LANG_RANGE_Q_PATTERN}([ ]*,[ ]*#{LANG_RANGE_Q_PATTERN})*"

    attr_reader :sorted_language_list

    def initialize(sorted_language_list)
      # if no preference list, then assume that all languages are equally acceptable
      @sorted_language_list = sorted_language_list.empty? ? ["*"] : sorted_language_list
    end

    def self.parse(accept_language_list)
      return nil unless accept_language_list =~ Regexp.new(ACCEPT_LANG_PATTERN, Regexp::EXTENDED | Regexp::IGNORECASE)

      list = accept_language_list.split(',').collect do |lang_range_q|
        lang_range, quality = lang_range_q.split(';')
        quality ||= 1
        LanguageRange.new(lang_range, quality)
      end

      new(list.sort)
    end

    def reduce(available_language_list)
      tag_list = available_language_list.collect do |tag|
        l_tag = Locale::Tag.parse(tag).to_rfc

        match = []
        @sorted_language_list.each do |lr|
          match << lr if lr.range == "*" or
            lr.range == l_tag.to_s or
            (lr.range + "-" == l_tag.to_s[0, lr.range.size + 1])
        end
        match.sort_by {|lr| lr.range.size }

        quality = match.empty? ? 0 : match.last.quality

        LanguageRange.new(l_tag, quality)
      end

      acceptable_tag_list = tag_list.select {|lr| lr.quality != 0 }
      acceptable_tag_list.sort
    end
  end

  class LanguageRange
    include Comparable

    attr_reader :quality

    def initialize(lang_range, quality)
      @l_lang_range = (lang_range.is_a? Locale::Tag::Simple) ? lang_range : Locale::Tag.parse(lang_range).to_rfc
      @quality = quality.to_f
    end

    def range
      @l_lang_range.to_s
    end

    def <=>(lr)
      r = self.quality <=> lr.quality
      # if quality is equal, then prefer the most specific language range
      if r == 0
        self.range.size <=> lr.range.size
      else
        r
      end
    end
  end
end
