--
-- Achivements mod
-- By Kaadmy, for Pixture
--

achievements = {}

achievements.achievements = {}
achievements.registered_achievements = {}
achievements.registered_achievements_list = {}

local achievements_file = minetest.get_worldpath() .. "/achievements.dat"
local saving = false

local function save_achievements()
   local f = io.open(achievements_file, "w")

   f:write(minetest.serialize(achievements.achievements))

   io.close(f)

   saving = false
end

local function delayed_save()
   if not saving then
      saving = true

      minetest.after(40, save_achievements)
   end
end

local function load_achievements()
   local f = io.open(achievements_file, "r")

   if f then
      achievements.achievements = minetest.deserialize(f:read("*all"))

      io.close(f)
   else
      save_achievements()
   end
end

function achievements.register_achievement(name, def)
   local rd = {
      title = def.title or name, -- good-looking name of the achievement
      description = def.description or "The " .. name .. " achievement", -- description of what the achievement is, and how to get it
      times = def.times or 1, -- how many times to trigger before getting the achievement
      dignode = def.dignode or nil, -- digging this node also triggers the achievement
      placenode = def.placenode or nil, -- placing this node also triggers the achievement
      craftitem = def.craftitem or nil, -- crafting this item also triggers the achievement
   }

   achievements.registered_achievements[name] = def

   table.insert(achievements.registered_achievements_list, name)
end

function achievements.trigger_achievement(player, aname, times)
   local name = player:get_player_name()

   times = times or 1

   if achievements.achievements[name][aname] == nil then
      achievements.achievements[name][aname] = 0
   end

   if achievements.achievements[name][aname] == -1 then
      return
   end

   achievements.achievements[name][aname] = achievements.achievements[name][aname] + times

   if not achievements.registered_achievements[aname] then
      default.log("[mod:achievements] Cannot find registered achievement "
                     .. aname, "error")
      return
   end

   if achievements.achievements[name][aname]
   >= achievements.registered_achievements[aname].times then
      achievements.achievements[name][aname] = -1
      minetest.after(
         2.0,
         function()
            minetest.chat_send_all(
               minetest.colorize(
                  "#0f0",
                  "*** " .. name .." has earned the achievement [" ..
                     achievements.registered_achievements[aname].title .. "]"))
      end)
   end

   delayed_save()
end

-- Load achievements table

local function on_load()
   load_achievements()
end

-- Save achievements table

local function on_shutdown()
   save_achievements()
end

-- Joining player

local function on_joinplayer(player)
   local name = player:get_player_name()

   if not achievements.achievements[name] then
      achievements.achievements[name] = {}
   end
end

-- Interaction callbacks

local function on_craft(itemstack, player)
   for aname, def in pairs(achievements.registered_achievements) do
      if def.craftitem ~= nil then
	 if def.craftitem == itemstack:get_name() then
	    achievements.trigger_achievement(player, aname)
	 else
	    local group = string.match(def.craftitem, "group:(.*)")

	    if group and minetest.get_item_group(itemstack:get_name(), group) ~= 0 then
	       achievements.trigger_achievement(player, aname)
	    end
	 end
      end
   end
end

local function on_dig(pos, oldnode, player)
   for aname, def in pairs(achievements.registered_achievements) do
      if def.dignode ~= nil then

	 if def.dignode == oldnode.name then
	    achievements.trigger_achievement(player, aname)
	 else
	    local group = string.match(def.dignode, "group:(.*)")

	    if group and minetest.get_item_group(oldnode.name, group) ~= 0 then
	       achievements.trigger_achievement(player, aname)
	    end
	 end
      end
   end
end

local function on_place(pos, newnode, player, oldnode, itemstack, pointed_thing)
   for aname, def in pairs(achievements.registered_achievements) do
      if def.placenode ~= nil then
	 if def.placenode == newnode.name then
	    achievements.trigger_achievement(player, aname)
	 else
	    local group = string.match(def.placenode, "group:(.*)")

	    if group and minetest.get_item_group(newnode.name, group) ~= 0 then
	       achievements.trigger_achievement(player, aname)
	    end
	 end
      end
   end
end

-- Add callback functions

minetest.after(0, on_load)

minetest.register_on_shutdown(on_shutdown)

minetest.register_on_joinplayer(on_joinplayer)

minetest.register_on_dignode(on_dig)
minetest.register_on_placenode(on_place)

crafting.register_on_craft(on_craft)

-- Formspecs

local form = default.ui.get_page("default:default")

form = form .. "tableoptions[background=#DDDDDD30]"
form = form .. "tablecolumns[text,align=left,width=11;text,align=left,width=28;"
   .. "text,align=left,width=5]"

default.ui.register_page("achievements:achievements", form)

function achievements.get_formspec(name, row)
   row = row or 1

   if not achievements.achievements[name] then
      achievements.achievements[name] = {}
   end

   local achievement_list = ""

   local amt_gotten = 0
   local amt_progress = 0

   for _, aname in ipairs(achievements.registered_achievements_list) do
      local def = achievements.registered_achievements[aname]

      local progress = ""
      if achievements.achievements[name][aname] then
	 if achievements.achievements[name][aname] == -1 then
	    progress = "Gotten"
	    amt_gotten = amt_gotten + 1
	 else
	    progress = achievements.achievements[name][aname] .. "/" .. def.times
	    amt_progress = amt_progress + 1
	 end
      else
	 progress = "Missing"
      end

      if achievement_list ~= "" then
	 achievement_list = achievement_list .. ","
      end

      achievement_list = achievement_list .. minetest.formspec_escape(def.title) .. ","
      achievement_list = achievement_list .. minetest.formspec_escape(def.description)
         .. ","
      achievement_list = achievement_list .. progress
   end

   local form = default.ui.get_page("achievements:achievements")

   form = form .. "table[0.25,2.5;7.75,5.5;achievement_list;" .. achievement_list
      .. ";" .. row .. "]"

   local aname = achievements.registered_achievements_list[row]
   local def = achievements.registered_achievements[aname]

   local progress = ""
   if achievements.achievements[name][aname] then
      if achievements.achievements[name][aname] == -1 then
	 progress = "Gotten"
      else
	 progress = achievements.achievements[name][aname] .. "/" .. def.times
      end
   else
      progress = "Missing"
   end

   form = form .. "label[0.25,8.15;"
      .. minetest.formspec_escape(amt_gotten.. " of "
                                     .. #achievements.registered_achievements_list
                                     .. " achievements gotten, " .. amt_progress
                                     .. " in progress") .. "]"

   form = form .. "label[0.25,0.25;" .. minetest.formspec_escape(def.title) .. "]"
   form = form .. "label[7.25,0.25;" .. minetest.formspec_escape(progress) .. "]"

   form = form .. "label[0.5,0.75;" .. minetest.formspec_escape(def.description) .. "]"

   return form
end

local function receive_fields(player, form_name, fields)
   local name = player:get_player_name()

   if form_name ~= "achievements:achievements" then return end

   if fields.quit then return end

   local selected = 1

   if fields.achievement_list then
      local selection = minetest.explode_table_event(fields.achievement_list)

      if selection.type == "CHG" or selection.type == "DCL" then
	 selected = selection.row
      end
   end

   minetest.show_formspec(
      name,
      "achievements:achievements",
      achievements.get_formspec(name, selected)
   )
end

minetest.register_on_player_receive_fields(receive_fields)

--
-- Below are the default achievements
--

-- Tools

achievements.register_achievement(
   "off_to_battle",
   {
      title = "Off to Battle",
      description = "Craft a Broadsword",
      times = 1,
      craftitem = "default:broadsword",
})

achievements.register_achievement(
   "hardened_miner",
   {
      title = "Hardened Miner",
      description = "Craft 3 Carbon Steel pickaxes.",
      times = 3,
      craftitem = "default:pick_carbon_steel",
})

-- Dirt/soil

achievements.register_achievement(
   "drain_the_swamp",
   {
      title = "Drain the Swamp",
      description = "Dig 30 swamp dirt.",
      times = 30,
      dignode = "default:swamp_dirt",
})

-- Placing planks

achievements.register_achievement(
   "plunks",
   {
      title = "Plunks",
      description = "Place 10 planks",
      times = 10,
      placenode = "group:planks",
})

achievements.register_achievement(
   "carpenter",
   {
      title = "Carpenter",
      description = "Place 100 planks",
      times = 100,
      placenode = "group:planks",
})

achievements.register_achievement(
   "master_carpenter",
   {
      title = "Master Carpenter",
      description = "Place 500 planks",
      times = 500,
      placenode = "group:planks",
})

-- Stone

achievements.register_achievement(
   "mineority",
   {
      title = "Mineority",
      description = "Mine 20 stone",
      times = 20,
      dignode = "group:stone",
})

achievements.register_achievement(
   "rockin",
   {
      title = "Rockin'",
      description = "Mine 200 stone",
      times = 200,
      dignode = "group:stone",
})

achievements.register_achievement(
   "rocksolid",
   {
      title = "Rock Solid",
      description = "Mine 1000 stone",
      times = 1000,
      dignode = "group:stone",
})

achievements.register_achievement(
   "cave_builder",
   {
      title = "Cave Builder",
      description = "Place 60 stone.",
      times = 60,
      placenode = "default:stone",
})

-- Digging wood

achievements.register_achievement(
   "timber",
   {
      title = "Timber",
      description = "Dig 10 tree trunks.",
      times = 10,
      dignode = "group:tree",
})

achievements.register_achievement(
   "timberer",
   {
      title = "Timberer",
      description = "Dig 100 tree trunks.",
      times = 100,
      dignode = "group:tree",
})

achievements.register_achievement(
   "timbererest",
   {
      title = "Timbererest",
      description = "Dig 500 tree trunks.",
      times = 500,
      dignode = "group:tree",
})

-- Crafting bricks

achievements.register_achievement(
   "builder",
   {
      title = "Builder",
      description = "Craft 180 bricks.",
      times = 180,
      craftitem = "default:brick",
})


-- Plants/farming

achievements.register_achievement(
   "gardener",
   {
      title = "Gardener",
      description = "Plant 10 flowers.",
      times = 10,
      placenode = "default:flower",
})

achievements.register_achievement(
   "master_gardener",
   {
      title = "Master Gardener",
      description = "Plant 100 flowers.",
      times = 100,
      placenode = "default:flower",
})

achievements.register_achievement(
   "welcome_to_the_mountains",
   {
      title = "Welcome to the Mountains",
      description = "Collect dry grass.",
      times = 1,
      dignode = "default:dry_grass",
})

achievements.register_achievement(
   "fertile",
   {
      title = "Fertile",
      description = "Craft 100 bags of fertilizer.",
      times = 100,
      craftitem = "default:fertilizer",
})

-- Crafting reinforced blocks

achievements.register_achievement(
   "master_carpenter",
   {
      title = "Master Carpenter",
      description = "Craft 200 reinforced frames.",
      times = 200,
      craftitem = "default:reinforced_frame",
})

achievements.register_achievement(
   "master_stonemason",
   {
      title = "Master Stonemason",
      description = "Craft 200 reinforced cobble.",
      times = 200,
      craftitem = "default:reinforced_cobble",
})

-- Crafting sand-related materials

achievements.register_achievement(
   "sandman",
   {
      title = "Sandman",
      description = "Craft 60 compressed sandstone.",
      times = 60,
      craftitem = "default:compressed_sandstone",
})

-- Literature

achievements.register_achievement(
   "librarian",
   {
      title = "Librarian",
      description = "Craft 10 bookshelves.",
      times = 10,
      craftitem = "default:bookshelf",
})

-- Misc.

achievements.register_achievement(
   "smelting_room",
   {
      title = "Smelting Room",
      description = "Craft 20 furnaces.",
      times = 20,
      craftitem = "default:furnace",
})

default.log("mod:achievements", "loaded")
