smartshop={user={},tmp={},dir={{x=0,y=0,z=-1},{x=-1,y=0,z=0},{x=0,y=0,z=1},{x=1,y=0,z=0}},dpos={
{{x=0.2,y=0.2,z=0},{x=-0.2,y=0.2,z=0},{x=0.2,y=-0.2,z=0},{x=-0.2,y=-0.2,z=0}},
{{x=0,y=0.2,z=0.2},{x=0,y=0.2,z=-0.2},{x=0,y=-0.2,z=0.2},{x=0,y=-0.2,z=-0.2}},
{{x=-0.2,y=0.2,z=0},{x=0.2,y=0.2,z=0},{x=-0.2,y=-0.2,z=0},{x=0.2,y=-0.2,z=0}},
{{x=0,y=0.2,z=-0.2},{x=0,y=0.2,z=0.2},{x=0,y=-0.2,z=-0.2},{x=0,y=-0.2,z=0.2}}}
}

-- table with itemname: number of items being traded
smartshop.itemstats = {}
smartshop.itemprices = {}
smartshop.stuffsold = {}



smartshop.itemsatpos = function(pos, item, count)
   -- set number of items of type 'item' sold at position 'pos'
   if smartshop.itemstats[item] == nil then
      smartshop.itemstats[item] = {}
   end
   smartshop.itemstats[item][pos] = count
   local file = io.open(minetest.get_worldpath().."/smartshop_itemcounts.txt", "w")
   if file then
      file:write(minetest.serialize(smartshop.itemstats))
      file:close()
   end
end

smartshop.itempriceatpos = function(pos, item, price)
   -- set number of items of type 'item' sold at position 'pos'
   if smartshop.itemprices[item] == nil then
      smartshop.itemprices[item] = {}
   end
   local file = io.open(minetest.get_worldpath().."/smartshop_itemprices.txt", "w")
   if file then
      file:write(minetest.serialize(smartshop.itemprices))
      file:close()
   end
   smartshop.itemprices[item][pos] = price
end

smartshop.minegeldtonumber = function(stack)
   -- return number of minegeld in stack, returns nil if stack is not composed of minegeld
   count = stack:get_count()
   if count == 0 then
      return 0
   end
   if stack:get_name() == "currency:minegeld" then
      return count
   elseif stack:get_name() == "currency:minegeld_5" then
      return count * 5
   elseif stack:get_name() == "currency:minegeld_10" then
      return count * 10
   else
      return nil
   end
end


minetest.register_craft({
	output = "smartshop:shop",
	recipe = {
		{"default:chest_locked", "default:chest_locked", "default:chest_locked"},
		{"default:sign_wall_wood", "default:chest_locked", "default:sign_wall_wood"},
		{"default:sign_wall_wood", "default:torch", "default:sign_wall_wood"},
	}
})
smartshop.get_human_name = function(item)
   if core.registered_items[item] then
      return core.registered_items[item].description
   else
      return "Unknown Item"
   end
end

smartshop.use_offer=function(pos,player,n)
	local pressed={}
	pressed["buy" .. n]=true
	smartshop.user[player:get_player_name()]=pos
	smartshop.receive_fields(player,pressed)
	smartshop.user[player:get_player_name()]=nil
	smartshop.update(pos)
end

smartshop.get_offer=function(pos)
	if not pos or not minetest.get_node(pos) then return end
	if minetest.get_node(pos).name~="smartshop:shop" then return end
	local meta=minetest.get_meta(pos)
	local inv=meta:get_inventory()
	local offer={}
	for i=1,4,1 do
		offer[i]={
		give=inv:get_stack("give" .. i,1):get_name(),
		give_count=inv:get_stack("give" .. i,1):get_count(),
		pay=inv:get_stack("pay" .. i,1):get_name(),
		pay_count=inv:get_stack("pay" .. i,1):get_count(),
		}
	end
	return offer
end

smartshop.send_mail=function(owner, pos, item)
   if not minetest.get_modpath( "mail" ) then
      return
   end
   local spos = "("..pos.x..", "..pos.y..", "..pos.z..")"
   mail.send("DO NOT REPLY", owner, "Out of "..smartshop.get_human_name(item).." at "..spos, "Your smartshop at "..spos.." is out of "..smartshop.get_human_name(item)..". Please restock")
end


smartshop.receive_fields=function(player,pressed)
		if pressed.customer then
			return smartshop.showform(smartshop.user[player:get_player_name()],player,true)
		elseif pressed.tooglelime then
			local pos=smartshop.user[player:get_player_name()]
			local meta=minetest.get_meta(pos)
			local pname=player:get_player_name()
			if meta:get_int("type")==0 then
				meta:set_int("type",1)
				minetest.chat_send_player(pname, "Your stock is limited")
			else
				meta:set_int("type",0)
				minetest.chat_send_player(pname, "Your stock is unlimited")
			end
		elseif not pressed.quit then
			local n=1
			for i=1,4,1 do
				n=i
				if pressed["buy" .. i] then break end
			end
			local pos=smartshop.user[player:get_player_name()]
			if not pos then
			   return
			end
			local meta=minetest.get_meta(pos)
			local type=meta:get_int("type")
			local inv=meta:get_inventory()
			local pinv=player:get_inventory()
			local pname=player:get_player_name()
			if pressed["buy" .. n] then
				local name=inv:get_stack("give" .. n,1):get_name()
				local stack=name .." ".. inv:get_stack("give" .. n,1):get_count()
				local pay=inv:get_stack("pay" .. n,1):get_name() .." ".. inv:get_stack("pay" .. n,1):get_count()
				if name~="" then
					if type==1 and inv:room_for_item("main", pay)==false then minetest.chat_send_player(pname, "Error: The owner's stock is full, can't receive, exchange aborted.") return end
					if meta:get_int("ghost") ~=1 then
					   -- transition shops to ghost inventory.
					   for i=1,4 do
					      if inv:room_for_item("main", "pay"..i) and inv:room_for_item("main", "give"..i) then
						 meta:set_int("ghost", 1)
						 inv:add_item("main", inv:get_stack("pay"..i,1))
						 inv:add_item("main", inv:get_stack("give"..i,1))
					      end
					   end
					end
					if type==1 and inv:contains_item("main", stack)==false then
					   minetest.chat_send_player(pname, "Error: "..smartshop.get_human_name(name).." is sold out.")
					   if not meta:get_int("alerted") or meta:get_int("alerted") == 0 then
					      meta:set_int("alerted",1) -- Do not alert twice
					      smartshop.send_mail(meta:get_string("owner"), pos, name)
					   end
					   return
					end
					if not pinv:contains_item("main", pay) then minetest.chat_send_player(pname, "Error: You don't have enough in your inventory to buy this, exchange aborted.") return end
					if not pinv:room_for_item("main", stack) then minetest.chat_send_player(pname, "Error: Your inventory is full, exchange aborted.") return end
					pinv:remove_item("main", pay)
					pinv:add_item("main", stack)
					if type==1 then 
						inv:remove_item("main", stack)
						inv:add_item("main", pay)
						if not inv:contains_item("main", stack)  and (not meta:get_int("alerted") or meta:get_int("alerted") == 0) then
						   meta:set_int("alerted",1) -- Do not alert twice
						   smartshop.send_mail(meta:get_string("owner"), pos, name)
						end
					end
				end
			end
		else
			local pos=smartshop.user[player:get_player_name()]
			smartshop.update_info(pos)
			if smartshop.user[player:get_player_name()] or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then
				local meta=minetest.get_meta(smartshop.user[player:get_player_name()])
				if meta:get_string("owner")==player:get_player_name() then
					smartshop.update(smartshop.user[player:get_player_name()],"update")
				end
			end
			smartshop.user[player:get_player_name()]=nil
		end
end

minetest.register_on_player_receive_fields(function(player, form, pressed)
	if form=="smartshop.showform" then
		smartshop.receive_fields(player,pressed)
	end
end)




smartshop.update_info=function(pos)
        if not pos then
	   return
        end
	local meta=minetest.get_meta(pos)
	local spos=minetest.pos_to_string(pos)
	local inv = meta:get_inventory()
	local owner=meta:get_string("owner")
	if meta:get_int("type")==0 then
		meta:set_string("infotext","(Smartshop by " .. owner ..") Stock is unlimited")
		return false
	end
	local name=""
	local count=0
	local stuff={}
	for i=1,4,1 do
		stuff["count" ..i]=inv:get_stack("give" .. i,1):get_count()
		stuff["name" ..i]=inv:get_stack("give" .. i,1):get_name()
		stuff["stock" ..i]=0 -- stuff["count" ..i]
		local mg_price = smartshop.minegeldtonumber(inv:get_stack("pay" .. i,1))
		if mg_price ~= nil then
		   stuff["pay"..i] = mg_price/stuff["count" ..i]
		end
		stuff["buy" ..i]=0
		for ii=1,32,1 do
			name=inv:get_stack("main",ii):get_name()
			count=inv:get_stack("main",ii):get_count()
			if name==stuff["name" ..i] then
				stuff["stock" ..i]=stuff["stock" ..i]+count
			end
		end
		local nstr=(stuff["stock" ..i]/stuff["count" ..i]) ..""
		nstr=nstr.split(nstr, ".")
		stuff["buy" ..i]=tonumber(nstr[1])
		if stuff["name" ..i]=="" or stuff["buy" ..i]==0 then
			stuff["buy" ..i]=""
			stuff["name" ..i]=""
			if smartshop.stuffsold[spos..i] then
			   smartshop.itemsatpos(spos, smartshop.stuffsold[spos..i], 0)
			   smartshop.itempriceatpos(spos, smartshop.stuffsold[spos..i], nil)
			   smartshop.stuffsold[spos..i] = nil
			end						   
		else
		   smartshop.itemsatpos(spos, stuff["name"..i], stuff["buy"..i]*stuff["count" ..i])
		   smartshop.itempriceatpos(spos, stuff["name"..i], stuff["pay"..i])
		   smartshop.stuffsold[spos..i] = stuff["name"..i]
		   stuff["name"..i] = smartshop.get_human_name(stuff["name"..i])
		   stuff["buy" ..i]="(" ..stuff["buy" ..i] ..") "
		   stuff["name" ..i]=stuff["name" ..i] .."\n"
		end
	end
		meta:set_string("infotext",
		"(Smartshop by " .. owner ..") Purchases left:\n"
		.. stuff.buy1 ..  stuff.name1
		.. stuff.buy2 ..  stuff.name2
		.. stuff.buy3 ..  stuff.name3
		.. stuff.buy4 ..  stuff.name4
		)
end




smartshop.update=function(pos,stat)
--clear
	local spos=minetest.pos_to_string(pos)
	for _, ob in ipairs(minetest.env:get_objects_inside_radius(pos, 2)) do
		if ob and ob:get_luaentity() and ob:get_luaentity().smartshop and ob:get_luaentity().pos==spos then
			ob:remove()	
		end
	end
	if stat=="clear" then return end
--update
	local meta=minetest.get_meta(pos)
	local inv = meta:get_inventory()
	local node=minetest.get_node(pos)
	local dp = smartshop.dir[node.param2+1]
	if not dp then return end
	pos.x = pos.x + dp.x*0.01
	pos.y = pos.y + dp.y*6.5/16
	pos.z = pos.z + dp.z*0.01
	for i=1,4,1 do
		local item=inv:get_stack("give" .. i,1):get_name()
		local pos2=smartshop.dpos[node.param2+1][i]
		if item~="" then
			smartshop.tmp.item=item
			smartshop.tmp.pos=spos
			local e = minetest.env:add_entity({x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z},"smartshop:item")
			e:setyaw(math.pi*2 - node.param2 * math.pi/2)
		end
	end
end


minetest.register_entity("smartshop:item",{
	hp_max = 1,
	visual="wielditem",
	visual_size={x=.20,y=.20},
	collisionbox = {0,0,0,0,0,0},
	physical=false,
	textures={"air"},
	smartshop=true,
	type="",
	on_activate = function(self, staticdata)
		if smartshop.tmp.item ~= nil then
			self.item=smartshop.tmp.item
			self.pos=smartshop.tmp.pos
			smartshop.tmp={}
		else
			if staticdata ~= nil and staticdata ~= "" then
				local data = staticdata:split(';')
				if data and data[1] and data[2] then
					self.item = data[1]
					self.pos = data[2]
				end
			end
		end
		if self.item ~= nil then
			self.object:set_properties({textures={self.item}})
		else
			self.object:remove()
		end
	end,
	get_staticdata = function(self)
		if self.item ~= nil and self.pos ~= nil then
			return self.item .. ';' ..  self.pos
		end
		return ""
	end,
})


smartshop.showform=function(pos,player,re)
	local meta=minetest.get_meta(pos)
	local creative=meta:get_int("creative")
	local inv = meta:get_inventory()
	local gui=""
	local spos=pos.x .. "," .. pos.y .. "," .. pos.z
	local owner=meta:get_string("owner")==player:get_player_name()
	if minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then owner=true end
	if re then owner=false end
	smartshop.user[player:get_player_name()]=pos
	if owner then
	        meta:set_int("alerted",0) -- Player has been there to refill
		gui=""
		.."size[8,10]"
		.."button_exit[6,0;1.5,1;customer;Customer]"
		.."label[0,0.2;Item:]"
		.."label[0,1.2;Price:]"
		.."list[nodemeta:" .. spos .. ";give1;2,0;1,1;]"
		.."list[nodemeta:" .. spos .. ";pay1;2,1;1,1;]"
		.."list[nodemeta:" .. spos .. ";give2;3,0;1,1;]"
		.."list[nodemeta:" .. spos .. ";pay2;3,1;1,1;]"
		.."list[nodemeta:" .. spos .. ";give3;4,0;1,1;]"
		.."list[nodemeta:" .. spos .. ";pay3;4,1;1,1;]"
		.."list[nodemeta:" .. spos .. ";give4;5,0;1,1;]"
		.."list[nodemeta:" .. spos .. ";pay4;5,1;1,1;]"
		if creative==1 then
			gui=gui .."label[0.5,-0.4;Your stock is unlimited becaouse you have creative or give]"
			.."button[6,1;2.2,1;tooglelime;Toggle limit]"
		end
		gui=gui
		.."list[nodemeta:" .. spos .. ";main;0,2;8,4;]"
		.."list[current_player;main;0,6.2;8,4;]"
		.."listring[nodemeta:" .. spos .. ";main]"
		.."listring[current_player;main]"
	else
		gui=""
		.."size[8,6]"
		.."list[current_player;main;0,2.2;8,4;]"
		.."label[0,0.2;Item:]"
		.."label[0,1.2;Price:]"
		.."list[nodemeta:" .. spos .. ";give1;2,0;1,1;]"
		.."item_image_button[2,1;1,1;".. inv:get_stack("pay1",1):get_name() ..";buy1;\n\n\b\b\b\b\b" .. inv:get_stack("pay1",1):get_count() .."]"
		.."list[nodemeta:" .. spos .. ";give2;3,0;1,1;]"
		.."item_image_button[3,1;1,1;".. inv:get_stack("pay2",1):get_name() ..";buy2;\n\n\b\b\b\b\b" .. inv:get_stack("pay2",1):get_count() .."]"
		.."list[nodemeta:" .. spos .. ";give3;4,0;1,1;]"
		.."item_image_button[4,1;1,1;".. inv:get_stack("pay3",1):get_name() ..";buy3;\n\n\b\b\b\b\b" .. inv:get_stack("pay3",1):get_count() .."]"
		.."list[nodemeta:" .. spos .. ";give4;5,0;1,1;]"
		.."item_image_button[5,1;1,1;".. inv:get_stack("pay4",1):get_name() ..";buy4;\n\n\b\b\b\b\b" .. inv:get_stack("pay4",1):get_count() .."]"
	end
	minetest.after((0.1), function(gui)
		return minetest.show_formspec(player:get_player_name(), "smartshop.showform",gui)
	end, gui)
end

minetest.register_node("smartshop:shop", {
	description = "Smartshop",
	tiles = {"default_chest_top.png^[colorize:#ffffff77^default_obsidian_glass.png"},
	groups = {choppy = 2, oddly_breakable_by_hand = 1,tubedevice = 1, tubedevice_receiver = 1},
	drawtype="nodebox",
	node_box = {type="fixed",fixed={-0.5,-0.5,-0.0,0.5,0.5,0.5}},
	paramtype2="facedir",
	paramtype = "light",
	sunlight_propagates = true,
	light_source = 10,
	tube = {insert_object = function(pos, node, stack, direction)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			local added = inv:add_item("main", stack)
			smartshop.update_info(pos)
			return added
		end,
		can_insert = function(pos, node, stack, direction)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			for i=1,4 do
			   local sellitem = inv:get_stack("give"..i,1):get_name()
			   if sellitem == stack:get_name() then
			      return inv:room_for_item("main", stack)
			   end
--			   minetest.chat_send_all(sellitem)
			end
			--			
			return false
		end,
		input_inventory = "main",
		connect_sides = {left = 1, right = 1, front = 1, back = 1, top = 1, bottom = 1}},
after_place_node = function(pos, placer)
		local meta=minetest.get_meta(pos)
		meta:set_string("owner",placer:get_player_name())
		meta:set_string("infotext", "Shop by: " .. placer:get_player_name())
		meta:set_int("type",1)
		if minetest.check_player_privs(placer:get_player_name(), {creative=true}) or minetest.check_player_privs(placer:get_player_name(), {give=true}) then
			meta:set_int("creative",1)
			meta:set_int("type",0)
		end
	end,
on_construct = function(pos)
		local meta=minetest.get_meta(pos)
		meta:set_int("state", 0)
		meta:get_inventory():set_size("main", 32)
		meta:get_inventory():set_size("give1", 1)
		meta:get_inventory():set_size("pay1", 1)
		meta:get_inventory():set_size("give2", 1)
		meta:get_inventory():set_size("pay2", 1)
		meta:get_inventory():set_size("give3", 1)
		meta:get_inventory():set_size("pay3", 1)
		meta:get_inventory():set_size("give4", 1)
		meta:get_inventory():set_size("pay4", 1)
		meta:set_int("ghost", 1)
	end,
on_rightclick = function(pos, node, player, itemstack, pointed_thing)
		smartshop.showform(pos,player)
	end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
   if minetest.get_meta(pos):get_string("owner")==player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then
      local meta = minetest.get_meta(pos)
      if meta:get_int("ghost") == 1 and (string.find(listname, "pay") or string.find(listname, "give")) then
	 local inv = minetest.get_inventory({type="node", pos=pos})
--	 minetest.chat_send_all( inv:get_stack(listname, index):get_name()..stack:get_name())
	 if inv:get_stack(listname, index):get_name() == stack:get_name() then
	    inv:add_item(listname, stack)
	 else
	    inv:set_stack(listname, index, stack)
	 end
	 return 0
      end 
      return stack:get_count()
   end
   return 0
end,
allow_metadata_inventory_take = function(pos, listname, index, stack, player)
   if minetest.get_meta(pos):get_string("owner")==player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then
      local meta = minetest.get_meta(pos)
      if meta:get_int("ghost") == 1 and (string.find(listname, "pay") or string.find(listname, "give")) then
	 local inv = minetest.get_inventory({type="node", pos=pos})
	 inv:set_stack(listname, index, ItemStack(""))
	 return 0
      end       
      return stack:get_count()
   end
   return 0
end,
allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
   if minetest.get_meta(pos):get_string("owner")==player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true}) then
      local meta = minetest.get_meta(pos)
      local inv = minetest.get_inventory({type="node", pos=pos})
      if meta:get_int("ghost") ~= 1 then
	 return count
      end
      if (string.find(from_list, "pay") or string.find(from_list, "give")) and to_list == "main" then
	 inv:set_stack(from_list, from_index, ItemStack(""))
	 return 0	 
      elseif (string.find(to_list, "pay") or string.find(to_list, "give")) and from_list == "main" then
	 if inv:get_stack(to_list, to_index):get_name() == inv:get_stack(from_list, from_index):get_name() then
	    inv:add_item(to_list, inv:get_stack(from_list, from_index))
	 else
	    inv:set_stack(to_list, to_index, inv:get_stack(from_list, from_index))
	    inv:set_stack(from_list, from_index, inv:get_stack(from_list, from_index))	    
	 end
	 return 0
      else
	 return count
      end
   end
   return 0
end,
can_dig = function(pos, player)
		local meta=minetest.get_meta(pos)
		local inv=meta:get_inventory()
		if ((meta:get_string("owner")==player:get_player_name() or minetest.check_player_privs(player:get_player_name(), {protection_bypass=true})) and inv:is_empty("main") and inv:is_empty("pay1") and inv:is_empty("pay2") and inv:is_empty("pay3") and inv:is_empty("pay4") and inv:is_empty("give1") and inv:is_empty("give2") and inv:is_empty("give3") and inv:is_empty("give4")) or meta:get_string("owner")=="" then
			smartshop.update(pos,"clear")
			return true
		end
	end,
})

minetest.register_chatcommand("smstats", {
	description = "Get number of items sold",
	params = "<item_name>",
	func = function(plname, params)
		local name = params:match("(%S+)")
		if not (name) then
			return false, "Usage: /smstats <itemname>"
		end
		if not smartshop.itemstats[name] then
		   return false, "No stats on "..name
		end
		sum = 0
		for i, k in pairs(smartshop.itemstats[name]) do
		   sum = sum + k
		end
		minetest.chat_send_player(plname, "Number of items: "..sum)
		if sum == 0 then
		   return
		end
		psum = 0
		for i, k in pairs(smartshop.itemprices[name]) do
		   psum = psum + k*smartshop.itemstats[name][i]
		end
		minetest.chat_send_player(plname, "Average price: "..string.format("%.3f",psum/sum))
		return true
--		local ok, e = xban.ban_player(plname, name, nil, reason)
--		return ok, ok and ("Banned %s."):format(plname) or e
	end,
})

minetest.register_lbm({
      name = "smartshop:update",
      nodenames = {"smartshop:shop"},
      action = function(pos, node)
	 smartshop.update_info(pos)	   
      end,
})


-- load itemstats
local file = io.open(minetest.get_worldpath().."/smartshop_itemcounts.txt", "r")
if file then
   local table = minetest.deserialize(file:read("*all"))
   if type(table) == "table" then
      smartshop.itemstats = table
   end
end
local file = io.open(minetest.get_worldpath().."/smartshop_itemprices.txt", "r")
if file then
   local table = minetest.deserialize(file:read("*all"))
   if type(table) == "table" then
      smartshop.itemprices = table
   end
end