local al = advtrains.lines
local F = core.formspec_escape
local ch_core = al.ch_core
--[[
Jednoduchá funkce, která vyhodnotí condition jako podmínku
a podle výsledku vrátí buď true_result, nebo false_result.
]]
local function ifthenelse(condition, true_result, false_result)
	if condition then
		return true_result
	else
		return false_result
	end
end
local rwt = assert(advtrains.lines.rwt)
local def
local function CF(s)
    if s ~= nil then return F(s) else return "" end
end
local has_signs_api = core.get_modpath("signs_api")
local has_unifieddyes = core.get_modpath("unifieddyes")
local rozhlas_node_name = "advtrains_line_automation:stanicni_rozhlas"

local RMODE_DEP = 1
local RMODE_ARR = 2
local RMODE_BOTH = 3

local PAGE_SETUP_1 = 1
local PAGE_SETUP_2 = 2
local PAGE_OWNERSHIP = 3

local hl_texty = {
    {
        id = "vcpp",
        sample = "Vážení cestující, prosíme pozor!",
        default = "",
    }, {
        id = "tvl",
        sample = "{TYPVLAKU}",
    }, {
        id = "vlk",
        sample = "Vlak",
    }, {
        id = "lnky",
        sample = "linky {LINKA}",
    }, {
        id = "jmvlku",
        sample = "{JMVLAKU}",
    }, {
        id = "zesm",
        sample = "ze směru {VYCHOZI}",
    }, {
        id = "prijnk",
        sample = "přijíždí na kolej {KOLEJ}.",
    }, {
        id = "prisnk",
        sample = "bude přistaven na kolej {KOLEJ}.",
    }, {
        id = "zpoz",
        sample = "Vlak má {ZPOZDENI} sekund zpoždění.",
        default = "",
    }, {
        id = "zpozz",
        sample = "Vlak má -{ZPOZDENI} sekund zpoždění.",
        default = "",
    }, {
        id = "vkonc",
        sample = "Vlak zde jízdu končí.",
    }, {
        id = "pokrnc",
        sample = "Vlak dále pokračuje směr {NASL} a {CIL}",
    }, {
        id = "pokrc",
        sample = "Vlak pokračuje směr {CIL}",
    }, {
        id = "ozasek",
        sample = ", odjezd za {ODJZA} sekund.",
    }, {
        id = "dek",
        sample = "Děkujeme, že používáte naše služby.",
        default = "",
    }
}
local hl_texty_id_to_idx = function()
    local result = {}
    for i, def in ipairs(hl_texty) do
        local id = assert(def.id)
        if result[id] ~= nil then
            error("Duplicity: id = "..id.."!")
        end
        def.fs_sample = F(def.sample)
        def.fs_default = F(def.default or def.sample)
        result[id] = i
    end
    return result
end
hl_texty_id_to_idx = hl_texty_id_to_idx()

local punch_context = {--[[
    [player_name] = {
        stn = string,
        epos = string,
        i = int,
    }
]]}

local function dist2(a, b)
    local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
    return x * x + y * y + z * z
end

local function goa(t, k) -- goa = get or add
    local result = t[k]
    if result == nil then
        result = {}
        t[k] = result
    end
    return result
end

--[[
    Vrací:
    - success = bool
    - min = int or nil (pro success == false vždy nil)
    - max = int or nil (pro success == false vždy nil)
    - align = "left" or "right" or "center"
]]
local function lengths_from_string(s)
    if s == "" or s == "-" then
        return true, nil, nil
    end
    local l = #s
    s = s:gsub("^ +", "")
    local left = #s < l
    l = #s
    s = s:gsub(" +$", "")
    local right = #s < l
    local align
    if not left then
        align = "left" -- mezery vpravo, nebo žádné mezery
    elseif right then
        align = "center" -- mezery na obou stranách
    else
        align = "right" -- mezery jen vlevo
    end
    local n = s:match("^%d+$")
    if n ~= nil then
        n = assert(tonumber(n))
        if n > 256 then
            n = 256
        end
        return true, n, n, align
    end
    local min, max = s:match("^(%d*)-(%d*)$")
    if min == nil then
        return false, nil, nil, nil -- bad format
    end
    min, max = tonumber(min), tonumber(max)
    if min ~= nil and min > 256 then min = 256 end
    if max ~= nil and max > 256 then max = 256 end
    if min ~= nil and max ~= nil and min > max then
        min = max
    end
    return true, min, max, align
end

--[[ (obsolete)
local function lengths_to_string(min, max)
    if min == nil then
        if max ~= nil then
            return "-"..max
        else
            return ""
        end
    elseif max == nil then
        return min.."-"
    elseif min == max then
        return tostring(min)
    else
        return min.."-"..max
    end
end
]]

local alphanum_chars_set = {
    ["a"] = true, ["A"] = true, ["á"] = true, ["Á"] = true, ["ä"] = true, ["Ä"] = true, ["b"] = true, ["B"] = true, ["č"] = true,
    ["Č"] = true, ["d"] = true, ["D"] = true, ["ď"] = true, ["Ď"] = true, ["e"] = true, ["E"] = true, ["é"] = true, ["É"] = true,
    ["ě"] = true, ["Ě"] = true, ["f"] = true, ["F"] = true, ["g"] = true, ["G"] = true, ["h"] = true, ["H"] = true, ["i"] = true,
    ["I"] = true, ["í"] = true, ["Í"] = true, ["j"] = true, ["J"] = true, ["k"] = true, ["K"] = true, ["l"] = true, ["L"] = true,
    ["ĺ"] = true, ["Ĺ"] = true, ["ľ"] = true, ["Ľ"] = true, ["m"] = true, ["M"] = true, ["n"] = true, ["N"] = true, ["ň"] = true,
    ["Ň"] = true, ["o"] = true, ["O"] = true, ["ó"] = true, ["Ó"] = true, ["ô"] = true, ["Ô"] = true, ["p"] = true, ["P"] = true,
    ["q"] = true, ["Q"] = true, ["r"] = true, ["R"] = true, ["ŕ"] = true, ["Ŕ"] = true, ["ř"] = true, ["Ř"] = true, ["s"] = true,
    ["S"] = true, ["š"] = true, ["Š"] = true, ["t"] = true, ["T"] = true, ["ť"] = true, ["Ť"] = true, ["u"] = true, ["U"] = true,
    ["ú"] = true, ["Ú"] = true, ["ů"] = true, ["Ů"] = true, ["v"] = true, ["V"] = true, ["w"] = true, ["W"] = true, ["x"] = true,
    ["X"] = true, ["y"] = true, ["Y"] = true, ["ý"] = true, ["Ý"] = true, ["z"] = true, ["Z"] = true, ["ž"] = true, ["Ž"] = true,
    ["0"] = true, ["1"] = true, ["2"] = true, ["3"] = true, ["4"] = true, ["5"] = true, ["6"] = true, ["7"] = true, ["8"] = true,
    ["9"] = true,
}

local function dosadit(format, data, defaults)
    assert(type(format) == "string")
    assert(type(data) == "table")
    if defaults == nil then
        defaults = {}
    end
    local result = {}
    local i = 1
    local e = 0
    local b = format:find("{", e + 1, true)
    while b ~= nil do
        if e < b - 1 then
            table.insert(result, format:sub(e + 1, b - 1))
        end
        e = format:find("}", b + 1, true)
        if e == nil then
            core.log("warning", "[advtrains_line_automation] dosadit(): invalid format: <"..format..">")
            table.insert(result, format:sub(b, -1))
            break
        end
        local tag = format:sub(b + 1, e - 1)
        local tag_name = tag
        local tagfmt, tagalt = ""
        local b2 = tag:find("[:|]")
        if b2 == nil then
            tag_name, tagfmt = tag, ""
        else
            tag_name = tag:sub(1, b2 - 1)
            repeat
                local e2 = tag:find("[:|]", b2 + 1) or (#tag + 1)
                local c = tag:sub(b2, b2)
                if c == ":" then
                    tagfmt = tag:sub(b2 + 1, e2 - 1)
                elseif c == "|" then
                    tagalt = tag:sub(b2 + 1, e2 - 1)
                end
                b2 = e2
            until b2 >= #tag
        end
        local min, max, align
        if tagfmt ~= "" then
            local success
            success, min, max, align = lengths_from_string(tagfmt)
        end
        tag = tag_name
        if tag:len() < 4 and ch_core.utf8_length(tag) == 1 and alphanum_chars_set[tag] == nil then
            -- speciální případ: zopakovat znak alespoň min-krát
            if min ~= nil then
                table.insert(result, string.rep(tag, min))
            else
                table.insert(result, tag)
            end
        else
            local value = data[tag] or tagalt or defaults[tag] or "ERR"
            local len = ch_core.utf8_length(value)
            if min ~= nil and len < min then
                -- řetězec je kratší než minimum => prodloužit
                local missing = min - len
                if align == "left" then
                    table.insert(result, value)
                    table.insert(result, string.rep(" ", missing))
                elseif align == "right" then
                    table.insert(result, string.rep(" ", missing))
                    table.insert(result, value)
                else
                    table.insert(result, string.rep(" ", math.floor(missing / 2)))
                    table.insert(result, value)
                    table.insert(result, string.rep(" ", missing - math.floor(missing / 2)))
                end
            elseif max ~= nil and len > max then
                -- řetězec je delší než maximum => oříznout
                if align == "left" then
                    local pos = assert(ch_core.utf8_seek(value, 1, max))
                    table.insert(result, value:sub(1, pos - 1))
                elseif align == "right" then
                    local pos = assert(ch_core.utf8_seek(value, 1, len - max))
                    table.insert(result, value:sub(pos, -1))
                else
                    local overflow = len - max
                    local half = math.floor(overflow / 2)
                    local pos1 = assert(ch_core.utf8_seek(value, 1, half))
                    local pos2 = (ch_core.utf8_seek(value, pos1, len - overflow) or 0) - 1
                    table.insert(result, value:sub(pos1, pos2))
                end
            else
                -- řetězec odpovídá
                table.insert(result, value)
            end
        end
        b = format:find("{", e + 1, true)
    end
    table.insert(result, format:sub(e + 1, -1))
    return table.concat(result)
end

-- Vrátí neuspořádaný seznam čísel řádků, které musejí být zformátovány pro daný formátovací řetězec.
local function ktere_radky(s, empty_as_string)
    local set = {}
    local list = {}
    for match in s:gmatch("{[1-9][:|}]") do
        local n = tonumber(match:sub(2,2))
        if n ~= nil and not set[n] then
            set[n] = true
            table.insert(list, n)
        end
    end
    if list[1] == nil and empty_as_string then
        return ""
    else
        return list
    end
end

local function sestavit_hlaseni(settings, settings_override, data, defaults)
    local parts = {}
    local function a(s)
        table.insert(parts, s)
    end
    local function t(id)
        if id == nil then
            return ""
        end
        local key = "tx_"..id
        local result = settings_override[key] or settings[key]
        if result == nil then
            local def = assert(hl_texty[hl_texty_id_to_idx[id]])
            result = assert(def.default or def.sample)
        end
        return result
    end
    assert(settings)
    if settings_override == nil then
        settings_override = {}
    end
    assert(data)
    a(t("vcpp"))
    a("{SEP}")
    a(t(ifthenelse(data.TYPVLAKU ~= nil, "tvl", "vlk")))
    a("{SEP}")
    if data.LINKA ~= nil then
        a(t("lnky"))
        a("{SEP}")
    end
    if data.VYCHOZI ~= nil then
        a(t("zesm"))
        a("{SEP}")
        a(t("prijnk"))
    else
        a(t("prisnk"))
    end
    a("{SEP}")
    if data.typ_zpozdeni == "+" then
        a(t("zpoz"))
        a("{SEP}")
    elseif data.typ_zpozdeni == "-" then
        a(t("zpozz"))
        a("{SEP}")
    end
    if not data.CIL then
        a(t("vkonc"))
    elseif data.NASL then
        a(t("pokrnc"))
    else
        a(t("pokrc"))
    end
    if data.ODJZA then
        a(t("ozasek"))
    else
        a(".")
    end
    a("{SEP}")
    a(t("dek"))
    data = table.copy(data)
    data.SEP = "{SEP}"
    local s = table.concat(parts)
    s = assert(dosadit(s, data, defaults))
    s = s:gsub(" *{SEP} *", "{SEP}")
    while true do
        t = s:gsub("{SEP}{SEP}", "{SEP}")
        if t:len() == s:len() then
            break
        end
        s = t
    end
    s = s:gsub("^{SEP}", "")
    s = s:gsub("{SEP}$", "")
    s = s:gsub("{SEP}", " ")
    return s
end

local function get_or_add_anns(stn)
    local station = advtrains.lines.stations[stn]
    if station == nil then
        return nil -- dopravna neexistuje!
    end
    local anns = station.anns
    if anns == nil then
        anns = {}
        station.anns = anns
    end
    return anns
end

local cedule_default_settings = {
    empty = "{-:5}",
    fs = "",
    pos = vector.zero(),
    row = "{LINKA: 3} za {ODJZA: 2-3} s",
    text = "{ZDE} - odjezdy\n{1}\n{2}",
    text_rtf = {1, 2},
}

local function init_ann_data(stn, epos)
    local anns = get_or_add_anns(stn)
    if anns == nil then
        return
    end
    local result = {
        cedule = {
            table.copy(cedule_default_settings), table.copy(cedule_default_settings),
            table.copy(cedule_default_settings), table.copy(cedule_default_settings)},
        chat_dosah = 50,
        fmt_delay = "{}",
        fmt_negdelay = "-{}",
        fmt_nodelay = "",
        fn_firstupper = false,
        fs_koleje = "",
        koleje = "",
        owner = "",
        version = 3,
    }
    anns[epos] = result
    return result
end

local function get_ann_data(stn, epos, make_copy)
    local anns = get_or_add_anns(stn)
    if anns == nil then
        core.log("error", "get_ann_data() called on non-existent stn '"..tostring(stn).."'!")
        return nil
    end
    local ann = assert(anns[epos] or init_ann_data(stn, epos))
    if make_copy then
        ann = table.copy(ann)
        local cedule = {}
        for i, v in ipairs(ann.cedule) do
            cedule[i] = table.copy(v)
        end
        ann.cedule = cedule
    end
    return ann
end

local function set_ann_data(stn, epos, data)
    local anns = get_or_add_anns(stn)
    if anns == nil then
        return false
    end
    local ann = anns[epos]
    if ann ~= nil then
        for k, v in pairs(data) do
            ann[k] = v
        end
    else
        anns[epos] = table.copy(data)
    end
    return true
end

local function attach_sign(stn, epos, i, sign_pos)
    -- zkontrolovat rozhlas:
    local rozhl_pos = advtrains.decode_pos(epos)
    core.load_area(rozhl_pos)
    local rozhl_node = core.get_node(rozhl_pos)
    if rozhl_node.name ~= rozhlas_node_name then
        return false, "Staniční rozhlas se nenachází na očekávané pozici."
    end
    local rozhl_meta = core.get_meta(rozhl_pos)
    if rozhl_meta:get_string("stn") ~= stn then
        return false, "Staniční rozhlas přísluší k jiné dopravně." -- vnitřní chyba!
    end
    local data = get_ann_data(stn, epos, false)
    if data == nil then
        return false, "Data staničního rozhlasu nebyla nalezena."
    end

    -- zkontrolovat ceduli:
    core.load_area(sign_pos)
    local sign_node = core.get_node_or_nil(sign_pos)
    if sign_node == nil or core.get_item_group(sign_node.name, "display_api") == 0 then
        return false, "Toto není podporovaná cedule."
    end

    -- zkontrolovat vzdálenost:
    if vector.distance(rozhl_pos, sign_pos) > 1024 then
        return false, "Cedule je příliš daleko. Vzdálenost může být max. 1024 metrů."
    end

    -- zkontrolovat zadání:
    local cedule = data.cedule[i]
    if cedule == nil then
        return false, "Chybný index."
    end

    -- připojit:
    local s = string.format("%d,%d,%d", sign_pos.x, sign_pos.y, sign_pos.z)
    local fs = F(s)
    cedule.fs = fs
    cedule.pos = sign_pos
    return true, "Cedule úspěšně připojena ke staničnímu rozhlasu."
end

local function detach_sign(stn, epos, i)
    -- zkontrolovat rozhlas:
    local rozhl_pos = advtrains.decode_pos(epos)
    core.load_area(rozhl_pos)
    local rozhl_node = core.get_node(rozhl_pos)
    if rozhl_node.name ~= rozhlas_node_name then
        return false, "Staniční rozhlas se nenachází na očekávané pozici."
    end
    local rozhl_meta = core.get_meta(rozhl_pos)
    if rozhl_meta:get_string("stn") ~= stn then
        return false, "Staniční rozhlas přísluší k jiné dopravně." -- vnitřní chyba!
    end
    local data = get_ann_data(stn, epos, false)
    if data == nil then
        return false, "Data staničního rozhlasu nebyla nalezena."
    end

    -- zkontrolovat zadání:
    local cedule = data.cedule[i]
    if cedule == nil then
        return false, "Chybný index."
    end

    if cedule.fs == "" then
        return false, "Cedule není připojena."
    end

    -- odpojit:
    cedule.fs = ""
    cedule.pos = vector.zero()
    return true, "Cedule úspěšně odpojena."
end


local function init_formspec_callback(custom_state, player, formname, fields)
    local player_name = player:get_player_name()
    local pos = custom_state.pos
    local node = core.get_node(pos)
    if node.name ~= rozhlas_node_name then
        core.chat_send_player(player_name, "CHYBA: staniční rozhlas nenalezen!")
        return
    end
    if not core.check_player_privs(player, "railway_operator") then
        core.chat_send_player(player_name, "*** K instalaci staničního rozhlasu je nutné právo railway_operator!")
        return
    end
    if fields.dopravna then
        local event = core.explode_textlist_event(fields.dopravna)
        local new_index = tonumber(event.index)
        if new_index ~= nil and custom_state.list[new_index] ~= nil then
            custom_state.selection_index = new_index
        end
        if event.type == "DCL" then
            fields.zvolit_dopravnu = "true"
        end
    end
    if fields.zvolit_dopravnu and custom_state.selection_index  ~= nil then
        local stn = assert(custom_state.list[custom_state.selection_index].stn)
        local stdata = advtrains.lines.stations[stn]
        if stdata ~= nil then
            local meta = core.get_meta(pos)
            meta:set_string("infotext", "staniční rozhlas ("..(stdata.name or "???")..")")
            meta:set_string("owner", player_name)
            meta:set_string("stn", stn)
            init_ann_data(stn, custom_state.epos)
            core.chat_send_player(player_name, "*** Úspěšně nastaveno.")
        end
    end
end

local function get_setup_formspec(custom_state)
    local player_name = assert(custom_state.player_name)
    local page = assert(custom_state.page)
    local stations = assert(custom_state.stations)
    local stn = custom_state.stn
    local data = custom_state.data

    local formspec = {
        "formspec_version[6]"..
        "size[15,16.5]"..
        -- "style_type[textarea;font=mono]"..
        "tabheader[0,0;0.75;tab;Nastavení 1,Nastavení 2",
        ifthenelse(custom_state.is_admin, ",Vlastnictví;", ";"),
        custom_state.page..";false;true]"..
        "button_exit[14,0.25;0.75,0.75;close;X]"..
        "item_image[0.5,0.5;1,1;"..rozhlas_node_name.."]"..
        "label[1.75,1;staniční rozhlas]"..
        "label[9,0.75;vlastník/ice:\n",
        ch_core.prihlasovaci_na_zobrazovaci(custom_state.owner),
        "]",
    }
    local function a(x)
        if type(x) == "table" then
            for _, s in ipairs(x) do
                table.insert(formspec, s)
            end
        else
            table.insert(formspec, x)
        end
    end
    if page == PAGE_SETUP_1 then
        a(  "container[0.5,1.5]"..
            "label[0,0.5;dopravna:]"..
            "dropdown[1.75,0.2;5,0.6;dopravna;")
        for i, station in ipairs(stations) do
            a(ifthenelse(i ~= 1, ",", "")..F(station.stn).." | "..F(station.name))
        end
        a{";"..custom_state.station_index..";true]"..
            "label[7,0.5;omezit jen na koleje:]"..
            "field[10,0.2;4,0.6;koleje;;",
            data.fs_koleje or "",
            "]label[0,1.25;režim:]"..
            "dropdown[1.75,1;3,0.6;rmode;odjezdy,příjezdy,odjezdy i příjezdy;",
            tostring(data.rmode),
            ";true]container_end[]",
            -- ----
            "container[0.5,3.25]"..
            "checkbox[0,0.25;fn_firstupper;první písmeno řádky odjezdu vždy velké;",
            ifthenelse(ifthenelse(custom_state.fn_firstupper ~= nil, custom_state.fn_firstupper, data.fn_firstupper), "true", "false"),
            "]field[0,1;3.25,0.75;fmt_nodelay;bez zpoždění;", F(data.fmt_nodelay or ""), "]"..
            "field[4,1;3.25,0.75;fmt_delay;zpoždění ({} = číslo);", F(data.fmt_delay or "{}"), "]"..
            "field[8,1;3.25,0.75;fmt_negdelay;záp.zpoždění ({} = číslo);", F(data.fmt_negdelay or "-{}"), "]"..
            "tooltip[fmt_nodelay;Text pro značku {ZPOZDENI} v případě\\, že vlak jede bez zpoždění.]"..
            "tooltip[fmt_delay;Formát pro značku {ZPOZDENI} v případě\\, že vlak má (kladné) zpoždění.\n"..
            "Značka {} se nahradí počtem sekund zpoždění.]"..
            "tooltip[fmt_negdelay;Formát pro značku {ZPOZDENI} v případě\\, že vlak má záporné zpoždění.\n"..
            "Značka {} se nahradí absolutní hodnotou počtu sekund záporného zpoždění.]"..
            "container_end[]"..
            "container[0.5,10.5]"..
            "box[0,0.15;14,0.05;#000000FF]"}
            --[[
            -- hlášení do četu (zatím neimplementováno):
        a{  "label[7,0.5;dosah hlášení v četu \\[m\\] (0=vyp):]"..
            "field[11.5,0.25;2,0.5;chat_dosah;;",
            tostring(data.chat_dosah or "50"),
            "]"..
            "tooltip[chat_dosah;Dosah je současně limitován doslechem jednotlivých hráčských postav.]"..
            "label[0,0.5;texty pro hlášení příjezdu/přistavení vlaku v četu:]"..
            "tablecolumns[text;text]"..
            "table[0,0.75;14,2;texty;"}
        for _, def in ipairs(hl_texty) do
            local id = "tx_"..def.id
            a(def.fs_sample)
            a(",")
            a(F(data[id] or def.default or def.sample))
            a(",")
        end
        formspec[#formspec] = ";"..(custom_state.tx_index or "").."]"
        a("label[0.1,3.125;text:]".."field[0.75,2.75;10,0.75;preklad;;")
        if custom_state.tx_index ~= nil then
            local def = assert(hl_texty[custom_state.tx_index])
            local id = "tx_"..def.id
            local s = data[id]
            if s ~= nil then
                a(F(s))
            else
                a(def.fs_default)
            end
        end
        a(  "]"..
            "button[11,2.75;3,0.75;preklad_set;nastavit]"..
            "tooltip[preklad;V levém sloupci tabulky jsou vzorové texty\\, v pravém sloupci jsou texty\\,\n"..
            "které budou na jejich místě použity pro tento staniční rozhlas (ty můžete měnit).]"..
            "textarea[0,3.5;14,1;;;příklad: ")
        local hlaseni = sestavit_hlaseni(data, custom_state, {
            LINKA = "S1",
            VYCHOZI = "Praha hl. n.",
            typ_zpozdeni = "+",
            CIL = "Bratislava hl. st.",
            NASL = "Pardubice hl. n.",
            ODJZA = "60",
            ZPOZDENI = "13",
            JMVLAKU = "Testovací expres",
            KOLEJ = "A",
        })
        a(F(hlaseni))
        a("]")
        ]]
        a("container_end[]"--[[..
            -- ----
            "container[0.5,9.25]"..
            "label[0,0.25;Formáty řádků ({1}, {2} atd.):]"..
            "container_end[]" ]]
        )
        a("button[0.5,15;14,1;btn_save;Uložit]")
    elseif page == PAGE_SETUP_2 then
        a(  "textarea[0.5,2;14,2;;;"..F("Pro nápovědu a použitou syntaxi viz wiki Českého hvozdu (článek „Staniční rozhlas“).").."]"..
            "container[0.5,4]")
        for i = 1, 4 do
            local cedule = data.cedule[i]
            local s = tostring(i)
            a{  "container[0,", string.format("%f", 2.5 * (i - 1)),
                "]label[0,0.3;cedule ", s, ":]"..
                "field[0,0.9;9.75,0.75;fmt_cedule", s, "_row;formát řádky / prázdný řádek:;",
                F(cedule.row),
                "]field[0,1.75;9.75,0.75;fmt_cedule", s, "_empty;;", F(cedule.empty),
                "]textarea[10,0.5;4,2;fmt_cedule", s, ";;"..F(cedule.text),
                "]field[2,0;4.5,0.5;pos_cedule", s, ";;", cedule.fs,
                "]button_exit[10,0;3,0.5;zam_cedule", s,
                ";zaměřit...]button[6.75,0;3,0.5;",
                ifthenelse(cedule.fs ~= "", "odp_cedule"..s..";odpojit", "pri_cedule"..s..";připojit"),
                "]container_end[]",
            }
        end
        a{  "container_end[]"..
            "label[0.5,14.5;", CF(custom_state.message), "]"..
            "button[0.5,15;14,1;btn_save;Uložit]",
        }
    elseif custom_state.is_admin and page == PAGE_OWNERSHIP then
        a{
            "field[0.5,2.5;5,0.75;owner;vlastník/ice:;",
            ch_core.prihlasovaci_na_zobrazovaci(custom_state.owner),
            "]button[5.75,2.5;3,0.75;set_owner;nastavit]"..
            "label[0.5,13;",
            CF(custom_state.message),
            "]button_exit[0.5,15;14,1;close2;zavřít]",
        }
    end
    return table.concat(formspec)
end

local function setup_formspec_callback(custom_state, player, formname, fields)
    assert(player:get_player_name() == custom_state.player_name)
    local node = core.get_node(custom_state.pos)
    if node.name ~= rozhlas_node_name then
        return
    end
    local is_admin = custom_state.is_admin
    local page = custom_state.page
    local stn = custom_state.stn
    local data = custom_state.data

    if page == PAGE_SETUP_1 then
        if
            fields.dopravna and
            fields.dopravna ~= tostring(custom_state.station_index) and
            tonumber(fields.dopravna) ~= nil and
            custom_state.stations[tonumber(fields.dopravna)] ~= nil
        then
            -- přepnout dopravnu
            custom_state.station_index = tonumber(fields.dopravna)
        end
        if fields.rmode then
            local new_rmode = tonumber(fields.rmode)
            if new_rmode ~= nil and new_rmode ~= data.rmode then
                data.rmode = new_rmode
            end
        end

        -- zaškrtávací pole:
        if fields.fn_firstupper then
            data.fn_firstupper = ifthenelse(fields.fn_firstupper == "true", true, false)
        end

        -- koleje:
        if fields.koleje and F(fields.koleje) ~= data.fs_koleje then
            local parts = string.split(fields.koleje, ",", false)
            local list, set = {}, {}
            for _, part in ipairs(parts) do
                if not set[part] then
                    set[part] = true
                    table.insert(list, part)
                end
            end
            if #list == 0 then
                -- všechny koleje
                data.fs_koleje = ""
                data.koleje = ""
            elseif #list == 1 then
                data.fs_koleje = F(list[1])
                data.koleje = list[1]
            else
                table.sort(list, function(a, b) return a < b end)
                data.fs_koleje = F(table.concat(list, ","))
                data.koleje = set
            end
        end
        if fields.fmt_delay then
            data.fmt_delay = fields.fmt_delay
        end
        if fields.fmt_nodelay then
            data.fmt_nodelay = fields.fmt_nodelay
        end
        if fields.fmt_negdelay then
            data.fmt_negdelay = fields.fmt_negdelay
        end

        -- texty
        if fields.texty then
            local event = core.explode_table_event(fields.texty)
            if (event.type == "CHG" or event.type == "DCL") and event.row ~= custom_state.tx_index and hl_texty[event.row] ~= nil then
                custom_state.tx_index = event.row
            end
        end

        if fields.preklad_set and custom_state.tx_index then
            local def = assert(hl_texty[custom_state.tx_index])
            custom_state.data["tx_"..def.id] = assert(fields.preklad)
            return get_setup_formspec(custom_state)
        end
        if fields.chat_dosah and tonumber(fields.chat_dosah) ~= nil then
            local new_dosah = tonumber(fields.chat_dosah)
            if new_dosah ~= nil and new_dosah == math.floor(new_dosah) and new_dosah >= 0 and new_dosah < 1024 then
                custom_state.data.chat_dosah = new_dosah
            end
        end

        -- uložit?
        if fields.btn_save then
            local new_stn = custom_state.stations[custom_state.station_index].stn
            if new_stn ~= stn then
                -- přesunout rozhlas na jinou dopravnu
                local meta = assert(core.get_meta(custom_state.pos))
                local old_anns = get_or_add_anns(stn)
                local new_anns = get_or_add_anns(new_stn)
                if old_anns ~= nil and new_anns ~= nil then
                    local epos = custom_state.epos
                    new_anns[epos] = old_anns[epos]
                    old_anns[epos] = nil
                    meta:set_string("stn", new_stn)
                    stn = new_stn
                    custom_state.stn = new_stn
                end
            end
            set_ann_data(stn, custom_state.epos, data)
            custom_state.data = get_ann_data(stn, custom_state.epos, true)
            -- update infotext:
            local meta = core.get_meta(custom_state.pos)
            local station_name = al.get_station_name(meta:get_string("stn"))
            local koleje = custom_state.data.fs_koleje
            if koleje ~= "" then
                koleje = " ["..koleje:gsub("\\", "").."]"
            end
            meta:set_string("infotext", "staniční rozhlas\n"..station_name..koleje)
        end
    elseif page == PAGE_SETUP_2 then
        -- update fields:
        for i = 1, 4 do
            local cedule = data.cedule[i]
            local s = fields["fmt_cedule"..i]
            if s ~= nil then
                cedule.text = s
                cedule.text_rtf = ktere_radky(s, true)
            end
            s = fields["fmt_cedule"..i.."_row"]
            if s ~= nil then
                cedule.row = s
            end
            s = fields["fmt_cedule"..i.."_empty"]
            if s ~= nil then
                cedule.empty = s
            end
        end
        if fields.btn_save then
            local new_cedule = {}
            for i = 1, 4 do
                new_cedule[i] = table.copy(data.cedule[i])
            end
            get_ann_data(stn, custom_state.epos, false).cedule = new_cedule
            return get_setup_formspec(custom_state)
        else
            for i = 1, 4 do
                if fields["pri_cedule"..i] then
                    -- připojit ceduli:
                    local pos_cedule_str = fields["pos_cedule"..i]
                    local x, y, z = pos_cedule_str:match("^(-?%d+),(-?%d+),(-?%d+)$")
                    if x == nil or y == nil or z == nil then
                        custom_state.message = "Chybný formát pozice: "..pos_cedule_str
                        return get_setup_formspec(custom_state)
                    end
                    local pos_cedule = vector.new(tonumber(x), tonumber(y), tonumber(z))
                    local success, message = attach_sign(stn, custom_state.epos, i, pos_cedule)
                    if success then
                        local current_data = get_ann_data(stn, custom_state.epos, true)
                        if current_data ~= nil then
                            custom_state.data = current_data
                        end
                        custom_state.message = "Cedule úspěšně připojena."
                    else
                        custom_state.message = "CHYBA: "..message
                    end
                    return get_setup_formspec(custom_state)
                end
                if fields["odp_cedule"..i] then
                    local success, message = detach_sign(stn, custom_state.epos, i)
                    if success then
                        local current_data = get_ann_data(stn, custom_state.epos, true)
                        if current_data ~= nil then
                            custom_state.data = current_data
                        end
                        custom_state.message = "Cedule úspěšně odpojena."
                    else
                        custom_state.message = "CHYBA: "..message
                    end
                    return get_setup_formspec(custom_state)
                end
                if fields["zam_cedule"..i] then
                    local player_name = player:get_player_name()
                    local stack = player:get_wielded_item()
                    if stack:is_empty() then
                        player:set_wielded_item("advtrains_line_automation:sign_selector")
                        core.chat_send_player(player_name, "*** Levým klikem vyberte ceduli k připojení...")
                    else
                        player:get_inventory():add_item("main", "advtrains_line_automation:sign_selector")
                        core.chat_send_player(player_name, "*** Levým klikem výběrovým nástrojem vyberte ceduli k připojení...")
                    end
                    punch_context[player_name] = {stn = assert(custom_state.stn), epos = custom_state.epos, i = i}
                    return
                end
            end
        end
    elseif page == PAGE_OWNERSHIP then
        if is_admin and fields.set_owner then
            local new_owner = ch_core.jmeno_na_existujici_prihlasovaci(fields.owner or "")
            if new_owner == nil then
                custom_state.message = "Postava '"..(fields.owner or "").."' neexistuje!"
            else
                local ann_data = assert(get_ann_data(stn, custom_state.epos, false))
                ann_data.owner = new_owner
                core.load_area(custom_state.pos)
                local meta = core.get_meta(custom_state.pos)
                meta:set_string("owner", new_owner)
                custom_state.owner = new_owner
            end
            return get_setup_formspec(custom_state)
        end
    end
    if fields.tab then
        -- přepnout stránku
        local new_tab = tonumber(fields.tab)
        if new_tab ~= nil and new_tab ~= page and (
            new_tab == PAGE_SETUP_1 or new_tab == PAGE_SETUP_2 or (is_admin and new_tab == PAGE_OWNERSHIP)
        ) then
            custom_state.page = new_tab
            custom_state.message = "" -- smazat zprávu
        end
        return get_setup_formspec(custom_state)
    end
    if not fields.quit then
        return get_setup_formspec(custom_state)
    end
end

local function fill(line, prefix, rwtime_now, rwtime_value)
    if rwtime_value == nil then
        return
    end
    local secs = math.ceil((rwtime_value - rwtime_now) / 5) * 5
    if secs < 0 then
        secs = -secs
        line[prefix.."_Z"] = "-"
    end
    local n_s = secs % 60
    local n_m = (secs - n_s) / 60
    local s = tostring(n_s)
    local m = tostring(n_m)
    line[prefix] = tostring(secs)
    line[prefix.."_M"] = m
    line[prefix.."_S"] = s
    if n_m < 10 then
        line[prefix.."_MM"] = "0"..m
    else
        line[prefix.."_MM"] = m
    end
    if n_s < 10 then
        line[prefix.."_SS"] = "0"..s
    else
        line[prefix.."_SS"] = s
    end
end

--[[
    stn -- kód dopravny, jejíž příjezdy/odjezdy se budou zapisovat na cedule
    epos -- kódovaná pozice st. rozhlasu (pro načtení dat)
    signs -- (volitelný) seznam až 4 pozic cedulí, které se budou aktualizovat
    records -- viz níže
    rwtime -- aktuální žel. čas (číselná forma)
    ---
    records je tabulka záznamů z al.predict_train(), doplněných o následující pole:
        start = string or nil, -- název výchozí zastávky, pokud vlak odněkud přijíždí, jinak ""
        destination = string, -- název cílové zastávky, pokud vlak někam pokračuje, jinak ""
        prev_stop = string, -- název předchozí zastávky, pokud vlak odněkud přijíždí a pokud jde o jinou zastávku než 'start', jinak ""
        next_stop = string, -- název násl. zast., pokud vlak pokračuje a pokud jde o jinou zastávku než 'destination', jinak ""
        last_pos = string, -- název poslední známé polohy vlaku, nebo ""
        --
        stn = string, track = string, delay = int,
        stdata = table or nil,
        dep = int or nil, dep_linevar_def = table or nil, dep_index = int or nil,
        arr = int or nil, arr_linevar_def = table or nil, arr_index = int or nil,
]]

local function update_ann(stn, epos, signs, records, rwtime)
    local ann = get_ann_data(stn, epos)
    if ann == nil then
        core.log("error", "update_ann() called for "..stn.."/"..epos..", but ann is nil!")
        return
    end
    local tracks
    local any_line = {
        ZDE = al.get_station_name(stn),
    }
    if ann.fs_koleje ~= "" then
        any_line.KOLEJE = ann.fs_koleje:gsub("\\", "")
        tracks = ann.koleje
        if tracks ~= nil and type(tracks) ~= "table" then
            if tracks == "" then
                tracks = nil
            else
                tracks = {[tracks] = true}
            end
        end
    end
    local lines = {}
    for _, record in ipairs(records) do
        assert(record.start ~= nil)
        assert(record.destination ~= nil)
        assert(record.prev_stop ~= nil)
        assert(record.next_stop ~= nil)
        assert(record.last_pos ~= nil)
        if
            (tracks == nil or tracks[record.track]) and (
                (ann.rmode == RMODE_ARR and record.arr ~= nil) or
                (ann.rmode == RMODE_DEP and record.dep ~= nil) or
                (ann.rmode == RMODE_BOTH and (record.arr ~= nil or record.dep ~= nil))
            )
        then
            local linevar_def, index
            if record.dep ~= nil then
                linevar_def, index = record.dep_linevar_def, record.dep_index
            else
                linevar_def, index = record.arr_linevar_def, record.arr_index
            end
            local stops = linevar_def.stops
            local line = setmetatable({}, {__index = any_line})
            line.LINKA = advtrains.lines.linevar_decompose(linevar_def.name)
            assert(line.LINKA)
            if record.start ~= "" then
                line.VYCHOZI = record.start
            end
            if record.destination ~= "" then
                line.CIL = record.destination
            end
            if record.track ~= "" then
                line.KOLEJ = record.track
            end
            fill(line, "PRIJZA", rwtime, record.arr)
            fill(line, "ODJZA", rwtime, record.dep)
            local abs_delay = math.abs(record.delay)
            if abs_delay < 5 then
                line.ZPOZDENI = ann.fmt_nodelay or ""
            else
                line.ZPOZDENI = dosadit(
                    ifthenelse(record.delay > 0, ann.fmt_delay or "{}", ann.fmt_negdelay or "-{}"),
                    {[""] = tostring(5 * math.ceil(abs_delay / 5))}
                )
            end
            -- PREDCH
            if record.prev_stop ~= "" then
                line.PREDCH = record.prev_stop
            end
            -- NASL
            if record.next_stop ~= "" then
                line.NASL = record.next_stop
            end
            -- POLOHA
            if record.last_pos ~= "" then
                line.POLOHA = record.last_pos
            end
            -- JMVLAKU
            if linevar_def.train_name ~= nil then
                line.JMVLAKU = linevar_def.train_name
            end
            -- TYP
            line.TYP = "Os"
            -- TYPVLAKU
            line.TYPVLAKU = "osobní vlak"
            table.insert(lines, line)
        end
    end
    if signs ~= nil then
        local empty_table = {}
        for i = 1, 4 do
            local cedule = ann.cedule[i]
            local sign_pos = signs[i]
            if sign_pos ~= nil and core.compare_block_status(sign_pos, "active") then
                local formatted_lines = setmetatable({}, {__index = any_line})
                if type(cedule.text_rtf) == "table" then
                    for _, i_row in ipairs(cedule.text_rtf) do
                        if lines[i_row] == nil then
                            -- prázdný řádek
                            formatted_lines[tostring(i_row)] = dosadit(cedule.empty, empty_table)
                        else
                            -- řádek odjezdu
                            local s = dosadit(cedule.row, lines[i_row])
                            if s ~= "" and ann.fn_firstupper then
                                local l = ch_core.utf8_seek(s, 1, 1)
                                if l == nil then
                                    s = ch_core.na_velka_pismena(s)
                                else
                                    local c = s:sub(1, l - 1)
                                    local uc = ch_core.na_velka_pismena(c)
                                    if uc ~= c then
                                        s = uc..s:sub(l, -1)
                                    end
                                end
                            end
                            formatted_lines[tostring(i_row)] = s
                        end
                    end
                end
                local s = dosadit(cedule.text, formatted_lines)
                if has_signs_api then
                    signs_api.set_display_text(sign_pos, s)
                end
            end
        end
    end
end

local globalstep_time = -5
local first_run = true

local function first_globalstep()
    for stn, stdata in pairs(advtrains.lines.stations) do
        local anns = stdata.anns
        if stdata.anns ~= nil then
            for rozh_epos, ann in pairs(stdata.anns) do
                if ann.version < 2 then
                    -- upgrade version 1 to version 2:
                    ann.rmode = RMODE_DEP
                    ann.version = 2
                end
                if ann.version < 3 then
                    local pos = advtrains.decode_pos(rozh_epos)
                    core.load_area(pos)
                    local meta = core.get_meta(pos)
                    ann.owner = meta:get_string("owner")
                    core.log("action", "strozhlas at position "..core.pos_to_string(pos).." upgraded to metadata version 3 (owner "..ann.owner..")")
                    ann.version = 3
                end
            end
        end
    end
end

local function get_start_by_linevar_def(cache, linevar_def)
    local result = cache[linevar_def.name]
    if result == nil then
        result = al.get_line_description(linevar_def, {first_stop = true, last_stop = false, last_stop_prefix = ""})
        if result == "???" then
            result = ""
        end
        cache[linevar_def.name] = result
    end
    return result
end

local function get_destination_by_linevar_def(cache, linevar_def)
    local result = cache[linevar_def.name]
    if result == nil then
        result = al.get_line_description(linevar_def, {first_stop = false, last_stop = true, last_stop_prefix = ""})
        if result == "???" then
            result = ""
        end
        cache[linevar_def.name] = result
    end
    return result
end

local function get_name_by_stn(cache, stn, alt)
    local result = cache[stn]
    if result == nil then
        result = al.get_station_name(stn)
        if result == "???" then
            result = ""
        end
        cache[stn] = result
    end
    if result == "" then
        return alt
    else
        return result
    end
end

local function globalstep(dtime)
    globalstep_time = globalstep_time + dtime
    if globalstep_time < 0 then
        return
    end
    globalstep_time = globalstep_time - 5

    if first_run then
        first_run = false
        return first_globalstep()
    end

    local rwtime = rwt.to_secs(rwt.get_time())
    local rwtime_limit = rwtime + 3600
    -- Shromáždit rozhlasy:
    local subscriptions = {--[[
        [stn] = {{
            rozh_pos = vector,
            rozh_epos = string,
            rozh_def = table,
            signs = {[1..4] = vector or nil} or nil,
        }...}
    ]]}
    local signs = {}
    for stn, stdata in pairs(advtrains.lines.stations) do
        local anns = stdata.anns
        if stdata.anns ~= nil then
            for rozh_epos, ann in pairs(stdata.anns) do
                local rozh_pos = advtrains.decode_pos(rozh_epos)
                local is_admin = core.check_player_privs(assert(ann.owner), "protection_bypass")
                local signs_count = 0
                for i = 1, 4 do
                    local cedule = assert(ann.cedule[i])
                    local s = cedule.fs
                    if s ~= nil and s ~= "" then
                        local sign_pos = assert(cedule.pos)
                        if core.compare_block_status(sign_pos, "active") then
                            signs[i] = sign_pos
                            signs_count = signs_count + 1
                            if not is_admin then
                                local sign_meta = core.get_meta(sign_pos)
                                local sign_owner = sign_meta:get_string("owner")
                                if sign_meta:get_int("access_level") ~= 1 and sign_owner ~= "" and ann.owner ~= sign_owner then
                                    -- unauthorized access to the sign
                                    core.log("warning", "Station announcement "..core.pos_to_string(rozh_pos).." owned by '"..
                                        ann.owner.."' cannot write to sign at "..core.pos_to_string(sign_pos).." owned by '"..
                                        sign_owner.."'! Access denied.")
                                    signs[i] = nil
                                    signs_count = signs_count - 1
                                end
                            end
                        end
                    end
                end
                if signs_count > 0 then
                    -- nějaké cedule jsou aktivní
                    table.insert(goa(subscriptions, stn), {rozh_pos = rozh_pos, rozh_epos = rozh_epos, rozh_def = ann, signs = signs})
                    signs = {}
                elseif core.compare_block_status(rozh_pos, "active") then
                    -- cedule nejsou aktivní, ale rozhlas ano
                    table.insert(goa(subscriptions, stn), {rozh_pos = rozh_pos, rozh_epos = rozh_epos, rozh_def = ann})
                end
            end
        end
    end

    -- Shromáždit vlaky:
    local by_stn = {}
    for stn, _ in pairs(subscriptions) do
        by_stn[stn] = {}
    end
    local start_by_linevar = {}
    local destination_by_linevar = {}
    local name_by_stn = {}

    for _, train in pairs(advtrains.trains) do
        local ls, linevar_def = al.get_line_status(train)
        if linevar_def ~= nil then
            local prediction = al.predict_train(ls, linevar_def, rwtime, true)
            local last_pos = al.get_last_pos_station_name(ls) or ""
            for i, record in ipairs(prediction) do
                local records = by_stn[record.stn]
                if
                    records ~= nil and
                    (record.dep ~= nil or record.arr ~= nil) and
                    (record.dep == nil or record.dep < rwtime_limit) and
                    (record.arr == nil or record.arr < rwtime_limit)
                then
                    -- nutno doplnit: start, destination, prev_stop, next_stop, last_pos
                    record.last_pos = last_pos -- název poslední dopravny, kde byl vlak spatřen
                    if record.final and record.arr ~= nil and record.dep ~= nil and record.arr_linevar_def.name ~= record.dep_linevar_def.name then
                        -- změna linky => rozdělit na příjezd a odjezd
                        local record2 = table.copy(record)
                        record2.dep = nil
                        record2.dep_linevar_def = nil
                        record2.dep_index = nil
                        record2.start = get_start_by_linevar_def(start_by_linevar, record.arr_linevar_def)
                        local other_index, other_data = al.get_prev_stop(record.arr_linevar_def, record.arr_index, false)
                        if other_index ~= nil then
                            record2.prev_stop = get_name_by_stn(name_by_stn, other_data.stn, "")
                        end
                        record2.next_stop = ""
                        record2.destination = get_destination_by_linevar_def(destination_by_linevar, record.arr_linevar_def)
                        table.insert(records, record2)
                        record.arr = nil
                        record.arr_linevar_def = nil
                        record.arr_index = nil
                        record.start = get_start_by_linevar_def(start_by_linevar, record.dep_linevar_def)
                        record.prev_stop = ""
                        other_index, other_data = al.get_next_stop(record.dep_linevar_def, record.dep_index, false)
                        if other_index ~= nil then
                            record.next_stop = get_name_by_stn(name_by_stn, other_data.stn, "")
                        end
                        record.destination = get_destination_by_linevar_def(destination_by_linevar, record.dep_linevar_def)
                        table.insert(records, record)
                    elseif record.dep ~= nil then
                        -- odjezd nebo příjezd/odjezd (ale na stejné lince!)
                        local linevar_def = record.dep_linevar_def
                        local index = record.dep_index
                        record.start = get_start_by_linevar_def(start_by_linevar, linevar_def)
                        record.destination = get_destination_by_linevar_def(destination_by_linevar, linevar_def)
                        record.prev_stop = ""
                        record.next_stop = ""
                        local other_index, other_data
                        if record.arr ~= nil then
                            other_index, other_data = al.get_prev_stop(linevar_def, index, false)
                            if other_index ~= nil then
                                record.prev_stop = get_name_by_stn(name_by_stn, other_data.stn, "")
                            end
                        end
                        other_index, other_data = al.get_next_stop(linevar_def, index, false)
                        if other_index ~= nil then
                            record.next_stop = get_name_by_stn(name_by_stn, other_data.stn, "")
                        end
                        record.destination = get_destination_by_linevar_def(destination_by_linevar, linevar_def)
                        table.insert(records, record)
                    elseif record.arr ~= nil then
                        -- jen příjezd
                        local linevar_def = record.arr_linevar_def
                        local index = record.arr_index
                        record.start = get_start_by_linevar_def(start_by_linevar, linevar_def)
                        local other_index, other_data = al.get_prev_stop(linevar_def, index, false)
                        if other_index ~= nil then
                            record.prev_stop = get_name_by_stn(name_by_stn, other_data.stn, "")
                        else
                            record.prev_stop = ""
                        end
                        record.next_stop = ""
                        record.destination = get_destination_by_linevar_def(destination_by_linevar, linevar_def)
                        table.insert(records, record)
                    end
                end
            end
        end
    end

    -- Aktualizovat rozhlasy:
    for stn, records in pairs(by_stn) do
        local deps = records
        local arrs = table.copy(records)
        table.sort(deps, function(a, b)
            return assert(a.dep or a.arr) < assert(b.dep or b.arr)
        end)
        table.sort(arrs, function(a, b)
            return (a.arr or 1.0e+100) < (b.arr or 1.0e+100)
        end)
        for i = #arrs, 1, -1 do
            if arrs[i].arr == nil then
                arrs[i] = nil -- ponechat jen příjezdy v rámci nejbližší hodiny
            end
        end
        for _, ann in ipairs(subscriptions[stn]) do
            if ann.rmode ~= RMODE_ARR then
                update_ann(assert(stn), assert(ann.rozh_epos), ann.signs, deps, rwtime)
            else
                update_ann(assert(stn), assert(ann.rozh_epos), ann.signs, arrs, rwtime)
            end
        end
    end
end

core.register_globalstep(globalstep)

local function can_dig(pos, player)
    if player ~= nil and core.is_player(player) then
        local owner = core.get_meta(pos):get_string("owner")
        local player_name = player:get_player_name()
        return owner == "" or owner == player_name or core.check_player_privs(player_name, "protection_bypass")
    end
    return false
end

local function on_construct(pos)
    local meta = core.get_meta(pos)
    meta:set_string("infotext", "staniční rozhlas")
end

local function on_destruct(pos)
    local meta = core.get_meta(pos)
    local stn = meta:get_string("stn")
    if stn ~= "" then
        local epos = advtrains.encode_pos(pos)
        local stdata = advtrains.lines.stations[stn]
        if stdata ~= nil then
            (stdata.anns or {})[epos] = nil -- odstranit data staničního rozhlasu
        end
    end
end

local function on_dig(pos, node, digger)
    -- TODO?
    -- return core.node_dig(pos, node, digger)
    if not can_dig(pos, digger) then
        return false
    end
    if has_unifieddyes then
        return unifieddyes.on_dig(pos, node, digger)
    else
        return core.node_dig(pos, node, digger)
    end
end

local function on_rightclick(pos, node, clicker, itemstack, pointed_thing)
    if clicker == nil or not clicker:is_player() then
        return
    end
    local player_name = assert(clicker:get_player_name())
    local meta = core.get_meta(pos)
    local owner = meta:get_string("owner")
    local stn = meta:get_string("stn")
    local epos = advtrains.encode_pos(pos)
    local stdata = advtrains.lines.stations[stn]
    if owner == "" or stn == "" or stdata == nil or stdata.anns == nil or stdata.anns[epos] == nil then
        if not core.check_player_privs(clicker, "railway_operator") then
            core.chat_send_player(player_name, "*** K instalaci staničního rozhlasu je nutné právo railway_operator!")
            return
        end
        local limit = 256 * 256
        local stations = advtrains.lines.load_stations_for_formspec()
        local list = {}
        for i, station_data in ipairs(stations) do
            local d2 = limit
            for _, stop in ipairs(station_data.tracks) do
                local d2new = dist2(pos, stop.pos)
                if d2new < d2 then
                    d2 = d2new
                end
            end
            if d2 < limit then
                table.insert(list, {
                    stn = assert(station_data.stn),
                    name = assert(station_data.name),
                    distance = math.floor(math.sqrt(d2)),
                })
            end
        end
        table.sort(list, function(a, b) return a.distance < b.distance end)
        local custom_state = {
            pos = pos,
            epos = advtrains.encode_pos(pos),
            list = list,
        }
        local formspec = {
            "formspec_version[6]"..
            "size[15,8]"..
            "button_exit[14,0.25;0.75,0.75;close;X]"..
            "item_image[0.5,0.5;1,1;"..rozhlas_node_name.."]"..
            "label[1.75,1;staniční rozhlas]"..
            "label[0.5,2.25;vyberte dopravnu:]"..
            "button_exit[0.5,6.75;14,1;zvolit_dopravnu;Zvolit]"..
            "textlist[0.5,2.5;14,4;dopravna;",
        }
        if #list > 0 then
            for i, candidate in ipairs(list) do
                if i ~= 1 then
                    table.insert(formspec, ",")
                end
                table.insert(formspec, F(candidate.stn.." | "..candidate.name.." ("..candidate.distance.." m)"))
            end
            custom_state.selection_index = 1
            table.insert(formspec, ";1]")
        else
            table.insert(formspec, ";]")
        end
        ch_core.show_formspec(clicker, "advtrains_line_automation:init_rozhlas", table.concat(formspec), init_formspec_callback, custom_state, {})
        return
    end
    if player_name ~= owner and not core.check_player_privs(player_name, "protection_bypass") then -- train_admin?
        core.chat_send_player(player_name, "Nemáte oprávnění nastavovat tento staniční rozhlas!")
        return
    end
    local stations = advtrains.lines.load_stations_for_formspec()
    local selection_index
    for i, station in ipairs(stations) do
        if station.stn == stn then
            selection_index = i
            break
        end
    end
    if selection_index == nil then
        core.log("error", "Cannot determine selection index for the station '"..stn.."' in the list of stations!")
        core.chat_send_player(player_name, "CHYBA: Vnitřní chyba: dopravna nenalezena ve výpisu!")
        return
    end
    local custom_state = {
        player_name = player_name,
        is_admin = ch_core.get_player_role(player_name) == "admin",
        pos = pos,
        epos = epos,
        owner = owner,
        stn = assert(stn), -- kam byl staniční rozhlas dosud přiřazen
        stations = stations, -- pro dropdown[]
        station_index = selection_index, -- pro dropdown[]
        data = assert(get_ann_data(stn, epos, true)),
        page = PAGE_SETUP_1,
    }
    ch_core.show_formspec(clicker, "advtrains_line_automation:rozhlas", get_setup_formspec(custom_state),
        setup_formspec_callback, custom_state, {})
end

local function selector_on_use(itemstack, user, pointed_thing)
    if pointed_thing.type ~= "node" then
        return
    end
    if user ~= nil then
        local player_name = user:get_player_name()
        local pc = punch_context[player_name]
        if pc ~= nil then
            local success, message = attach_sign(assert(pc.stn), assert(pc.epos), assert(pc.i), assert(pointed_thing.under))
            if not success then
                message = message.." Zaměřování bude zrušeno."
            end
            core.chat_send_player(player_name, message)
        end
    end
    return ItemStack()
end

local box = {
    type = "fixed",
    fixed = {
        {-0.125, 0.125, 0.25, 0.125, 0.5, 0.5},
        {-0.25, 0.05, 0, 0.25, 0.45, 0.25},
    },
}

def = {
    description = "staniční rozhlas",
    tiles = {{name = "default_steel_block.png", backface_culling = true}},
    drawtype = "mesh",
    mesh = "advtrains_tuber.obj",
    paramtype = "light",
    paramtype2 = "4dir",
    selection_box = box,
    collision_box = box,
    groups = {oddly_breakable_by_hand = 3, ud_param2_colorable = 1},
    sounds = default.node_sound_metal_defaults(),
    can_dig = can_dig,
    on_dig = on_dig,
    on_construct = on_construct,
    on_destruct = on_destruct,
    on_rightclick = on_rightclick,
}
if has_unifieddyes then
    def.paramtype2 = "color4dir"
    def.palette = "unifieddyes_palette_color4dir.png"
end
core.register_node(rozhlas_node_name, def)

def = {
    description = "levým klikem zvolte ceduli pro připojení",
    groups = {not_in_creative_inventory = 1, tool = 1},
    inventory_image = "[combine:16x16:7,0=blank.png\\^[noalpha\\^[resize\\:2x16:0,7=blank.png\\^[noalpha\\^[resize\\:16x2",
    wield_image = "blank.png",
    on_use = selector_on_use,
}

core.register_tool("advtrains_line_automation:sign_selector", def)

core.register_craft({
    output = rozhlas_node_name,
    recipe = {
        {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
        {"default:steel_ingot", "mesecons_noteblock:noteblock", "default:steel_ingot"},
        {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
    },
})
