require 'bit-struct/vector'

class BitStruct
  # Class for embedding a BitStruct::Vector as a field within a BitStruct.
  # Declared with BitStruct.vector.
  class VectorField < Field
    # Used in describe.
    def self.class_name
      @class_name ||= "vector"
    end

    # Used in describe.
    def class_name
      @class_name ||= vector_class.name[/\w+$/]
    end

    # Returns the subclass of Vector that is used to manage the value of this
    # field. If the class was specified in the BitStruct.vector declaration,
    # #vector_class will return it, otherwise it will be an anonymous class
    # (which you can assign to a constant to make nonymous ;).
    def vector_class
      @vector_class ||= options[:vector_class] || options["vector_class"]
    end

    def describe opts # :nodoc:
      if opts[:expand]
        opts = opts.dup
        opts[:byte_offset] = offset / 8
        opts[:omit_header] = opts[:omit_footer] = true
        vector_class.describe(nil, opts) {|desc| yield desc}
      else
        super
      end
    end

    def add_accessors_to(cl, attr = name) # :nodoc:
      unless offset % 8 == 0
        raise ArgumentError,
          "Bad offset, #{offset}, for vector field #{name}." +
          " Must be multiple of 8."
      end

      unless length % 8 == 0
        raise ArgumentError,
          "Bad length, #{length}, for vector field #{name}." +
          " Must be multiple of 8."
      end

      offset_byte = offset / 8
      length_byte = length / 8
      last_byte = offset_byte + length_byte - 1
      byte_range = offset_byte..last_byte

      vc = vector_class

      cl.class_eval do
        define_method attr do ||
          vc.new(self[byte_range])
        end

        define_method "#{attr}=" do |val|
          if val.length != length_byte
            raise ArgumentError, "Size mismatch in vector field assignment " +
              "to #{attr} with value #{val.inspect}"
          end

          if val.class != vc
            warn "Type mismatch in vector field assignment " +
              "to #{attr} with value #{val.inspect}"
          end

          self[byte_range] = val
        end
      end
    end
  end
end
