function LifeSafer_LowHealth_OnLoad()

  local health, mana, sound = "UNIT_HEALTH", "UNIT_MANA", "SOUND"

  LifeSafer_LowHealth_Version = "2.2.2"

  -- these are the default values
  LifeSafer_LowHealth_Default_Enabled = true
  LifeSafer_LowHealth_Default_SoundEnabled = true
  LifeSafer_LowHealth_Default_HealthEnabled = true
  LifeSafer_LowHealth_Default_ManaEnabled = true
  LifeSafer_LowHealth_Default_SoundCombatOnly = false
  LifeSafer_LowHealth_Default_HealthCombatOnly = false
  LifeSafer_LowHealth_Default_ManaCombatOnly = false
  LifeSafer_LowHealth_Default_HealthSync = false
  LifeSafer_LowHealth_Default_ManaSync = false
  LifeSafer_LowHealth_Default_HealthEmote = false
  LifeSafer_LowHealth_Default_ManaEmote = false
  LifeSafer_LowHealth_Default_UseAltTextures = false
  LifeSafer_LowHealth_Default_Thresholds = {
    [health] = { [1] = 40, [2] = 20 }, -- regular and critical, in percent
    [mana] = { [1] = 40, [2] = 20 },
    [sound] = { [1] = 50 },
  }

  -- set variables to their default values. these will be overriden when variables are loaded
  LifeSafer_LowHealth_Enabled = LifeSafer_LowHealth_Default_Enabled
  LifeSafer_LowHealth_SoundEnabled = LifeSafer_LowHealth_Default_SoundEnabled
  LifeSafer_LowHealth_HealthEnabled = LifeSafer_LowHealth_Default_HealthEnabled
  LifeSafer_LowHealth_ManaEnabled = LifeSafer_LowHealth_Default_ManaEnabled
  LifeSafer_LowHealth_SoundCombatOnly = LifeSafer_LowHealth_Default_SoundCombatOnly
  LifeSafer_LowHealth_HealthCombatOnly = LifeSafer_LowHealth_Default_HealthCombatOnly
  LifeSafer_LowHealth_ManaCombatOnly = LifeSafer_LowHealth_Default_ManaCombatOnly
  LifeSafer_LowHealth_HealthSync = LifeSafer_LowHealth_Default_HealthSync
  LifeSafer_LowHealth_ManaSync = LifeSafer_LowHealth_Default_ManaSync
  LifeSafer_LowHealth_HealthEmote = LifeSafer_LowHealth_Default_HealthEmote
  LifeSafer_LowHealth_ManaEmote = LifeSafer_LowHealth_Default_ManaEmote
  LifeSafer_LowHealth_Thresholds = LifeSafer_LowHealth_Default_Thresholds
  LifeSafer_LowHealth_UseAltTextures = LifeSafer_LowHealth_Default_UseAltTextures

  LifeSafer_LowHealth_HealthFrame = LowHealthFrame
  LifeSafer_LowHealth_ManaFrame = OutOfControlFrame

  -- these values keep track of animation
  this.states = { [health] = 0, [mana] = 0 }

  -- change the blue frame to act as the red one
  OutOfControlFrame:ClearAllPoints()
  OutOfControlFrame:SetParent("WorldFrame")
  OutOfControlFrame:SetAllPoints("WorldFrame")

  -- set both frames transparent to avoid onload flashing
  LowHealthFrame:SetAlpha(0)
  OutOfControlFrame:SetAlpha(0)

  -- command line
  SlashCmdList["LOWHEALTHWARNINGCMD"] = LifeSafer_LowHealth_CMD
  SLASH_LOWHEALTHWARNINGCMD1 = "/lowhealthwarning"
  SLASH_LOWHEALTHWARNINGCMD2 = "/lowhealth"
  SLASH_LOWHEALTHWARNINGCMD3 = "/lhw"

end


function LifeSafer_LowHealth_ApplySettings()

  -- restore defaults if saved variables are missing
  if (not LifeSafer_LowHealth_Thresholds) then
    LifeSafer_LowHealth_Thresholds = LifeSafer_LowHealth_Default_Thresholds
  end

  -- for compatibility with older versions of saved variables
  if (not LifeSafer_LowHealth_Thresholds["SOUND"]) then
    LifeSafer_LowHealth_Thresholds["SOUND"] = { [1] = 30 }
  end

  local frame = getglobal("LowHealthWarningFrame")

  LifeSafer_LowHealth_FlashFrameStop(LifeSafer_LowHealth_HealthFrame, "UNIT_HEALTH")
  LifeSafer_LowHealth_FlashFrameStop(LifeSafer_LowHealth_ManaFrame, "UNIT_MANA")

  -- register or unregister events
  if (LifeSafer_LowHealth_Enabled) then
    LifeSafer_LowHealth_HealthFrame = LifeSafer_LowHealth_UseAltTextures and LowHealthWarningFrameAltTexture1 or LowHealthFrame
    frame:RegisterEvent("UNIT_DISPLAYPOWER")

    if (LifeSafer_LowHealth_HealthEnabled or LifeSafer_LowHealth_SoundEnabled) then
      frame:RegisterEvent("UNIT_HEALTH")
    end

    LifeSafer_LowHealth_ManaCheck()
    LifeSafer_LowHealth_ManaFrame = LifeSafer_LowHealth_UseAltTextures and LowHealthWarningFrameAltTexture2 or OutOfControlFrame
  else
    frame:UnregisterEvent("UNIT_HEALTH")
    frame:UnregisterEvent("UNIT_MANA")
    frame:UnregisterEvent("UNIT_DISPLAYPOWER")
  end

end


function LifeSafer_LowHealth_FlashFrame(value, regular, critical, frame, state)

  local REGULAR_FLASH, CRITICAL_FLASH = 1, 2

  -- disable
  if (value > regular or LifeSafer_LowHealth_CombatCheck(state) or UnitIsDeadOrGhost("player")) then
    if (UIFrameIsFlashing(frame) or UIFrameIsFading(frame)) then
      LifeSafer_LowHealth_FlashFrameStop(frame, state)
    end

  -- regular flash
  elseif (value > critical) then
    if (not (LowHealthWarningFrame.states[state] == REGULAR_FLASH)) then
      if (UIFrameIsFlashing(frame)) then
        --frame.flashDuration = frame.flashDuration + 10
        frame.fadeInTime = 0.4
        frame.fadeOutTime = 0.6
        frame.flashInHoldTime = 1
      else
        UIFrameFlash(frame, 0.4, 0.6, -1, nil, 1, 0)
      end
      LowHealthWarningFrame.states[state] = REGULAR_FLASH
    end

  -- critical flash
  else
    if (not (LowHealthWarningFrame.states[state] == CRITICAL_FLASH)) then
      if (LifeSafer_LowHealth_HealthEmote and state == "UNIT_HEALTH") then DoEmote("healme") end
      if (LifeSafer_LowHealth_ManaEmote and state == "UNIT_MANA") then DoEmote("oom") end

      if (UIFrameIsFlashing(frame)) then
        --frame.flashDuration = frame.flashDuration + 10
        frame.fadeInTime = 0.2
        frame.fadeOutTime = 0.8
        frame.flashInHoldTime = 0
      else
        UIFrameFlash(frame, 0.2, 0.8, -1, nil, 0, 0)
      end

      LowHealthWarningFrame.states[state] = CRITICAL_FLASH
    end
  end

end


function LifeSafer_LowHealth_SmoothFlashFrame(value, threshold, frame, state)

  -- disable
  if (value > threshold or LifeSafer_LowHealth_CombatCheck(state) or UnitIsDeadOrGhost("player")) then
    LifeSafer_LowHealth_FlashFrameStop(frame, state)

  -- smooth flash
  else
    local rate = LifeSafer_LowHealth_GetHeartRate(state)
    local flashIn, flashOut = rate * 0.1, rate * 0.4

    if (UIFrameIsFlashing(frame)) then
      frame.flashDuration = frame.flashDuration + rate + 1

      if (rate < 1) then
        frame.fadeInTime = rate * 0.15
        frame.fadeOutTime = rate * 0.85
        frame.flashInHoldTime = 0
      else
        frame.fadeInTime = 0.2
        frame.fadeOutTime = 0.8
        frame.flashInHoldTime = rate - 1
      end
    else
      UIFrameFlash(frame, 0.2, 0.8, 10, nil, rate -1, 0)
    end
  end

end


function LifeSafer_LowHealth_FlashFrameStop(frame, state)

  UIFrameFlashRemoveFrame(frame)
  UIFrameFadeRemoveFrame(frame)
  UIFrameFadeOut(frame, 0.5, frame:GetAlpha(), 0)
  LowHealthWarningFrame.states[state] = 0

end


function LifeSafer_LowHealth_OnEvent()

  if (event == "UNIT_DISPLAYPOWER") then
    LifeSafer_LowHealth_ManaCheck()
    return
  end

  -- unpacks regular threshold, critical threshold, nil, nil, nil
  local t1, t2, value, frame, smooth = unpack(LifeSafer_LowHealth_Thresholds[event])

  -- pick frame based on event
  if (event == "UNIT_HEALTH") then
    value = UnitHealth("player") / UnitHealthMax("player")
    LifeSafer_LowHealth_SoundCheck(this, value * 100, unpack(LifeSafer_LowHealth_Thresholds["SOUND"]))

    if (not LifeSafer_LowHealth_HealthEnabled) then
      return -- this hack is because both sound and health flash need this event
    end

    frame = LifeSafer_LowHealth_HealthFrame
    smooth = LifeSafer_LowHealth_HealthSync
  elseif (event == "UNIT_MANA") then
    value = UnitMana("player") / UnitManaMax("player")
    frame = LifeSafer_LowHealth_ManaFrame
    smooth = LifeSafer_LowHealth_ManaSync
  end

  if (smooth) then -- flash differently depending on settings
    LifeSafer_LowHealth_SmoothFlashFrame(value * 100, t1, frame, event)
  else
    LifeSafer_LowHealth_FlashFrame(value * 100, t1, t2, frame, event)
  end

end


function LifeSafer_LowHealth_CombatCheck(state)

  local combat = UnitAffectingCombat("player")

  if (not state or state == "UNIT_HEALTH") then
    return LifeSafer_LowHealth_HealthCombatOnly and not combat
  elseif (state == "UNIT_MANA") then
    return LifeSafer_LowHealth_ManaCombatOnly and not combat
  else
    return LifeSafer_LowHealth_SoundCombatOnly and not combat
  end

end


function LifeSafer_LowHealth_SoundCheck(frame, value, threshold)

  -- disable
  if (value > threshold or not LifeSafer_LowHealth_SoundEnabled or LifeSafer_LowHealth_CombatCheck("SOUND") or UnitIsDeadOrGhost("player")) then
    if (frame.heartRate) then
      frame:SetScript("OnUpdate", nil)
      frame.heartRate = nil
    end

  -- regular
  elseif (not frame.heartRate) then
    frame:SetScript("OnUpdate", LifeSafer_LowHealth_OnUpdate)
    frame.timer = 1
    frame.heartRate = 0
  end

end


function LifeSafer_LowHealth_ManaCheck()

  local mana, frame = "UNIT_MANA", getglobal("LowHealthWarningFrame")

  if (LifeSafer_LowHealth_ManaEnabled and UnitPowerType("player") == 0) then
    -- player is using mana
    frame:RegisterEvent(mana)
  else
    frame:UnregisterEvent(mana)
    LifeSafer_LowHealth_FlashFrameStop(LifeSafer_LowHealth_ManaFrame, mana)
  end

end


function LifeSafer_LowHealth_OnUpdate()

  if (this.timer > this.heartRate) then
    PlaySoundFile("Interface\\AddOns\\LifeSafer_LowHealthWarning\\Sounds\\LifeSafer_LifeAlert_Low.mp3")
    this.heartRate = LifeSafer_LowHealth_GetHeartRate()
    this.timer = 0
  else
    this.timer = this.timer + arg1
  end

end


function LifeSafer_LowHealth_GetHeartRate(state)

  if (state and state == "UNIT_MANA") then
    return 0.5 + 2.0 * UnitMana("player") / UnitManaMax("player")
  else
    return 0.5 + 2.0 * UnitHealth("player") / UnitHealthMax("player")
  end

end


function LifeSafer_LowHealth_CMD(arg1)

  if (not arg1) then
    return
  end

  if (string.find(arg1, "^settings$") or arg1 == "") then
    ShowUIPanel(LowHealthSettingsFrame)
    return
  end

end
