-- vim: ft=terra
local m = {
	shorthand = {maxlen = 14}
}

local pstring = lib.mem.ptr(int8)

-- swap in place -- faster on little endian
m.netswap_ip = macro(function(ty, src, dest)
	if ty:astype().type ~= 'integer' then error('bad type') end
	local bytes = ty:astype().bytes
	src = `[&uint8](src)
	dest = `[&uint8](dest)
	if config.endian == 'little' then
		return quote for i = 0, bytes do dest[i] = src[bytes - (i+1)] end end
	elseif config.endian == 'big' then
		return quote for i = 0, bytes do dest[i] = src[i] end end
	else error('unknown endianness '..config.endian) end
end)

-- swap out of place -- safer, more flexible, and optimized to an intrinsic call; trivial on big endian
m.netswap = macro(function(tyq, src)
	if config.endian == 'little' then
		local ty = tyq:astype()
		local a,b = symbol(ty), symbol(ty)
		local bytes = ty.bytes
		local steps = {}
		for i=0,bytes-1 do
			steps[#steps + 1] = quote
				b = b << 8
				b = b or (a and 0xff)
				a = a >> 8
			end
		end
		return quote
			var [a] = src
			var [b] = 0
			[steps]
		in b end
	elseif config.endian == 'big' then return `src
	else error('unknown endianness '..config.endian) end
end)

terra m.shorthand.cval(character: int8): {uint8, bool}
	var ch = [uint8](character)

	if ch >= 0x30 and ch <= 0x39 then
		ch = 00 + (ch - 0x30)
	elseif ch == 0x2d then ch = 10
	elseif ch >= 0x41 and ch <= 0x5a then
		ch = 11 + (ch - 0x41)
	elseif ch == 0x3a then ch = 37
	elseif ch >= 0x61 and ch <= 0x7a then
		ch = 38 + (ch - 0x61)
	else return 0, false end

	return ch, true 
end

terra m.shorthand.gen(val: uint64, dest: rawstring): ptrdiff
	var lst = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ:abcdefghijklmnopqrstuvwxyz"
	var buf: int8[m.shorthand.maxlen]
	var ptr = [&int8](buf)
	while val ~= 0 do
		var v = val % 64
		@ptr = lst[v]
		ptr = ptr + 1
		val = val / 64
	end
	var len = ptr - buf
	for i = 0, len do
		dest[i] = buf[len - (i+1)]
	end
	dest[len] = 0
	return len
end

terra m.shorthand.parse(s: rawstring, len: intptr): {uint64, bool}
	var val: uint64 = 0
	for i = 0, len do
		var v, ok = m.shorthand.cval(s[i])
		if ok == false then return 0, false end
		val = (val * 64) + v
	end
	return val, true
end

terra m.truncate64(val: &uint8, len: intptr): uint64
	var r: uint64 = 0
	for i=0, len do
		r = r << 3
		r = r + @val
		val = val + 1
	end
	return r
end

m.biggest = macro(function(a,b)
	local ty = a.tree.type
	if b.tree.type.bytes > ty.bytes then ty = b.tree.type end
	return quote
		var _a = [a]
		var _b = [b]
		var r: ty if _a > _b then r = _a else r = _b end
	in r end
end)

m.smallest = macro(function(a,b)
	local ty = a.tree.type
	if b.tree.type.bytes < ty.bytes then ty = b.tree.type end
	return quote
		var _a = [a]
		var _b = [b]
		var r: ty if _a < _b then r = _a else r = _b end
	in r end
end)

terra m.hexdigit(hb: uint8): int8
	var a = hb and 0x0F
	if a < 10 then return 0x30 + a
	else return 0x61 + (a-10) end
end

terra m.hexbyte(b: uint8): int8[2]
	return array(m.hexdigit((b and 0xF0) >> 4), m.hexdigit(b and 0x0F))
end

terra m.hexstr(src: &uint8, str: rawstring, sz: intptr)
	for i = 0, sz do
		var d = m.hexbyte(src[i])
		str[i*2] = d[0]
		str[i*2 + 1] = d[1]
	end
end

terra m.b32char(v: uint8): int8
	if v <= 25 then return 0x61 + v
	elseif v < 31 then return 0x32 + (v-26)
	else return 0 end
end

terra m.b32(v: uint64, buf: rawstring) -- 5 bytes -> 8 chars
	while v > 0 do
		var val = v % 32
		v = v / 32
		@buf = m.b32char(val)
		buf = buf + 1
	end
end

terra m.b32str(a: lib.mem.ptr(uint64))
	
end

terra m.decstr(val: intptr, buf: &int8): rawstring
-- works backwards to avoid copies. log10(2^64) ≈ 19.2 and we
-- need a byte for NUL so buf MUST point to THE END OF a buffer
-- at least 21 bytes long
	@buf = 0
	if val > 0 then while val > 0 do
		buf = buf - 1
		var dgt = val % 10
		val = val / 10
		@buf = 0x30 + dgt
	end else
		buf = buf - 1
		@buf = 0x30
	end
	return buf
end

terra m.decstr_friendly(val: intptr, buf: &int8): rawstring
-- as above except needs size-28 buffers, on account of all the commas
	@buf = 0
	var dgtct: uint8 = 0
	if val > 0 then while val > 0 do
		buf = buf - 1
		var dgt = val % 10
		val = val / 10
		@buf = 0x30 + dgt
		if dgtct == 2 and val > 0 then
			buf = buf - 1 @buf = @',' 
			dgtct = 0
		else dgtct = dgtct + 1 end
	end else
		buf = buf - 1
		@buf = 0x30
	end
	return buf
end

terra m.decparse(s: pstring): {intptr, bool}
	if not s then return 0, false end
	var val:intptr = 0
	var c = s.ptr
	while @c ~= 0 do
		if @c >= 0x30 and @c <= 0x39 then
			val = val * 10
			val = val + (@c - 0x30)
		else
			return 0, false
		end

		c = c + 1
		if s.ct ~= 0 and (c - s.ptr > s.ct) then lib.dbg('reached end') return val, true end
	end
	return val, true
end

terra m.ndigits(n: intptr, base: intptr): intptr
	var c = base
	var i = 1
	while true do
		if n < c then return i end
		c = c * base
		i = i + 1
	end
end

terra m.fsz_parse(f: pstring): {intptr, bool}
-- take a string representing a file size and return {nbytes, true}
-- or {0, false} if the parse fails
	if f.ct == 0 then f.ct = lib.str.sz(f.ptr) end
	var sz: intptr = 0
	for i = 0, f.ct do
		if f(i) == @',' then goto skip end
		if f(i) >= 0x30 and f(i) <= 0x39 then
			sz = sz * 10
			sz = sz + f(i) - 0x30
		else
			if i+0 == f.ct or f(i) == 0 then return sz, true end
			if i+1 == f.ct or f(i+1) == 0 then
				if f(i) == @'b' then return sz/8, true end -- bits
			else
				var s: intptr = 0
				if i+2 == f.ct or f(i+2) == 0 then 
					s = i + 1
				elseif (i+3 == f.ct or f(i+3) == 0) and f(i+1) == @'i' then
				-- grudgingly tolerate ~mebibits~ and its ilk, without
				-- affecting the result in any way
					s = i + 2
				else return 0, false end

				if f(s) == @'b' then sz = sz/8 -- bits
				elseif f(s) ~= @'B' then return 0, false end -- wth
			end
			var c = f(i)
			if c >= @'A' and c <= @'Z' then c = c + 0x20 end
			switch c do -- normal char literal syntax doesn't work here, leads to llvm error (!!)
				case [uint8]([string.byte('k')]) then return sz * [1024ULL ^ 1], true end
				case [uint8]([string.byte('m')]) then return sz * [1024ULL ^ 2], true end
				case [uint8]([string.byte('g')]) then return sz * [1024ULL ^ 3], true end
				case [uint8]([string.byte('t')]) then return sz * [1024ULL ^ 4], true end
				case [uint8]([string.byte('p')]) then return sz * [1024ULL ^ 5], true end
				case [uint8]([string.byte('e')]) then return sz * [1024ULL ^ 6], true end
				case [uint8]([string.byte('z')]) then return sz * [1024ULL ^ 7], true end
				case [uint8]([string.byte('y')]) then return sz * [1024ULL ^ 8], true end
				else return 0, false
			end
		end
	::skip::end
	return sz, true
end

return m
