local NAME = minetest.get_current_modname()
local ENERGYCOST  = {
	static_strut = 20,
	dynamic_struct = 24,
	hardness_mod = 1,
	mining = 25,
}
local MAX_ENERGY = 3000
local FORMSPEC = sparktech.add_inventory(19.8,8,
	"list[current_name;quarry;0,0;16,6;]")
local MAX_SIZE = 64
local MAX_DEPTH = 128
local MAX_HEIGHT = 16


local function Position(x, y, z)
	return {x=x, y=y, z=z}
end


local function try_drain_energy(target_pos, amount)
	-- Returns true if drained, false otherwise
	-- Removes amount energy from target_pos if energy >= amount
	local target = minetest.get_meta(target_pos)
	local current = target:get_int("energy")
	if current >= amount then
		target:set_int("energy", current - amount)
		return true
	else
		return false
	end
end


local function try_remove_item(target_pos, listname, itemname, amount)
	local inv = minetest.get_inventory({type="node", pos=target_pos})
	local stack = ItemStack(itemname)
	local amount = amount or 1
	stack:set_count(amount)

	local result = inv:remove_item(listname, stack)

	if result:get_count() ~= amount then
		inv:add_item(listname, result)
	else
		return true
	end

	return false
end


local function on_construct(pos, player)
	player:get_meta():set_string(NAME .. ":quarry_pos",
		minetest.pos_to_string(pos))

	local meta = minetest.get_meta(pos)
	meta:set_string("formspec", FORMSPEC)
	local inventory = meta:get_inventory()
	inventory:set_size('quarry', 96)
end


local function dig_node(pos, quarrypos)
	local node = minetest.get_node(pos)

	if node.name ~= "air" and node.name ~= "ignore" and
		sparktech.drain_energy(quarrypos, ENERGYCOST.mining) then

		local quarry = minetest.get_meta(quarrypos)
		local quarry_inv = quarry:get_inventory()

		return sparktech.dig_node(pos, quarry_inv, "quarry", nil)
	end

	return false
end


local function on_marker_placed(pos, quarry_pos, player)
	if quarry_pos.x == pos.x and
	   quarry_pos.y == pos.y and
	   quarry_pos.z == pos.z then
		 notify.hud.sendtext(player, "Congratulations, you replaced the quarry.")
		return
	end

	local diffx = pos.x - quarry_pos.x
	local diffy = pos.y - quarry_pos.y
	local diffz = pos.z - quarry_pos.z

	if not (diffx >= 3 +1 or diffx <= -3 -1) or
	   not (diffy >= 2 or diffy <= -2) or
	   not (diffz >= 3 +1 or diffz <= -3 -1) then
		notify.hud.sendtext(player,
			"The quarry is too small! Must be atleast 4x4 and 3 high!", 10)
		return
	end

	if not (diffx <= MAX_SIZE +1 and diffx >= -MAX_SIZE -1) or
	   not (diffy <= MAX_HEIGHT and diffy >= -MAX_HEIGHT) or
	   not (diffz <= MAX_SIZE +1 and diffz >= -MAX_SIZE -1) then
		notify.hud.sendtext(player,
			"The quarry is too big! Must be maximum "
			.. MAX_SIZE .. "x" ..MAX_SIZE.. " and " .. MAX_HEIGHT.. " high!", 10)
		return
	end

	local modx, modz
	if diffx < 0 then modx = 1 else modx = -1 end
	if diffz < 0 then modz = 1 else modz = -1 end
	-- Set quarry metadata
	local meta = minetest.get_meta(quarry_pos)
	meta:set_string("marker", minetest.pos_to_string(
		Position(quarry_pos.x + diffx +modx, quarry_pos.y + diffy, quarry_pos.z +diffz +modz)))
	meta:set_int("current_frame", 1)

	minetest.get_node_timer(quarry_pos):start(0.1)
end


local function marker_construct(pos, player)
	local quarry_pos = minetest.string_to_pos(
		player:get_meta():get_string(NAME .. ":quarry_pos"))
	local quarry = minetest.get_node(quarry_pos)
	on_marker_placed(pos, quarry_pos, player)
end


local function place_strut(position, quarrypos)
	if minetest.get_node(position).name == "air"
		and try_drain_energy(quarrypos, ENERGYCOST.static_strut)
		and try_remove_item(quarrypos, "quarry", NAME .. ":static_strut") then
			minetest.set_node(position, { name = NAME .. ":static_strut"})
			return true
	end
	return false
end


local function prepare_area(pos, pos2)
	local placement_done = true
	local minx, maxx = math.order(pos.x, pos2.x)
	local miny, maxy = math.order(pos.y, pos2.y)
	local minz, maxz = math.order(pos.z, pos2.z)
	for x=minx , maxx do
		for y=miny, maxy do
			for z=minz, maxz do
				-- dont remove quarry
				if not (x == pos.x and y == pos.y and z == pos.z) then
					local count = 0
					if x == pos.x or x == pos2.x then count = count +1 end
					if y == pos.y or y == pos2.y then count = count +1 end
					if z == pos.z or z == pos2.z then count = count +1 end
					if count >= 2 then
						local node = minetest.get_node(Position(x, y, z))
						if node.name ~= NAME .. ":static_strut" then
							if node.name ~= "air" then
								dig_node(Position(x, y, z), pos)
								return false
							end
							place_strut(Position(x, y, z), pos)
							return false
						end
					else
						if dig_node(Position(x, y, z), pos) then
							return false
						end
					end
				end
			end
		end
	end
	return true --we cleared the area
end


local anim_table = {}
anim_table.listx = {}
anim_table.listz = {}
anim_table.listy = {}
local function animate_spawn(pos, pos2)
	if anim_table.finished then return end
	local sizex = math.abs(pos.x - pos2.x)
	local sizey = math.abs(pos.y - pos2.y)
	local sizez = math.abs(pos.z - pos2.z)
	local listx = anim_table.listx
	local listz = anim_table.listz
	local listy = anim_table.listy

	table.insert(listx, minetest.add_entity(pos, NAME .. ":tracer", "l" ))
	for i=0, sizez -2 do
		table.insert(listx, minetest.add_entity(pos, NAME .. ":holder", "n"))
	end
	table.insert(listx, minetest.add_entity(pos, NAME .. ":tracer", "r" ))

	table.insert(listz, minetest.add_entity(pos, NAME .. ":tracer", "f" ))
	for i=0, sizex -2 do
		table.insert(listz, minetest.add_entity(pos, NAME .. ":holder", "u"))
	end
	table.insert(listz, minetest.add_entity(pos, NAME .. ":tracer", "b" ))

	table.insert(listy, minetest.add_entity(pos, NAME .. ":grabber"))
	for i=0, sizey +1 do
		table.insert(listy, minetest.add_entity(pos, NAME .. ":laser"))
	end
	anim_table.finished = true
	return true
end


local function animate_spawn_laser(pos, height)
	table.insert(anim_table.listy, minetest.add_entity(Position(pos.x, pos.y -height -1, pos.z), NAME .. ":laser"))
	return true
end


local lastx = 0
local function animate(startpos, endpos, pos)
	local minx, maxx = math.order(startpos.x, endpos.x)
	local minz, maxz = math.order(startpos.z, endpos.z)
	local miny, maxy = math.order(startpos.y, endpos.y)

	if pos.x ~= lastx then
		lastx = pos.x
		local i = minz
		for k, v in pairs(anim_table.listx) do
			v:move_to(Position(pos.x, maxy, i))
			i = i+1
		end
	end

	i = minx
	for k,v in pairs(anim_table.listz) do
		v:move_to(Position(i, maxy, pos.z))
		i = i+1
	end

	i = maxy
	for k,v in pairs(anim_table.listy) do
		v:move_to(Position(pos.x, i, pos.z))
		i = i-1
	end
	return true
end


local last_pos = Position(0, 0, 0)
local function timer_trigger(pos, elapsed)
	local meta = minetest.get_meta(pos)
	if not meta then assert("Meta not found of quarry") end
	local framenum = meta:get_int("current_frame")
	local marker_pos = minetest.string_to_pos(meta:get_string("marker"))

	while true do -- Make this section breakable
		if framenum < 0 then
			-- Operation Phase
			--
			local start_pos = minetest.string_to_pos(meta:get_string("dig_area_start"))
			local end_pos = minetest.string_to_pos(meta:get_string("dig_area_end"))
			if not start_pos or not end_pos then return end
			local height = start_pos.y + framenum

			local exitloop = false
			local startx, endx, modx
			if framenum % 2 == 1 then
				startx = start_pos.x
				endx = end_pos.x
				modx = 1
			else
				startx = end_pos.x
				endx = start_pos.x
				modx = -1
			end

			for x=startx, endx, modx do
				local startz, endz, modz = end_pos.z, start_pos.z, 1
				if framenum % 2 == 1 then
					if x % 2 == 0 then
						startz, endz = start_pos.z, end_pos.z
					end
				else
					if x % 2 == 1 then
						startz, endz = start_pos.z, end_pos.z
					end
				end
				if startz > endz then modz = -1 end

				for z=startz, endz, modz do
					if dig_node(Position(x, height, z), pos) then
						if animate(pos, marker_pos, Position(x, height, z)) then
							exitloop = true
							last_pos = Position(x, height, z)
						end
					end
					if exitloop then break end
				end
				if exitloop then break end
			end
			if not exitloop then
				-- Layer cleared, next layer
				framenum = framenum - 1
				animate(pos, marker_pos, Position(last_pos.x, height, last_pos.y))
				animate_spawn_laser(last_pos, framenum)
			end
		elseif framenum == 1 then
			-- preparation phase
			if prepare_area(pos, marker_pos) then
				--animate_spawn(pos, marker_pos)
				framenum = 0
			end
		else
			-- shrink the operational area here,
			-- to a 2d space with one piece of border taken away to drill there
			local posx, pos2x = math.order(pos.x, marker_pos.x)
			local posy, pos2y = math.order(pos.y, marker_pos.y)
			local posz, pos2z = math.order(pos.z, marker_pos.z)
			posx = posx +1
			pos2x = pos2x -1
			posz = posz + 1
			pos2z = pos2z -1
			meta:set_string("dig_area_start", minetest.pos_to_string(
				Position(posx, posy, posz)))
			meta:set_string("dig_area_end", minetest.pos_to_string(
				Position(pos2x, posy, pos2z)))
			framenum = -1
		end

		break
	end

	meta:set_int("current_frame", framenum)
	
	if framenum >= -MAX_DEPTH then minetest.get_node_timer(pos):start(0.1) end
end


minetest.register_node( NAME .. ":quarry_marker", {
	descritption = "quarry marker",
	tiles = {
		NAME .. "marker.png"
	},
	groups = { sparktech_techy = 1 },
	after_place_node = marker_construct,
})


minetest.register_node( NAME .. ":static_strut", {
	description = "supporting strut",
	drawtype = "nodebox",
	paramtype = "light",
	connects_to = {
		NAME .. ":static_strut",
		NAME .. ":quarry"
	},
	tiles = {
		NAME .. "_strut.png",
	},
	node_box = {
		type = "connected",
		disconnected = {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
		connect_top = {-0.25, -0.25, -0.25, 0.25, 0.75, 0.25},
		connect_bottom = {-0.25, -0.75, -0.25, 0.25, 0.25, 0.25},
		connect_back = {-0.25, -0.25, -0.25, 0.25, 0.25, 0.75},
		connect_right = {-0.25, -0.25, -0.25, 0.75, 0.25, 0.25},
		connect_front = {-0.25, -0.25, -0.75, 0.25, 0.25, 0.25},
		connect_left = {-0.75, -0.25, -0.25, 0.25, 0.25, 0.25},
	},
	groups = { sparktech_techy = 1 }
})


minetest.register_node( NAME .. ":quarry", {
	description = "Electric Quarry",

	tiles = {
		NAME .. "_steel_sideplate.png",
		NAME .. "_steel_sideplate.png",
		NAME .. "_steel_sideplate.png",
		NAME .. "_steel_sideplate.png",
		NAME .. "_steel_backplate.png",
		NAME .. "_quarry_frontplate.png"
	},
	paramtype2 = "facedir",

	groups = {
		sparktech_techy = WRENCHABLE,
		sparktech_energy_type = ENERGY_CONSUMER,
		sparktech_net_trigger = TRUE,
		sparktech_energy_max = MAX_ENERGY,
		sparktech_energy_wakeup = ENERGYCOST.mining
	},

	on_timer = timer_trigger,

	after_place_node = on_construct,

	--on_destruct = function(pos, player) player:get_meta():set_string(NAME .. ":quarry_pos" , "") end,
	allow_metadata_inventory_put = function(_,_,_, stack) return stack:get_count() end,
	allow_metadata_inventory_take = function(_, _, _, stack) return stack:get_count() end,
	_sparkoptionchanged = function(pos, name)
		if name == "enabled" then
			local timer = minetest.get_node_timer(pos)
			if sparktech.getoption(pos, name) then
				timer:start(0.1)
			else
				timer:stop()
			end
		elseif name == "depth" then
			if sparktech.getoption(pos, name) > MAX_DEPTH then
				local meta = minetest.get_meta(nodepos)
				meta:set_int("depth", MAX_DEPTH)
			end
		end
	end,
	_sparkoption = {
		enabled = true,
		depth = MAX_DEPTH,
	},
	_sparkoutput = {
		items = function(nodepos)
			local meta = minetest.get_meta(nodepos)
			return meta:get_inventory()
		end,
		reachedDepth = function(nodepos)
			return 0
		end
	},
	_sparkoutputtype = {
		items = "Inventory",
		reachedDepth = type(0)
	}
})


minetest.register_craft({
	output = NAME .. ":lv_quarry",
	recipe = {
		{ NAME .. ":static_strut", "sparktool:handdrill", NAME .. ":static_strut" },
		{ NAME .. ":static_strut", "orioncraft:iron_bar", NAME .. ":static_strut" },
		{ NAME .. ":static_strut", "sparkcore:cable", NAME .. ":static_strut" }
	}
})


minetest.register_craft({
	output = NAME .. ":static_strut 16",
	recipe = {
		{ "orioncraft:iron_bar", "orioncraft:iron_bar", "orioncraft:iron_bar" },
		{ "", "", "" },
		{ "orioncraft:iron_bar", "orioncraft:iron_bar", "orioncraft:iron_bar" }
	}
})
