local DEBUG = false;
local statsHalo = true;


local function doSkill(_player, _amount, _name, _perk)
    if _amount == nil or _amount <= 0 then return; end

    --local curXp = _player:getXp():getXP(_perk);
    local amount = 50*_amount;
    --if curXp>0 then
        --amount = (2 / curXp)*50;
    --end

    local oldXp = _player:getXp():getXP(_perk);
    _player:getXp():AddXP(_perk , amount);
    amount = _player:getXp():getXP(_perk) - oldXp;
    --_player:setHaloNote(_name.." +"..round(amount,2));
    --ISRadioInteractions:getInstance().addHalo(_name.." +"..round(amount,2));
    if oldXp~=_player:getXp():getXP(_perk) then
        ISRadioInteractions:getInstance().addHalo(_name, amount, true);
    end
end

local function applyBoredom(_player, _amount, _isSet)
    local bodyDamage = _player:getBodyDamage();
    if bodyDamage~=nil then
        local val = bodyDamage:getBoredomLevel();
        local valCache = val;

        if _isSet then
            val = _amount;
        else
            local am = _amount*5;
            val = val+am;
        end

        if val<0 then val = 0; end
        if val>100 then val = 100; end

        bodyDamage:setBoredomLevel(val);

        if DEBUG then
            _player:setHaloNote("Boredom "..tostring(val));
        else
            if statsHalo and (not _isSet) then
                if valCache~=bodyDamage:getBoredomLevel() then
                    ISRadioInteractions:getInstance().addHalo(getText("IGUI_HaloNote_Boredom"),_amount);
                end
            end
        end
    end
end

local function applyUnhappiness(_player, _amount, _isSet)
    local bodyDamage = _player:getBodyDamage();
    if bodyDamage~=nil then
        local val = bodyDamage:getUnhappynessLevel();
        local valCache = val;

        if _isSet then
            val = _amount;
        else
            local am = _amount*5;
            val = val+am;
        end

        if val<0 then val = 0; end
        if val>100 then val = 100; end

        bodyDamage:setUnhappynessLevel(val);

        if DEBUG then
            _player:setHaloNote("Unhappiness "..tostring(val));
        else
            if statsHalo and (not _isSet) then
                if valCache~=bodyDamage:getUnhappynessLevel() then
                    ISRadioInteractions:getInstance().addHalo(getText("IGUI_HaloNote_Unhappiness"),_amount);
                end
            end
        end
    end
end

local function doStat(_statStr, _player, _amount, _isSet)
    if _statStr=="Boredom" then
        applyBoredom(_player,_amount,_isSet);
        return;
    elseif _statStr=="Unhappiness" then
        applyUnhappiness(_player,_amount,_isSet);
        return;
    end

    local stats = _player:getStats();
    if stats["get".._statStr]~=nil and stats["get".._statStr]~=nil then
        local val = stats["get".._statStr](stats);
        local valCache = val;

        local range100 = false;
        if _statStr=="Panic" then
            range100 = true;
        end

        if _isSet then
            val = _amount;
        else
            local mod = range100 and 5 or 0.05;
            local am = _amount*mod;
            val = val+am;
        end

        if val<0 then val = 0; end
        if (not range100) and val>1 then val = 1; end
        if range100 and val>100 then val = 100; end

        stats["set".._statStr](stats,val);
        if DEBUG then
            local val = stats["get".._statStr](stats);
            _player:setHaloNote(getText("IGUI_HaloNote_".._statStr).." "..tostring(val));
        else
            if statsHalo and (not _isSet) then
                if valCache~=stats["get".._statStr](stats) then
                    ISRadioInteractions:getInstance().addHalo(getText("IGUI_HaloNote_".._statStr),_amount);
                end
            end
        end
    end
end

local Interactions = {};
--Stats
Interactions.ANG = function(_player, _amount, _opIsSet) doStat("Anger",_player,_amount, _opIsSet); end       -- Anger
Interactions.BOR = function(_player, _amount, _opIsSet) doStat("Boredom",_player,_amount, _opIsSet); end     -- boredom
Interactions.END = function(_player, _amount, _opIsSet) doStat("Endurance",_player,_amount, _opIsSet); end   -- endurance
Interactions.FAT = function(_player, _amount, _opIsSet) doStat("Fatigue",_player,_amount, _opIsSet); end     -- fatigue
Interactions.FIT = function(_player, _amount, _opIsSet) doStat("Fitness",_player,_amount, _opIsSet); end     -- fitness
Interactions.HUN = function(_player, _amount, _opIsSet) doStat("Hunger",_player,_amount, _opIsSet); end      -- hunger
Interactions.MOR = function(_player, _amount, _opIsSet) doStat("Morale",_player,_amount, _opIsSet); end      -- morale
Interactions.STS = function(_player, _amount, _opIsSet) doStat("Stress",_player,_amount, _opIsSet); end      -- stress
Interactions.FEA = function(_player, _amount, _opIsSet) doStat("Fear",_player,_amount, _opIsSet); end        -- Fear
Interactions.PAN = function(_player, _amount, _opIsSet) doStat("Panic",_player,_amount, _opIsSet); end       -- Panic
Interactions.SAN = function(_player, _amount, _opIsSet) doStat("Sanity",_player,_amount, _opIsSet); end      -- Sanity
Interactions.SIC = function(_player, _amount, _opIsSet) doStat("Sickness",_player,_amount, _opIsSet); end    -- Sickness
Interactions.PAI = function(_player, _amount, _opIsSet) doStat("Pain",_player,_amount, _opIsSet); end        -- Pain
Interactions.DRU = function(_player, _amount, _opIsSet) doStat("Drunkenness",_player,_amount, _opIsSet); end -- Drunkenness
Interactions.THI = function(_player, _amount, _opIsSet) doStat("Thirst",_player,_amount, _opIsSet); end      -- thirst
Interactions.UHP = function(_player, _amount, _opIsSet) doStat("Unhappiness",_player,_amount, _opIsSet); end      -- thirst
--Skills
--agility
Interactions.SPR = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Sprinting"), Perks.Sprinting); end         --sprinting
Interactions.LFT = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Lightfooted"), Perks.Lightfoot); end         --lightfooded
Interactions.NIM = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Nimble"), Perks.Nimble); end            --nimble
Interactions.SNE = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Sneaking"), Perks.Sneak); end             --sneaking
--blade
Interactions.BAA = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Axe"), Perks.Axe); end       -- Axe
Interactions.BUA = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Blunt"), Perks.Blunt); end       -- Blunt
--crafting
Interactions.CRP = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Carpentry"), Perks.Woodwork); end           --carpentry
Interactions.COO = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Cooking"), Perks.Cooking); end           --cooking
Interactions.FRM = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Farming"), Perks.Farming); end           --farming
Interactions.DOC = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Doctor"), Perks.Doctor); end            --firstaid
Interactions.ELC = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Electricity"), Perks.Electricity); end           --electricty
Interactions.MTL = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Metalworking"), Perks.MetalWelding); end            --metalwelding
--firearm
Interactions.AIM = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Aiming"), Perks.Aiming); end            --aiming
Interactions.REL = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Reloading"), Perks.Reloading); end         --reloading
--survivalist
Interactions.FIS = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Fishing"), Perks.Fishing); end           --fishing
Interactions.TRA = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Trapping"), Perks.Trapping); end          --trapping
Interactions.FOR = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Foraging"), Perks.PlantScavenging); end   --foraging
--new
Interactions.TAI = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Tailoring"), Perks.Tailoring); end          --tailoring
Interactions.MEC = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Mechanics"), Perks.Mechanics); end   --mechanics

Interactions.CMB = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Combat"), Perks.Combat); end
Interactions.SPE = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_Spear"), Perks.Spear); end
Interactions.SBU = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_SmallBlunt"), Perks.SmallBlunt); end
Interactions.LBA = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_LongBlade"), Perks.LongBlade); end
Interactions.SBA = function(_player, _amount) doSkill(_player, _amount, getText("IGUI_perks_SmallBlade"), Perks.SmallBlade); end

local instance = nil;

ISRadioInteractions = {}

function ISRadioInteractions:getInstance()
    if instance ~= nil then
        return instance;
    end

    local channelLog = {};
    local interactLog = {};
    local interactions = {};
    local cooldowns = {};
    local self = {};
    local noHalo = {};
    local currentPlayer;

    function self.split(str,sep)
        local sep, fields = sep or ":", {};
        local pattern = string.format("([^%s]+)", sep);
        str:gsub(pattern, function(c) fields[#fields+1] = c end);
        return fields;
    end

    function self.playerInRange(_player, _x, _y, _z)
        if math.floor(_player:getZ()) == math.floor(_z) then
            if _player:getX() >= _x-5 and _player:getX() <= _x+5 and _player:getY() >= _y-5 and _player:getY() <= _y+5 then
                return true;
            end
        end
        return false;
    end

    function self.checkPlayer(player, _guid, _interactCodes, _x, _y, _z, _line, _source)
        local source = (not (_x==-1 and _y==-1 and _z==-1)) and getCell():getGridSquare(_x,_y,_z) or nil;
        local plrsquare = player:getSquare();
        if source and source:isOutside() ~= plrsquare:isOutside() then
            return;
        end

        if player:isAsleep() then
            return;
        end

        if _guid ~= nil and _guid ~= "" then
            if player:isKnownMediaLine(_guid) then
                return;
            end
            player:addKnownMediaLine(_guid)
        end

        if _interactCodes == nil or _interactCodes:len() == 0 or _line == nil then
            return;
        end

        --player:Say(_line);
        currentPlayer = player;
        local playerNum = player:getPlayerNum()+1
        local stats = player:getStats();
        local xp = player:getXp();

        if stats ~= nil and xp ~= nil then
            local codes = self.split(_interactCodes, ",");
            for _,_v in ipairs(codes) do
                if _v:len() > 4 then
                    local code = string.sub(_v, 1, 3);
                    local op = string.sub(_v, 4, 4);
                    local amount = code~="RCP" and tonumber(string.sub(_v, 5, _v:len())) or nil;
                    if amount ~= nil and code~="RCP" then
                        amount = op=="-" and amount*-1 or amount;

                        if Interactions[code] ~= nil then
                            if not cooldowns[playerNum] or not cooldowns[playerNum][code] or cooldowns[playerNum][code]<=0 then
                                Interactions[code](player, amount, op=="=");
                                cooldowns[playerNum] = cooldowns[playerNum] or {}
                                cooldowns[playerNum][code] = 30;
                            end
                        end
                    end
                    -- the following will attempt to learn a recipe, ex: "RCP=Make Butter Knife"
                    if code=="RCP" then
                        local recipe = string.sub(_v, 5, _v:len());
                        if recipe then
                            local learned = player:learnRecipe(recipe);
                            if learned then
                                local index = string.find(recipe, "%.");
                                if index then
                                    recipe = string.sub(recipe, index+1, recipe:len())
                                    HaloTextHelper.addText(player, getText("IGUI_HaloNote_LearnedRecipe", getRecipeDisplayName(tostring(recipe))), HaloTextHelper.getColorGreen());
                                    --self.addHalo("Learned "..tostring(string.sub(recipe, index+1, recipe:len())));
                                else
                                    HaloTextHelper.addText(player, getText("IGUI_HaloNote_LearnedRecipe", getRecipeDisplayName(recipe)), HaloTextHelper.getColorGreen());
                                    --self.addHalo("Learned "..recipe);
                                end
                            end
                        end
                    end
                end
            end

            local moodles = player:getMoodles();
            if moodles ~= nil then
                moodles:Update();
            end
        end
    end

    function self.OnDeviceText( _guid, _interactCodes, _x, _y, _z, _line )
        for playerNum=1,4 do
            local player = getSpecificPlayer(playerNum-1)
            if player and player:isDead() then player = nil end
            if player ~=nil and ((_x==-1 and _y==-1 and _z==-1) or self.playerInRange(player, _x, _y, _z)) then
                self.checkPlayer(player, _guid, _interactCodes, _x, _y, _z, _line)
            end
        end
    end

    function self.OnTick()
        for playerNum=1,4 do
            local tbl = cooldowns[playerNum]
            if tbl then
                for code,value in pairs(tbl) do
                    if value > 0 then
                        tbl[code] = value - (1*getGameTime():getMultiplier());
                    end
                end
            end
        end
    end

    function self.addHalo(_str, _amount, _inverseCols)
        if noHalo[_str] or (not currentPlayer) then
            return;
        end

        local color = HaloTextHelper.getColorGreen();
        local doArrow = 0;

        if _amount and (type(_amount) == "number") then
            if _amount<0 then
                color = _inverseCols and HaloTextHelper.getColorRed() or HaloTextHelper.getColorGreen();
                doArrow = -1;
            elseif _amount>0 then
                color = _inverseCols and HaloTextHelper.getColorGreen() or HaloTextHelper.getColorRed();
                doArrow = 1;
            end
        end

        if doArrow~=0 then
            HaloTextHelper.addTextWithArrow(currentPlayer, _str, doArrow==1 and true or false, color);
        else
            HaloTextHelper.addText(currentPlayer, _str, color);
        end
    end

    function self.setNoHalo(_type, _b)
        noHalo[_type] = _b;
    end

    local function Init()
        Events.OnDeviceText.Add( self.OnDeviceText );
        Events.OnTick.Add( self.OnTick );

        instance = self;
        return self;
    end

    return Init();
end

-- ISRadioInteractions:getInstance().OnDeviceText("ANG+1",-1,-1,-1,"")

Events.OnGameBoot.Add(function() ISRadioInteractions:getInstance(); end);

