require 'forwardable'

class HashParam < Hash
  extend Forwardable

  def_delegator :self, :merge!, :<<

  def self.<< h
    new << h
  end
  def self.defs
    @defs ||= {}
  end
  def self.defaults h
    defs.merge! h
  end
  def self.default key, value
    defs[key] = value
  end

  def initialize *args
    merge! self.class.defs
    super
  end
end

class HashMap < HashParam
  def self.stringify v
    @stringify = v
  end
  def self.ignore_case v # implies stringify
    @icase = v
  end

  def self.stringify?; @stringify; end
  def self.ignore_case?; @icase; end

  def self.prep_key k
    k = k.to_s if stringify?
    ignore_case? and k.to_s.downcase or k
  end

  def self.map key1, key2
    mappings[key1] = key2
  end
  def self.mappings
    @mappings ||= {}
    if stringify? or ignore_case?
      h = {}
      @mappings.each_key do |k|
        k = k.to_s if stringify?
        k = k.to_s.downcase if ignore_case?
        h[k] = @mappings[k]
      end
      h
    else
      @mappings
    end
  end
  def self.map_key k
    tk = prep_key k
    k = mappings[tk] || k
    return k if self == HashMap
    ancestors[1].map_key k
  end

  def map_key *args
    self.class.map_key *args
  end

  def self.map_keys arg
    case arg
      when Hash
        arg = arg.dup
        arg.each_key do |k|
          arg[map_key(k)] = arg.delete k
        end
        arg
      else
        arg.map {|k| map_key k}
    end
  end

  def []= k, v
    super map_key(k), v
  end
  def [] k
    super map_key(k)
  end

  def merge *args
    dup.merge! *args
  end
  def merge! hash
    self.class.map_keys(hash).each_pair {|k,v| self[k] = v}
    self
  end
end

class HashAmb < HashMap
  # note: always stringified for ambiguous mappings

  def self.add_param *p
    self.ambs += p
  end
  def self.ambs
    @ambs ||= []
  end
  class << self; attr_writer :ambs; end

  def self.disamb key
    tk = prep_key(key).to_s
    tmp = ambs.select do |k|
      prep_key(k).to_s == tk
    end
    return tmp.first unless tmp.empty?
    tmp = ambs.select do |k|
      prep_key(k).to_s[/^#{tk}/]
    end
    return tmp.first if tmp.length == 1
    tmp = ambs.select do |k|
      prep_key(k).to_s.rindex tk
    end
    return tmp.first if tmp.length == 1
    return key
  end

  def self.map_key k, inv=false
    inv ? super(disamb(k)) : disamb(super(k))
  end
end

class Hash
  def to_param klass=HashParam
    klass.new << self
  end
end
