
VisualHeal = LibStub("AceAddon-3.0"):NewAddon("VisualHeal", "AceConsole-3.0");
VisualHeal.Data =
{
    Name = "VisualHeal",
    Version = 8,
};

VisualHeal.EventFrame = CreateFrame("Frame");
VisualHeal.EventFrame:SetScript("OnEvent", function (this, event, ...) VisualHeal[event](VisualHeal, ...) end);
VisualHeal.HealComm = LibStub:GetLibrary("LibHealComm-3.0");
VisualHeal.SharedMedia = LibStub("LibSharedMedia-3.0");
local L = LibStub("AceLocale-3.0"):GetLocale("VisualHeal");

--[ Frequently Accessed Globals ]--

local UnitIsUnit = UnitIsUnit;
local UnitName = UnitName;
local GetTime = GetTime;
local UnitHealth = UnitHealth;
local UnitHealthMax = UnitHealthMax;
local UnitClass = UnitClass;
local UnitIsPlayer = UnitIsPlayer;
local RAID_CLASS_COLORS = RAID_CLASS_COLORS;
local string = string;
local select = select;


--[ Settings ]--

function VisualHeal:SetOptionHealBarScale(info, value)
    if (not self.db.profile.VisualHealBarLayout.default) then
        local layout = self.db.profile.VisualHealBarLayout;
        local point, _, relpoint, x, y = VisualHealBar:GetPoint();
        local xscale = VisualHealBar:GetScale();
        layout.x = x * xscale / value;
        layout.y = y * xscale / value;
        VisualHealBar:ClearAllPoints();
        VisualHealBar:SetPoint(point, nil, relpoint, layout.x, layout.y);
    end
    self.db.profile.HealBarScale = value;
    VisualHealBar:SetScale(value);
end

function VisualHeal:SetOptionPlayerBarScale(info, value)
    if (not self.db.profile.VisualHealPlayerBarLayout.default) then
        local layout = self.db.profile.VisualHealPlayerBarLayout;
        local point, _, relpoint, x, y = VisualHealPlayerBar:GetPoint();
        local xscale = VisualHealPlayerBar:GetScale();
        layout.x = x * xscale / value;
        layout.y = y * xscale / value;
        VisualHealPlayerBar:ClearAllPoints();
        VisualHealPlayerBar:SetPoint(point, nil, relpoint, layout.x, layout.y);
    end
    self.db.profile.PlayerBarScale = value;
    VisualHealPlayerBar:SetScale(value);
end

function VisualHeal:SetOptionBorderBrightness(info, value)
    self.db.profile.BorderBrightness = value;
    self:PlayerBarUpdateTexture();
    self:HealBarUpdateTexture();
end

function VisualHeal:ShowBars()
    self:HealBarHide();
    self:PlayerBarHide();
    VisualHealPlayerBarTextLeft:SetText("");
    VisualHealPlayerBarTextRight:SetText("");
    VisualHealPlayerBarLeft:SetStatusBarColor(1, 1, 1);
    VisualHealPlayerBarLeft:SetValue(1);
    VisualHealPlayerBarMiddle:SetValue(1);
    VisualHealPlayerBarRight:SetValue(1);
    VisualHealPlayerBarSparkOne:SetPoint("CENTER", "VisualHealPlayerBarLeft", "LEFT", 0, 0);
    VisualHealPlayerBarSparkTwo:SetPoint("CENTER", "VisualHealPlayerBarLeft", "LEFT", 372/2, 0);
    VisualHealPlayerBarSparkOne:Show();
    VisualHealPlayerBarSparkTwo:Show();
    self:PlayerBarShow();
    if (self.IsHealer) then
        VisualHealBarText:SetText("");
        VisualHealBarLeft:SetStatusBarColor(1, 1, 1);
        VisualHealBarLeft:SetValue(1);
        VisualHealBarMiddle:SetValue(1);
        VisualHealBarRight:SetValue(1);
        VisualHealBarSparkOne:SetPoint("CENTER", "VisualHealBarLeft", "LEFT", 0, 0);
        VisualHealBarSparkTwo:SetPoint("CENTER", "VisualHealBarLeft", "LEFT", 372/2, 0);
        VisualHealBarSparkOne:Show();
        VisualHealBarSparkTwo:Show();
        self:HealBarShow();
    end
end

function VisualHeal:HideBars()
    self:HealBarHide();
    self:PlayerBarHide();
end

function VisualHeal:GetOptionsTable()
    local optionstable =
    {
        type = "group",
        icon = "",
        name = self.Data.Name .. " " .. self.Data.Version,
        handler = self,
        childGroups = "tree",
        args =
        {
            intro = {
                order = 1,
                type = "description",
                name = L["DESCRIPTION"],
            },
            general =
            {
                type = "group",
                name = L["General Options"],
                guiInline = true,
                order = 10,
                args =
                {
                    healbar =
                    {
                        name = L["Enable HealBar"],
                        type = "toggle",
                        order = 1,
                        desc = L["Toggles display of the HealBar when you are healing"],
                        get = function() return self.db.profile.ShowHealBar end,
                        set = function() self.db.profile.ShowHealBar = not self.db.profile.ShowHealBar end,
                    },
                    playerbar =
                    {
                        name = L["Enable PlayerBar"],
                        type = "toggle",
                        order = 2,
                        desc = L["Toggles display of the PlayerBar when heals are incoming to you"],
                        get = function() return self.db.profile.ShowPlayerBar end,
                        set = function() self.db.profile.ShowPlayerBar = not self.db.profile.ShowPlayerBar end,
                    },
                    stickyhealbar =
                    {
                        name = L["Sticky HealBar"],
                        type = "toggle",
                        order = 3,
                        desc = L["If enabled the HealBar will stay on screen when your heal completes"],
                        get = function() return self.db.profile.StickyHealBar end,
                        set = function() self.db.profile.StickyHealBar = not self.db.profile.StickyHealBar end,
                    },
                },
            },
            appearance =
            {
                type = "group",
                name = L["Appearance Options"],
                guiInline = true,
                order = 20,
                args =
                {
                    show =
                    {
                        name = L["Show Bars"],
                        type = "execute",
                        order = 1,
                        desc = L["Show the HealBar and PlayerBar to allow moving them around"],
                        func = "ShowBars",
                    },
                    resetbars =
                    {
                        name = L["Reset Bars"],
                        type = "execute",
                        order = 2,
                        desc = L["Reset the HealBar and PlayerBar to default positions and scales"],
                        func = function()
                            VisualHealBar:ClearAllPoints();
                            VisualHealBar:SetScale(1);
                            self.db.profile.HealBarScale = 1;
                            self.db.profile.VisualHealBarLayout.default = true;
                            self.db.profile.VisualHealBarLayout.x = 0;
                            self.db.profile.VisualHealBarLayout.y = 0;
                            self.db.profile.VisualHealBarLayout.point = "CENTER";
                            self.db.profile.VisualHealBarLayout.relpoint = "CENTER";
                            VisualHealBar:SetPoint("CENTER", CastingBarFrame, "CENTER", 0, VisualHealBar.DefaultOffset);

                            VisualHealPlayerBar:ClearAllPoints();
                            VisualHealPlayerBar:SetScale(1);
                            self.db.profile.PlayerBarScale = 1;
                            self.db.profile.VisualHealPlayerBarLayout.default = true;
                            self.db.profile.VisualHealPlayerBarLayout.x = 0;
                            self.db.profile.VisualHealPlayerBarLayout.y = 0;
                            self.db.profile.VisualHealPlayerBarLayout.point = "CENTER";
                            self.db.profile.VisualHealPlayerBarLayout.relpoint = "CENTER";
                            VisualHealPlayerBar:SetPoint("CENTER", CastingBarFrame, "CENTER", 0, VisualHealPlayerBar.DefaultOffset);

                            self.db.profile.BorderBrightness = 0.2;
                            self:PlayerBarUpdateTexture();
                            self:HealBarUpdateTexture();
                        end
                    },
                    healbarscale =
                    {
                        name = L["Scale of the HealBar"],
                        type = "range",
                        order = 3,
                        desc = L["Set the scale of the HealBar"],
                        min = 0.5,
                        max = 2.0,
                        isPercent = true,
                        get = function() return self.db.profile.HealBarScale end,
                        set = "SetOptionHealBarScale",
                    },
                    playerbarscale =
                    {
                        name = L["Scale of the PlayerBar"],
                        type = "range",
                        order = 4,
                        desc = L["Set the scale of the PlayerBar"],
                        min = 0.5,
                        max = 2.0,
                        isPercent = true,
                        get = function() return self.db.profile.PlayerBarScale end,
                        set = "SetOptionPlayerBarScale",
                    },
                    texture =
                    {
                        name = L["Bar Texture"],
                        type = 'select',
                        order = 5,
                        desc = L["Select texture to use for the HealBar and PlayerBar"],
                        dialogControl = 'LSM30_Statusbar',
                        values = AceGUIWidgetLSMlists.statusbar,
                        get = function()
                            return self.db.profile.BarTexture;
                        end,
                        set = function(info, value)
                            self.db.profile.BarTexture = value;
                            self:PlayerBarUpdateTexture();
                            self:HealBarUpdateTexture();
                        end,
                    },
                    border =
                    {
                        name = L["Border Texture"],
                        type = 'select',
                        order = 6,
                        desc = L["Select texture to use for the border of the HealBar and PlayerBar"],
                        dialogControl = 'LSM30_Border',
                        values = AceGUIWidgetLSMlists.border,
                        get = function()
                            return self.db.profile.BorderTexture;
                        end,
                        set = function(info, value)
                            self.db.profile.BorderTexture = value;
                            self:PlayerBarUpdateTexture();
                            self:HealBarUpdateTexture();
                        end,
                    },
                    borderbrightness =
                    {
                        name = L["Border Brightness"],
                        type = "range",
                        order = 7,
                        desc = L["Set the brightness of the border of the HealBar and PlayerBar"],
                        min = 0.0,
                        max = 1.0,
                        isPercent = true,
                        get = function() return self.db.profile.BorderBrightness end,
                        set = "SetOptionBorderBrightness",
                    },
                },
            },
            versions =
            {
                type = "group",
                name = L["LibHealComm-3.0 Versions"],
                guiInline = true,
                order = 30,
                args =
                {
                    intro = {
                        order = 1,
                        type = "description",
                        name = L["HEALCOMM_DESCRIPTION"],
                    },
                    guildversions =
                    {
                        name = L["Dump Guild Versions"],
                        type = "execute",
                        order = 2,
                        desc = L["Print the versions of LibHealComm-3.0 used by guild members"],
                        func = function() self:DumpVersions(self.HealComm:GetGuildVersions()); end,
                    },
                    groupversions =
                    {
                        name = L["Dump Group Versions"],
                        type = "execute",
                        order = 3,
                        desc = L["Print the versions of LibHealComm-3.0 used by group members"],
                        func = function() self:DumpVersions(self.HealComm:GetRaidOrPartyVersions()); end,
                    },
                },
            },
        },
    };
    return optionstable;
end

function VisualHeal:OpenConfig()
    InterfaceOptionsFrame_OpenToCategory(self.BlizzardOptionsFrame);
end

function VisualHeal:FrameMoved(frame)
    local layout = self.db.profile[frame:GetName() .. "Layout"];
    local point, parent, relpoint, x, y = frame:GetPoint();
    if (parent) then
        layout.default = true;
        layout.point, layout.relpoint, layout.x, layout.y = "CENTER", "CENTER", 0, 0;
    else
        layout.default = false;
        layout.point, layout.relpoint, layout.x, layout.y = point, relpoint, x, y;
    end
end

--[ ConfigMode Support ]--

CONFIGMODE_CALLBACKS = CONFIGMODE_CALLBACKS or {};
CONFIGMODE_CALLBACKS["VisualHeal"] =
function(action)
    if (action == "ON") then
        VisualHeal:ShowBars();
    elseif (action == "OFF") then
        VisualHeal:HideBars();
    end
end

--[ Init/Enable/Disable ]--

function VisualHeal:OnInitialize()
    self.db = LibStub("AceDB-3.0"):New("VisualHealDB",
    {
        profile =
        {
            ShowHealBar = true,
            ShowPlayerBar = true,
            PlayerBarScale = 1.0,
            HealBarScale = 1.0,
            StickyHealBar = false,
            BorderBrightness = 0.2,
            VisualHealBarLayout =
            {
                default = true,
                point = "CENTER",
                relpoint = "CENTER",
                x = 0,
                y = 0,
            },
            VisualHealPlayerBarLayout =
            {
                default = true,
                point = "CENTER",
                relpoint = "CENTER",
                x = 0,
                y = 0,
            },
        }
    });

    VisualHealPlayerBar:SetScript("OnUpdate", self.PlayerBarOnUpdate);

    LibStub("AceConfig-3.0"):RegisterOptionsTable("VisualHeal", self:GetOptionsTable());
    self.BlizzardOptionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("VisualHeal", "VisualHeal");
    self:RegisterChatCommand("vh", "OpenConfig");
    self:RegisterChatCommand("visualheal", "OpenConfig");

    local playerClass = string.lower(select(2, UnitClass('player')));

    if (playerClass == "druid" or playerClass == "paladin" or playerClass == "priest" or playerClass == "shaman") then
        self.IsHealer = true;
    end

    self.PlayerName = self:UnitFullName('player');
end

function VisualHeal:OnEnable()
    self.EventFrame:RegisterEvent("UNIT_HEALTH");
    self.EventFrame:RegisterEvent("UNIT_MAXHEALTH")
    self.EventFrame:RegisterEvent("PLAYER_TARGET_CHANGED");
    self.EventFrame:RegisterEvent("PLAYER_FOCUS_CHANGED");
    self.HealComm.RegisterCallback(self, "HealComm_DirectHealStart");
    self.HealComm.RegisterCallback(self, "HealComm_DirectHealDelayed");
    self.HealComm.RegisterCallback(self, "HealComm_DirectHealStop");
    self.HealComm.RegisterCallback(self, "HealComm_HealModifierUpdate");
    self.SharedMedia.RegisterCallback(self, "LibSharedMedia_Registered", "LibSharedMedia_Callback")
    self.SharedMedia.RegisterCallback(self, "LibSharedMedia_SetGlobal", "LibSharedMedia_Callback")

    VisualHealBar:SetScale(self.db.profile.HealBarScale);
    if (not self.db.profile.VisualHealBarLayout.default) then
        local layout = self.db.profile.VisualHealBarLayout;
        VisualHealBar:ClearAllPoints();
        VisualHealBar:SetPoint(layout.point, nil, layout.relpoint, layout.x, layout.y);
    end

    VisualHealPlayerBar:SetScale(self.db.profile.PlayerBarScale);
    if (not self.db.profile.VisualHealPlayerBarLayout.default) then
        local layout = self.db.profile.VisualHealPlayerBarLayout;
        VisualHealPlayerBar:ClearAllPoints();
        VisualHealPlayerBar:SetPoint(layout.point, nil, layout.relpoint, layout.x, layout.y);
    end

    VisualHeal:PlayerBarUpdateTexture();
    VisualHeal:HealBarUpdateTexture();
end

function VisualHeal:OnDisable()
    self.EventFrame:UnregisterEvent("UNIT_HEALTH");
    self.EventFrame:UnregisterEvent("UNIT_MAXHEALTH")
    self.EventFrame:UnregisterEvent("PLAYER_TARGET_CHANGED");
    self.EventFrame:UnregisterEvent("PLAYER_FOCUS_CHANGED");
    self.HealComm.UnregisterCallback(self, "HealComm_DirectHealStart");
    self.HealComm.UnregisterCallback(self, "HealComm_DirectHealDelayed");
    self.HealComm.UnregisterCallback(self, "HealComm_DirectHealStop");
    self.HealComm.UnregisterCallback(self, "HealComm_HealModifierUpdate");

    self:HealBarHide();
    self:PlayerBarHide();
end


--[ Utilities ]--

function VisualHeal:UnitFullName(unit)
    local name, realm = UnitName(unit);
    if (realm and realm ~= "") then
        return name .. "-" .. realm;
    else
        return name;
    end
end

-- Determine if player is in a battleground
function VisualHeal:InBattleground()
    return (select(2, IsInInstance()) == "pvp") and true or false
end

function VisualHeal:GetUnitID(targetName)
    return UnitExists(targetName) and targetName or (targetName == self:UnitFullName('target') and 'target') or (targetName == self:UnitFullName('focus') and 'focus') or (targetName == self:UnitFullName('targettarget') and 'targettarget') or (targetName == self:UnitFullName('focustarget') and 'focustarget') or nil;
end

function VisualHeal:DumpVersions(versions)
    local ownVersion = select(2, LibStub("LibHealComm-3.0"));
    for player, version in pairs(versions) do
        local color;
        local nversion = tonumber(version);
        if (nversion) then
            if (nversion >= ownVersion) then
                color = "|cFF00FF00";
            else
                color = "|cFFFFFF00";
            end
        else
            color = "|cFFFF0000";
        end
        DEFAULT_CHAT_FRAME:AddMessage(player .. ": " .. color .. (nversion or "Not detected!"));
    end
end


--[ PlayerBar Functions ]--

local PlayerBarLastUpdate = GetTime();
function VisualHeal:PlayerBarOnUpdate()
    local time = GetTime();
    if (time > PlayerBarLastUpdate + 0.05) then
        PlayerBarLastUpdate = time;
        VisualHeal:PlayerBarUpdate();
    end
end

function VisualHeal:PlayerBarShow(isLive)
    VisualHealPlayerBar.IsLive = isLive;
    VisualHealPlayerBar:Show();
    self:PlayerBarUpdate();
end

function VisualHeal:PlayerBarHide()
    VisualHealPlayerBar.IsLive = false;
    VisualHealPlayerBar:Hide();
end

function VisualHeal:PlayerBarUpdate()

    if (not VisualHealPlayerBar.IsLive) then
        return;
    end

    local now = GetTime();
    local hp, hppre, hppost;
    local _, incomingHeal, nextTime, nextSize, nextName = self.HealComm:UnitIncomingHealGet('player', now);

    if (not incomingHeal) then
        self:PlayerBarHide();
        return;
    end

    -- Calculate time left to next heal
    local nextTimeLeft = nextTime - now;

    -- Determine health percentages
    hp = UnitHealth('player') / UnitHealthMax('player');
    hppre = hp + (nextSize * self.PlayerModifier) / UnitHealthMax('player');
    hppost = hp + (incomingHeal * self.PlayerModifier) / UnitHealthMax('player');

    -- Sanity check on values
    if (hp > 1.0) then
        hp = 1.0;
    end
    if (hppre > 2.0) then
        hppre = 2.0;
    end
    if (hppost > 3.0) then
        hppost = 3.0;
    end
    if (hp > hppre) then
        hppre = hp;
    end
    if (hppre > hppost) then
        hppost = hppre;
    end

    -- Update bars
    VisualHealPlayerBarLeft:SetValue(hp);
    VisualHealPlayerBarMiddle:SetValue(hppre);
    VisualHealPlayerBarRight:SetValue(hppost);
    VisualHealPlayerBarSparkOne:SetPoint("CENTER", "VisualHealPlayerBarLeft", "LEFT", 372/2 * hp, 0);
    VisualHealPlayerBarSparkTwo:SetPoint("CENTER", "VisualHealPlayerBarLeft", "LEFT", 372/2 * ((incomingHeal == nextSize) and hp or hppre), 0);

    -- Set colour for health
    local r = hp < 0.5 and 1.0 or 2.0 * (1.0 - hp);
    local g = hp > 0.5 and 0.8 or 1.6 * hp;
    local b = 0.0;
    VisualHealPlayerBarLeft:SetStatusBarColor(r, g, b);

    -- Set text
    VisualHealPlayerBarTextLeft:SetText(nextName);
    VisualHealPlayerBarTextRight:SetFormattedText("%.1fs", nextTimeLeft);

    -- Set colour for remaining incoming heal
    VisualHealPlayerBarRight:SetStatusBarColor(0.4, 0.6, 0.4, 0.5);

    -- Set colour for next heal
    VisualHealPlayerBarMiddle:SetStatusBarColor(0.0, 0.8, 0.0, (1.5 - nextTimeLeft) / 1.5);
end

function VisualHeal:PlayerBarUpdateTexture()
    if (self.db.profile.BarTexture) then
        local barTexture = self.SharedMedia:Fetch('statusbar', self.db.profile.BarTexture);
        VisualHealPlayerBarLeft:SetStatusBarTexture(barTexture);
        VisualHealPlayerBarMiddle:SetStatusBarTexture(barTexture);
        VisualHealPlayerBarRight:SetStatusBarTexture(barTexture);
    end
    if (self.db.profile.BorderTexture) then
        local borderTexture = self.SharedMedia:Fetch('border', self.db.profile.BorderTexture);
        VisualHealPlayerBar:SetBackdrop({edgeFile = borderTexture, edgeSize = 16, tileSize = 16, insets = {bottom = 5, top = 5, right = 5, left = 5}});
    end
    if (self.db.profile.BorderBrightness) then
        VisualHealPlayerBar:SetBackdropBorderColor(self.db.profile.BorderBrightness, self.db.profile.BorderBrightness, self.db.profile.BorderBrightness, 1.0);
    end
end

--[ HealBar Functions ]--

function VisualHeal:HealBarShow(isLive)
    VisualHealBar.IsLive = isLive;
    self:HealBarUpdate();
    VisualHealBar:Show();
end

function VisualHeal:HealBarHide()
    VisualHealBar.IsLive = false;
    VisualHealBar:Hide();
end

function VisualHeal:HealBarUpdate()

    if (not VisualHealBar.IsLive) then
        return;
    end

    -- Check validity of UnitID
    if (self.HealingTargetUnitID) then
        if (self:UnitFullName(self.HealingTargetUnitID) ~= self.HealingTargetName) then
            self.HealingTargetUnitID = nil;
        end
    end

    -- If no UnitID, try again to determine the UnitID
    if (not self.HealingTargetUnitID) then
        self.HealingTargetUnitID = self:GetUnitID(self.HealingTargetName);
    end

    if (not self.HealingTargetUnitID) then
        -- Lost the unit
        VisualHealBarText:SetTextColor(255, 0, 0);
        VisualHealBarLeft:SetValue(0);
        VisualHealBarMiddle:SetValue(0);
        VisualHealBarRight:SetValue(0);
        VisualHealBarSparkOne:Hide();
        VisualHealBarSparkTwo:Hide();
        return;
    else
        VisualHealBar:Show();
    end

    VisualHealBarText:SetText(self.HealingTargetName);
    if (UnitIsPlayer(self.HealingTargetUnitID)) then
        local tab = RAID_CLASS_COLORS[select(2, UnitClass(self.HealingTargetUnitID)) or ""];
        VisualHealBarText:SetTextColor(tab.r, tab.g, tab.b);
    else
        VisualHealBarText:SetTextColor(230, 230, 0);
    end

    local hp, hppre, hppost, waste, incomingHeal;

    if (self.IsCasting) then
        incomingHeal = self.HealComm:UnitIncomingHealGet(self.HealingTargetUnitID, self.EndTime);
    else
        incomingHeal = self.HealComm:UnitIncomingHealGet(self.HealingTargetUnitID, GetTime() + 1000);
    end

    -- Determine health percentages of healing target
    local unitHealthMax = UnitHealthMax(self.HealingTargetUnitID);
    local unitHealth = UnitHealth(self.HealingTargetUnitID);
    hp = unitHealth / unitHealthMax;
    if (incomingHeal) then
        hppre = (unitHealth + incomingHeal * self.HealingTargetModifier) / unitHealthMax;
    else
        hppre = hp;
    end
    if (self.IsCasting) then
        hppost = (unitHealth + ((incomingHeal or 0) + self.HealingSize) * self.HealingTargetModifier) / unitHealthMax;
    else
        hppost = hppre;
    end

    if (hp > 1.0) then
        hp = 1.0;
    end
    if (hppre > 2.0) then
        hppre = 2.0;
    end
    if (hppost > 3.0) then
        hppost = 3.0;
    end
    if (hp > hppre) then
        hppre = hp;
    end
    if (hppre > hppost) then
        hppost = hppre;
    end

    -- Determine waste (in percent)
    if (hppost > 1.0 and hppost > hppre) then
        waste = (hppost - 1.0) / (hppost - hppre);
    else
        waste = 0.0;
    end
    if (waste > 1.0) then
        waste = 1.0;
    end

    -- Calculate colour for overheal severity
    local red = waste > 0.1 and 1 or waste * 10;
    local green = waste < 0.1 and 1 or -2.5 * waste + 1.25;
    if (waste < 0.0) then
        green = 1.0;
        red = 0.0;
    end

    -- Update bars
    VisualHealBarLeft:SetValue(hp);
    VisualHealBarMiddle:SetValue(hppre);
    VisualHealBarRight:SetValue(hppost);
    VisualHealBarSparkOne:SetPoint("CENTER", "VisualHealBarLeft", "LEFT", 372/2 * hp, 0)
    VisualHealBarSparkTwo:SetPoint("CENTER", "VisualHealBarLeft", "LEFT", 372/2 * hppre, 0)

    if (incomingHeal or self.IsCasting) then
        VisualHealBarSparkOne:Show();
        if (incomingHeal and self.IsCasting) then
            VisualHealBarSparkTwo:Show();
        else
            VisualHealBarSparkTwo:Hide();
        end
    else
        VisualHealBarSparkOne:Hide();
    end

    -- Set colour for health
    VisualHealBarLeft:SetStatusBarColor(hp < 0.5 and 1.0 or 2.0 * (1.0 - hp), hp > 0.5 and 0.8 or 1.6 * hp, 0.0);

    -- Set colour for heal
    VisualHealBarRight:SetStatusBarColor(red, green, 0.0);
end

function VisualHeal:HealBarUpdateTexture()
    if (self.db.profile.BarTexture) then
        local barTexture = self.SharedMedia:Fetch('statusbar', self.db.profile.BarTexture);
        VisualHealBarLeft:SetStatusBarTexture(barTexture);
        VisualHealBarMiddle:SetStatusBarTexture(barTexture);
        VisualHealBarRight:SetStatusBarTexture(barTexture);
    end
    if (self.db.profile.BorderTexture) then
        local borderTexture = self.SharedMedia:Fetch('border', self.db.profile.BorderTexture);
        VisualHealBar:SetBackdrop({edgeFile = borderTexture, edgeSize = 16, tileSize = 16, insets = {bottom = 5, top = 5, right = 5, left = 5}});
    end
    if (self.db.profile.BorderBrightness) then
        VisualHealBar:SetBackdropBorderColor(self.db.profile.BorderBrightness, self.db.profile.BorderBrightness, self.db.profile.BorderBrightness, 1.0);
    end
end

--[ Event Handlers ]--

function VisualHeal:UNIT_HEALTH(unit)
    if (self.HealingTargetUnitID and UnitIsUnit(self.HealingTargetUnitID, unit)) then
        -- Update the heal bar when unit health changes on the healing target
        self:HealBarUpdate();
    end
    if (unit == 'player') then
        -- Update the player bar when the health of the player changes
        self:PlayerBarUpdate();
    end
end

function VisualHeal:UNIT_MAXHEALTH(unit)
    self:UNIT_HEALTH(unit);
end

function VisualHeal:PLAYER_FOCUS_CHANGED()
    self:HealBarUpdate();
end

function VisualHeal:PLAYER_TARGET_CHANGED()
    self:HealBarUpdate();
end

function VisualHeal:HealComm_DirectHealStart(event, healerName, healSize, endTime, ...)
    if (healerName == self.PlayerName) then
        self.IsCasting = true;
        self.HealingTargetName = ...;
        self.HealingTargetUnitID = self:GetUnitID(...);
        self.HealingSize = healSize;
        self.EndTime = endTime;
        self.HealingTargetModifier = self.HealComm:UnitHealModifierGet(...);
        if (self.db.profile.ShowHealBar) then
            self:HealBarShow(true);
        end
    else
        self:HealBarUpdate();
        for i = 1, select('#', ...) do
            local targetName = select(i, ...);
            if (self.db.profile.ShowPlayerBar and (targetName == self.PlayerName)) then
                self.PlayerModifier = self.HealComm:UnitHealModifierGet(targetName);
                self:PlayerBarShow(true);
            end
        end
    end
end

function VisualHeal:HealComm_DirectHealDelayed(event, healerName, healSize, endTime, ...)
    if (healerName == self.PlayerName) then
        self.EndTime = endTime;
        self:HealBarUpdate();
    else
        self:HealBarUpdate();
        self:PlayerBarUpdate();
    end
end

function VisualHeal:HealComm_DirectHealStop(event, healerName, healSize, succeeded, ...)
    if (healerName == self.PlayerName) then
        self.IsCasting = false;
        if (not self.db.profile.StickyHealBar) then
            self:HealBarHide();
        else
            self:HealBarUpdate();
        end
    else
        self:HealBarUpdate();
        self:PlayerBarUpdate();
    end
end

function VisualHeal:HealComm_HealModifierUpdate(event, unit, targetName, healModifier)
    if (self.HealingTargetUnitID and UnitIsUnit(self.HealingTargetUnitID, unit)) then
        self.HealingTargetModifier = healModifier;
        self:HealBarUpdate();
    end
    if (UnitIsUnit(unit, 'player')) then
        self.PlayerModifier = healModifier;
        self:HealBarUpdate();
        self:PlayerBarUpdate();
    end
end

function VisualHeal:LibSharedMedia_Callback()
    self:PlayerBarUpdateTexture();
    self:HealBarUpdateTexture();
end
