--[[
Ideally all the level data should be read/written from storage, rather than node metadata. Maybe store in metadata until the final submission.
This would allow for a node to be connected to an already existing ID, which could be useful if a spawn changed for any reason.
This can be changed in the future I expect, as the data all exists in both locations currently.

In addition to the already existing level data it would be nice to capture some statistics that the owner/builders could see.
Stats might include things like, number of times played, number of singleplayer visits, average group size, and counts of player/traitor wins.
These stats could help a level editor decide if they need to change the amount of XP or tasks in the level.
]]

local esc = minetest.formspec_escape

local function random_pos()
   local pos_x = math.random(-30000,30000)
   local pos_y = math.random(-100,30000)
   local pos_z = math.random(-30000,30000)
   local pos = (pos_x..','..pos_y..','..pos_z)
   return pos
end

local function button_create(id, name, player_count, xp, pos_string, desc, builders, solo)
   local pos = minetest.string_to_pos(pos_string)
   local clean_pos = pos.x..','..pos.y..','..pos.z
   local builders = builders or ''
   local solo = solo or 'true'
   local formspec =
      'formspec_version[3]'..
      'size[12,8.5]'..
      'label[.5,.75;Map ID: '..esc(id)..']'..
      'field[4.375,.5;3.25,.5;name;Map Name;'..esc(name)..']'..
      'tooltip[name;Will be used in chat messages and as the infotext]'..
      'checkbox[8.25,.75;solo;Allow Singleplayer;'..solo..']'..
      'field[0.5,1.5;3.25,.5;player_count;Required # of Players;'..esc(player_count)..']'..
      'field[4.375,1.5;3.25,.5;xp;Required XP;'..esc(xp)..']'..
      'field[8.25,1.5;3.25,.5;pos;Spawn POS (x,y,z);'..esc(clean_pos)..']'..
      'field[0.5,2.5;11,.5;builders;Allowed Builders;'..esc(builders)..']'..
      'tooltip[builders;List of players that should be able to visit the map as builders.]'..
      'textarea[0.5,3.5;11,3.5;desc;Description of map;'..esc(desc)..']'..
      'button_exit[1,7.25;2,.75;save;Save]'..
      'tooltip[save;Saves values without making the button active.]'..
      'button_exit[5,7.25;2,.75;submit;Submit]'..
      'tooltip[submit;Saves values and makes the button active.]'..
      'button_exit[9,7.25;2,.75;discard;Unclaim]'..
      'tooltip[discard;Clears all values and unclaims the button.]'
   return formspec
end

local function button_display(id, name, owner, player_count, xp, pos, desc)
   local formspec =
      'formspec_version[3]'..
      'size[12,8]'..
      'style_type[textarea;textcolor=#ccddff;border=true]'..
      'label[0.5,.7;Map Name:]'..
      'textarea[3.25,.55;8.25,.4;;;'..esc(name)..' (by '..owner..')]'..
      'label[0.5,1.1;Map ID:]'..
      'textarea[3.25,.95;8.25,.4;;;'..esc(id)..']'..
      'label[0.5,1.5;Req. # of Players:]'..
      'textarea[3.25,1.35;4,.4;;;'..esc(player_count)..']'..
      'label[0.5,1.9;Req. XP:]'..
      'textarea[3.25,1.75;4,.4;;;'..esc(xp)..']'..
      'label[0.5,2.5;Description:]'..
      'textarea[0.5,2.75;11,2.75;;;'..esc(desc)..']'..
      'style_type[textarea;textcolor=#999999]'..
      'textarea[0.5,6.4;9,1.1;;;Sneak punch to visit without starting a game. Return with /lobby.\n'..
      'Be sure all interested parties are standing within three nodes of the button before punching.]'..
      'button[10,6.75;1.5,0.75;stats;Stats]'
   return formspec
end

local button_claim =
   'formspec_version[3]'..
   'size[12,6]'..
   'label[4,1;Claim this button (15XP)]'..
   'button_exit[3.5,4;4,1;claim;Claim This Button]'

minetest.register_node('lobby:button_0', {
   description = 'Unconfigured Button',
   tiles = {'lobby_button_0.png'},
   groups = {breakable=1, not_in_creative_inventory=1},
   light_source = 5,
   after_place_node = function(pos, placer)
      local meta = minetest.get_meta(pos)
      meta:set_string('infotext', 'Unconfigured/Claimed button')
      meta:set_string('name', '')
      meta:set_string('player_count', '')
      meta:set_string('xp', '')
      meta:set_string('pos', '')
      meta:set_string('desc', '')
      meta:set_string('builders', '')
      meta:set_string('solo', 'true')
   end,
   on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
      local player_name = clicker:get_player_name()
      local meta = minetest.get_meta(pos)
      local owner = meta:get_string('owner')
      local id = meta:get_string('id')
      local name = meta:get_string('name')
      local player_count = meta:get_string('player_count')
      local xp = meta:get_string('xp')
      local pos = meta:get_string('pos')
      local desc = meta:get_string('desc')
      local builders = meta:get_string('builders')
      local solo = meta:get_string('solo')
      if owner == '' then
         meta:set_string('formspec', button_claim)
      elseif owner == player_name or minetest.check_player_privs(clicker, {server = true}) then
         meta:set_string('formspec', button_create(id, name, player_count, xp, pos, desc, builders, solo))
      else
         minetest.show_formspec(player_name, 'lobby:button_display', button_display(id, name, owner, player_count, xp, pos, desc))
         lobby.stat[player_name] = id
      end
   end,
   on_punch = function(pos, node, puncher, pointed_thing)
      local meta = minetest.get_meta(pos)
      local owner = meta:get_string('owner')
      local loc = meta:get_string('pos')
      local pos = minetest.string_to_pos(loc)
      local name = puncher:get_player_name()
      local map_id = meta:get_string('id')
      local builders = meta:get_string('builders')
      local player_attributes = puncher:get_meta()
      if name == owner then
         puncher:set_pos(pos)
         minetest.chat_send_player(name, 'Taking you to the level. Return to the lobby with /spawn')
         player_attributes:set_string('mode', 'builder')
         lobby.game[name] = map_id..'_builder'
      elseif minetest.check_player_privs(name, { creative = true }) and puncher:get_player_control().sneak and string.find(builders, name) then
         puncher:set_pos(pos)
         minetest.chat_send_player(name, 'Taking you to the level. Return to the lobby with /spawn')
         player_attributes:set_string('mode', 'builder')
         lobby.game[name] = map_id..'_builder'
      end
   end,
   on_receive_fields = function(pos, formname, fields, sender)
      local meta = minetest.get_meta(pos)
      local owner = meta:get_string('owner')
      local player_name = sender:get_player_name()
      if fields ['claim'] then
         if lobby.take_xp(sender, 15) then
            minetest.show_formspec(player_name, 'lobby:get_started', lobby.getting_started)
            meta:set_string('owner', player_name)
            local i = 1
            local map_id = player_name..'_'..i
            while lobby.savedata.IDs[map_id] do
               i = i + 1
               map_id = player_name..'_'..i
            end
            lobby.savedata.IDs[map_id] = true
            meta:set_string('id', map_id)
            local pos_string = random_pos()
            local pos = minetest.string_to_pos(pos_string)
            local run = true
            while run do
               if not minetest.is_protected(pos, sender) and run then
                  minetest.load_area(pos)
                  meta:set_string('pos', pos_string)
                  worldedit.cube(pos, 5, 1, 5, 'color:grey_blocks', false)
                  minetest.set_node(pos, {name = 'lights:block_4'})
                  run = false
               end
            end
         else
            minetest.chat_send_player(player_name, 'You need more XP!')
         end
      end
      if owner == player_name or minetest.check_player_privs(sender, {server = true}) then
         local map_id = meta:get_string('id')
         if fields.solo then
            meta:set_string('solo', fields.solo)
         elseif fields ['save'] then
            meta:set_string('infotext', fields.name)
            meta:set_string('name', fields.name)
            meta:set_string('player_count', fields.player_count)
            meta:set_string('xp', fields.xp)
            meta:set_string('pos', fields.pos)
            meta:set_string('desc', fields.desc)
            meta:set_string('builders', fields.builders)
         elseif fields ['submit'] then
            meta:set_string('infotext', fields.name)
            meta:set_string('name', fields.name)
            meta:set_string('player_count', fields.player_count)
            meta:set_string('xp', fields.xp)
            meta:set_string('pos', fields.pos)
            meta:set_string('desc', fields.desc)
            meta:set_string('builders', fields.builders)
            local dist = tonumber(minetest.settings:get("map_generation_limit") or 31000)
            local x, y, z = string.match(fields.pos, "^(-?%d+),(-?%d+),(-?%d+)$")
            local pos_x = tonumber(x)
            local pos_y = tonumber(y)
            local pos_z = tonumber(z)
            local saveable = 0
            if fields.name == '' then
               minetest.chat_send_player(player_name, 'Sorry, a name is required. You can always change it later')
               saveable = 1
            end
            if pos_x > dist or pos_x < -dist or pos_y > dist or pos_y < -dist or pos_z > dist or pos_z < -dist then
               minetest.chat_send_player(player_name, 'Double check your spawn pos, values seem to be invalid.')
               saveable = 1
            end
            if not lobby.is_integer(fields.player_count ) then
               minetest.chat_send_player(player_name, 'An Integer is a whole number, your required player count is not.')
               saveable = 1
            end
            if not lobby.is_integer(fields.xp) then
               minetest.chat_send_player(player_name, 'An Integer is a whole number, your required XP amount is not.')
               saveable = 1
            end
            if saveable == 0 then
               local data = {}
               data.builders = fields.builers
               data.description = fields.desc
               data.level_pos = {x = pos_x, y = pos_y+1, z = pos_z}
               data.map_name = fields.name
               data.owner_name = owner
               data.required_players = math.max(fields.player_count, 3)
               data.xp = math.max(tonumber(fields.xp),1)
               lobby.savedata.data[map_id] = data
               lobby.savedata.name_2_id[fields.name] = map_id
               lobby.savedata.IDs[map_id] = true
               minetest.swap_node(pos, {name = 'lobby:button_1'})
            end
         elseif fields ['discard'] then
            meta:set_string('owner', '')
            meta:set_string('infotext', '')
            meta:set_string('id', '')
            meta:set_string('name', '')
            meta:set_string('player_count', '')
            meta:set_string('xp', '')
            meta:set_string('pos', '')
            meta:set_string('desc', '')
            meta:set_string('builders', '')
            meta:set_string('solo', 'true')
            local stats = lobby.savedata.stats[map_id]
            if not stats then
               lobby.savedata.IDs[map_id] = nil
            end
            lobby.give_xp(sender, 15)
         end
      end
   end,
})

minetest.register_node('lobby:button_1', {
   description = 'Configured Button',
   tiles = {'lobby_button_1.png'},
   groups = {not_in_creative_inventory=1},
   drop = 'lobby:button_0',
   light_source = 14,
   on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
      local meta = minetest.get_meta(pos)
      local id = meta:get_string('id')
      local item = itemstack:get_name()
      local player_name = clicker:get_player_name()
      local owner = meta:get_string('owner')
      if item == 'creative:tool_breaking' or item == 'tasks:configurator' then
         if owner == player_name or minetest.check_player_privs(clicker, {server = true}) then
            minetest.swap_node(pos, {name = 'lobby:button_0'})
         end
      else
         local name = meta:get_string('name')
         local player_count = meta:get_string('player_count')
         local xp = meta:get_string('xp')
         local pos = meta:get_string('pos')
         local desc = meta:get_string('desc')
         local owner = meta:get_string('owner')
         minetest.show_formspec(player_name, 'lobby:button_display', button_display(id, name, owner, player_count, xp, pos, desc))
         lobby.stat[player_name] = id
      end
   end,
   on_punch = function(pos, node, puncher, pointed_thing)
      local name = puncher:get_player_name()
      local meta = minetest.get_meta(pos)
      local map_id = meta:get_string('id')
      local game_data = lobby.savedata.data[map_id]
      local game_pos = game_data['level_pos']
      local builders = meta:get_string('builders')
      local player_attributes = puncher:get_meta()
      if minetest.check_player_privs(name, { creative = true }) and puncher:get_player_control().sneak and string.find(builders, name) then
         puncher:set_pos({x=game_pos.x+(math.random(-2,2)),y=game_pos.y+2,z=game_pos.z+(math.random(-2,2))})
         minetest.chat_send_player(name, 'Taking you to the level. Return to the lobby with /spawn')
         player_attributes:set_string('mode', 'builder')
         lobby.game[name] = map_id..'_builder'
      else
         local needed_players = tonumber(meta:get_string('player_count'))
         local objs = minetest.get_objects_inside_radius(pos, 3)
         local player_count = 0
         local map_players = {}
         for _, obj in pairs(objs) do
            if obj:is_player() then
               player_count = player_count + 1
               local player_name = obj:get_player_name()
               table.insert(map_players, player_name)
            end
         end
         if player_count >= needed_players then
            if lobby.map[map_id] == 0 or lobby.map[map_id] == nil then
               minetest.chat_send_player(name, 'you can start the game!')
               lobby.map[map_id] = player_count
               lobby.update_stats(map_id, 'player', '', player_count)
               local traitor = math.random(1, player_count)
               lobby.xp[map_id] = 0
               lobby.votes[map_id] = 0
               lobby.corpses[map_id] = {}
               for i=1,player_count do
                  minetest.chat_send_player(map_players[i], 'Hold tight, loading the level.')
                  local player = minetest.get_player_by_name(map_players[i])
                  local player_attributes = player:get_meta()
                  player_attributes:set_string('voting', 'false')
                  player_attributes:set_string('mode', 'player')
                  lobby.voted[map_players[i]] = true
                  if i == traitor then
                     minetest.chat_send_player(map_players[i], 'You\'re the imposter. Try to kill all the other players.')
                     minetest.chat_send_player(map_players[i], 'You\'ll get a weapon in a few seconds.')
                     lobby.traitors[map_id] = map_players[i]
                     local player = minetest.get_player_by_name(map_players[i])
                     local player_inv = player:get_inventory()
                     minetest.after(10, function()
                        player_inv:add_item('main', 'lobby:shank')
                     end)
                     player_attributes:set_string('mode', 'traitor')
                  end
                  local privs = minetest.get_player_privs(map_players[i])
                  local player_inv = player:get_inventory()
                  player_inv:set_list('main', {})
                  player_inv:set_size('main', 16)
                  lobby.game[map_players[i]] = map_id
                  player:set_pos({x=game_pos.x+(math.random(-2,2)),y=game_pos.y,z=game_pos.z+(math.random(-2,2))})
                  privs.fly = nil
                  privs.fast = nil
                  privs.creative = nil
                  privs.areas = nil
                  minetest.set_player_privs(map_players[i], privs)
               end
            else
               minetest.chat_send_player(puncher:get_player_name(), 'You can\'t join an in progress game.\nYou can possibly play solo by sneak+punching.')
            end
         elseif puncher:get_player_control().sneak then
            local solo = meta:get_string('solo')
            if solo == 'true' then
               lobby.update_stats(map_id, 'solo')
               local name = puncher:get_player_name()
               puncher:set_nametag_attributes({
                  color = {a = 0, r = 255, g = 255, b = 255}
               })
               puncher:set_properties({visual_size = {x = 0, y = 0}, collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}})
               lobby.builder_to_player(puncher)
               puncher:set_pos(game_pos)
               lobby.game[name] = map_id..'_solo'
               minetest.chat_send_player(name, 'Travel back to the lobby with the /lobby chat command.')
            else
               minetest.chat_send_player(name, 'Sorry this level is not able to be played solo.')
            end
         else
            minetest.chat_send_player(puncher:get_player_name(), 'Maybe not enough players? Right click the button for more information.')
         end
      end
   end,
})

minetest.register_on_player_receive_fields(function(player, formname, fields)
   local name = player:get_player_name()
   if formname == 'lobby:button_display' then
      if fields.stats then
         local map_id = lobby.stat[name]
         minetest.show_formspec(name, 'lobby:stats', lobby.retrieve_stats(map_id))
      end
   end
end)
