require "forwardable"

module Hurley
  class Header
    def initialize(initial = nil)
      @hash = {}
      update(initial) if initial
    end

    extend Forwardable
    def_delegators(:@hash,
      :each,
      :keys,
      :size,
    )

    def [](key)
      @hash[self.class.canonical(key)]
    end

    def []=(key, value)
      @hash[self.class.canonical(key)] = value.to_s
    end

    def key?(key)
      @hash.key?(self.class.canonical(key))
    end

    def delete(key)
      @hash.delete(self.class.canonical(key))
    end

    def update(hash)
      hash.each do |key, value|
        self[key] = value
      end
    end

    def dup
      self.class.new(@hash.dup)
    end

    def to_hash
      @hash
    end

    def inspect
      "#<%s %s>" % [
        self.class.name,
        @hash.inspect,
      ]
    end

    def self.canonical(input)
      KEYS[input] || begin
        key = input.to_s.tr(UNDERSCORE, DASH)
        key.downcase!
        key.gsub!(/(\A|\-)(\S)/) { |s| s.upcase! ; s }
        key
      end
    end

    def self.add_canonical_key(*canonicals)
      canonicals.each do |canonical|
        canonical.freeze
        KEYS[canonical] = canonical
        shortcut = canonical.downcase
        KEYS[shortcut.freeze] = canonical
        KEYS[shortcut.tr(DASH, UNDERSCORE).to_sym] = canonical
      end
    end

    private

    # hash of "shortcut key" => "canonical header key"
    # string keys are converted to canonical header names:
    #
    # KEYS["content_type"] # => "Content-Type"
    #
    KEYS = {"ETag".freeze => "Etag".freeze}
    DASH = "-".freeze
    UNDERSCORE = "_".freeze

    # Adds canonical header keys for common headers.
    #
    # KEYS[:content_type]  # => "Content-Type"
    # KEYS["Content-Type"] # => "Content-Type"
    # KEYS["content-type"] # => "Content-Type"
    #
    add_canonical_key(
      "Accept",
      "Access-Control-Allow-Credentials",
      "Access-Control-Allow-Origin",
      "Access-Control-Expose-Headers",
      "Age",
      "Authorization",
      "Cache-Control",
      "Connection",
      "Content-Disposition",
      "Content-Language",
      "Content-Length",
      "Content-MD5",
      "Content-Range",
      "Content-Security-Policy",
      "Content-Type",
      "Cookie",
      "Date",
      "Etag",
      "Expect",
      "Expires",
      "From",
      "Host",
      "If-Modified-Since",
      "If-None-Match",
      "Last-Modified",
      "Link",
      "Location",
      "Origin",
      "Range",
      "Referer",
      "Refresh",
      "Retry-After",
      "Server",
      "Set-Cookie",
      "Status",
      "String-Transport-Security",
      "Trailer",
      "Transfer-Encoding",
      "User-Agent",
      "Upgrade",
      "Vary",
      "Via",
      "WWW-Authenticate",
      "X-Content-Type-Options",
      "X-Frame-Options",
      "X-Powered-By",
      "X-Served-By",
      "X-Xss-Protection",
    )

    # Some weird exceptions
    KEYS["ETag"] = "Etag"
  end
end