-- Show/change the path used by Guide Cart orders
--[====[

gui/guide-path
==============
Activate in the :guilabel:`Hauling` menu with the cursor over
a :guilabel:`Guide` order.

.. image:: /docs/images/guide-path.png

The script displays the cached path that will be used by the order; the game
computes it when the order is executed for the first time.

]====]
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
local dlg = require 'gui.dialogs'

local tile_attrs = df.tiletype.attrs

GuidePathUI = defclass(GuidePathUI, guidm.MenuOverlay)

GuidePathUI.focus_path = 'guide-path'

GuidePathUI.ATTRS {
    route = DEFAULT_NIL,
    stop = DEFAULT_NIL,
    order = DEFAULT_NIL,
}

function GuidePathUI:init()
    self.saved_mode = df.global.ui.main.mode

    for i=0,#self.route.stops-1 do
        if self.route.stops[i] == self.stop then
            self.stop_idx = i
            break
        end
    end

    if not self.stop_idx then
        error('Could not find stop index')
    end

    self.next_stop = self.route.stops[(self.stop_idx+1)%#self.route.stops]
end

function GuidePathUI:onShow()
    -- with cursor, but without those ugly lines from native hauling mode
    df.global.ui.main.mode = df.ui_sidebar_mode.Stockpiles
end

function GuidePathUI:onDestroy()
    df.global.ui.main.mode = self.saved_mode
end

local function getTileType(cursor)
    local block = dfhack.maps.getTileBlock(cursor)
    if block then
        return block.tiletype[cursor.x%16][cursor.y%16]
    else
        return 0
    end
end

local function isTrackTile(tile)
    return tile_attrs[tile].special == df.tiletype_special.TRACK
end

local function paintMapTile(dc, vp, cursor, pos, ...)
    if not same_xyz(cursor, pos) then
        local stile = vp:tileToScreen(pos)
        if stile.z == 0 then
            dc:map(true):seek(stile.x,stile.y):char(...):map(false)
        end
    end
end

local function get_path_point(gpath,i)
    return xyz2pos(gpath.x[i], gpath.y[i], gpath.z[i])
end

function GuidePathUI:renderPath(cursor)
    local gpath = self.order.guide_path
    local startp = self.stop.pos
    local endp = self.next_stop.pos
    local vp = self:getViewport()
    local dc = gui.Painter.new(self.df_layout.map)
    local visible = gui.blink_visible(500)

    if visible then
        paintMapTile(dc, vp, cursor, endp, '+', COLOR_LIGHTGREEN)
    end

    local ok = nil
    local pcnt = #gpath.x
    if pcnt > 0 then
        ok = true

        for i = 0,pcnt-1 do
            local pt = get_path_point(gpath, i)
            if i == 0 and not same_xyz(startp,pt) then
                ok = false
            end
            if i == pcnt-1 and not same_xyz(endp,pt) then
                ok = false
            end
            local tile = getTileType(pt)
            if not isTrackTile(tile) then
                ok = false
            end
            if visible then
                local char = '+'
                if i < pcnt-1 then
                    local npt = get_path_point(gpath, i+1)
                    if npt.x == pt.x+1 then
                        char = 26
                    elseif npt.x == pt.x-1 then
                        char = 27
                    elseif npt.y == pt.y+1 then
                        char = 25
                    elseif npt.y == pt.y-1 then
                        char = 24
                    end
                end
                local color = COLOR_LIGHTGREEN
                if not ok then color = COLOR_LIGHTRED end
                paintMapTile(dc, vp, cursor, pt, char, color)
            end
        end
    end

    if gui.blink_visible(120) then
        paintMapTile(dc, vp, cursor, startp, 240, COLOR_LIGHTGREEN, COLOR_GREEN)
    end

    return ok
end

function GuidePathUI:onRenderBody(dc)
    dc:clear():seek(1,1):pen(COLOR_WHITE):string("Guide Path")

    dc:seek(2,3)

    local cursor = guidm.getCursorPos()
    local path_ok = self:renderPath(cursor)

    if path_ok == nil then
        dc:string('No saved path', COLOR_DARKGREY)
    elseif path_ok then
        dc:string('Valid path', COLOR_GREEN)
    else
        dc:string('Invalid path', COLOR_RED)
    end

    dc:newline():newline(1)
    dc:key('CUSTOM_Z'):string(": Reset path",COLOR_GREY,nil,path_ok~=nil)
    --dc:key('CUSTOM_P'):string(": Find path",COLOR_GREY,nil,false)
    dc:newline(1)
    dc:key('CUSTOM_C'):string(": Zoom cur, ")
    dc:key('CUSTOM_N'):string(": Zoom next")

    dc:newline():newline(1):string('At cursor:')
    dc:newline():newline(2)

    local tile = getTileType(cursor)
    if isTrackTile(tile) then
        dc:string('Track '..tile_attrs[tile].direction, COLOR_GREEN)
    else
        dc:string('No track', COLOR_DARKGREY)
    end

    dc:newline():newline(1)
    dc:key('LEAVESCREEN'):string(": Back")
end

function GuidePathUI:onInput(keys)
    if keys.CUSTOM_C then
        self:moveCursorTo(copyall(self.stop.pos))
    elseif keys.CUSTOM_N then
        self:moveCursorTo(copyall(self.next_stop.pos))
    elseif keys.CUSTOM_Z then
        local gpath = self.order.guide_path
        gpath.x:resize(0)
        gpath.y:resize(0)
        gpath.z:resize(0)
    elseif keys.LEAVESCREEN then
        self:dismiss()
    elseif self:propagateMoveKeys(keys) then
        return
    end
end

if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/Hauling/DefineStop/Cond/Guide') then
    qerror("This script requires the main dwarfmode view in 'h' mode over a Guide order")
end

local hauling = df.global.ui.hauling
local route = hauling.view_routes[hauling.cursor_top]
local stop = hauling.view_stops[hauling.cursor_top]
local order = hauling.stop_conditions[hauling.cursor_stop]

local list = GuidePathUI{ route = route, stop = stop, order = order }
list:show()
