-- A GUI front-end for the workflow plugin.

local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
local guimat = require 'gui.materials'
local widgets = require 'gui.widgets'
local dlg = require 'gui.dialogs'

local workflow = require 'plugins.workflow'

function check_enabled(cb)
    if workflow.isEnabled() then
        return cb()
    else
        dlg.showYesNoPrompt(
            'Enable Plugin',
            { 'The workflow plugin is not enabled currently.', NEWLINE, NEWLINE,
              'Press ', { key = 'MENU_CONFIRM' }, ' to enable it.' },
            COLOR_YELLOW,
            function()
                workflow.setEnabled(true)
                return cb()
            end
        )
    end
end

function check_repeat(job, cb)
    if job.flags['repeat'] then
        return cb()
    else
        dlg.showYesNoPrompt(
            'Not Repeat Job',
            { 'Workflow only tracks repeating jobs.', NEWLINE, NEWLINE,
              'Press ', { key = 'MENU_CONFIRM' }, ' to make this one repeat.' },
            COLOR_YELLOW,
            function()
                job.flags['repeat'] = true
                return cb()
            end
        )
    end
end

JobConstraints = defclass(JobConstraints, guidm.MenuOverlay)

JobConstraints.focus_path = 'workflow-job'

JobConstraints.ATTRS {
    job = DEFAULT_NIL,
    frame_inset = 1,
    frame_background = COLOR_BLACK,
}

local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false }

function JobConstraints:init(args)
    self.building = dfhack.job.getHolder(self.job)

    self:addviews{
        widgets.Label{
            frame = { l = 0, t = 0 },
            text = {
                'Workflow Constraints'
            }
        },
        widgets.List{
            view_id = 'list',
            frame = { t = 2, b = 6 },
            row_height = 4,
            scroll_keys = widgets.SECONDSCROLL,
        },
        widgets.Label{
            frame = { l = 0, b = 3 },
            enabled = self:callback('isAnySelected'),
            text = {
                { key = 'BUILDING_TRIGGER_ENABLE_CREATURE',
                  text = function()
                    local cons = self:getCurConstraint() or null_cons
                    if cons.goal_by_count then
                        return ': Count stacks  '
                    else
                        return ': Count items   '
                    end
                  end,
                  on_activate = self:callback('onChangeUnit') },
                { key = 'BUILDING_TRIGGER_ENABLE_MAGMA', text = ': Modify',
                  on_activate = self:callback('onEditRange') },
                  NEWLINE, '  ',
                { key = 'BUILDING_TRIGGER_MIN_SIZE_DOWN',
                  on_activate = self:callback('onIncRange', 'goal_gap', 5) },
                { key = 'BUILDING_TRIGGER_MIN_SIZE_UP',
                  on_activate = self:callback('onIncRange', 'goal_gap', -1) },
                { text = function()
                    local cons = self:getCurConstraint() or null_cons
                    return string.format(': Min %-4d ', cons.goal_value - cons.goal_gap)
                  end },
                { key = 'BUILDING_TRIGGER_MAX_SIZE_DOWN',
                  on_activate = self:callback('onIncRange', 'goal_value', -1) },
                { key = 'BUILDING_TRIGGER_MAX_SIZE_UP',
                  on_activate = self:callback('onIncRange', 'goal_value', 5) },
                { text = function()
                    local cons = self:getCurConstraint() or null_cons
                    return string.format(': Max %-4d', cons.goal_value)
                  end },
            }
        },
        widgets.Label{
            frame = { l = 0, b = 0 },
            text = {
                { key = 'CUSTOM_N', text = ': New limit, ',
                  on_activate = self:callback('onNewConstraint') },
                { key = 'CUSTOM_X', text = ': Delete',
                  enabled = self:callback('isAnySelected'),
                  on_activate = self:callback('onDeleteConstraint') },
                NEWLINE, NEWLINE,
                { key = 'LEAVESCREEN', text = ': Back',
                  on_activate = self:callback('dismiss') }
            }
        },
    }

    self:initListChoices(args.clist)
end

function JobConstraints:onGetSelectedBuilding()
    return self.building
end

function JobConstraints:onGetSelectedJob()
    return self.job
end

function describe_item_type(iobj)
    local itemline = 'any item'
    if iobj.is_craft then
        itemline = 'any craft'
    elseif iobj.item_type >= 0 then
        itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type
        local subtype = iobj.item_subtype or -1
        local def = dfhack.items.getSubtypeDef(iobj.item_type, subtype)
        local count = dfhack.items.getSubtypeCount(iobj.item_type, subtype)
        if def then
            itemline = def.name
        elseif count >= 0 then
            itemline = 'any '..itemline
        end
    end
    return itemline
end

function is_caste_mat(iobj)
    return dfhack.items.isCasteMaterial(iobj.item_type or -1)
end

function describe_material(iobj)
    local matline = 'any material'
    if is_caste_mat(iobj) then
        matline = 'no material'
    elseif (iobj.mat_type or -1) >= 0 then
        local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index)
        if info then
            matline = info:toString()
        else
            matline = iobj.mat_type..':'..iobj.mat_index
        end
    end
    return matline
end

function JobConstraints:initListChoices(clist, sel_token)
    clist = clist or workflow.listConstraints(self.job)

    local choices = {}

    for i,cons in ipairs(clist) do
        local goal = (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value
        local curval
        if cons.goal_by_count then
            goal = goal .. ' stacks'
            curval = cons.cur_count
        else
            goal = goal .. ' items'
            curval = cons.cur_amount
        end
        local order_pen = COLOR_GREY
        if cons.request == 'resume' then
            order_pen = COLOR_GREEN
        elseif cons.request == 'suspend' then
            order_pen = COLOR_BLUE
        end
        local itemstr = describe_item_type(cons)
        if cons.min_quality > 0 then
            itemstr = itemstr .. ' ('..df.item_quality[cons.min_quality]..')'
        end
        local matstr = describe_material(cons)
        local matflagstr = ''
        local matflags = utils.list_bitfield_flags(cons.mat_mask)
        if #matflags > 0 then
            matflags[1] = 'any '..matflags[1]
            if matstr == 'any material' then
                matstr = table.concat(matflags, ', ')
                matflags = {}
            end
        end
        if #matflags > 0 then
            matflagstr = table.concat(matflags, ', ')
        end

        table.insert(choices, {
            text = {
                goal, ' ', { text = '(now '..curval..')', pen = order_pen }, NEWLINE,
                '  ', itemstr, NEWLINE, '  ', matstr, NEWLINE, '  ', matflagstr
            },
            token = cons.token,
            obj = cons
        })
    end

    local selidx = nil
    if sel_token then
        selidx = utils.linear_index(choices, sel_token, 'token')
    end

    self.subviews.list:setChoices(choices, selidx)
end

function JobConstraints:isAnySelected()
    return self.subviews.list:getSelected() ~= nil
end

function JobConstraints:getCurConstraint()
    local i,v = self.subviews.list:getSelected()
    if v then return v.obj end
end

function JobConstraints:saveConstraint(cons)
    local out = workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap)
    self:initListChoices(nil, out.token)
end

function JobConstraints:onChangeUnit()
    local cons = self:getCurConstraint()
    cons.goal_by_count = not cons.goal_by_count
    self:saveConstraint(cons)
end

function JobConstraints:onEditRange()
    local cons = self:getCurConstraint()
    dlg.showInputPrompt(
        'Input Range',
        'Enter the new constraint range:',
        COLOR_WHITE,
        (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value,
        function(text)
            local maxv = string.match(text, '^%s*(%d+)%s*$')
            if maxv then
                cons.goal_value = maxv
                return self:saveConstraint(cons)
            end
            local minv,maxv = string.match(text, '^%s*(%d+)-(%d+)%s*$')
            if minv and maxv and minv ~= maxv then
                cons.goal_value = math.max(minv,maxv)
                cons.goal_gap = math.abs(maxv-minv)
                return self:saveConstraint(cons)
            end
            dlg.showMessage('Invalid Range', 'This range is invalid: '..text, COLOR_LIGHTRED)
        end
    )
end

function JobConstraints:onIncRange(field, delta)
    local cons = self:getCurConstraint()
    if not cons.goal_by_count then
        delta = delta * 5
    end
    cons[field] = math.max(1, cons[field] + delta)
    self:saveConstraint(cons)
end

function JobConstraints:onNewConstraint()
    local outputs = workflow.listJobOutputs(self.job)
    if #outputs == 0 then
        dlg.showMessage('Unsupported', 'Workflow cannot guess the outputs of this job.', COLOR_LIGHTRED)
        return
    end

    local variants = workflow.listWeakenedConstraints(outputs)

    local choices = {}
    for i,cons in ipairs(variants) do
        local itemstr = describe_item_type(cons)
        local matstr = describe_material(cons)
        local matflags = utils.list_bitfield_flags(cons.mat_mask)
        if #matflags > 0 then
            local fstr = table.concat(matflags, '/')
            if matstr == 'any material' then
                matstr = 'any '..fstr
            else
                matstr = 'any '..fstr..' '..matstr
            end
        end

        table.insert(choices, { text = itemstr..' of '..matstr, obj = cons })
    end

    dlg.showListPrompt(
        'Job Outputs',
        'Select one of the possible outputs:',
        COLOR_WHITE,
        choices,
        function(idx,item)
            self:saveConstraint(item.obj)
        end
    )
end

function JobConstraints:onDeleteConstraint()
    local cons = self:getCurConstraint()
    dlg.showYesNoPrompt(
        'Delete Constraint',
        'Really delete the current constraint?',
        COLOR_YELLOW,
        function()
            workflow.deleteConstraint(cons.token)
            self:initListChoices()
        end
    )
end

function JobConstraints:onInput(keys)
    if self:propagateMoveKeys(keys) then
        if df.global.world.selected_building ~= self.building then
            self:dismiss()
        end
    else
        JobConstraints.super.onInput(self, keys)
    end
end

if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then
    qerror("This script requires a workshop job selected in the 'q' mode")
end

local job = dfhack.gui.getSelectedJob()

check_enabled(function()
    check_repeat(job, function()
        local clist = workflow.listConstraints(job)
        if not clist then
            dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED)
            return
        end
        JobConstraints{ job = job, clist = clist }:show()
    end)
end)