require 'forwardable'

module HTTP
  class Headers < Hash
    def strtoindex str
      str = str.to_s.gsub /_/, "-"
      str.gsub! /\w+/ do |w| w.capitalize end
    end
    def [] i
      super strtoindex(i)
    end
    def []= i, v
      super strtoindex(i), v
    end
    def << hash
      hash.each_pair do |k,v|
	self[k] = v
      end
      self
    end
    def to_s
      map {|k,v| "#{k}: #{v}"}.join "\n"
    end

    def method_missing meth, *args
      meth = meth.to_s
      key = meth[/^\w+/]
      meth[/^\w+/] = "[]"
      send meth.to_sym, key, *args
    end
  end

  class Transfer
    extend Forwardable

    DEFAULT_PROTOCOL = "HTTP/1.1"
    attr_accessor :protocol, :body
    def_delegators :@headers, :[], :[]=, :keys, :each_pair, :method_missing
    def initialize protocol=DEFAULT_PROTOCOL
      @protocol = protocol
      @headers = Headers.new
      @body = ""
    end
    def << val
      case val
	when Hash
	  @headers << val
	else
	  @body << val
      end
      self
    end

    def content_length
      @headers.content_length || @body.length
    end
    def to_s
      str = ""
      str << protocol.to_s << "\n"
      str << "#{@headers.to_s}\n\n"
      str << body
    end
    def send io=nil
      self << {:content_length => content_length}
      io ? io << to_s : to_s
    end

    def parse io # headers plus body
      until (line = io.readline.chomp) == ""
	k, v = line.split ": "
	self << {k => v}
      end
      if content_length
	self << io.read(content_length.to_i)
      end
      self
    end
  end
  class Request < Transfer
    attr_accessor :method, :uri
    def initialize uri, method="GET"
      super()
      @uri, @method = uri, method
    end

    def self.parse io
      meth, uri, proto = io.readline.chomp.split " ", 3
      req = Request.new uri, meth
      req.protocol = proto
      req.parse io
    end

    def to_s
      super.sub /^.*$/, "#{method} #{uri} #{protocol}"
    end

    def resource
      uri.split("?").first
    end
    def query
      uri.split("?",2)[1] || ""
    end
  end

  class Response < Transfer
    attr_accessor :status, :message
    DEFAULT_OPTIONS = {:status => 200, :message => "OK"}
    def initialize options = {}
      options = DEFAULT_OPTIONS.merge options
      @status, @message = options[:status], options[:message]
      proto = (options[:proto] || options[:protocol])
      proto ? super(proto) : super()
    end

    def self.parse io
      p, s, m = io.readline.chomp.split " ", 3
      resp = Response.new :proto => p, :status => s, :message => m
      resp.parse io
    end

    def to_s
      super.sub /$/, " #{status} #{message}"
    end
  end
end
