--scripts/modtools/attack-trigger.lua
--author expwnent
--based on itemsyndrome by Putnam
--triggers scripts when a unit attacks another with a weapon type, a weapon of a particular material, or a weapon contaminated with a particular material, or when a unit equips/unequips a particular item type, an item of a particular material, or an item contaminated with a particular material

local eventful = require 'plugins.eventful'
local utils = require 'utils'

itemTriggers = itemTriggers or {}
materialTriggers = materialTriggers or {}
contaminantTriggers = contaminantTriggers or {}

eventful.enableEvent(eventful.eventType.UNIT_ATTACK,1) -- this event type is cheap, so checking every tick is fine
eventful.enableEvent(eventful.eventType.INVENTORY_CHANGE,5) --this is expensive, but you might still want to set it lower
eventful.enableEvent(eventful.eventType.UNLOAD,1)

eventful.onUnload.itemTrigger = function()
 itemTriggers = {}
 materialTriggers = {}
 contaminantTriggers = {}
end

function processTrigger(command)
 local command2 = {}
 for i,arg in ipairs(command.command) do
  if arg == '\\ATTACKER_ID' then
   command2[i] = '' .. command.attacker.id
  elseif arg == '\\DEFENDER_ID' then
   command2[i] = '' .. command.defender.id
  elseif arg == '\\ITEM_MATERIAL' then
   command2[i] = command.itemMat:getToken()
  elseif arg == '\\ITEM_MATERIAL_TYPE' then
   command2[i] = command.itemMat['type']
  elseif arg == '\\ITEM_MATERIAL_INDEX' then
   command2[i] = command.itemMat.index
  elseif arg == '\\ITEM_ID' then
   command2[i] = '' .. command.item.id
  elseif arg == '\\ITEM_TYPE' then
   command2[i] = command.itemType
  elseif arg == '\\CONTAMINANT_MATERIAL' then
   command2[i] = command.contaminantMat:getToken()
  elseif arg == '\\CONTAMINANT_MATERIAL_TYPE' then
   command2[i] = command.contaminantMat['type']
  elseif arg == '\\CONTAMINANT_MATERIAL_INDEX' then
   command2[i] = command.contaminantMat.index
  elseif arg == '\\MODE' then
   command2[i] = command.mode
  elseif arg == '\\UNIT_ID' then
   command2[i] = command.unit.id
  elseif string.sub(arg,1,1) == '\\' then
   command2[i] = string.sub(arg,2)
  else
   command2[i] = arg
  end
 end
 dfhack.run_command(table.unpack(command2))
end

function handler(table)
 local itemMat = dfhack.matinfo.decode(table.item)
 local itemMatStr = itemMat:getToken()
 local itemType = dfhack.items.getSubtypeDef(table.item:getType(),table.item:getSubtype()).id
 table.itemMat = itemMat
 table.itemType = itemType
 
 for _,command in ipairs(itemTriggers[itemType] or {}) do
  if command[table.mode] then
   utils.fillTable(command,table)
   processTrigger(command)
   utils.unfillTable(command,table)
  end
 end

 for _,command in ipairs(materialTriggers[itemMatStr] or {}) do
  if command[table.mode] then
   utils.fillTable(command,table)
   processTrigger(command)
   utils.unfillTable(command,table)
  end
 end
 
 for _,contaminant in ipairs(table.item.contaminants or {}) do
  local contaminantMat = dfhack.matinfo.decode(contaminant.mat_type, contaminant.mat_index)
  local contaminantStr = contaminantMat:getToken()
  table.contaminantMat = contaminantMat
  for _,command in ipairs(contaminantTriggers[contaminantStr] or {}) do
   utils.fillTable(command,table)
   processTrigger(command)
   utils.unfillTable(command,table)
  end
  table.contaminantMat = nil
 end
end

function equipHandler(unit, item, isEquip)
 local mode = (isEquip and 'onEquip') or (not isEquip and 'onUnequip')

 local table = {}
 table.mode = mode
 table.item = df.item.find(item)
 table.unit = df.unit.find(unit)
 handler(table)
end

eventful.onInventoryChange.equipmentTrigger = function(unit, item, item_old, item_new)
 if item_old and item_new then
  return
 end
 
 local isEquip = item_new and not item_old
 equipHandler(unit,item,isEquip)
end

eventful.onUnitAttack.attackTrigger = function(attacker,defender,wound)
 attacker = df.unit.find(attacker)
 defender = df.unit.find(defender)
 
 if not attacker then
  return
 end
 
 local attackerWeapon
 for _,item in ipairs(attacker.inventory) do
  if item.mode == df.unit_inventory_item.T_mode.Weapon then
   attackerWeapon = item.item
   break
  end
 end
 
 if not attackerWeapon then
  return
 end

 local table = {}
 table.attacker = attacker
 table.defender = defender
 table.item = attackerWeapon
 table.mode = 'onStrike'
 handler(table)
end

validArgs = validArgs or utils.invert({
 'clear',
 'help',
 'checkAttackEvery',
 'checkInventoryEvery',
 'command',
 'itemType',
 'onStrike',
 'onEquip',
 'onUnequip',
 'material',
 'contaminant',
})
local args = utils.processArgs({...}, validArgs)

if args.help then
 print([[scripts/modtools/item-trigger.lua usage
arguments:
    -help
        print this help message
    -clear
        clear all registered triggers
    -checkAttackEvery n 
        check the attack event at least every n ticks
    -checkInventoryEvery n
        check inventory event at least every n ticks
    -itemType type
        trigger the command for items of this type
        examples:
            ITEM_WEAPON_PICK
    -onStrike
        trigger the command when someone strikes someone with an appropriate weapon
    -onEquip
        trigger the command when someone equips an appropriate item
    -onUnequip
        trigger the command when someone unequips an appropriate item
    -material mat
        trigger the commmand on items with the given material
        examples
            INORGANIC:IRON
            CREATURE_MAT:DWARF:BRAIN
            PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK
    -contaminant mat
        trigger the command on items with a given material contaminant
        examples
            INORGANIC:IRON
            CREATURE_MAT:DWARF:BRAIN
            PLANT_MAT:MUSHROOM_HELMET_PLUMP:DRINK
    -command [ commandStrs ]
        specify the command to be executed
        commandStrs
            \\ATTACKER_ID
            \\DEFENDER_ID
            \\ITEM_MATERIAL
            \\ITEM_MATERIAL_TYPE
            \\ITEM_ID
            \\ITEM_TYPE
            \\CONTAMINANT_MATERIAL
            \\CONTAMINANT_MATERIAL_TYPE
            \\CONTAMINANT_MATERIAL_INDEX
            \\MODE
            \\UNIT_ID
            \\anything -> \anything
            anything -> anything
]])
 return
end

if args.clear then
 itemTriggers = {}
 materialTriggers = {}
 contaminantTriggers = {}
end

if args.checkAttackEvery then
 if not tonumber(args.checkAttackEvery) then
  error('checkAttackEvery must be a number')
 end
 eventful.enableEvent(eventful.eventType.UNIT_ATTACK,tonumber(args.checkAttackEvery))
end

if args.checkInventoryEvery then
 if not tonumber(args.checkInventoryEvery) then
  error('checkInventoryEvery must be a number')
 end
 eventful.enableEvent(eventful.eventType.INVENTORY_CHANGE,tonumber(args.checkInventoryEvery))
end

if not args.command then
 if not args.clear then
  error 'specify a command'
 end
 return
end

if args.itemType then
 local temp
 for _,itemdef in ipairs(df.global.world.raws.itemdefs.all) do
  if itemdef.id == args.itemType then
   temp = args.itemType --itemdef.subtype
   break
  end
 end
 if not temp then
  error 'Could not find item type.'
 end
 args.itemType = temp
end

local numConditions = (args.material and 1 or 0) + (args.itemType and 1 or 0) + (args.contaminant and 1 or 0)
if numConditions > 1 then
 error 'too many conditions defined: not (yet) supported (pester expwnent if you want it)'
elseif numConditions == 0 then
 error 'specify a material, weaponType, or contaminant'
end

if args.material then
 if not materialTriggers[args.material] then
  materialTriggers[args.material] = {}
 end
 table.insert(materialTriggers[args.material],args)
elseif args.itemType then
 if not itemTriggers[args.itemType] then
  itemTriggers[args.itemType] = {}
 end
 table.insert(itemTriggers[args.itemType],args)
elseif args.contaminant then
 if not contaminantTriggers[args.contaminant] then
  contaminantTriggers[args.contaminant] = {}
 end
 table.insert(contaminantTriggers[args.contaminant],args)
end