-- scripts/modtools/reaction-trigger.lua
-- author expwnent
-- replaces autoSyndrome: trigger commands when custom reactions are completed
--@ module = true

local eventful = require 'plugins.eventful'
local syndromeUtil = require 'syndrome-util'
local utils = require 'utils'

reactionHooks = reactionHooks or {}

eventful.enableEvent(eventful.eventType.UNLOAD,1)
eventful.onUnload.reactionTrigger = function()
 reactionHooks = {}
end

function getWorkerAndBuilding(job)
 local workerId = -1
 local buildingId = -1
 for _,generalRef in ipairs(job.general_refs) do
  if generalRef:getType() == df.general_ref_type.UNIT_WORKER then
   if workerId ~= -1 then
    print(job)
    printall(job)
    error('reaction-trigger: two workers on same job: ' .. workerId .. ', ' .. generalRef.unit_id)
   else
    workerId = generalRef.unit_id
    if workerId == -1 then
     print(job)
     printall(job)
     error('reaction-trigger: invalid worker')
    end
   end
  elseif generalRef:getType() == df.general_ref_type.BUILDING_HOLDER then
   if buildingId ~= -1 then
    print(job)
    printall(job)
    error('reaction-trigger: two buildings same job: ' .. buildingId .. ', ' .. generalRef.building_id)
   else
    buildingId = generalRef.building_id
    if buildingId == -1 then
     print(job)
     printall(job)
     error('reaction-trigger: invalid building')
    end
   end
  end
 end
 return workerId,buildingId
end

local function processCommand(job, worker, target, building, command)
 local result = {}
 for _,arg in ipairs(command) do
  if arg == '\\WORKER_ID' then
   table.insert(result,''..worker.id)
  elseif arg == '\\TARGET_ID' then
   table.insert(result,''..target.id)
  elseif arg == '\\BUILDING_ID' then
   table.insert(result,''..building.id)
  elseif arg == '\\LOCATION' then
   table.insert(result,''..job.pos.x)
   table.insert(result,''..job.pos.y)
   table.insert(result,''..job.pos.z)
  elseif arg == '\\REACTION_NAME' then
   table.insert(result,''..job.reaction_name)
  elseif string.sub(arg,1,1) == '\\' then
   table.insert(result,string.sub(arg,2))
  else
   table.insert(result,arg)
  end
 end
 return result
end

eventful.onJobCompleted.reactionTrigger = function(job)
 if job.completion_timer > 0 then
  return
 end

-- if job.job_type ~= df.job_type.CustomReaction then
--  --TODO: support builtin reaction triggers if someone asks
--  return
-- end

 if not job.reaction_name or job.reaction_name == '' then
  return
 end
-- print('reaction name: ' .. job.reaction_name)
 if not job.reaction_name or not reactionHooks[job.reaction_name] then
  return
 end

 local worker,building = getWorkerAndBuilding(job)
 worker = df.unit.find(worker)
 building = df.building.find(building)
 if not worker or not building then
  --this probably means that it finished before EventManager could get a copy of the job while the job was running
  --TODO: consider printing a warning once
  return
 end

 local function doAction(action)
  local didSomething
  if action.command then
   local processed = processCommand(job, worker, worker, building, action.command)
   dfhack.run_command(table.unpack(processed))
  end
  if action.syndrome then
   didSomething = syndromeUtil.infectWithSyndromeIfValidTarget(worker, action.syndrome, action.resetPolicy) or didSomething
  end
  if action.workerOnly then
   return
  end
  if didSomething and not action.allowMultipleTargets then
   return
  end
  local function foreach(unit)
   if unit == worker then
    return false
   elseif unit.pos.z ~= building.z then
    return false
   elseif unit.pos.x < building.x1 or unit.pos.x > building.x2 then
    return false
   elseif unit.pos.y < building.y1 or unit.pos.y > building.y2 then
    return false
   else
    if action.command then
     processCommand(job, worker, unit, building, action.command)
    end
    if action.syndrome then
     didSomething = syndrome.infectWithSyndromeIfValidTarget(unit,action.syndrome,action.resetPolicy) or didSomething
    end
    if didSomething and not action.allowMultipleTargets then
     return true
    end
    return false
   end
  end
  for _,unit in ipairs(df.global.world.units.all) do
   if foreach(unit) then
    break
   end
  end
 end
 for _,action in ipairs(reactionHooks[job.reaction_name]) do
  doAction(action)
 end
end
eventful.enableEvent(eventful.eventType.JOB_COMPLETED,0) --0 is necessary to catch cancelled jobs and not trigger them

validArgs = validArgs or utils.invert({
 'help',
 'clear',
 'reactionName',
 'syndrome',
 'command',
 'allowNonworkerTargets',
 'allowMultipleTargets'
})

if moduleMode then
 return
end
local args = utils.processArgs({...}, validArgs)

if args.help then
 print([[scripts/modtools/reaction-trigger.lua
arguments:
    -help
        print this help message
    -clear
        unregister all reaction hooks
    -reactionName name
        specify the name of the reaction
    -syndrome name
        specify the name of the syndrome to be applied to the targets
    -allowNonworkerTargets
        allow other units in the same building to be targetted by either the script or the syndrome
    -allowMultipleTargets
        allow multiple targets to the script or syndrome
        if absent:
         if running a script, only one target will be used
         if applying a syndrome, then only one target will be infected
    -resetPolicy policy
        set the reset policy in the case that the syndrome is already present
        policy
            NewInstance (default)
            DoNothing
            ResetDuration
            AddDuration
    -command [ commandStrs ]
        specify the command to be run on the target(s)
        special args
            \\WORKER_ID
            \\TARGET_ID
            \\BUILDING_ID
            \\LOCATION
            \\REACTION_NAME
            \\anything -> \anything
            anything -> anything
]])
 return
end

if args.clear then
 reactionHooks = {}
end

if not args.reactionName then
 return
end

if not reactionHooks[args.reactionName] then
 reactionHooks[args.reactionName] = {}
end

if args.syndrome then
 local foundIt
 for _,syndrome in ipairs(df.global.world.raws.syndromes.all) do
  if syndrome.syn_name == args.syndrome then
   args.syndrome = syndrome
   foundIt = true
   break
  end
 end
 if not foundIt then
  error('Could not find syndrome ' .. args.syndrome)
 end
end

table.insert(reactionHooks[args.reactionName], args)