local core = require('openmw.core')
local ui = require('openmw.ui')
local input = require('openmw.input')
local self = require('openmw.self')
local types = require('openmw.types')
local async = require('openmw.async')

-- handle settings
local storage = require('openmw.storage')
local I = require('openmw.interfaces')

I.Settings.registerPage({
   key = 'SolChargeAttackParry',
   l10n = 'SolChargeAttackParry',
   name = 'name',
   description = 'description',
})

-- default values!
local enabled = true
local verbose = 2
local hideChargePopup = true
local incRanged = true -- include ranged weapons ?
local buffControl = 0 -- 0 both, 1 DEF only, 2 AGI only
local buffBase = 2
local tradeOffBase = 5
local doSpeedRelease = true
local maxCharge = 2
local buffDuration = 0.5
I.Settings.registerGroup({
   key = 'Settings_SolChargeAttackParry',
   page = 'SolChargeAttackParry',
   l10n = 'SolChargeAttackParry',
   name = 'group_name',
   permanentStorage = true,
   settings = {
      {
         key = 'enabled',
         default = enabled,
         renderer = 'checkbox',
         name = 'enabled_name',
      },
      {
         key = 'verbose',
         default = verbose,
         renderer = 'number',
         name = 'verbose_name',
         description = 'verbose_description',
         argument = {
            integer = true,
            min = 0,
            max = 2,
         },
      },
      {
         key = 'hideChargePopup',
         default = hideChargePopup,
         renderer = 'checkbox',
         name = 'hideChargePopup_name',
         description = 'hideChargePopup_description',
      },
      {
         key = 'incRanged',
         default = incRanged,
         renderer = 'checkbox',
         name = 'incRanged_name',
      },
      {
         key = 'buffControl',
         default = buffControl,
         renderer = 'number',
         name = 'buffControl_name',
         description = 'buffControl_description',
         argument = {
            integer = true,
            min = 0,
            max = 2,
         },
      },
      {
         key = 'buffBase',
         default = buffBase,
         renderer = 'number',
         name = 'buffBase_name',
         description = 'buffBase_description',
         argument = {
            min = 0,
            max = 10,
         },
      },
      {
         key = 'tradeOffBase',
         default = tradeOffBase,
         renderer = 'number',
         name = 'tradeOffBase_name',
         description = 'tradeOffBase_description',
         argument = {
            min = 0,
            max = 20,
         },
      },
      {
         key = 'doSpeedRelease',
         default = doSpeedRelease,
         renderer = 'checkbox',
         name = 'doSpeedRelease_name',
         description = 'doSpeedRelease_description'
      },
      {
         key = 'maxCharge',
         default = maxCharge,
         renderer = 'number',
         name = 'maxCharge_name',
         description = 'maxCharge_description',
         argument = {
            min = 1,
            max = 5,
         },
      },
      {
         key = 'buffDuration',
         default = buffDuration,
         renderer = 'number',
         name = 'buffDuration_name',
         description = 'buffDuration_description',
         argument = {
            min = 0.25,
            max = 5,
         },
      },
   },
})

local settingsGroup = storage.playerSection('Settings_SolChargeAttackParry')

-- shorthand for convenience
local Weapon = types.Weapon
local attributes = types.Actor.stats.attributes
local skills = types.NPC.stats.skills

-- reduce effectiveness of hybrid stances
local function hybridVal(base,mult,count)
    return math.ceil(base*math.pow(mult,math.max(count,0)))
end

-- script config
local modType = 1 --1 skill, -1 debug reset all stat modifiers
local incH2H = true -- include handtohand for heavy charged attacks?
  -- if true, must define "weight" and "speed" values for h2h in buff/debuff fncs

-- and store stance idxs for indexing into tables
local stanceIndex = {charge=1, release=2}
local maxStance = 0
for _ in pairs(stanceIndex) do
  maxStance = maxStance + 1
end

-- init to defaults
local stanceBuff = {-tradeOffBase,buffBase}
local stanceNames = {'',''}
-- update
local function updateSettings()
  enabled = settingsGroup:get('enabled')
  hideChargePopup = settingsGroup:get('hideChargePopup')
  verbose = settingsGroup:get('verbose')
    -- update verbose
    if verbose == 1 then
    stanceNames = {'DEFENSE UP', 'AGILITY UP', 'CHARGE'}
    elseif verbose == 2 then
    stanceNames  = {'DEF+', 'AGI+', 'SPD ETC'}
    end
  incRanged = settingsGroup:get('incRanged')
  buffControl = settingsGroup:get('buffControl')
  buffBase = settingsGroup:get('buffBase')
  tradeOffBase = settingsGroup:get('tradeOffBase')
  -- calculate new buff vals
    stanceBuff = {-tradeOffBase,buffBase}
  doSpeedRelease = settingsGroup:get('doSpeedRelease')
  maxCharge = settingsGroup:get('maxCharge')
  buffDuration = settingsGroup:get('buffDuration')
end
local function init()
    updateSettings()
end
settingsGroup:subscribe(async:callback(updateSettings))

-- stance effects 
local function chargeMod(modVal)
  attributes.speed(self).modifier = attributes.speed(self).modifier + modVal
  skills.heavyarmor(self).modifier = skills.heavyarmor(self).modifier + modVal
  skills.lightarmor(self).modifier = skills.lightarmor(self).modifier + modVal
  skills.mediumarmor(self).modifier = skills.mediumarmor(self).modifier + modVal
  skills.unarmored(self).modifier = skills.unarmored(self).modifier + modVal
  -- damage check
  if attributes.speed(self).modifier == attributes.speed(self).damage then
    attributes.speed(self).modifier = 0
    attributes.speed(self).damage = 0
  end
  if skills.heavyarmor(self).modifier == skills.heavyarmor(self).damage then
    skills.heavyarmor(self).modifier = 0
    skills.heavyarmor(self).damage = 0
  end
  if skills.lightarmor(self).modifier == skills.lightarmor(self).damage then
    skills.lightarmor(self).modifier = 0
    skills.lightarmor(self).damage = 0
  end
  if skills.mediumarmor(self).modifier == skills.mediumarmor(self).damage then
    skills.mediumarmor(self).modifier = 0
    skills.mediumarmor(self).damage = 0
  end
  if skills.unarmored(self).modifier == skills.unarmored(self).damage then
    skills.unarmored(self).modifier = 0
    skills.unarmored(self).damage = 0
  end
end
local function releaseMod(modVal,buffType)
  if doSpeedRelease then
	  -- if debuff speed on release
	  attributes.speed(self).modifier = attributes.speed(self).modifier - tradeOffBase*modVal
	  if attributes.speed(self).modifier == attributes.speed(self).damage then
		attributes.speed(self).modifier = 0
		attributes.speed(self).damage = 0
	  end
  end
  if buffType == 1 then
    skills.heavyarmor(self).modifier = skills.heavyarmor(self).modifier + modVal
    skills.lightarmor(self).modifier = skills.lightarmor(self).modifier + modVal
    skills.mediumarmor(self).modifier = skills.mediumarmor(self).modifier + modVal
    skills.unarmored(self).modifier = skills.unarmored(self).modifier + modVal
  else
    attributes.agility(self).modifier = attributes.agility(self).modifier + modVal
    attributes.endurance(self).modifier = attributes.endurance(self).modifier - modVal
  end
  -- damage check
  if skills.heavyarmor(self).modifier == skills.heavyarmor(self).damage then
    skills.heavyarmor(self).modifier = 0
    skills.heavyarmor(self).damage = 0
  end
  if skills.lightarmor(self).modifier == skills.lightarmor(self).damage then
    skills.lightarmor(self).modifier = 0
    skills.lightarmor(self).damage = 0
  end
  if skills.mediumarmor(self).modifier == skills.mediumarmor(self).damage then
    skills.mediumarmor(self).modifier = 0
    skills.mediumarmor(self).damage = 0
  end
  if skills.unarmored(self).modifier == skills.unarmored(self).damage then
    skills.unarmored(self).modifier = 0
    skills.unarmored(self).damage = 0
  end
  if attributes.agility(self).modifier == attributes.agility(self).damage then
    attributes.agility(self).modifier = 0
    attributes.agility(self).damage = 0
  end
  if attributes.endurance(self).modifier == attributes.endurance(self).damage then
    attributes.endurance(self).modifier = 0
    attributes.endurance(self).damage = 0
  end
end

-- init stance total tracking, used for verbose outputs, and for onSave/onLoad tracking
local chargeBuffTotal = {}
for i=1,maxStance,1 do 
  chargeBuffTotal[i] = 0
end

local function weaponCheck()
  local isWeapon = false
  if types.Actor.stance(self) == types.Actor.STANCE.Weapon then
    local usedWeapon = types.Actor.equipment(self, types.Actor.EQUIPMENT_SLOT.CarriedRight)
    if (not usedWeapon) then -- handtohand
      isWeapon = incH2H
    else
      if not types.Lockpick.objectIsInstance(usedWeapon) and not types.Probe.objectIsInstance(usedWeapon) then
        local weaponType = Weapon.record(usedWeapon).type
        if (weaponType < 9) then -- melee weapon
          isWeapon = true
        elseif (weaponType <= 13) then -- ranged weapon
          isWeapon = incRanged
        elseif (weaponType > 13) then -- unknown weapon
          isWeapon = true
        end
      end
    end 
  end
  return(isWeapon)
end

local function getChargeMod()
  -- get relevant stats
  local strength = attributes.strength(self).modified
  local usedWeapon = types.Actor.equipment(self, types.Actor.EQUIPMENT_SLOT.CarriedRight)
  local weapWeight = 1
  if not (not usedWeapon) then -- NOT handtohand
    weapWeight = Weapon.record(usedWeapon).weight
  end
  -- setup mod
  --local buffMod = (1 + math.sqrt(weapWeight))*(50/strength)
  local buffMod = (1 + math.sqrt(weapWeight))*math.max(0,(2.5-(strength/50)))
    -- mod up as weapon weight up, with minimum
    -- mod down as strength up
  return(buffMod)
end

local function getReleaseMod(chargeTime)
  -- get relevant stats
  local usedWeapon = types.Actor.equipment(self, types.Actor.EQUIPMENT_SLOT.CarriedRight)
  local weapSpeed = 1
  local weapWeight = 1
  if not (not usedWeapon) then -- NOT handtohand
    weapSpeed = Weapon.record(usedWeapon).speed
    weapWeight = Weapon.record(usedWeapon).weight
  end
  -- setup mod
  local buffMod = (1 + math.sqrt(weapWeight))*math.min(maxCharge,(chargeTime*weapSpeed))--/weapSpeed
    -- mod up as weapon weight up, with minimum
    -- mod up as charge increases, vs weapon speed
    -- mod up as weapon speed up?
  return(buffMod)
end

-- save state to be removed on load
local function onSave()
    return{
      chargeBuffTotal = chargeBuffTotal
    }
end
local function onLoad(data)
  if data then
    chargeBuffTotal = data.chargeBuffTotal
    chargeMod(-chargeBuffTotal[1])
    releaseMod(-chargeBuffTotal[2])
    for i=1,maxStance,1 do
      chargeBuffTotal[i] = 0
    end
  end
end

local weaponWeight = 0
local chargeTime = 0
local doBuff = false
local isCharge = false -- true if trigger input action
local isWeapon = false
return { 
  engineHandlers = { 
    -- init settings
    onActive = init,
    -- save and load handling so you don't get stuck with modified stats
    onSave = onSave,
    onLoad = onLoad,
    
    onUpdate = function(dt)
      if enabled then
        local isUse = input.isActionPressed(input.ACTION.Use)
        
        if isUse and not isCharge then -- on first frame you hit use        
--        if isUse and not isCharge and not types.Actor.isOnGround(self) then -- on first frame you hit use        
          isCharge = true -- update stance
          isWeapon = weaponCheck()
          -- apply charge debuff
          if isWeapon then
            chargeTime = core.getSimulationTime() -- set timer value
            local buffNext = hybridVal(stanceBuff[1],getChargeMod(),1)
            chargeMod(buffNext)
            chargeBuffTotal[1] = chargeBuffTotal[1] + buffNext
            if verbose>0 and not hideChargePopup then
              ui.showMessage(stanceNames[3] .. ' ' .. tostring(chargeBuffTotal[1]))
            end
          end
      
        elseif not isUse and isCharge then -- on first frame you release use
          isCharge = false
          -- remove charge debuff
          chargeMod(-chargeBuffTotal[1])
          chargeBuffTotal[1] = 0
          if isWeapon and chargeBuffTotal[2]==0 then -- only buff if unbuffed
            chargeTime = core.getSimulationTime() - chargeTime -- get timer value
            -- buff DEFENSE or Agility depending on movement and config val
            local mf = self.controls.movement
            local ms = self.controls.sideMovement
            local ig = types.Actor.isOnGround(self)
            local moveType = 0
            if ig and mf == -1 then -- backwards component
              moveType = 1 -- def
            elseif ig and mf == 0 and ms ~= 0 then -- sideways only
              moveType = 2 -- agi
            end
            if moveType > 0 then
              doBuff = true
            else
              doBuff = false
            end
            
            if doBuff then
              -- get release buff
              local buffNext = hybridVal(stanceBuff[2],getReleaseMod(chargeTime),1)
              local buffType = 0
              if buffControl == 0 then
                if moveType == 1 then
                  buffType = 1 -- def
                elseif moveType == 2 then
                  buffType = 2 -- agi
                end
              else
                buffType = buffControl
              end
              -- apply buff
              releaseMod(buffNext,buffType)
              chargeBuffTotal[2] = chargeBuffTotal[2] + buffNext
              -- status info
              if verbose>0 then
                local buffMax = hybridVal(stanceBuff[2],getReleaseMod(maxCharge),1)
                if buffType == 1 then
                  ui.showMessage(stanceNames[1] .. ' ' .. tostring(chargeBuffTotal[2]) .. ' / ' .. tostring(buffMax))
                else
                  ui.showMessage(stanceNames[2] .. ' ' .. tostring(chargeBuffTotal[2]) .. ' / ' .. tostring(buffMax))
                end
              end
              -- start release timer
              async:newUnsavableSimulationTimer(
                buffDuration,
                function()
                releaseMod(-chargeBuffTotal[2],buffType)
                chargeBuffTotal[2] = 0
                end
              )
            end
          end
        end
      end
    end
  } 
}