-- vim: ft=terra
local m = {
	zero = macro(function(r)
		return quote
			for i = 0, [r.tree.type.N] do r[i] = 0 end
		end
	end);
	heapa_raw = terralib.externfunction('malloc', intptr -> &opaque);
	heapr_raw = terralib.externfunction('realloc', {&opaque, intptr} -> &opaque);
	heapf = terralib.externfunction('free', &opaque -> {});
	cpy = terralib.externfunction('mempcpy',{&opaque, &opaque, intptr} -> &opaque);
}

m.heapa = macro(function(ty, sz)
	local p = m.ptr(ty:astype())
	return `p {
		ptr = [&ty:astype()](m.heapa_raw(sizeof(ty) * sz));
		ct = sz;
	}
end)

function m.cache(ty,sz)
	sz = sz or 32
	local struct c {
		store: ty[sz]
		top: intptr
		cur: intptr
	}
	c.name = string.format('cache<%s,%u>', tostring(ty), sz)
	terra c:insert(v: ty)
		if [ty.ptr_basetype ~= nil] then
			if self.cur < self.top then self.store[self.cur]:free() end
		end
		self.store[self.cur] = v
		self.top = lib.math.biggest(self.top, self.cur + 1)
		self.cur = (self.cur + 1) % sz
		return v
	end
	c.metamethods.__apply = terra(self: &c, idx: intptr) return &self.store[idx] end
	if ty.ptr_basetype then
		terra c:free()
			for i=0,self.top do self.store[i]:free() end
		end
	end
	return c
end

local function mkptr(ty, dyn)
	local t = terralib.types.newstruct(string.format('%s<%s>', dyn and 'ptr' or 'ref', ty))
	t.entries = {
		{'ptr', &ty};
		{'ct', intptr};
	}
	t.ptr_basetype = ty
	local recurse = false
	--if ty:isstruct() then
		--if ty.methods.free then recurse = true end
	--end
	if dyn then
		t.methods = {
			free = terra(self: &t): bool
				[recurse and quote
					self.ptr:free()
				end or {}]
				if self.ct > 0 then
					m.heapf(self.ptr)
					self.ptr = nil
					self.ct = 0
					return true
				end
				return false
			end;
			init = terra(self: &t, newct: intptr): bool
				if newct == 0 then self.ct = 0 self.ptr = nil return false end
				var nv = [&ty](m.heapa_raw(sizeof(ty) * newct))
				if nv ~= nil then
					self.ptr = nv
					self.ct = newct
					return true
				else return false end
			end;
			resize = terra(self: &t, newct: intptr): bool
				var nv: &ty
				if self.ct > 0
					then nv = [&ty](m.heapr_raw(self.ptr, sizeof(ty) * newct))
					else nv = [&ty](m.heapa_raw(sizeof(ty) * newct))
				end
				if nv ~= nil then
					self.ptr = nv
					self.ct = newct
					return true
				else return false end
			end;
		}
		terra t:ensure(n: intptr)
			if self.ptr == nil then
				if not self:init(n) then return t {ptr=nil,ct=0} end
			end
			if self.ct >= n then return @self end
			self:resize(n)
			return @self
		end
	end
	terra t:advance(n: intptr)
		self.ptr = self.ptr + n
		self.ct = self.ct - n
		return self.ptr
	end
	terra t.methods.null(): t return t { ptr = nil, ct = 0 } end -- maybe should be a macro?
	terra t:ref() return self.ptr ~= nil end
	t.metamethods.__not = macro(function(self) return `not self:ref() end)
	t.metamethods.__apply = macro(function(self,idx) return `self.ptr[idx] end)
	if not ty:isstruct() then
		terra t:cmp_raw(other: &ty)
			for i=0, self.ct do
				if self.ptr[i] ~= other[i] then return false end
			end
			return true
		end
		terra t:cmp(other: t)
			if other.ct ~= self.ct then return false end
			return self:cmp_raw(other.ptr)
		end
	end
	return t
end

m.ptr = terralib.memoize(function(ty) return mkptr(ty, true) end)
m.ref = terralib.memoize(function(ty) return mkptr(ty, false) end)
m.lstptr = function(ty) return m.ptr(m.ptr(ty)) end -- make code more readable

m.vec = terralib.memoize(function(ty)
	local v = terralib.types.newstruct(string.format('vec<%s>', ty.name))
	v.entries = {
		{field = 'storage', type = m.ptr(ty)};
		{field = 'sz', type = intptr};
		{field = 'run', type = intptr};
	}
	local terra biggest(a: intptr, b: intptr)
		if a > b then return a else return b end
	end
	terra v:init(run: intptr): bool
		if not self.storage:init(run) then return false end
		self.run = run
		self.sz = 0
		return true
	end;
	terra v:assure(n: intptr)
		if self.storage.ct < n then
			self.storage:resize(biggest(n, self.storage.ct + self.run))
		end
	end
	terra v:new(): &ty
		self:assure(self.sz + 1)
		self.sz = self.sz + 1
		return self.storage.ptr + (self.sz - 1)
	end;
	terra v:push(val: ty)
		self:assure(self.sz + 1)
		self.storage.ptr[self.sz] = val
		self.sz = self.sz + 1
	end;
	terra v:free() self.storage:free() end;
	terra v:last(idx: intptr): &ty
		if self.sz > idx then
			return self.storage.ptr + (self.sz - (idx+1))
		else lib.bail('vector underrun!') end
	end;
	terra v:crush()
		self.storage:resize(self.sz)
		return self.storage
	end;
	v.metamethods.__apply = terra(self: &v, idx: intptr): &ty -- no index??
		if self.sz > idx then
			return self.storage.ptr + idx
		else lib.bail('vector overrun!') end
	end
	return v 
end)

return m
