--[[
    This file is part of Ice Lua Components.

    Ice Lua Components is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Ice Lua Components is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Ice Lua Components.  If not, see <http://www.gnu.org/licenses/>.
]]

local map_cache = nil
local map_cache_aerate = nil

-- returns a list consisting of {t,r,g,b} tuplets
function map_pillar_raw_unpack(tpack)
	local xlen,ylen,zlen 
	xlen,ylen,zlen = common.map_get_dims()
	local t = {}
	local i,j,y
	i = 1
	y = 0

	while true do
		-- fill with air
		while y < tpack[i+1] do
			t[y+1] = nil
			y = y + 1
		end

		-- fill with top data
		j = i + 4
		while y <= tpack[i+2] do
			t[y+1] = {tpack[j+3],tpack[j+2],tpack[j+1],tpack[j+0]}
			y = y + 1
			j = j + 4
		end

		-- check if end
		if tpack[i] == 0 then
			-- fill the rest with invisible
			while y < ylen do
				t[y+1] = false
				y = y + 1
			end
			-- that's it
			break
		end

		local ntr = tpack[i]-1-(tpack[i+2]-tpack[i+1]+1)
		i = i + 4*tpack[i]
		ntr = tpack[i+3] - ntr

		-- fill with invisible
		while y < ntr do
			t[y+1] = false
			y = y + 1
		end

		-- fill with bottom data
		while y < tpack[i+3] do
			t[y+1] = {tpack[j+3],tpack[j+2],tpack[j+1],tpack[j+0]}
			y = y + 1
			j = j + 4
		end
	end

	return t
end

function map_pillar_raw_get(x,z)
	if map_cache then
		local o = map_hashcoord2(x,z)
		map_cache[o] = map_cache[o] or {
			x = x,
			z = z,
			l = map_pillar_raw_unpack(common.map_pillar_get(x,z)),
		}
		return map_cache[o].l
	end
	return map_pillar_raw_unpack(common.map_pillar_get(x,z))
end

function map_pillar_raw_pack(t)
	local xlen,ylen,zlen 
	xlen,ylen,zlen = common.map_get_dims()
	local tpack = {}
	local y,i
	local rmode = 0
	local n,s,e,a
	a = 0
	i = nil
	y = 0

	while true do
		-- skip air
		while t[y+1] == nil and y < ylen do
			y = y + 1
		end

		if i then tpack[i] = n end

		if y >= ylen then
			-- inject empty run
			i = #tpack+1
			tpack[i+0] = 0
			tpack[i+1] = ylen
			tpack[i+2] = ylen-1
			tpack[i+3] = a
			break
		end

		-- allocate slot
		i = #tpack+1
		tpack[i+0] = 0
		tpack[i+1] = y
		tpack[i+2] = 0
		tpack[i+3] = a

		-- copy top run
		n = 1
		while t[y+1] do
			tpack[#tpack+1] = t[y+1][4]
			tpack[#tpack+1] = t[y+1][3]
			tpack[#tpack+1] = t[y+1][2]
			tpack[#tpack+1] = t[y+1][1]
			y = y + 1
			n = n + 1
		end
		tpack[i+2] = y-1

		-- skip dirt
		while t[y+1] == false do
			y = y + 1
			if y >= ylen then break end
		end
		if y >= ylen then break end

		-- build bottom run
		while t[y+1] do
			tpack[#tpack+1] = t[y+1][4]
			tpack[#tpack+1] = t[y+1][3]
			tpack[#tpack+1] = t[y+1][2]
			tpack[#tpack+1] = t[y+1][1]
			n = n + 1
			y = y + 1
		end

		a = y
	end

	return tpack
end

function map_pillar_raw_set(x,z,t)
	if map_cache then
		map_cache[map_hashcoord2(x,z)] = {
			x = x,
			z = z,
			l = t,
		}
		return
	end

	local tpack = map_pillar_raw_pack(t)

	common.map_pillar_set(x,z,tpack)

	if img_overview and tpack[5] then
		-- TODO: check for wrapping
		local r,g,b
		b = tpack[5]
		g = tpack[6]
		r = tpack[7]
		local c = argb_split_to_merged(r,g,b)
		common.img_pixel_set(img_overview, x, z, c)
		common.img_pixel_set(img_overview_hmap, x, z, tpack[2])
	end
end

function map_cache_start()
	map_cache = map_cache or {}
	map_cache_aerate = map_cache_aerate or {}
end

function map_cache_end()
	local _, m

	local old_map_cache_aerate = map_cache_aerate
	map_cache_aerate = nil
	for _, m in pairs(old_map_cache_aerate or {}) do
		map_pillar_aerate(m.x, m.z)
	end

	local old_map_cache = map_cache
	map_cache = nil
	for _, m in pairs(old_map_cache or {}) do
		map_pillar_raw_set(m.x, m.z, m.l)
	end
end

function map_block_aerate(x,y,z)
	return ({
		1,
		64+math.sin((x+z-y)*math.pi/4)*8,
		32-math.sin((x-z+y)*math.pi/4)*8,
		0
	})
end

function map_pillar_aerate(x,z)
	if map_cache_aerate then
		map_cache_aerate[map_hashcoord2(x,z)] = {x = x, z = z}
		return
	end
	local xlen,ylen,zlen 
	xlen,ylen,zlen = common.map_get_dims()
	local t = map_pillar_raw_get(x,z)
	local l = {
		map_pillar_raw_get(x-1,z),
		map_pillar_raw_get(x+1,z),
		map_pillar_raw_get(x,z-1),
		map_pillar_raw_get(x,z+1),
	}
	local y

	for y=1,ylen do
		if t[y] then
			if l[1][y] ~= nil and l[2][y] ~= nil
					and l[3][y] ~= nil and l[4][y] ~= nil
					and t[y-1] ~= nil and (y == ylen or t[y+1] ~= nil) then
				t[y] = false
			end
		elseif t[y] == false then
			if l[1][y] == nil or l[2][y] == nil
					or l[3][y] == nil or l[4][y] == nil
					or t[y-1] == nil or (y ~= ylen and t[y+1] == nil) then
				t[y] = map_block_aerate(x,y-1,z)
			end
		end
	end

	map_pillar_raw_set(x,z,t)
end

function map_hashcoord3(x,y,z)
	local xlen,ylen,zlen
	xlen,ylen,zlen = common.map_get_dims()

	return 
		 (y % ylen) + ylen*((x % xlen) + xlen*(z % zlen))
end

function map_hashcoord2(x,z)
	local xlen,ylen,zlen
	xlen,ylen,zlen = common.map_get_dims()

	return (x % xlen)
		+xlen*(z % zlen)
end

function map_chkdisbrk(x,y,z)
	-- A* ftw
	local loadq = {
		{x-1,y,z},
		{x+1,y,z},
		{x,y-1,z},
		{x,y+1,z},
		{x,y,z-1},
		{x,y,z+1},
	}
	local tmap = {}
	local pmap = {}
	local plist = {}
	local ptag = {}
	local ptaglist = {}
	local nukeq = {}

	local xlen,ylen,zlen
	xlen,ylen,zlen = common.map_get_dims()

	-- build chunks
	local i,j
		for i=1,#loadq do
		local prio,tx,ty,tz
		tx,ty,tz = loadq[i][1],loadq[i][2],loadq[i][3]

		if not pmap[map_hashcoord2(tx,tz)] then
			pmap[map_hashcoord2(tx,tz)] = map_pillar_raw_get(tx,tz)
			plist[#plist+1] = {tx,tz}
		end

		if (not tmap[map_hashcoord3(tx,ty,tz)]) and pmap[map_hashcoord2(tx,tz)][ty+1] ~= nil then
			local pq = collect_new_prioq(function(p,q)
				return p[1] < q[1]
			end)

			tmap[map_hashcoord3(tx,ty,tz)] = {
				heur = -ty,
				dist = 0,
				i = i,
			}
			pq.push({-ty,tx,ty,tz})

			local nukeasm = {}
			while nukeasm and not pq.empty() do
				local c = pq.pop()
				prio,tx,ty,tz = c[1],c[2],c[3],c[4]
				--print(prio,tx,ty,tz)
				local tm = tmap[map_hashcoord3(tx,ty,tz)]
				if prio <= tm.heur + tm.dist then
					--print(i,prio,tx,ty,tz)
					nukeasm[#nukeasm+1] = {tx,ty,tz}
					if not pmap[map_hashcoord2(tx,tz)] then
						pmap[map_hashcoord2(tx,tz)] = map_pillar_raw_get(tx,tz)
						plist[#plist+1] = {tx,tz}
					end

					local nb = {
						{tx-1,ty,tz},
						{tx+1,ty,tz},
						{tx,ty-1,tz},
						{tx,ty+1,tz},
						{tx,ty,tz-1},
						{tx,ty,tz+1},
					}

					local dist = tm.dist+1
					for j=1,6 do
						local cx,cy,cz = nb[j][1], nb[j][2], nb[j][3]
						local cm = tmap[map_hashcoord3(cx,cy,cz)]
						if cy == ylen or (cm and cm.i ~= i) then
							--print("BAIL!")
							nukeasm = nil
							break
						end

						if not pmap[map_hashcoord2(cx,cz)] then
							pmap[map_hashcoord2(cx,cz)] = map_pillar_raw_get(cx,cz)
							plist[#plist+1] = {cx,cz}
						end

						if pmap[map_hashcoord2(cx,cz)][cy+1] ~= nil then
							local heur = -cy
							if not cm then
								cm = {
									heur = heur,
									dist = dist,
									i = i,
								}
								tmap[map_hashcoord3(cx,cy,cz)] = cm
								pq.push({heur+dist,cx,cy,cz})
							else
								if cm.heur+cm.dist > heur+dist then
									cm.heur = heur
									cm.dist = dist
									pq.push({heur+dist,cx,cy,cz})
								end
							end
						end
					end
				end
			end

			if nukeasm then
				nukeq[#nukeq+1] = nukeasm
				--print(#nukeq,#nukeasm)
			end
		end
	end

	-- nuke it all
	-- TODO: assemble falling PMFs and drop the buggers
	local brokestuff = false
	for i=1,#nukeq do
		local tx,ty,tz
		local nl = nukeq[i]
		if #nl > 0 then brokestuff = true end
		local lpmf = {}
		local pmfx, pmfy, pmfz = nil, nil, nil
		for j=1,#nl do
			local c = nl[j]
			tx,ty,tz = c[1],c[2],c[3]
			local hc2 = map_hashcoord2(tx,tz)
			local cl = pmap[hc2][ty+1]
			if cl and cl ~= true then
				if not pmfx then
					pmfx, pmfy, pmfz = tx, ty, tz
				end
				local r,g,b
				r,g,b = cl[2], cl[3], cl[4]
				if #lpmf < 4000 then
					lpmf[#lpmf+1] = {
						radius=16,
						x = (tx - pmfx)*32,
						y = (ty - pmfy)*32,
						z = (tz - pmfz)*32,
						r = r, g = g, b = b,
					}
				end
			end
			if not ptag[hc2] then
				ptag[hc2] = true
				ptaglist[#ptaglist+1] = {tx,tz}
			end
			pmap[hc2][ty+1] = nil
		end
		if #lpmf > 0 then
			local mdl = common.model_new(1)
			mdl = common.model_bone_new(mdl, #lpmf)
			common.model_bone_set(mdl, 0, "fall", lpmf)
			particles_add(new_particle({
				x = pmfx + 0.5, y = pmfy + 0.5, z = pmfz + 0.5,
				model = mdl, free_model = true,
				noclip = true,
				vry = math.random()*2-1, vrx = 0.4, vry2 = math.random()*2-1,
				size = 8,
			}))
		end
	end

	if brokestuff and client then
		client.wav_play_global(wav_grif,x+0.5,y+0.5,z+0.5)
	end

	-- apply nukings
	local nptag = {}
	local nptaglist = {}
	for i=1,#ptaglist do
		local tx,tz
		local c = ptaglist[i]
		tx,tz = c[1], c[2]
		map_pillar_raw_set(tx,tz,pmap[map_hashcoord2(tx,tz)])

		if not nptag[map_hashcoord2(tx,tz)] then
			nptag[map_hashcoord2(tx,tz)] = true
			nptaglist[#nptaglist+1] = {tx,tz}
		end
	end

	-- aerate
	for i=1,#nptaglist do
		local tx,tz
		local c = nptaglist[i]
		tx,tz = c[1], c[2]

		map_pillar_aerate(tx,tz)
	end
end

function map_block_get(x,y,z)
	local xlen,ylen,zlen 
	xlen,ylen,zlen = common.map_get_dims()
	if y < 0 then return nil end
	if y >= ylen then return false end

	if map_cache then
		local t = map_pillar_raw_get(x,z)
		return t[y+1]
	else
		local l = common.map_pillar_get(x,z)
		local i = 1
		local ltop = 0
		while true do
			-- Check if air
			if y < l[i+1] then return nil end

			-- Check lower colour group
			if y <= l[i+2] then 
				i = i + ((y-l[i+1])+1)*4
				return {l[i+3], l[i+2], l[i+1], l[i+0]}
			end

			-- Check N
			if l[i+0] == 0 then
				-- Solid invisible
				return false
			end

			-- Calculate ltop
			ltop = (l[i+0] - 1) - (l[i+2] - l[i+1] + 1)

			-- Advance
			i = i + l[i+0]*4

			-- Check for ceiling clash
			if y < l[i+3] then
				-- Check upper colour group
				if y < l[i+3]-ltop then
					-- Solid invisible
					return false
				else
					-- Coloured solid
					i = i + (y-l[i+3])*4
					return {l[i+3], l[i+2], l[i+1], l[i+0]}
				end
			end

		end
	end
end


function map_block_set(x,y,z,typ,r,g,b)
	local xlen,ylen,zlen 
	xlen,ylen,zlen = common.map_get_dims()
	if y < 0 or y >= ylen then return end

	local t = map_pillar_raw_get(x,z)
	t[y+1] = {typ, r, g, b}
	map_pillar_raw_set(x,z,t)

	map_pillar_aerate(x,z)
	map_pillar_aerate(x-1,z)
	map_pillar_aerate(x+1,z)
	map_pillar_aerate(x,z-1)
	map_pillar_aerate(x,z+1)

	if bhealth_clear then
		bhealth_clear(x,y,z,false)
	end
end

function map_block_paint(x,y,z,typ,r,g,b)
	local xlen,ylen,zlen 
	xlen,ylen,zlen = common.map_get_dims()
	if y < 0 or y >= ylen then return end

	local t = map_pillar_raw_get(x,z)
	if t[y+1] then
		t[y+1] = {typ, r, g, b}
		map_pillar_raw_set(x,z,t)
	end
end

function map_block_break(x,y,z)
	local xlen,ylen,zlen 
	xlen,ylen,zlen = common.map_get_dims()
	if y < 0 or y >= ylen-1 then return false end

	local t = map_pillar_raw_get(x,z)
	if t[y+1] == nil then return false end
	t[y+1] = nil
	map_pillar_raw_set(x,z,t)

	map_pillar_aerate(x,z)
	map_pillar_aerate(x-1,z)
	map_pillar_aerate(x+1,z)
	map_pillar_aerate(x,z-1)
	map_pillar_aerate(x,z+1)

	map_chkdisbrk(x,y,z)

	bhealth_clear(x,y,z,false)

	return true
end

function map_block_delete(x,y,z)
	local xlen,ylen,zlen 
	xlen,ylen,zlen = common.map_get_dims()
	if y < 0 or y >= ylen-1 then return end

	local t = map_pillar_raw_get(x,z)
	t[y+1] = nil
	map_pillar_raw_set(x,z,t)

	map_pillar_aerate(x,z)
	map_pillar_aerate(x-1,z)
	map_pillar_aerate(x+1,z)
	map_pillar_aerate(x,z-1)
	map_pillar_aerate(x,z+1)
end

function map_block_pick(x,y,z)
	local xlen,ylen,zlen 
	xlen,ylen,zlen = common.map_get_dims()
	if x < 0 or x >= xlen then return end
	if y < 0 or y >= ylen then return end
	if z < 0 or z >= zlen then return end

	local t = map_pillar_raw_get(x,z)
	local c = t[y+1]

	if c==nil then error(x..","..y..","..z) end

	if c==false then return nil end

	return c[1],c[2],c[3],c[4]
end

--checks for neighbors
function map_is_buildable(x, y, z)
	local xlen,ylen,zlen
	xlen,ylen,zlen = common.map_get_dims()
	--warning! a long condition
	if map_block_get(x,y,z) == nil then
		if map_block_get(x + 1,y,z) ~= nil or map_block_get(x - 1,y,z) ~= nil or map_block_get(x,y + 1,z) ~= nil or map_block_get(x,y - 1,z) ~= nil or map_block_get(x,y,z - 1) ~= nil or map_block_get(x,y,z + 1) ~= nil then
			if x >=0 and x < xlen and y >= 0 and y < ylen - 2 and z >= 0 and z < zlen then
				for i=1,players.max do
					local p = players[i]
					if p and p.alive then
						if math.floor(p.x) == x and math.floor(p.y) == y and math.floor(p.z) == z then
							return false
						end
					end
				end
				return true
			end
		end
	else
		return false;
	end
end
