require 'uri'

module Merb
  module Test
    class Cookie
      
      # :api: private
      attr_reader :name, :value
      
      # :api: private
      def initialize(raw, default_host)
        # separate the name / value pair from the cookie options
        @name_value_raw, options = raw.split(/[;,] */n, 2)
        
        @name, @value = Merb::Parse.query(@name_value_raw, ';').to_a.first
        @options = Merb::Parse.query(options, ';')
        
        @options.delete_if { |k, v| !v || v.empty? }
        
        @options["domain"] ||= default_host
      end
      
      # :api: private
      def raw
        @name_value_raw
      end
      
      # :api: private
      def empty?
        @value.nil? || @value.empty?
      end
      
      # :api: private
      def domain
        @options["domain"]
      end

      # :api: private
      def path
        @options["path"] || "/"
      end
      
      # :api: private
      def expires
        Time.parse(@options["expires"]) if @options["expires"]
      end
      
      # :api: private
      def expired?
        expires && expires < Time.now
      end
      
      # :api: private
      def valid?(uri)
        domain_ = domain.index('.') == 0 ? domain[1..-1] : domain
        uri_path = uri.path.blank? ? "/" : uri.path

        uri.host =~ Regexp.new("#{Regexp.escape(domain_)}$") &&
        uri_path =~ Regexp.new("^#{Regexp.escape(path)}")
      end
      
      # :api: private
      def matches?(uri)
        ! expired? && valid?(uri)
      end
      
      # :api: private
      def <=>(other)
        # Orders the cookies from least specific to most
        [name, path, domain.reverse] <=> [other.name, other.path, other.domain.reverse]
      end
      
    end

    class CookieJar
      
      # :api: private
      def initialize
        @jars = {}
      end
      
      # :api: private
      def update(jar, uri, raw_cookies)
        return unless raw_cookies
        # Initialize all the the received cookies
        cookies = []
        raw_cookies.each do |raw|
          c = Cookie.new(raw, uri.host)
          cookies << c if c.valid?(uri)
        end
        
        @jars[jar] ||= []
        
        # Remove all the cookies that will be updated
        @jars[jar].delete_if do |existing|
          cookies.find { |c| [c.name, c.domain, c.path] == [existing.name, existing.domain, existing.path] }
        end
        
        @jars[jar].concat cookies
        
        @jars[jar].sort!
      end
      
      # :api: private
      def for(jar, uri)
        cookies = {}
        
        @jars[jar] ||= []
        # The cookies are sorted by most specific first. So, we loop through
        # all the cookies in order and add it to a hash by cookie name if
        # the cookie can be sent to the current URI. It's added to the hash
        # so that when we are done, the cookies will be unique by name and
        # we'll have grabbed the most specific to the URI.
        @jars[jar].each do |cookie|
          cookies[cookie.name] = cookie.raw if cookie.matches?(uri)
        end
        
        cookies.values.join(';')
      end
      
    end
  end
end
