require 'byteorder'

# This mixin solely depends on method read(n), which must be defined
# in the class/module where you mixin this module.
module BinaryReaderMixin

  # == 8 bit

  # no byteorder for 8 bit! 

  def read_word8
    ru(1, 'C')
  end

  def read_int8
    ru(1, 'c')
  end

  alias read_byte read_word8
  
  # == 16 bit

  # === Unsigned
  
  def read_word16_native
    ru(2, 'S')
  end

  def read_word16_little
    ru(2, 'v')
  end

  def read_word16_big
    ru(2, 'n')
  end

  # === Signed

  def read_int16_native
    ru(2, 's')
  end

  def read_int16_little
    # swap bytes if native=big (but we want little)
    ru_swap(2, 's', ByteOrder::Big)
  end

  def read_int16_big
    # swap bytes if native=little (but we want big)
    ru_swap(2, 's', ByteOrder::Little) 
  end

  # == 32 bit

  # === Unsigned

  def read_word32_native
    ru(4, 'L')
  end

  def read_word32_little
    ru(4, 'V')
  end

  def read_word32_big
    ru(4, 'N')
  end

  # === Signed

  def read_int32_native
    ru(4, 'l')
  end

  def read_int32_little
    # swap bytes if native=big (but we want little)
    ru_swap(4, 'l', ByteOrder::Big) 
  end

  def read_int32_big
    # swap bytes if native=little (but we want big)
    ru_swap(4, 'l', ByteOrder::Little) 
  end

  # == Aliases

  alias read_uint8 read_word8

  # add some short-cut functions 
  %w(word16 int16 word32 int32).each do |typ|
    alias_method "read_#{typ}_network", "read_#{typ}_big"
  end

  {:word16 => :uint16, :word32 => :uint32}.each do |old, new|
    ['_native', '_little', '_big', '_network'].each do |bo|
      alias_method "read_#{new}#{bo}", "read_#{old}#{bo}"
    end
  end

  # read exactly n characters, otherwise raise an exception.
  def readn(n)
    str = read(n)
    raise "couldn't read #{n} characters" if str.nil? or str.size != n
    str
  end

  private

  # shortcut method for readn+unpack
  def ru(size, template)
    readn(size).unpack(template).first
  end

  # same as method +ru+, but swap bytes if native byteorder == _byteorder_  
  def ru_swap(size, template, byteorder)
    str = readn(size)
    str.reverse! if ByteOrder.byteorder == byteorder 
    str.unpack(template).first
  end
end
