module BinData::Int

def bits_per_word(nbits)

def bits_per_word(nbits)
  (nbits % 64).zero? ? 64 :
  (nbits % 32).zero? ? 32 :
  (nbits % 16).zero? ? 16 :
                        8
end

def create_clamp_code(nbits, signed)

def create_clamp_code(nbits, signed)
  if signed == :signed
    max = "(1 << (#{nbits} - 1)) - 1"
    min = "-((#{max}) + 1)"
  else
    max = "(1 << #{nbits}) - 1"
    min = "0"
  end
  "val = val.clamp(#{min}, #{max})"
end

def create_int2uint_code(nbits)

def create_int2uint_code(nbits)
  "val &= #{(1 << nbits) - 1}"
end

def create_raw_read_code(nbits, endian, signed)

def create_raw_read_code(nbits, endian, signed)
  # special case 8bit integers for speed
  if nbits == 8
    "io.readbytes(1).ord"
  else
    unpack_str   = create_read_unpack_code(nbits, endian, signed)
    assemble_str = create_read_assemble_code(nbits, endian)
    "(#{unpack_str} ; #{assemble_str})"
  end
end

def create_read_assemble_code(nbits, endian)

def create_read_assemble_code(nbits, endian)
  nwords = nbits / bits_per_word(nbits)
  idx = (0...nwords).to_a
  idx.reverse! if endian == :big
  parts = (0...nwords).collect do |i|
            "(ints.at(#{idx[i]}) << #{bits_per_word(nbits) * i})"
          end
  parts[0] = parts[0].sub(/ << 0\b/, "")  # Remove " << 0" for optimisation
  parts.join(" + ")
end

def create_read_code(nbits, endian, signed)

def create_read_code(nbits, endian, signed)
  read_str = create_raw_read_code(nbits, endian, signed)
  if need_signed_conversion_code?(nbits, signed)
    "val = #{read_str} ; #{create_uint2int_code(nbits)}"
  else
    read_str
  end
end

def create_read_unpack_code(nbits, endian, signed)

def create_read_unpack_code(nbits, endian, signed)
  nbytes         = nbits / 8
  pack_directive = pack_directive(nbits, endian, signed)
  "ints = io.readbytes(#{nbytes}).unpack('#{pack_directive}')"
end

def create_to_binary_s_code(nbits, endian, signed)

def create_to_binary_s_code(nbits, endian, signed)
  # special case 8bit integers for speed
  return "(val & 0xff).chr" if nbits == 8
  pack_directive = pack_directive(nbits, endian, signed)
  words          = val_as_packed_words(nbits, endian)
  pack_str       = "[#{words}].pack('#{pack_directive}')"
  if need_signed_conversion_code?(nbits, signed)
    "#{create_int2uint_code(nbits)} ; #{pack_str}"
  else
    pack_str
  end
end

def create_uint2int_code(nbits)

def create_uint2int_code(nbits)
  "(val >= #{1 << (nbits - 1)}) ? val - #{1 << nbits} : val"
end

def define_class(name, nbits, endian, signed)

def define_class(name, nbits, endian, signed)
  @@mutex.synchronize do
    unless BinData.const_defined?(name)
      new_class = Class.new(BinData::BasePrimitive)
      Int.define_methods(new_class, nbits, endian.to_sym, signed.to_sym)
      RegisteredClasses.register(name, new_class)
      BinData.const_set(name, new_class)
    end
  end
  BinData.const_get(name)
end

def define_methods(int_class, nbits, endian, signed)

def define_methods(int_class, nbits, endian, signed)
  raise "nbits must be divisible by 8" unless (nbits % 8).zero?
  int_class.module_eval <<-END
    def assign(val)
      #{create_clamp_code(nbits, signed)}
      super(val)
    end
    def do_num_bytes
      #{nbits / 8}
    end
    #---------------
    private
    def sensible_default
      0
    end
    def value_to_binary_string(val)
      #{create_clamp_code(nbits, signed)}
      #{create_to_binary_s_code(nbits, endian, signed)}
    end
    def read_and_return_value(io)
      #{create_read_code(nbits, endian, signed)}
    end
  END
end

def need_signed_conversion_code?(nbits, signed)

def need_signed_conversion_code?(nbits, signed)
  signed == :signed && ![64, 32, 16].include?(nbits)
end

def pack_directive(nbits, endian, signed)

def pack_directive(nbits, endian, signed)
  nwords = nbits / bits_per_word(nbits)
  directives = { 8 => 'C', 16 => 'S', 32 => 'L', 64 => 'Q' }
  d = directives[bits_per_word(nbits)]
  d += ((endian == :big) ? '>' : '<') unless d == 'C'
  if signed == :signed && directives.key?(nbits)
    (d * nwords).downcase
  else
    d * nwords
  end
end

def val_as_packed_words(nbits, endian)

def val_as_packed_words(nbits, endian)
  nwords = nbits / bits_per_word(nbits)
  mask   = (1 << bits_per_word(nbits)) - 1
  vals = (0...nwords).collect { |i| "val >> #{bits_per_word(nbits) * i}" }
  vals[0] = vals[0].sub(/ >> 0\b/, "")  # Remove " >> 0" for optimisation
  vals.reverse! if (endian == :big)
  vals = vals.collect { |val| "#{val} & #{mask}" }  # TODO: "& mask" is needed to work around jruby bug. Remove this line when fixed.
  vals.join(',')
end