-- Library for reading low level data

local assert = assert
local unpack = unpack
local floor = math.floor
local strbyte , strchar = string.byte , string.char

local ll = { }

local le_uint_to_num = function ( s , i , j )
	i , j = i or 1 , j or #s
	local b = { strbyte ( s , i , j ) }
	local n = 0
	for i=#b , 1 , -1 do
		n = n*2^8 + b [ i ]
	end
	return n
end
local le_int_to_num = function ( s , i , j )
	i , j = i or 1 , j or #s
	local n = le_uint_to_num ( s , i , j )
	local overflow = 2^(8*(j-i) + 7)
	if n > 2^overflow then
		n = - ( n % 2^overflow )
	end
	return n
end
local num_to_le_uint = function ( n , bytes )
	bytes = bytes or 4
	local b = { }
	for i=1 , bytes do
		b [ i ] , n = n % 2^8 , floor ( n / 2^8 )
	end
	assert ( n == 0 )
	return strchar ( unpack ( b ) )
end
local num_to_le_int = function ( n , bytes )
	bytes = bytes or 4
	if n < 0 then -- Converted to unsigned.
		n = 2^(8*bytes) + n
	end
	return num_to_le_uint ( n , bytes )
end

local be_uint_to_num = function ( s , i , j )
	i , j = i or 1 , j or #s
	local b = { strbyte ( s , i , j ) }
	local n = 0
	for i=1 , #b do
		n = n*2^8 + b [ i ]
	end
	return n
end
local num_to_be_uint = function ( n , bytes )
	bytes = bytes or 4
	local b = { }
	for i=bytes , 1 , -1 do
		b [ i ] , n = n % 2^8 , floor ( n / 2^8 )
	end
	assert ( n == 0 )
	return strchar ( unpack ( b ) )
end

-- Returns (as a number); bits i to j (indexed from 0)
local extract_bits = function ( s , i , j )
	j = j or i
	local i_byte = floor ( i / 8 ) + 1
	local j_byte = floor ( j / 8 ) + 1

	local n = be_uint_to_num ( s , i_byte , j_byte )
	n = n % 2^( j_byte*8 - i )
	n = floor ( n / 2^( (-(j+1) ) % 8 ) )
	return n
end

-- Look at ith bit in given string (indexed from 0)
-- Returns boolean
local le_bpeek = function ( s , bitnum )
	local byte = floor ( bitnum / 8 ) + 1
	local bit = bitnum % 8
	local char = strbyte ( s , byte )
	return floor ( ( char % 2^(bit+1) ) / 2^bit ) == 1
end
-- Test with:
--local sum = 0 for i=0,31 do v = le_bpeek ( num_to_le_uint ( N , 4 ) , i ) sum=sum + ( v and 1 or 0 )*2^i end assert ( sum == N )

local be_bpeek = function ( s , bitnum )
	local byte = floor ( bitnum / 8 ) + 1
	local bit = 7-bitnum % 8
	local char = strbyte ( s , byte )
	return floor ( ( char % 2^(bit+1) ) / 2^bit ) == 1
end
-- Test with:
--local sum = 0 for i=0,31 do v = be_bpeek ( num_to_be_uint ( N , 4 ) , i ) sum=sum + ( v and 1 or 0 )*2^(31-i) end assert ( sum == N )


local hasffi , ffi = pcall ( require , "ffi" )
local to_double , from_double
do
	local s , e , d
	if hasffi then
		d = ffi.new ( "double[1]" )
	else
		-- Can't use with to_double as we can't strip debug info :(
		d = string.dump ( loadstring ( [[return 523123.123145345]] ) )
		s , e = d:find ( "\3\54\208\25\126\204\237\31\65" )
		s = d:sub ( 1 , s )
		e = d:sub ( e+1 , -1 )
	end

	function to_double ( n )
		if hasffi then
			d [ 0 ] = n
			return ffi.string ( d , 8 )
		else
			-- Should be the 8 bytes following the second \3 (LUA_TSTRING == '\3')
			local str = string.dump ( loadstring ( [[return ]] .. n ) )
			local loc , en , mat = str:find ( "\3(........)" , str:find ( "\3" ) + 1 )
			return mat
		end
	end
	function from_double ( str )
		assert ( #str == 8 )
		if hasffi then
			ffi.copy ( d , str , 8 )
			return d [ 0 ]
		else
			str = s .. str .. e
			return loadstring ( str ) ( )
		end
	end
end

return {
	le_uint_to_num = le_uint_to_num ;
	le_int_to_num = le_int_to_num ;
	num_to_le_uint = num_to_le_uint ;
	num_to_le_int = num_to_le_int ;

	be_uint_to_num = be_uint_to_num ;
	num_to_be_uint = num_to_be_uint ;

	extract_bits = extract_bits ;

	le_bpeek = le_bpeek ;
	be_bpeek = be_bpeek ;

	to_double = to_double ;
	from_double = from_double ;
}
