#
# The ArrayFields module implements methods which allow an Array to be indexed
# by String or Symbol. It is not required to manually use this module to extend
# Array's - they are auto-extended when Array#fields= is called
#
  module ArrayFields 
#{{{
  #
  # multiton cache of fields - wraps fields and fieldpos map to save memory
  #
    class FieldSet
#{{{
      class << self
#{{{
        def new fields
#{{{
          @sets ||= {}
          obj = @sets[fields]
          unless obj
            obj = super  
            @sets[fields] = obj
          end
          obj
#}}}
        end
#}}}
      end
      attr :fields
      attr :fieldpos
      def initialize fields
#{{{
        raise ArgumentError, "<#{ fields.inspect }> not inject-able" unless
          fields.respond_to? :inject

        @fieldpos =
          fields.inject({}) do |h, f|
            unless String === f or Symbol === f
              raise ArgumentError, "<#{ f.inspect }> neither String nor Symbol"
            end
            h[f] = h.size
            h
          end

        @fields = fields
#}}}
      end
      def pos field
#{{{
        @fieldpos[field]
#}}}
      end
#}}}
    end

    VERSION = '3.1.0'

    def [](idx, *args) 
#{{{
      if @fieldset and String === idx or Symbol === idx
        pos = @fieldset.pos idx
        return nil unless pos
        super(pos, *args)
      else
        super
      end
#}}}
    end
    alias slice []
    def []=(idx, *args) 
#{{{
      if @fieldset and String === idx or Symbol === idx 
        pos = @fieldset.pos idx
        @fieldpos[idx] = pos = size unless pos
        super(pos, *args)
      else
        super
      end
#}}}
    end
    def at idx
#{{{
      if @fieldset and String === idx or Symbol === idx
        pos = @fieldset.pos idx
        return nil unless pos
        super pos
      else
        super
      end
#}}}
    end
    def delete_at idx
#{{{
      if @fieldset and String === idx or Symbol === idx
        pos = @fieldset.pos idx
        return nil unless pos
        super pos
      else
        super
      end
#}}}
    end
    def fill(obj, *args)
#{{{
      idx = args.first
      if idx and @fieldset and String === idx or Symbol === idx
        idx = args.shift
        pos = @fieldset.pos idx
        super(obj, pos, *args)
      else
        super
      end
#}}}
    end
    def values_at(*idxs)
#{{{
      idxs.flatten!
      if @fieldset
        idxs.map!{|i| (String === i or Symbol === i) ? @fieldset.pos(i) : i}
      end
      super(*idxs)
#}}}
    end
    alias indices values_at
    alias indexes values_at 
    def slice!(*args)
#{{{
      ret = self[*args]
      self[*args] = nil
      ret
#}}}
    end
    def each_with_field
#{{{
      each_with_index do |elem, i|
        yield elem, @fieldset.fields[i]
      end
#}}}
    end
#}}}
  end

#
# The Array class is extened with a methods to allow keyword access 
#
  class Array
#{{{
  #
  # sets fields an dynamically extends this Array instance with methods for
  # keyword access
  #
    def fields= fields
#{{{
      extend ArrayFields unless defined? @fieldset

      @fieldset = 
        if ArrayFields::FieldSet === fields
          fields
        else
          ArrayFields::FieldSet.new fields
        end
#}}}
    end
  #
  # access to fieldset
  #
    attr_reader :fieldset
  #
  # access to field list
  #
    def fields
#{{{
      @fieldset and @fieldset.fields
#}}}
    end
#}}}
  end
