celevator.callbutton = {}

local S = core.get_translator("celevator")

local function makebuttontex(dir,upon,downon,inventory)
	local tex = "[combine:64x64"..
	            ":0,0=celevator_cabinet_sides.png"..
	            ":32,0=celevator_cabinet_sides.png"..
	            ":0,32=celevator_cabinet_sides.png"..
	            ":32,32=celevator_cabinet_sides.png"..
	            ":22,24=celevator_callbutton_panel.png"
	if inventory then tex = "[combine:32x32:5,0=celevator_callbutton_panel.png" end
	if dir == "up" then
		if inventory then
			tex = tex..":7,11=celevator_callbutton_up.png"
		else
			tex = tex..":24,35=celevator_callbutton_up.png"
		end
		if upon then
			tex = tex..":33,36=celevator_callbutton_light.png"
		end
	elseif dir == "down" then
		if inventory then
			tex = tex..":7,11=celevator_callbutton_down.png"
		else
			tex = tex..":24,35=celevator_callbutton_down.png"
		end
		if downon then
			tex = tex..":33,36=celevator_callbutton_light.png"
		end
	elseif dir == "both" then
		if inventory then
			tex = tex..":7,4=celevator_callbutton_up.png:7,19=celevator_callbutton_down.png"
		else
			tex = tex..":24,28=celevator_callbutton_up.png:24,43=celevator_callbutton_down.png"
		end
		if upon then
			tex = tex..":33,29=celevator_callbutton_light.png"
		end
		if downon then
			tex = tex..":33,44=celevator_callbutton_light.png"
		end
	end
	return(tex)
end

local upname = S("Elevator Up Call Button")
local dnname = S("Elevator Down Call Button")
local bothname = S("Elevator Up and Down Call Buttons")
local onname = S("Elevator Call Button (on state - you hacker you!)")

local validstates = {
	{"up",false,false,upname},
	{"up",true,false,onname},
	{"down",false,false,dnname},
	{"down",false,true,onname},
	{"both",false,false,bothname},
	{"both",true,false,onname},
	{"both",false,true,onname},
	{"both",true,true,onname},
}

function celevator.callbutton.setlight(pos,dir,newstate)
	local node = celevator.get_node(pos)
	if core.get_item_group(node.name,"_celevator_callbutton") ~= 1 then return end
	if dir == "up" then
		if core.get_item_group(node.name,"_celevator_callbutton_has_up") ~= 1 then return end
		local lit = core.get_item_group(node.name,"_celevator_callbutton_up_lit") == 1
		if lit == newstate then return end
		local newname = "celevator:callbutton_"
		if core.get_item_group(node.name,"_celevator_callbutton_has_down") == 1 then
			newname = newname.."both"
		else
			newname = newname.."up"
		end
		if newstate then newname = newname.."_upon" end
		if core.get_item_group(node.name,"_celevator_callbutton_down_lit") == 1 then
			newname = newname.."_downon"
		end
		node.name = newname
		core.swap_node(pos,node)
	elseif dir == "down" then
		if core.get_item_group(node.name,"_celevator_callbutton_has_down") ~= 1 then return end
		local lit = core.get_item_group(node.name,"_celevator_callbutton_down_lit") == 1
		if lit == newstate then return end
		local newname = "celevator:callbutton_"
		if core.get_item_group(node.name,"_celevator_callbutton_has_up") == 1 then
			newname = newname.."both"
		else
			newname = newname.."down"
		end
		if core.get_item_group(node.name,"_celevator_callbutton_up_lit") == 1 then
			newname = newname.."_upon"
		end
		if newstate then newname = newname.."_downon" end
		node.name = newname
		core.swap_node(pos,node)
	end
end

local function disambiguatedir(pos,player)
	if player and not player.is_fake_player then
		local eyepos = vector.add(player:get_pos(),vector.add(player:get_eye_offset(),vector.new(0,1.5,0)))
		local lookdir = player:get_look_dir()
		local distance = vector.distance(eyepos,pos)
		local endpos = vector.add(eyepos,vector.multiply(lookdir,distance+1))
		local ray = core.raycast(eyepos,endpos,true,false)
		local pointed,button,hitpos
		repeat
			pointed = ray:next()
			if pointed and pointed.type == "node" then
				local node = core.get_node(pointed.under)
				if node.name and (core.get_item_group(node.name,"_celevator_callbutton") == 1) then
					button = pointed.under
					hitpos = vector.subtract(pointed.intersection_point,button)
				end
			end
		until button or not pointed
		if not hitpos then return end
		hitpos.y = -1*hitpos.y
		hitpos.y = math.floor((hitpos.y+0.5)*64+0.5)+1
		return hitpos.y >= 40 and "down" or "up"
	end
end

for _,state in ipairs(validstates) do
	local boringside = "[combine:64x64"..
	                   ":0,0=celevator_cabinet_sides.png"..
	                   ":32,0=celevator_cabinet_sides.png"..
	                   ":0,32=celevator_cabinet_sides.png"..
	                   ":32,32=celevator_cabinet_sides.png"
	local nname = "celevator:callbutton_"..state[1]
	local dropname = nname
	local light = 0
	if state[2] then
		nname = nname.."_upon"
		light = light + 5
	end
	if state[3] then
		nname = nname.."_downon"
		light = light + 5
	end
	local idle = not (state[2] or state[3])
	local description = state[4]
	core.register_node(nname,{
		description = description,
		groups = {
			dig_immediate = 2,
			not_in_creative_inventory = (idle and 0 or 1),
			_celevator_callbutton = 1,
			_celevator_callbutton_has_up = (state[1] == "down" and 0 or 1),
			_celevator_callbutton_has_down = (state[1] == "up" and 0 or 1),
			_celevator_callbutton_up_lit = (state[2] and 1 or 0),
			_celevator_callbutton_down_lit = (state[3] and 1 or 0),
		},
		inventory_image = makebuttontex(state[1],state[2],state[3],true),
		drop = dropname,
		tiles = {
			boringside,
			boringside,
			boringside,
			boringside,
			boringside,
			makebuttontex(state[1],state[2],state[3])
		},
		paramtype = "light",
		paramtype2 = "facedir",
		light_source = light,
		drawtype = "nodebox",
		node_box = {
			type = "fixed",
			fixed = {
				{-0.16,-0.37,0.475,0.17,0.13,0.5},
			},
		},
		after_place_node = function(pos)
			local meta = core.get_meta(pos)
			local idmsg = S("Car ID")
			local landingmsg = S("Landing Number")
			local savemsg = S("Save")
			local fs = "formspec_version[7]size[8,5]"
			fs = fs.."field[0.5,0.5;7,1;carid;"..idmsg..";]"
			fs = fs.."field[0.5,2;7,1;landing;"..landingmsg..";]"
			fs = fs.."button[3,3.5;2,1;save;"..savemsg.."]"
			meta:set_string("formspec",fs)
		end,
		on_receive_fields = function(pos,_,fields)
			if tonumber(fields.carid) and tonumber(fields.landing) then
				local carid = tonumber(fields.carid)
				local carinfo = core.deserialize(celevator.storage:get_string(string.format("car%d",carid)))
				if not carinfo then return end
				table.insert(carinfo.callbuttons,{pos=pos,landing=tonumber(fields.landing)})
				celevator.storage:set_string(string.format("car%d",carid),core.serialize(carinfo))
				local meta = core.get_meta(pos)
				meta:set_int("carid",carid)
				meta:set_int("landing",tonumber(fields.landing))
				meta:set_string("formspec","")
			end
		end,
		on_destruct = function(pos)
			local meta = core.get_meta(pos)
			local carid = meta:get_int("carid")
			if carid == 0 then return end
			local carinfo = core.deserialize(celevator.storage:get_string(string.format("car%d",carid)))
			if not carinfo then return end
			for i,button in pairs(carinfo.callbuttons) do
				if vector.equals(pos,button.pos) then
					table.remove(carinfo.callbuttons,i)
					celevator.storage:set_string(string.format("car%d",carid),core.serialize(carinfo))
				end
			end
		end,
		on_rightclick = function(pos,_,clicker)
			local meta = core.get_meta(pos)
			local carid = meta:get_int("carid")
			if carid == 0 then return end
			local carinfo = core.deserialize(celevator.storage:get_string(string.format("car%d",carid)))
			if not carinfo then return end
			local controllerpos = carinfo.controllerpos or carinfo.dispatcherpos
			local isdispatcher = carinfo.dispatcherpos
			if not controllerpos then return end
			local controllermeta = core.get_meta(controllerpos)
			if controllermeta:get_int("carid") ~= carid then return end
			local landing = meta:get_int("landing")
			if state[1] == "up" then
				if isdispatcher then
					celevator.dispatcher.handlecallbutton(controllerpos,landing,"up")
				else
					celevator.controller.handlecallbutton(controllerpos,landing,"up")
				end
			elseif state[1] == "down" then
				if isdispatcher then
					celevator.dispatcher.handlecallbutton(controllerpos,landing,"down")
				else
					celevator.controller.handlecallbutton(controllerpos,landing,"down")
				end
			elseif state[1] == "both" then
				local dir = disambiguatedir(pos,clicker)
				if dir == "up" then
					if isdispatcher then
						celevator.dispatcher.handlecallbutton(controllerpos,landing,"up")
					else
						celevator.controller.handlecallbutton(controllerpos,landing,"up")
					end
				elseif dir == "down" then
					if isdispatcher then
						celevator.dispatcher.handlecallbutton(controllerpos,landing,"down")
					else
						celevator.controller.handlecallbutton(controllerpos,landing,"down")
					end
				end
			end
		end,
	})
end

core.register_abm({
	label = "Check call buttons for missing/replaced controllers",
	nodenames = {"group:_celevator_callbutton",},
	interval = 15,
	chance = 1,
	action = function(pos)
		local meta = core.get_meta(pos)
		local carid = meta:get_int("carid")
		if not (carid and carid > 0) then return end --Not set up yet
		local carinfo = core.deserialize(celevator.storage:get_string("car"..carid))
		if not carinfo then
			meta:set_string("infotext",S("Error reading car information!\nPlease remove and replace this node."))
			return
		end
		local iscontroller = (carinfo.controllerpos and celevator.controller.iscontroller(carinfo.controllerpos))
		local isdispatcher = (carinfo.dispatcherpos and celevator.dispatcher.isdispatcher(carinfo.dispatcherpos))
		if not (iscontroller or isdispatcher) then
			meta:set_string("infotext",S("Controller/dispatcher is missing!\nPlease remove and replace this node."))
			return
		end
		local metacarid = 0
		if iscontroller then
			metacarid = celevator.get_meta(carinfo.controllerpos):get_int("carid")
		elseif isdispatcher then
			metacarid = celevator.get_meta(carinfo.dispatcherpos):get_int("carid")
		end
		if metacarid ~= carid then
			meta:set_string("infotext",S("Controller/dispatcher found but with incorrect ID!\nPlease remove and replace this node."))
		end
	end,
})
