-- 
-- Please see the readme.txt file included with this distribution for 
-- attribution and copyright information.
--

--
--  NODE TRANSLATION
--

function getActiveCT()
	local node_list = DB.findNode("combattracker");
	for k,v in pairs(node_list.getChildren()) do
		if NodeManager.getSafeChildValue(v, "active", 0) == 1 then
			return v;
		end
	end
end

function getCTFromPC(charnodename)
	-- Make sure we have a CT to browse through
	local ctnode = DB.findNode("combattracker");
	if not ctnode then
		return;
	end
	
	-- Iterate through CT nodes to find this PC
	for k,v in pairs(ctnode.getChildren()) do
		local link = v.getChild("link");
		if link then
			local refclass, refname = link.getValue();
			if refname == charnodename then
				return v;
			end
		end
	end
	
	-- If no match, then return nil
	return nil;
end

function getUserFromCT(ctentrynode)
	-- Get the link field from the entry
	local link = ctentrynode.getChild("link");
	if not link then
		return;
	end
	
	-- Get the class and name from the link
	local refclass, refname = link.getValue();
	
	-- If this is not a PC entry, then there is no owner
	if refclass ~= "charsheet" then
		return;
	end
	
	-- Get the node for the target PC reference
	local pcnode = DB.findNode(refname);
	if not pcnode then
		return;
	end
	
	-- Get the user for the target PC
	return pcnode.getOwner();
end

--
-- DROP HANDLING
--

function onDrop(nodetype, nodename, draginfo)
	local dragtype = draginfo.getType();
	
	-- ATTACK ROLLS
	if dragtype == "attack" then
		onAttackClassDrop(nodetype, nodename, draginfo);
		return true;
	end
	if dragtype == "fullattack" then
		onFullAttackClassDrop(nodetype, nodename, draginfo);
		return true;
	end
	
	-- DAMAGE ROLLS
	if dragtype == "damage" then
		onDamageDrop(nodetype, nodename, draginfo);
		return true;
	end

	-- EFFECT (LABEL DRAG)
	if dragtype == "effect" then
		onEffectClassDrop(nodetype, nodename, draginfo);
		return true;
	end
	
	-- NUMBER DROPS
	if dragtype == "number" then
		onNumberDrop(nodetype, nodename, draginfo);
		return true;
	end
end

function onNumberDrop(nodetype, nodename, draginfo)
	-- Check for attack strings
	if string.match(draginfo.getDescription(), "%[ATTACK") then
		onAttackResultDrop(nodetype, nodename, draginfo);
		return;
	end

	-- Check for damage strings
	if string.match(draginfo.getDescription(), "%[DAMAGE") then
		onDamageResultDrop(nodetype, nodename, draginfo);
		return;
	end

	-- Check for effect strings
	if string.match(draginfo.getDescription(), "%[EFFECT") then
		onEffectResultDrop(nodetype, nodename, draginfo);
		return;
	end
end

function buildCustomRollArray(nodetype, nodename, draginfo)
	local custom = {};

	local class = draginfo.getShortcutData();
	if class == "charsheet" then
		custom["pc"] = draginfo.getDatabaseNode();
	else
		custom["npc"] = draginfo.getDatabaseNode();
	end
	if nodetype == "pc" then
		custom["targetpc"] = nodename;
	elseif nodetype == "ct" then
		custom["targetct"] = nodename;
	end

	return custom;
end

function onFullAttackClassDrop(nodetype, nodename, draginfo)
	if User.isHost() or not OptionsManager.isOption("PATK", "off") then
		local custom = buildCustomRollArray(nodetype, nodename, draginfo);

		for i = 1, draginfo.getSlotCount() do
			draginfo.setSlot(i);
			onAttackDrop(draginfo.getStringData(), draginfo.getNumberData(), custom);
		end
	end	
end

function onAttackClassDrop(nodetype, nodename, draginfo)
	if User.isHost() or not OptionsManager.isOption("PATK", "off") then
		local custom = buildCustomRollArray(nodetype, nodename, draginfo);

		onAttackDrop(draginfo.getDescription(), draginfo.getNumberData(), custom);
	end
end

function onAttackDrop(atk_name, atk_bonus, custom)
	ChatManager.d20Check("attack", atk_bonus, atk_name, custom);
end

function onDamageDrop(nodetype, nodename, draginfo)
	if User.isHost() or not OptionsManager.isOption("PDMG", "off") then
		local custom = buildCustomRollArray(nodetype, nodename, draginfo);

		local dice = {};
		if draginfo.getDieList() then
			for k, v in pairs(draginfo.getDieList()) do
				table.insert(dice, v["type"]);
			end
		else
			table.insert(dice, "d0");
		end

		ChatManager.DieControlThrow("damage", draginfo.getNumberData(), draginfo.getDescription(), custom, dice);
	end
end

function onEffectClassDrop(nodetype, nodename, draginfo)
	-- Get the effect database node
	draginfo.setSlot(2);
	local eff_node = nil;
	if draginfo.getStringData() then
		eff_node = DB.findNode(draginfo.getStringData());
	end
	if not eff_node then
		return;
	end

	-- Get the basic effect information
	local eff_name = NodeManager.getSafeChildValue(eff_node, "label", "");
	local eff_dur = NodeManager.getSafeChildValue(eff_node, "duration", 0);
	local eff_adj = NodeManager.getSafeChildValue(eff_node, "adjustment", 0);
	local eff_src = "";
	
	-- Attempt to pull source node from the effect information (only valid in CT effect)
	local ctsrcname = NodeManager.getSafeChildValue(eff_node, "caster_ref_node", "");
	local ctsrcnode = getCTFromPC(ctsrcname);
	if ctsrcnode then
		eff_src = ctsrcnode.getNodeName();
	end
	
	-- If source field not explicitly set, then check the database node for a PC source
	if eff_src == "" then
		draginfo.setSlot(1);
		local class, drag_nodename = draginfo.getShortcutData();
		if class and drag_nodename then
			if class == "charsheet" then
				local ctnode = CombatCommon.getCTFromPC(drag_nodename);
				if ctnode then
					eff_src = ctnode.getNodeName();
				end
			end
		end
	end

	-- Call the general effect drop code
	onEffectDrop(nodetype, nodename, eff_name, eff_dur, eff_adj, eff_src);
end

function onAttackResultDrop(nodetype, nodename, draginfo)
	if User.isHost() or OptionsManager.isOption("PATK", "on") then
		-- Get the target defense
		local defenseval = nil;
		if nodetype == "pc" then
			local node = DB.findNode(nodename .. ".ac.totals.general");
			if node then
				defenseval = node.getValue();
			end
		elseif nodetype == "ct" then
			local node = DB.findNode(nodename .. ".ac");
			if node then
				local ac_str = string.match(node.getValue(), "%d+");
				if ac_str then
					defenseval = tonumber(ac_str);
				end
			end
		end

		-- If we found a defense value, then compare to the number and output
		if defenseval then
			local targetname = "";
			if nodetype == "ct" then
				targetname = NodeManager.getSafeChildValue(DB.findNode(nodename), "name", "");
			elseif nodetype == "pc" then
				targetname = NodeManager.getSafeChildValue(DB.findNode(nodename), "name", "");
			end
			
			local result_str = "Attack drop [" .. draginfo.getNumberData() .. "] [to " .. targetname .."]";
			if draginfo.getNumberData() >= defenseval then
				result_str = result_str .. " [HIT]";
			else
				result_str = result_str .. " [MISS]";
			end

			if User.isHost() then
				ChatManager.Message(result_str);
			else
				ChatManager.Message(result_str, true);
			end
		end
	end
end

function onDamageResultDrop(nodetype, nodename, draginfo)
	if User.isHost() or OptionsManager.isOption("PDMG", "on") then
		local targetname = "";
		if nodetype == "ct" then
			targetname = NodeManager.getSafeChildValue(DB.findNode(nodename), "name", "");
		elseif nodetype == "pc" then
			targetname = NodeManager.getSafeChildValue(DB.findNode(nodename), "name", "");
		end

		local result_str = "Damage drop [" .. draginfo.getNumberData() .. "] [to " .. targetname .."]";
		if User.isHost() then
			ChatManager.Message(result_str);
		else
			result_str = result_str .. " [by " .. User.getIdentityLabel() .. "]";
			ChatManager.Message(result_str, "");
		end

		ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_APPLYDMG, {draginfo.getNumberData(), nodetype, nodename});
	end
end

function onEffectResultDrop(nodetype, nodename, draginfo)
	-- Parse the effect information
	local eff_name, eff_dur, eff_adj = string.match(draginfo.getDescription(), "%[EFFECT%] (.+) DURATION ?(.+) ADJUST ?(.+)");
	if eff_name and eff_dur and eff_adj then
		-- Default source settings are empty
		local eff_src = "";
		
		-- If appliedby field not explicitly set, then check the database node for a PC source
		if eff_src == "" then
			local class, drag_nodename = draginfo.getShortcutData();
			if class and drag_nodename then
				if class == "charsheet" then
					local ctnode = CombatCommon.getCTFromPC(drag_nodename);
					if ctnode then
						eff_src = ctnode.getNodeName();
					end
				end
			end
		end

		-- Call the general effect drop code
		onEffectDrop(nodetype, nodename, eff_name, eff_dur, eff_adj, eff_src);
	end
end

function onEffectDrop(nodetype, nodename, eff_name, eff_dur, eff_adj, eff_src)
	-- Special handling for reduced client feature access
	if not User.isHost() then
		-- No work, if client access disabled
		if OptionsManager.isOption("PEFF", "off") then
			return;
		end
		
		-- Report only, if client access set to report
		if OptionsManager.isOption("PEFF", "report") then
			local targetname = "";
			if nodetype == "ct" then
				targetname = NodeManager.getSafeChildValue(DB.findNode(nodename), "name", "");
			elseif nodetype == "pc" then
				targetname = NodeManager.getSafeChildValue(DB.findNode(nodename), "name", "");
			end

			ChatManager.reportEffectFull(eff_name, eff_dur, eff_adj, targetname);
			return;
		end
	end

	-- If we dropped on character portrait, make sure that character exists in the CT
	if nodetype == "pc" then
		local ctnode = CombatCommon.getCTFromPC(nodename);
		if ctnode then
			nodename = ctnode.getNodeName();
		else
			ChatManager.SystemMessage("[ERROR] Effect dropped on PC which is not listed in the combat tracker.");
			return;
		end
	end
	
	-- If still no source setting, then determine who is applying the effect by current active
	if eff_src == "" then
		local ctnode = nil;
		if User.isHost() then
			ctnode = CombatCommon.getActiveCT();
		else
			ctnode = CombatCommon.getCTFromPC("charsheet." .. User.getCurrentIdentity());
		end
		if ctnode then
			eff_src = ctnode.getNodeName();
		end
	end

	-- Add the effect
	ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_APPLYEFF, {nodename, eff_name, eff_dur, eff_adj, eff_src});
end


--
-- DAMAGE APPLICATION
--

function applyDamage(nodetype, node, dmg)
	-- Initialize variables
	local totalhp = 0;
	local wounds = 0;
	local result = "";
	
	-- Validate
	if not node then
		return result;
	end
	
	-- Get the correct hit point fields
	if nodetype == "pc" then
		totalhp = NodeManager.getSafeChildValue(node, "hp.total", 0);
		wounds = NodeManager.getSafeChildValue(node, "hp.wounds", 0);
	elseif nodetype == "ct" then
		totalhp = NodeManager.getSafeChildValue(node, "hp", 0);
		wounds = NodeManager.getSafeChildValue(node, "wounds", 0);
	else
		return result;
	end
	
	-- Track the original wounds value
	orig_wounds = wounds;

	-- Apply the damage
	wounds = wounds + dmg;
	if wounds < 0 then
		wounds = 0;
	end

	-- Set the correct hit point fields with the new values
	if nodetype == "pc" then
		NodeManager.setSafeChildValue(node, "hp.wounds", "number", wounds);
	else
		NodeManager.setSafeChildValue(node, "wounds", "number", wounds);
	end

	-- Add to the result is damage positive and status changed
	if dmg > 0 then
		local moderate_hp = totalhp / 3;
		local heavy_hp = 2 * moderate_hp;
		if (orig_wounds < totalhp) and (wounds >= totalhp) then
			result = result .. " [DYING]";
		elseif (orig_wounds < totalhp) and (wounds == totalhp) then
			result = result .. " [DISABLED]";
		elseif (orig_wounds < heavy_hp) and (wounds >= heavy_hp) then
			result = result .. " [HEAVY DAMAGE]";
		elseif (orig_wounds < moderate_hp) and (wounds >= moderate_hp) then
			result = result .. " [MODERATE DAMAGE]";
		end
	end
	
	-- Return the result text
	if result ~= "" then
		local msg = {font = "msgfont"};
		msg.text = NodeManager.getSafeChildValue(node, "name", "") .. " ->" .. result;
		ChatManager.deliverMessage(msg);
	end
end

--
-- EFFECTS
--

function parseEffect(s)
	local effectlist = {};
	
	local eff_clause;
	for eff_clause in string.gmatch(s, "([^;]*);?") do
		if eff_clause ~= "" then
			local eff_type, eff_valstr, eff_remainder = string.match(eff_clause, "(%a+): ?(%d+) ?(.*)");
			if eff_type and eff_valstr then
				local eff_val = tonumber(eff_valstr) or 0;
				table.insert(effectlist, {type = eff_type, val = eff_val, remainder = eff_remainder});
			else
				table.insert(effectlist, {type = "", val = 0, remainder = eff_clause});
			end
		end
	end
	
	return effectlist;
end

function removeEffect(node_ctentry, eff_name)
	-- Parameter validation
	if not node_ctentry or not eff_name then
		return;
	end

	-- Make sure we can get to the effects list
	local node_list_effects = NodeManager.createSafeChild(node_ctentry, "effects");
	if not node_list_effects then
		return;
	end

	-- Cycle through the effects list, and compare names
	for k,v in pairs(node_list_effects.getChildren()) do
		local s = NodeManager.getSafeChildValue(v, "label", "");
		if string.match(s, eff_name) then
			v.delete();
			return;
		end
	end
end

function addEffect(msguser, msgidentity, node_ctentry, eff_name, eff_dur, eff_adj, eff_src)
	-- Parameter validation
	if not node_ctentry or not eff_name then
		return;
	end
	
	-- Make sure we can get to the effects list
	local name_ctentry = NodeManager.getSafeChildValue(node_ctentry, "name", "");
	local node_list_effects = NodeManager.createSafeChild(node_ctentry, "effects");
	if not node_list_effects then
		return;
	end
	
	-- Get the details of the new effect
	local parse_neweffect = parseEffect(eff_name);
	if not eff_dur then
		eff_dur = 0;
	end
	if not eff_adj then
		eff_adj = 0;
	end
	
	-- First, check for duplicate effects to overwrite
	-- Or exit if the new effect is weaker
	local target_effect = nil;
	for k, v in pairs(node_list_effects.getChildren()) do
		local isStronger = false;
		local isWeaker = false;

		local label_effect = NodeManager.getSafeChildValue(v, "label", "");
		if label_effect == eff_name then
			local old_dur = NodeManager.getSafeChildValue(v, "duration", 0);
			if old_dur >= eff_dur then
				isWeaker = true;
			else
				isStronger = true;
			end
		end

		if not isDifferent then
			if isStronger then
				target_effect = v;
				break;
			elseif isWeaker then
				local msg = {font = "systemfont", icon = "indicator_effect"};
				msg.text = "Stronger effect already exists [on " .. name_ctentry .. "]";
				if msguser == "" then
					ChatManager.addMessage(msg);
				else
					ChatManager.deliverMessage(msg, msguser);
				end
				return;
			end
		end
	end
	
	-- Next check for blank effects to fill in
	if not target_effect then
		for k, v in pairs(node_list_effects.getChildren()) do
			if NodeManager.getSafeChildValue(v, "label", "") == "" then
				target_effect = v;
				break;
			end
		end
	end
	
	-- Finally, if we haven't found a target, then create a window to hold the effect
	if not target_effect then
		target_effect = NodeManager.createSafeChild(node_list_effects);
	end
	
	-- Add the effect details
	NodeManager.setSafeChildValue(target_effect, "label", "string", eff_name);
	NodeManager.setSafeChildValue(target_effect, "duration", "number", eff_dur);
	NodeManager.setSafeChildValue(target_effect, "adjustment", "number", eff_adj);
	
	-- Create the message to display
	local msg = {font = "systemfont", icon = "indicator_effect"};
	msg.text = "Effect '" .. eff_name .. "' applied [to " .. name_ctentry .. "]";
	
	-- If the effect applied by someone, then make some changes
	if eff_src and eff_src ~= "" then
		local ctnode = DB.findNode(eff_src);
		if ctnode then
			NodeManager.setSafeChildValue(target_effect, "caster_ref_node", "string", eff_src);
			msg.text = msg.text .. " [by " .. NodeManager.getSafeChildValue(ctnode, "name", "") .. "]";
		end
	end
	
	-- Send the notification messages
	if (NodeManager.getSafeChildValue(node_ctentry, "type", "") == "pc") or 
	  ((NodeManager.getSafeChildValue(node_ctentry, "show_npc", 0) == 1) and (string.sub(eff_name, 1, 5) ~= "RCHG:")) then
	  	ChatManager.deliverMessage(msg);
	else
		ChatManager.addMessage(msg);
		if msguser ~= "" then
			ChatManager.deliverMessage(msg, msguser);
		end
	end
end

--
-- END TURN
--

function endTurn(msguser)
	-- Parameter validation
	if msguser == "" then
		return;
	end
	
	-- Make sure we have a CT to browse through
	local ctnode = DB.findNode("combattracker");
	if not ctnode then
		return;
	end
	
	-- Iterate through CT nodes to find the active entry
	local activeentry = nil;
	for k,v in pairs(ctnode.getChildren()) do
		local active = v.getChild("active");
		if active then
			if active.getValue() == 1 then
				activeentry = v;
				break;
			end
		end
	end
	if not activeentry then
		return;
	end
	
	-- Make sure the active entry is a PC
	local link = activeentry.getChild("link");
	if not link then
		return;
	end
	local refclass, refname = link.getValue();
	if refclass ~= "charsheet" then
		return;
	end
	
	-- Find the PC node
	local pcnode = DB.findNode(refname);
	if not pcnode then
		return;
	end
	
	-- Check if the special message user is the same as the owner of this node
	if msguser ~= pcnode.getOwner() then
		return;
	end
	
	-- Make sure the combat tracker is up on the host
	local wnd = Interface.findWindow("combattracker_window", "combattracker");
	if not wnd then
		local msg = {font = "systemfont"};
		msg.text = "[WARNING] Turns can only be ended when host combat tracker is active";
		deliverMessage(msg, msguser);
		return;
	end
	
	-- Everything checks out, so advance the turn
	wnd.list.nextActor();
end
