function ls_crystal.load()
   local file = io.open(core.get_worldpath() .. '/crystal', 'r')
   if file then
      ls_crystal.data = core.deserialize(file:read('*a'))
      file:close()
   else
      ls_crystal.data = {}
   end
end

function ls_crystal.save()
   local file = io.open(core.get_worldpath() .. '/crystal', 'w')
   file:write(core.serialize(ls_crystal.data))
   file:close()
end

core.register_on_newplayer(function(player) --Creates a table for storing player data
   local name = player:get_player_name()
   local data = {}
   data.nodes = 0 --Just a count of beacons, might be useful
   ls_crystal.data[name] = data
end)

function ls_crystal.on_place_beacon(player_name, pos, var)
   local key = core.pos_to_string(pos)
   local data = ls_crystal.data[player_name]
   data.nodes = data.nodes + 1
   local fresh_data = {}
   fresh_data.desc = 'nameless beacon'
   fresh_data.varient = var
   fresh_data.links = 0
   fresh_data.beacons_list = {}
   ls_crystal.data[player_name] = data
   ls_crystal.data[player_name][key] = fresh_data
   ls_crystal.find_beacons(player_name, pos)
end

function ls_crystal.find_beacons(player_name, pos)
   local other_beacons = ls_crystal.data[player_name]
   if other_beacons then
      local num_of_beacons = 0
      for key, data in pairs(other_beacons) do
         local other_pos = core.string_to_pos(key)
         if other_pos then --check the distance
            local dist = vector.distance(pos, other_pos)
            local data = ls_crystal.data[player_name][key]
            local var = data.varient
            if var == 'lr' and dist < ls_crystal.LR_dist and dist > 0 then
               local ray = core.raycast(pos, other_pos, false)
               local count = 0
               for pointed_thing in ray do
                  local node = core.get_node(pointed_thing.under)
                  if core.get_item_group(node.name, 'non_blocking') <= 0 then
                     count = count + 1
                  end
               end
               if count == 0 then
                  local node = core.get_node(pos)
                  if core.get_item_group(node.name, 'beacons') >= 1 then
                     ls_crystal.link_beacons(player_name, pos, other_pos)
                  else
                     num_of_beacons = num_of_beacons + 1
                  end
               else
                  local this_node = core.get_node(pos)
                  if core.get_item_group(this_node.name, 'beacons') >= 1 then
                     local this_key = core.pos_to_string(pos)
                     local other_beacons = ls_crystal.data[player_name][this_key].beacons_list
                     ls_crystal.unlink_beacons(pos, player_name, other_beacons)
                  end
               end
            elseif var == 'sr' and dist < ls_crystal.SR_dist and dist > 0 then
               local ray = core.raycast(pos, other_pos, false)
               local count = 0
               for pointed_thing in ray do
                  local node = core.get_node(pointed_thing.under)
                  if core.get_item_group(node.name, 'non_blocking') <= 0 then
                     count = count + 1
                  end
               end
               if count == 0 then
                  local node = core.get_node(pos)
                  if core.get_item_group(node.name, 'beacons') >= 1 then
                     ls_crystal.link_beacons(player_name, pos, other_pos)
                  else
                     num_of_beacons = num_of_beacons + 1
                  end
               else
                  local this_node = core.get_node(pos)
                  if core.get_item_group(this_node.name, 'beacons') >= 1 then
                     local this_key = core.pos_to_string(pos)
                     local other_beacons = ls_crystal.data[player_name][this_key].beacons_list
                     ls_crystal.unlink_beacons(pos, player_name, other_beacons)
                  end
               end
            end
         end
      end
      return num_of_beacons
   else
      return false
   end
end

function ls_crystal.update_lights(pos, beacon_type, light_level, op)
   local pos_table
   if beacon_type == 'sr' then
      pos_table = {
         {x=-3,y=0,z=0},
         {x=-1,y=0,z=0},
         {x=1,y=0,z=0},
         {x=3,y=0,z=0},
         {x=0,y=0,z=-3},
         {x=0,y=0,z=-1},
         {x=0,y=0,z=1},
         {x=0,y=0,z=3},
         {x=2,y=0,z=2},
         {x=2,y=0,z=-2},
         {x=-2,y=0,z=2},
         {x=-2,y=0,z=-2},
      }
   elseif beacon_type == 'lr' then
      pos_table = {
         {x=-1,y=0,z=0},
         {x=1,y=0,z=0},
         {x=0,y=0,z=-1},
         {x=0,y=0,z=1},
         {x=-3,y=0,z=0},
         {x=3,y=0,z=0},
         {x=0,y=0,z=-3},
         {x=0,y=0,z=3},
         {x=2,y=0,z=2},
         {x=2,y=0,z=-2},
         {x=-2,y=0,z=2},
         {x=-2,y=0,z=-2},
         {x=-6,y=0,z=0},
         {x=6,y=0,z=0},
         {x=0,y=0,z=-6},
         {x=0,y=0,z=6},
         {x=4,y=0,z=4},
         {x=4,y=0,z=-4},
         {x=-4,y=0,z=4},
         {x=-4,y=0,z=-4},
      }
   end
   for _, entry in pairs(pos_table) do
      local new_pos = vector.add(pos, entry)
      if op == 'place' then
         ls_crystal.drop_light(new_pos, light_level)
      elseif op == 'remove' then
         ls_crystal.remove_dropped_light(new_pos)
      end
   end
end

function ls_crystal.drop_light(pos, val) --Drops a light to the ground
   local i = 0
   while i < 20 do
      local new_pos = vector.add(pos, {x=0,y=-i,z=0})
      local new_node = minetest.get_node(new_pos)
      if new_node.name == 'air' then
         i = i + 1
      elseif core.get_item_group(new_node.name, 'invisible_light') > 0 then
         core.remove_node(new_pos)
      elseif new_node.name ~= 'air' then
         i = i - 1
         new_pos = vector.add(pos, {x=0,y=-i,z=0})
         new_node = minetest.get_node(new_pos)
         if new_node.name == 'air' then
            core.set_node(new_pos, {name='ls_crystal:light_source_'..val})
            i = 21
         end
      end
   end
end

function ls_crystal.remove_dropped_light(pos) --Removes a dropped light.
   local i = 0
   while i < 20 do
      local new_pos = vector.add(pos, {x=0,y=-i,z=0})
      local new_node = minetest.get_node(new_pos)
      if core.get_item_group(new_node.name, 'invisible_light') > 0 then
         core.remove_node(new_pos)
      end
      i = i + 1
   end
end

core.register_lbm({
   label = 'crystal particles timer',
   name = 'ls_crystal:particles',
   nodenames = {'group:beacons'},
   run_at_every_load = true,
   action = function(pos)
      local meta = core.get_meta(pos)
      local timer = core.get_node_timer(pos)
      local player_name = meta:get_string('player_name')
      timer:start(10)
      ls_crystal.spawn_beacon_particles(pos, player_name)
   end
})

core.register_abm({
   label = 'check beacons',
   name = 'ls_crystal:check_beacons',
   nodenames = {'group:beacons'},
   interval = 10,
   chance = 2,
   action = function(pos)
      local meta = core.get_meta(pos)
      local player_name = meta:get_string('player_name')
      ls_crystal.find_beacons(player_name, pos)
   end
})

core.register_abm({
   label = 'damage beacons',
   name = 'ls_crystal:damage_beacons',
   nodenames = {'group:beacons'},
   interval = 12,
   chance = 3,
   action = function(pos)
      local meta = core.get_meta(pos)
      local player_name = meta:get_string('player_name')
      local key = core.pos_to_string(pos)
      local data = ls_crystal.data[player_name][key]
      local links = data.links
      local chance = math.random(0,3)
      if links <= chance then
         print 'beacon damaged'
         --break beacon
      end
   end
})

core.register_abm({
   label = 'burns out torches',
   name = 'ls_crystal:torches',
   nodenames = {'group:torches'},
   interval = 60,
   chance = 10,
   action = function(pos)
      local meta = core.get_meta(pos)
      local timer = core.get_node_timer(pos)
      local player_name = meta:get_string('player_name')
      if ls_crystal.find_beacons(player_name, pos) == 0 then
         local timer = core.get_node_timer(pos)
         print 'starting node timer'
         timer:start(600)
      end
   end
})

function ls_crystal.spawn_beacon_particles(pos, name)
   local key = core.pos_to_string(pos)
   local table = ls_crystal.data[name][key]
   if table then
      local remote_beacons = table.beacons_list
      if remote_beacons then
         for entry in pairs(remote_beacons) do
            local pos2 = core.string_to_pos(entry)
            ls_crystal.particle_system(pos, pos2)
         end
      end
   end
end

function ls_crystal.link_beacons(player_name, pos, pos2) --pos is newly placed beacon, pos2 is found beacon
   local key1 = core.pos_to_string(pos)
   local key2 = core.pos_to_string(pos2)
   local data1 = ls_crystal.data[player_name][key1]
   local data2 = ls_crystal.data[player_name][key2]
   if data1.beacons_list[key2] and data2.beacons_list[key1] then
      --beacons already linked, do nothing
   else
      local timer1 = core.get_node_timer(pos)
      local timer2 = core.get_node_timer(pos2)
      data1.links = data1.links + 1
      data2.links = data2.links + 1
      data1.beacons_list[key2] = true
      data2.beacons_list[key1] = true
      ls_crystal.data[player_name][key1] = data1
      ls_crystal.data[player_name][key2] = data2
      ls_crystal.particle_system(pos2, pos)
      ls_crystal.particle_system(pos, pos2)
      timer1:start(10)
      timer2:start(10)
      local light_level_1 = math.min(data1.links + 6, 14)
      local light_level_2 = math.min(data2.links + 6, 14)
      ls_crystal.update_lights(pos, 'sr', light_level_1, 'place')
      ls_crystal.update_lights(pos2, 'sr', light_level_2, 'place')
      ls_crystal.save()
   end
end

function ls_crystal.unlink_beacons(pos, name, table, remove)
   local key = core.pos_to_string(pos)
   local table = ls_crystal.data[name][key]
   if table then
      local remote_beacons = table.beacons_list
      if remote_beacons then
         for entry in pairs(remote_beacons) do
            local pos2 = core.string_to_pos(entry)
            local remote_data = ls_crystal.data[name][entry]
            if remote_data then
               remote_data.beacons_list[key] = nil
               remote_data.links = remote_data.links - 1
               table.beacons_list[entry] = nil
               table.links = table.links - 1
               local var = remote_data.varient
               if var == 'sr' then
                  local light_level = math.min(table.links + 6, 14)
                  ls_crystal.update_lights(pos2, 'sr', light_level, 'place')
               elseif var == 'lr' then
                  local light_level = math.min(table.links + 8, 14)
                  ls_crystal.update_lights(pos2, 'lr', light_level, 'place')
               end
            end
         end
      end
      if remove then
         ls_crystal.data[name][key] = nil
         ls_crystal.update_lights(pos, 'lr', 0, 'remove')
      end
   end
end

function ls_crystal.particle_system(pos, pos2)
   core.add_particlespawner({
      amount = 12,
      time = 10,
      size = {min = 1, max = 4},
      collisiondetection = false,
      glow = 12,
      texpool = {
         {name = 'ls_crystal_spark.png'},
         {name = 'ls_crystal_spark_01.png'}},
      pos = pos,
      jitter = {
         min = vector.new(-.125, -.125, -.125),
         max = vector.new(.125, .125, .125)
      },
      exptime = 12,
      attract = {
         kind = 'point',
         origin = pos2,
         strength = .1,
      },
   })
end
