# Stolen (mostly) from Net::SSH

# a* string a10 string 10 bytes A* string trimmed
# C uchar
# L native u32, l signed
# N BE u32, n 16
# Qq quad
# Ss native u16
# Vv LE u32

# Encapsulate a buffer of data with structured data accessors
class Ragweed::Sbuf
  attr_reader :content
  attr_accessor :position

  def self.from(*args)
    raise ArgumentError, "odd argument count" if args.length.odd?

    b = self.new
    while not args.empty?
      t = args.shift; v = args.shift
      if t == :raw
        b.straw(v)
      elsif v.kind_of? Array
        b.st t, *v
      else
        b.st t, v
      end
    end

    return b
  end

  def st(t, *args)
    send "st#{ t }", *args
  end

  def straw(*args)
    args.each do |raw|
      @content << raw.to_s
    end
    self
  end

  def initialize(opts={})
    @content = opts[:content] || ""
    @position = 0
  end

  def consume!(n=position)
    if(n >= length); clear!
    elsif n > 0
      @content = remainder(n)
      @position -= n
      @position = 0 if @position < 0
    end
    self
  end

  def ldraw(n=length)
    n = ((self.length) - position) if(position + n > length)
    @position += n
    @content[position-n, n]
  end

  def ld(t, *args)
    return ldraw(t) if t.kind_of? Numeric

    begin
      send "ld#{ t }", *args
    rescue => e
      case t.to_s
      when /^strz(\d+)?/
        n = $1.to_i if not $1.empty?
        n ||= 0
        ldsz(n)
      when /^strs(\d+)?/
        n = $1.to_i if not $1.empty?
        n ||= 0
        ldss(n)
      else
        raise e
      end
    end
  end

  def sz(t, *args); self.class.sz(t, *args); end
  def self.sz(t, *args)
    begin
      send "sz#{ t }", *args
    rescue => e
      case t.to_s
      when /^strz(\d+)/
        $1.to_i
      when /^strs(\d+)/
        $1.to_i
      when /^str.*/
        raise Exception, "can't take size of unbounded string"
      else
        raise e
      end
    end
  end

  def shraw(n=length)
    ret = ldraw(n)
    consume!
    return ret
  end

  def sh(t, *args)
    return shraw(t) if t.kind_of? Numeric
    ret = send "ld#{ t }", *args
    consume!
    return ret
  end

  def ldsz(n=0)
    n = @content.size - @position if n == 0
    ld(n).unpack("a#{ n }").first
  end

  def ldss(n=0)
    n = @content.size - @position if n == 0
    ld(n).unpack("A#{ n }").first
  end

  def length; @content.length; end
  def size; @content.size; end
  def empty?; @content.empty?; end

  def available; length - position; end
  alias_method :remaining, :available

  def to_s; @content.dup; end
  def ==(b); to_s == b.to_s; end
  def reset; @position = 0; end
  def eof?; @position >= length; end
  def clear!; @content = "" and reset; end
  def remainder(n = position); @content[n..-1] || ""; end
  def remainder_as_buffer(t=Sbuf); t.new(:content => remainder); end

  def self.szl64; 8; end
  def self.szb64; 8; end
  def self.szn64; 8; end

  def ldl64; ld(8).unpack("Q").first; end
  def ldb64; ld(8).reverse.unpack("Q").first; end
  alias_method :ldn64, :ldb64

  def stl64(v); straw([v].pack("Q")); end
  def stb64(v); straw([v].pack("Q").reverse); end
  alias_method :stn64, :stb64

  def self.szl32; 4; end
  def self.szb32; 4; end
  def self.szn32; 4; end

  def ldl32; ld(4).unpack("L").first; end
  def ldb32; ld(4).unpack("N").first; end
  alias_method :ldn32, :ldb32

  def stl32(v); straw([v].pack("L")); end
  def stb32(v); straw([v].pack("N")); end
  alias_method :stn32, :stb32

  def self.szl16; 2; end
  def self.szb16; 2; end
  def self.szn16; 2; end

  def ldl16; ld(2).unpack("v").first; end
  def ldb16; ld(2).unpack("n").first; end
  alias_method :ldn16, :ldb16

  def stl16(v); straw([v].pack("v")); end
  def stb16(v); straw([v].pack("n")); end
  alias_method :stn16, :stb16

  def self.szl8; 1; end;
  def self.szb8; 1; end;
  def self.szn8; 1; end;

  def ldl8; ld(1)[0]; end
  def ldb8; ldl8; end
  alias_method :ldn8, :ldb8

  def stl8(v)
    if v.kind_of? String
      straw(v[0].chr)
    else
      straw([v].pack("c"))
    end
  end

  def stb8(v); stl8(v); end
  alias_method :stn8, :stb8
end