require 'bindata/base'
require 'bindata/dsl'

module BinData
  # A Buffer is conceptually a substream within a data stream.  It has a
  # defined size and it will always read or write the exact number of bytes to
  # fill the buffer.  Short reads will skip over unused bytes and short writes
  # will pad the substream with "\0" bytes.
  #
  #   require 'bindata'
  #
  #   obj = BinData::Buffer.new(:length => 5, :type => [:string, {:value => "abc"}])
  #   obj.to_binary_s #=> "abc\000\000"
  #
  #
  #   class MyBuffer < BinData::Buffer
  #     default_parameter :length => 8
  #
  #     endian :little
  #
  #     uint16 :num1
  #     uint16 :num2
  #     # padding occurs here
  #   end
  #
  #   obj = MyBuffer.read("\001\000\002\000\000\000\000\000")
  #   obj.num1 #=> 1
  #   obj.num1 #=> 2
  #   obj.num_bytes #=> 8
  #
  #
  #   class StringTable < BinData::Record
  #     endian :little
  #
  #     uint16 :table_size_in_bytes
  #     buffer :strings, :length => :table_size_in_bytes do
  #       array :read_until => :eof do
  #         uint8 :len
  #         string :str, :length => :len
  #       end
  #     end
  #   end
  #     
  #
  # == Parameters
  #
  # Parameters may be provided at initialisation to control the behaviour of
  # an object.  These params are:
  #
  # <tt>:length</tt>::   The number of bytes in the buffer.
  # <tt>:type</tt>::     The single type inside the buffer.  Use a struct if
  #                      multiple fields are required.
  class Buffer < BinData::Base
    include DSLMixin

    dsl_parser :buffer

    mandatory_parameters :length, :type

    class << self
      def arg_extractor
        MultiFieldArgExtractor
      end

      def sanitize_parameters!(params) #:nodoc:
        params.merge!(dsl_params)

        if params.needs_sanitizing?(:type)
          el_type, el_params = params[:type]
          params[:type] = params.create_sanitized_object_prototype(el_type, el_params)
        end
      end
    end

    def initialize_instance
      @type = get_parameter(:type).instantiate(nil, self)
    end

    def clear? #:nodoc:
      @type.clear?
    end

    def assign(val)
      @type.assign(val)
    end

    def snapshot
      @type.snapshot
    end

    def respond_to?(symbol, include_private = false) #:nodoc:
      @type.respond_to?(symbol, include_private) || super
    end

    def safe_respond_to?(symbol, include_private = false) #:nodoc:
      base_respond_to?(symbol, include_private)
    end

    def method_missing(symbol, *args, &block) #:nodoc:
      @type.__send__(symbol, *args, &block)
    end

    def do_read(io) #:nodoc:
      io.with_buffer(eval_parameter(:length)) do
        @type.do_read(io)
      end
    end

    def do_write(io) #:nodoc:
      io.with_buffer(eval_parameter(:length)) do
        @type.do_write(io)
      end
    end

    def do_num_bytes #:nodoc:
      eval_parameter(:length)
    end
  end
end
