dofile(minetest.get_modpath("interact") .. "/config.lua")
dofile(minetest.get_modpath("interact") .. "/rules.lua") --I put the rules in their own file so that they don't get lost/overlooked!

local rule1 = 0
local rule2 = 0
local rule3 = 0
local rule4 = 0
local multi = 0

function interact.accepted_rules(name)
	local player = name
	if type(name) == "string" then 
		player = minetest.get_player_by_name(name)
	end
	local meta = player:get_meta()
	return meta:get_string("accepted_rules") == "true"
end

local function set_accepted_rules(name, value)
	local player = name
	if type(name) == "string" then 
		player = minetest.get_player_by_name(name)
	end
	local meta = player:get_meta()
	meta:set_string("accepted_rules", tostring(value))
end

local function make_formspec(player)
	local name = player:get_player_name()
	local size = { "size[10,4]" }
	table.insert(size, "label[0.5,0.5;" ..interact.s1_header.. "]")
	table.insert(size, "label[0.5,1.5;" ..interact.s1_l2.. "]")
	table.insert(size, "label[0.5,2;" ..interact.s1_l3.. "]")
	table.insert(size, "button_exit[5.5,3.4;2,0.5;no;" ..interact.s1_b1.. "]")
	table.insert(size, "button[7.5,3.4;2,0.5;yes;" ..interact.s1_b2.. "]")
	return table.concat(size)
end

local function make_formspec2(player)
	local name = player:get_player_name()
	local size = { "size[10,4]" }
	table.insert(size, "label[0.5,0.5;" ..interact.s2_l1.. "]")
	table.insert(size, "label[0.5,1;" ..interact.s2_l2.. "]")
	table.insert(size, "button_exit[2.5,3.4;3.5,0.5;interact;" ..interact.s2_b1.. "]")
	table.insert(size, "button_exit[6.4,3.4;3.6,0.5;visit;" ..interact.s2_b2.. "]")
	return table.concat(size)
end

local function make_formspec3(player)
	local size = { "size[10,8]" }
	table.insert(size, "textarea[0.5,0.5;9.5,7.5;TOS;" ..interact.s3_header.. ";" ..(interact.rules_screen or "").. "]")
	table.insert(size, "button[5.5,7.4;2,0.5;decline;" ..interact.s3_b2.. "]")
	table.insert(size, "button_exit[7.5,7.4;2,0.5;accept;" ..interact.s3_b1.. "]")
	return table.concat(size)
end

local function make_formspec4(player)
	local name = player:get_player_name()
	local size = { "size[10,9]" }
	if interact.s4_to_rules_button == true then
		table.insert(size, "button_exit[7.75,0.25;2.1,0.1;rules;" ..interact.s4_to_rules.. "]")
	end
	table.insert(size, "label[0.25,0;" ..interact.s4_header.."]")
	table.insert(size, "label[0.5,0.5;" ..interact.s4_question1.."]")
	table.insert(size, "checkbox[0.25,1;rule1_true;" ..interact.s4_question1_true.."]")
	table.insert(size, "checkbox[4,1;rule1_false;" ..interact.s4_question1_false.. "]")
	table.insert(size, "label[0.5,2;" ..interact.s4_question2.. "]")
	table.insert(size, "checkbox[0.25,2.5;rule2_true;" ..interact.s4_question2_true.. "]")
	table.insert(size, "checkbox[4,2.5;rule2_false;" ..interact.s4_question2_false.. "]")
	table.insert(size, "label[0.5,3.5;" ..interact.s4_question3.. "]")
	table.insert(size, "checkbox[0.25,4;rule3_true;" ..interact.s4_question3_true.. "]")
	table.insert(size, "checkbox[4,4;rule3_false;" ..interact.s4_question3_false.. "]")
	table.insert(size, "label[0.5,5;" ..interact.s4_question4.. "]")
	table.insert(size, "checkbox[0.25,5.5;rule4_true;" ..interact.s4_question4_true.. "]")
	table.insert(size, "checkbox[4,5.5;rule4_false;" ..interact.s4_question4_false.."]")
	table.insert(size, "label[0.5,6.5;" ..interact.s4_multi_question.. "]")
	table.insert(size, "checkbox[4.75,6.25;multi_choice1;" ..interact.s4_multi1.. "]")
	table.insert(size, "checkbox[0.25,7;multi_choice2;" ..interact.s4_multi2.. "]")
	table.insert(size, "checkbox[4.75,7;multi_choice3;" ..interact.s4_multi3.."]")
	table.insert(size, "button_exit[3,8.4;3.5,0.5;submit;" ..interact.s4_submit.."]")
	return table.concat(size)
end

local server_formspec = "size[10,4]" ..
	"label[0.5,0.5;Hey, you! Yes, you, the admin! What do you think you're doing]" ..
	"label[0.5,0.9;ignoring warnings in the terminal? You should watch it carefully!]" ..
	"label[0.5,1.5;Before you do anything else, open rules.lua in the interact mod]" ..
	"label[0.5,1.9;and put your rules there. Then, open config.lua, and look at the]" ..
	"label[0.5,2.3;settings. Configure them so that they match up with your rules.]" ..
	"label[0.5,2.7;Then, set interact.configured to true, and this message will go away]" ..
	"label[0.5,3.1;once you've restarted the server.]" ..
	"label[0.5,3.6;Thank you!]"

minetest.register_on_player_receive_fields(function(player, formname, fields)
	if formname ~= "interact_welcome" then return end
	local name = player:get_player_name()
	if fields.no then
		if interact.screen2 == false then
			minetest.after(1, function()
				minetest.show_formspec(name, "interact_rules", make_formspec3(player))
			end)
		else
			minetest.after(1, function()
				minetest.show_formspec(name, "interact_visit", make_formspec2(player))
			end)
		end
		return
	elseif fields.yes then
		if interact.grief_ban ~= true then
			minetest.kick_player(name, interact.msg_grief)
		else
			minetest.ban_player(name)
		end
	return
	end
end)

minetest.register_on_player_receive_fields(function(player, formname, fields)
	if formname ~= "interact_visit" then return end
	local name = player:get_player_name()
	if fields.interact then
		minetest.after(1, function()
			minetest.show_formspec(name, "interact_rules", make_formspec3(player))
		end)
		return
	elseif fields.visit then
		minetest.chat_send_player(name, interact.visit_msg)
		minetest.log("action", name.. " is just visiting.")
	return
	end
end)


minetest.register_on_player_receive_fields(function(player, formname, fields)
	if formname ~= "interact_rules" then return end
	local name = player:get_player_name()
	if fields.accept then
		if interact.screen4 == false then
			if minetest.check_player_privs(name, interact.priv) then
				minetest.chat_send_player(name, interact.interact_msg1)
				minetest.chat_send_player(name, interact.interact_msg2)
				local privs = minetest.get_player_privs(name)
				local granted_privs = {}
				for priv_name, _ in pairs(interact.grant_privs) do
					table.insert(granted_privs, priv_name)
					privs[priv_name] = true
				end
				if minetest.get_modpath("protection_warning") then
					if protection_warning.is_timeout_active(player) then
						privs["interact"] = false
					end
				end
				minetest.set_player_privs(name, privs)
				set_accepted_rules(name, true)
				minetest.log("action", "Granted " ..name.. " " .. table.concat(granted_privs, ", ") .. ".")
			end
		else
			minetest.after(1, function()
				minetest.show_formspec(name, "interact_quiz", make_formspec4(player))
			end)
		end
		return
	elseif fields.decline then
		if interact.disagree_action == "kick" then
			minetest.kick_player(name, interact.disagree_msg)
		elseif interact.disagree_action == "ban" then
			minetest.ban_player(name)
		else
			minetest.chat_send_player(name, interact.disagree_msg)
		end
	return
	end
end)

minetest.register_on_player_receive_fields(function(player, formname, fields)
	if formname ~= "interact_quiz" then return end
	local name = player:get_player_name()
	if fields.rules then
		minetest.after(1, function()
			minetest.show_formspec(name, "interact_rules", make_formspec3(player))
		end)
		return
	end
	if fields.rule1_true then rule1 = true
	elseif fields.rule1_false then rule1 = false
	elseif fields.rule2_true then rule2 = true
	elseif fields.rule2_false then rule2 = false
	elseif fields.rule3_true then rule3 = true
	elseif fields.rule3_false then rule3 = false
	elseif fields.rule4_true then rule4 = true
	elseif fields.rule4_false then rule4 = false
	elseif fields.multi_choice1 then multi = 1
	elseif fields.multi_choice2 then multi = 2
	elseif fields.multi_choice3 then multi = 3 end
	if fields.submit and rule1 == interact.quiz1 and rule2 == interact.quiz2 and
	rule3 == interact.quiz3 and rule4 == interact.quiz4 and multi == interact.quiz_multi then
		rule1 = 0
		rule2 = 0
		rule3 = 0
		rule4 = 0
		multi = 0
		if minetest.check_player_privs(name, interact.priv) then
			minetest.chat_send_player(name, interact.interact_msg1)
			minetest.chat_send_player(name, interact.interact_msg2)
			local privs = minetest.get_player_privs(name)
			local granted_privs = {}
			for priv_name, _ in pairs(interact.grant_privs) do
				table.insert(granted_privs, priv_name)
				privs[priv_name] = true
			end
			minetest.set_player_privs(name, privs)
			minetest.log("action", "Granted " ..name.. " " .. table.concat(granted_privs, ", ") .. ".")
		end
	elseif fields.submit then
		rule1 = 0
		rule2 = 0
		rule3 = 0
		rule4 = 0
		multi = 0
		if interact.on_wrong_quiz == "kick" then
			minetest.kick_player(name, interact.wrong_quiz_kick_msg)
		elseif interact.on_wrong_quiz == "ban" then
			minetest.ban_player(name)
		elseif interact.on_wrong_quiz == "reshow" then
			minetest.chat_send_player(name, interact.quiz_try_again_msg)
			minetest.after(1, function()
				minetest.show_formspec(name, "interact_quiz", make_formspec4(player))
			end)
		elseif interact.on_wrong_quiz == "rules" then
			minetest.chat_send_player(name, interact.quiz_rules_msg)
			minetest.after(1, function()
				minetest.show_formspec(name, "interact_rules", make_formspec3(player))
			end)
		else
			minetest.chat_send_player(name, interact.quiz_fail_msg)
		end
	end
end)

minetest.register_chatcommand("rules",{
	params = "",
	description = "Shows the server rules",
	privs = interact.priv,
	func = function (name,params)
		local player = minetest.get_player_by_name(name)
		local show_screens = minetest.check_player_privs(name, {interact=true}) or interact.accepted_rules(player) == false
		if not show_screens then
			if interact.screen1 ~= false then
				minetest.after(1, function()
					minetest.show_formspec(name, "interact_welcome", make_formspec(player))
				end)
			elseif interact.screen2 ~= false then
				minetest.after(1, function()
					minetest.show_formspec(name, "interact_visit", make_formspec2(player))
				end)
			else
				minetest.after(1, function()
					minetest.show_formspec(name, "interact_rules", make_formspec3(player))
				end)
			end
		else
			minetest.chat_send_player(name, interact.rules or "")
		end
	end
})

minetest.register_on_joinplayer(function(player)
	local name = player:get_player_name()
	if not minetest.get_player_privs(name).interact or interact.accepted_rules(player) == false then
		if interact.screen1 ~= false then
			minetest.show_formspec(name, "interact_welcome", make_formspec(player))
		elseif interact.screen2 ~= false then
			minetest.show_formspec(name, "interact_visit", make_formspec2(player))
		else
			minetest.show_formspec(name, "interact_rules", make_formspec3(player))
		end
	elseif minetest.get_player_privs(name).server and interact.configured == false then
		minetest.show_formspec(name, "interact_no_changes_made", server_formspec)
	end
end)

if not interact.configured then
	minetest.log("warning", "Mod \"Interact\" has not been configured! Please open config.lua in its folder and configure it. See the readme of the mod for more details.")
end


---------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------

local storage = minetest.get_mod_storage()
local data = minetest.deserialize(storage:get_string("data")) or {}

--default data
if data.use == nil then data.use = true end
data.records = data.records or {}
data.max_violations = data.max_violations or 25
data.first_timeout_amount = data.first_timeout_amount or 30 --seconds

local function save_data()
    storage:set_string("data", minetest.serialize(data))
end

minetest.register_privilege("protection_warning_bypass", {
    description = "Prevents players from receiving timeouts when violating protected areas.",
    give_to_singleplayer = false
})

local commands = {}
local function register_command(name, def)
    commands[name] = def
end

minetest.register_chatcommand("my_protection_violation_record", {
    description = "Shows a player their protection violation record.",
    func = function(name, text)
        protection_warning.show_record(name, name)
    end
})

minetest.register_chatcommand("protection_warning", {
    privs = { server = true },
    description = "Executes a command of the [protection_warning] mod.",
    params = "<cmd>",
    func = function(name, text)
        local s = string.split(text, " ")
        local cmdname = s[1]
        local subtext = table.concat({select(2, unpack(s))}, " ") or ""
        if not cmdname then return false, "Enter a command name." end
        local cmd = commands[cmdname]
        if cmd then
            return cmd.func(name, subtext)
        end
        return false, "'"..cmdname.."' is not a recognized command."
    end
})

register_command("help", {
    description = "Shows all commands under the 'protection_warning' command.",
    func = function(name, text)
        for name, def in pairs(commands) do
            local params = ""
            if def.params then
                params = " "..params
            end
            minetest.chat_send_player(name, "'"..name..params.."' --> "..(def.description or "No description."))
        end
    end
})

register_command("toggle_state", {
    description = "Toggles the usage of this mod without having to uninstall it.",
    func = function(name, text)
        data.use = not data.use
        for _, plr in pairs(minetest.get_connected_players()) do
            protection_warning.refresh_interact_priv(plr)
        end
        local state = "on"
        if not data.use then state = "off" end
        return true, "Toggled "..state.."."
    end
})

register_command("get_record", {
    description = "Gets the record of a player.",
    params = "<player>",
    func = function(name, text)
        local exists = data.records[text] ~= nil
        if exists then
            protection_warning.show_record(name, text)
            return true
        end
        return false, "No records could be found for '"..text.."'."
    end
})

local function grant_interact(name)
    local privs = minetest.get_player_privs(name)
    privs.interact = true
    minetest.set_player_privs(name, privs)
end

local function revoke_interact(name)
    local privs = minetest.get_player_privs(name)
    privs.interact = false
    minetest.set_player_privs(name, privs)
end

register_command("clear_record", {
    description = "Clears a player's protection viotation record.",
    params = "<player>",
    func = function(name, text)
        protection_warning.clear_record(text)
        return true, text.."'s record has been cleared."
    end
})

register_command("set_timeout_time_left", {
    description = "Sets the time a player has left until the get interact.",
    params = "<player>",
    func = function(name, text)
        protection_warning.clear_record(text)
        return true, text.."'s record has been cleared."
    end
})

register_command("set_max_violations", {
    description = "Sets the maximum amount of violations before a player will have a timeout from interact.",
    params = "<number>",
    func = function(name, text)
        local n = tonumber(text)
        if n and n > -1 then
            data.max_violations = n
            save_data()
            return "Maximum violations set to "..text.."."
        end
        return false, "Please enter a valid number that is greater than negative one."
    end
})

--api
--note: protection_bypass priv
protection_warning = {}

function protection_warning.get_record(player)
    local name = player
    if type(name) == "userdata" then
        name = player:get_player_name()
    end
    local r = data.records[name]
    if r then
        return r
    else
        r = {
            name = name,
            timeout_time_left = 0,
            total_violations = 0,
            total_violations_slt = 0, --total violations since last timeout
            total_timeouts = 0
        }
        data.records[name] = r
        return r
    end
end

function protection_warning.clear_record(name)
    local name = player
    if type(name) == "userdata" then
        name = player:get_player_name()
    end
    if not minetest.get_player_by_name(name) then return end
    data.records[name] = nil
    grant_interact(name)
    save_data()
end

function protection_warning.show_record(showto, name)
    local record = protection_warning.get_record(name)
    local text = "Protection Violation Record:\n"..
        "Player: "..name.."\n"..
        "Timeout Time Left: "..record.timeout_time_left.."seconds\n"..
        "Total Violations: "..record.total_violations.."\n"..
        "Total Violations Since Last Timeout: "..record.total_violations_slt.."\n"..
        "Total Timeouts: "..record.total_timeouts
    minetest.chat_send_player(showto, text)
end

function protection_warning.can_bypass_timeout(player)
    local name = player
    if type(name) == "userdata" then
        name = player:get_player_name()
    end
    local privs = minetest.get_player_privs(name)
    return privs.protection_bypass or privs.protection_warning_bypass
end

function protection_warning.is_timeout_active(player)
    local name = player
    if type(name) == "userdata" then
        name = player:get_player_name()
    end
    if not data.use then return false end
    if protection_warning.can_bypass_timeout(player) then return false end
    local record = protection_warning.get_record(player)
    return record.timeout_time_left > 0
end

function protection_warning.refresh_interact_priv(player)
    local name = player
    if type(name) == "userdata" then
        name = player:get_player_name()
    end
    if protection_warning.is_timeout_active(name) then
        revoke_interact(name)
    else
        if player:get_meta():get_string("accepted_rules") == "true" then
            grant_interact(name)
        end
    end
end

minetest.register_on_protection_violation(function(pos, name)
    if data.use then
        local record = protection_warning.get_record(name)
        if protection_warning.can_bypass_timeout(name) ~= true then
            record.total_violations = record.total_violations + 1
            record.total_violations_slt = record.total_violations_slt + 1
            if record.total_violations_slt >= data.max_violations then
                if not protection_warning.is_timeout_active(name) then
                    record.total_timeouts = record.total_timeouts + 1
                end
                local time_left = data.first_timeout_amount
                for i = 1, record.total_timeouts - 1 do
                    time_left = time_left * 2
                end
                record.timeout_time_left = time_left
                revoke_interact(name)
                local text = "You will have interact taken away for "..record.timeout_time_left..
                    " seconds becuase you have violated protection too many times. Use the chat command "..
                    "'/my_protection_violation_record' for more details."
                local fs = "formspec_version[6]" ..
                "size[10.5,2.0]" ..
                "textarea[0.2,0.2;10.1,1.8;;;"..minetest.formspec_escape(text).."]"
                minetest.show_formspec(name, "protection_warning:"..name, fs)
            else
                local violations_left = data.max_violations - record.total_violations_slt
                local notmuch = 0.25 * data.max_violations
                local text = "WARNING: You have just violated a protected area or node. \nTo prevent "..
                    "interact from being taken away from you, stop violating protected areas. You have "..
                    violations_left.." attempts left.\n"..
                    "Use the chat command '/my_protection_violation_record' for more details."
                if violations_left > notmuch then
                    text = minetest.colorize("#ffcb30", text)
                else
                    text = minetest.colorize("#ff0000",  text)
                end
                minetest.chat_send_player(name, text)
            end
            save_data()
        end
    end
end)

minetest.register_globalstep(function(dtime)
    if data.use then
        for _, plr in pairs(minetest.get_connected_players()) do
            if plr then
                local record = protection_warning.get_record(plr)
                if record.timeout_time_left > 0 then
                    local new_time_left = record.timeout_time_left - dtime
                    if new_time_left <= 0 then
                        new_time_left = 0
                        record.total_violations_slt = 0
                        grant_interact(plr:get_player_name())
                    end
                    record.timeout_time_left = new_time_left
                    save_data()
                end
            end
        end
    end
end)

minetest.register_on_joinplayer(function(player)
    protection_warning.refresh_interact_priv(player)
end)