local def
local F = minetest.formspec_escape
local ch_core = advtrains.lines.ch_core
local S = advtrains.lines.translate
--[[
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
-- Singularis-Merge: Emulate certain ch functionality
local function prihlasovaci_na_zobrazovaci(prihlasovaci)
	return prihlasovaci -- no-op. On CH used to colorize the name to reflect the rank of the player
end

local max_stations = 60

local MODE_NORMAL = 0 -- normální zastávka (výchozí nebo mezilehlá)
local MODE_REQUEST_STOP = 1 -- zastávka na znamení (mezilehlá)
local MODE_HIDDEN = 2 -- skrytá zastávka (výchozí nebo mezilehlá)
local MODE_DISABLED = 3 -- vypnutá zastávka (mezilehlá nebo koncová)
local MODE_FINAL = 4 -- koncová zastávka (linka zde jízdu končí)
local MODE_FINAL_HIDDEN = 5 -- koncová zastávka skrytá
local MODE_FINAL_CONTINUE = 6 -- koncová zastávka (vlak pokračuje jako jiná linka)

local color_red = core.get_color_escape_sequence("#ff0000")
local color_green = core.get_color_escape_sequence("#00ff00")

local cancel_linevar = assert(advtrains.lines.cancel_linevar)
local get_last_passages = assert(advtrains.lines.get_last_passages)
local get_line_description = assert(advtrains.lines.get_line_description)
local linevar_decompose = assert(advtrains.lines.linevar_decompose)
local try_get_linevar_def = assert(advtrains.lines.try_get_linevar_def)

local show_last_passages_formspec -- forward declaration

local function check_rights(pinfo, owner)
    if pinfo.role == "new" or pinfo.role == "none" then
        return false
    end
    if owner == nil or pinfo.role == "admin" or pinfo.player_name == owner then
        return true
    end
    return false
end

local function get_stn_from_linevar(linevar)
    local line, stn = linevar_decompose(linevar)
    return ifthenelse(line ~= nil, stn, nil)
end

local function add_linevar(stn, linevar_def)
    local station = advtrains.lines.stations[stn]
    if station == nil then
        return false, "Chybná stanice!"
    end
    local linevars = station.linevars
    if linevars == nil then
        linevars = {}
        station.linevars = linevars
    end
    if linevars[linevar_def.name] ~= nil then
        return false, "Nemohu přidat, varianta linky '"..linevar_def.name.."' již existuje."
    end
    linevars[linevar_def.name] = linevar_def
    return true, nil
end

local function same_stops(stops1, stops2)
    assert(stops1)
    assert(stops2)
    if #stops1 ~= #stops2 then
        return false
    end
    for i, stop in ipairs(stops1) do
        if stop.stn ~= stops2[i].stn then
            return false
        end
    end
    return true
end

local function replace_linevar(stn, linevar_def)
    local station = advtrains.lines.stations[stn]
    if station == nil or station.linevars == nil then
        return false, S("No such station")
    end
    local linevar = assert(linevar_def.name)
    local linevars = station.linevars
    local old_linevar_def = linevars[linevar]
    if old_linevar_def == nil then
        return false, S("Cannot replace, because lineval @1 does not yet exist", linevar)
    end
    linevars[linevar] = linevar_def
    local restart_count = 0
    if not same_stops(old_linevar_def.stops, linevar_def.stops) then
        -- změnily se zastávky, musíme restartovat vlaky:
        for train_id, train in pairs(advtrains.trains) do
            local ls = train.line_status
            if ls ~= nil and ls.linevar == linevar then
                core.log("action", "Train "..train_id.." restarted from index "..ls.linevar_index.." due to replacement of linevar '"..linevar.."'.")
                ls.linevar_index = 1
                restart_count = restart_count + 1
            end
        end
    end
    if restart_count > 0 then
        return true, S("@1 trains restarted due to changes", restart_count)
    else
        return true, nil
    end
end

local function delete_linevar(stn, linevar)
    local station = advtrains.lines.stations[stn]
    if station == nil or station.linevars == nil then
        return false, S("No such station")
    end
    local linevars = station.linevars
    if linevars[assert(linevar)] == nil then
        return false, S("Cannot delete, because lineval @1 does not yet exist", linevar)
    end
    linevars[linevar] = nil
    for train_id, train in pairs(advtrains.trains) do
        local ls = train.line_status
        if ls ~= nil and ls.linevar == linevar then
            core.log("action", "Train "..train_id.." removed from deleted linevar '"..linevar.."'.")
            cancel_linevar(train)
        end
    end
    return true, nil
end

local function get_formspec(custom_state)
	local pinfo = ch_core.normalize_player(assert(custom_state.player_name))
	if pinfo.player == nil then
		minetest.log("error", "Expected player not in game!")
		return ""
	end

    local selection_index_raw = custom_state.selection_index
	local selection_index = selection_index_raw or 1
    local formspec = {
        ch_core.formspec_header({formspec_version = 6, size = {20, 16}, auto_background = true}),
        "label[0.5,0.6;"..S("Line Editor").."]"..
        "style[s01_pos",
    }
    for i = 2, max_stations do
        table.insert(formspec, string.format(",s%02d_pos", i))
    end
    table.insert(formspec, ";font=mono;font_size=-4]"..
        "style[rc;font=mono]"..
        "tablecolumns[color;text,align=right;text;text,align=center;color;text,width=7;color;text]"..
        "table[0.5,1.25;19,5;linevar;#ffffff,"..S("LINE,ROUTE,VAR.")..",#ffffff,"..S("OWNER")..",#ffffff,STAV")

    for _, linevar_def in ipairs(custom_state.linevars) do
        local lv_line, lv_stn, lv_rc = linevar_decompose(linevar_def.name)
        local color = ifthenelse(linevar_def.disabled, "#cccccc", "#ffffff")
        table.insert(formspec,
            ","..color..","..F(lv_line)..","..F(get_line_description(linevar_def, {first_stop = true, last_stop = true}))..
            ","..F(lv_rc)..","..ifthenelse(linevar_def.owner == pinfo.player_name, "#00ff00", color)..",")
        table.insert(formspec, F(prihlasovaci_na_zobrazovaci(linevar_def.owner)))
        table.insert(formspec, ","..color..",")
        if linevar_def.disabled then
            table.insert(formspec, "vypnutá")
        end
    end
    if selection_index_raw ~= nil then
        table.insert(formspec, ";"..selection_index.."]")
    else
        table.insert(formspec, ";]")
    end
    if pinfo.role ~= "new" then
        table.insert(formspec, "button[14.5,0.3;3.5,0.75;create;"..S("New Line...").."]")
    end
    local has_rights_to_open_variant =
        pinfo.role == "admin" or selection_index == 1 or
        pinfo.player_name == custom_state.linevars[selection_index - 1].owner

    if selection_index > 1 and has_rights_to_open_variant then
        table.insert(formspec, "button[10.5,0.3;3.5,0.75;delete;"..S("Delete Line").."]")
    end
    table.insert(formspec, "button_exit[18.75,0.3;0.75,0.75;close;X]"..
        "field[0.5,7;1.25,0.75;line;"..S("Line:")..";"..F(custom_state.line).."]"..
        "field[2,7;1.5,0.75;rc;"..S("Variant:")..";"..F(custom_state.rc).."]"..
        "field[3.75,7;3,0.75;train_name;"..S("Dest. Display:")..";"..F(custom_state.train_name).."]")
    if pinfo.role ~= "admin" then
        table.insert(formspec, "label[7,6.75;"..S("Owner:").."\n")
    else
        table.insert(formspec, "field[7,7;4,0.75;owner;"..S("Owner:")..";")
    end
    table.insert(formspec, F(custom_state.owner).."]"..
        "checkbox[11.25,7.25;disable_linevar;"..S("Disable")..";"..custom_state.disable_linevar.."]"..
        "field[13.5,7;3,0.75;continues;"..S("Continues:")..";"..F(custom_state.continues).."]")

    if custom_state.message ~= "" then
        table.insert(formspec, "label[0.5,8.25;"..F(custom_state.message).."]")
    end
    if selection_index > 1 then
        table.insert(formspec, "button[5,15;4.5,0.75;last_passages;"..S("Last passages").."]"..
        "tooltip[last_passages;Zobrazí přehled časů několika posledních jízd na dané variantě linky.]")
    end
    if has_rights_to_open_variant then
        table.insert(formspec, "button[10,15;4.5,0.75;save;"..
            ifthenelse(custom_state.compiled_linevar == nil, S("Verify/Save").."]", S("Save changes").."]"))
    end
    table.insert(formspec, "button[15,15.25;4,0.5;reset;"..S("Reset changes").."]")
    table.insert(formspec, "tooltip[line;"..
        "Označení linky. Musí být neprázdné. Varianta linky bude použita pouze na vlaky s tímto označením linky.]"..
        "tooltip[rc;Směrový kód. Může být prázdný. Varianta linky bude použita pouze na vlaky\\,\n"..
        "jejichž směrový kód se přesně shoduje se zadaným. Obvykle se toto pole nechává prázdné.]"..
        "tooltip[train_name;Volitelný údaj. Je-li zadán\\, jízdní řády budou uvádět u spojů této\n"..
        "varianty zadané jméno.]"..
        "tooltip[disable_linevar;Zaškrtnutím variantu linky vypnete. Vypnutá varianta linky není používána\n"..
        "na žádné další vlaky\\, stávající vlaky však mohou dojet do svých koncových zastávek.]")

    table.insert(formspec, "container[0,8.75]"..
        "label[0.5,0.25;"..S("Dep.").."]"..
        "label[2,0.25;"..S("St.Time").."]"..
        "label[3.5,0.25;"..S("Station Code").."]"..
        "label[6.25,0.25;"..S("Mode").."]"..
        "label[11,0.25;"..S("Track").."]"..
        "label[12.5,0.25;"..S("Position").."]"..
        "scrollbaroptions[min=0;max=550;arrows=show]"..
        "scrollbar[19,0.5;0.5,5.5;vertical;evl_scroll;"..custom_state.evl_scroll.."]"..
        "scroll_container[0.5,0.5;18.5,5.5;evl_scroll;vertical]"..
        "box[0,0;20,70;#00808040]") -- box[] = pozadí

    -- výchozí zastávka:
    table.insert(formspec,
        "label[0.1,0.4;0]"..
        "field[1.5,0;1.25,0.75;s01_wait;;"..F(custom_state.stops[1].wait).."]"..
        "field[3,0;2.5,0.75;s01_stn;;"..F(custom_state.stops[1].stn).."]"..
        "dropdown[5.75,0;4.5,0.75;s01_mode;"..S("Normal")..","..S("Hidden (normal)")..";"..custom_state.stops[1].mode..";true]"..
        "field[10.5,0;1.25,0.75;s01_track;;"..F(custom_state.stops[1].track).."]"..
        "field[12,0;3,0.75;s01_pos;;"..F(custom_state.stops[1].pos).."]"..
        "label[15.25,0.4;"..F(custom_state.stops[1].label).."]")

    -- ostatní zastávky:
    local y_base, y_scale = 0, 1
    for i = 2, max_stations do
        local stop = custom_state.stops[i]
        local n
        if i < 10 then
            n = "0"..i
        else
            n = tostring(i)
        end
        local y = string.format("%f", y_base + (i - 1) * y_scale)
        local y2 = string.format("%f", y_base + (i - 1) * y_scale + 0.4) -- for a label
        table.insert(formspec,
            "field[0,"..y..";1.25,0.75;s"..n.."_dep;;"..F(stop.dep).."]"..
            "field[1.5,"..y..";1.25,0.75;s"..n.."_wait;;"..F(stop.wait).."]"..
            "field[3,"..y..";2.5,0.75;s"..n.."_stn;;"..F(stop.stn).."]"..
            "dropdown[5.75,"..y..";4.5,0.75;s"..n..
                "_mode;"..S("Normal")..","..S("Request Stop (experimental)")..","..S("Hidden")..","..S("Inactive")..","..S("Terminus")..","..S("Terminus (hidden)")..","..
            S("Terminus (continuing)")..";"..stop.mode..";true]"..
            "field[10.5,"..y..";1.25,0.75;s"..n.."_track;;"..F(stop.track).."]"..
            "field[12,"..y..";3,0.75;s"..n.."_pos;;"..F(stop.pos).."]"..
            "label[15.25,"..y2..";"..F(stop.label).."]")
    end

    table.insert(formspec,
        "scroll_container_end[]"..
        "tooltip[0,0;1.5,1;Odjezd: očekávaná jízdní doba v sekundách od odjezdu z výchozí zastávky\n"..
        "do odjezdu z dané zastávky. Podle ní se počítá zpoždění. Hodnota musí být jedinečná\n"..
        "pro každou zastávku na lince a podle ní se zastávky seřadí.\n"..
        "Pro úplné smazání dopravny z linky nechte pole prázdné.]"..
        "tooltip[1.5,0;1.5,1;Stání: očekáváná doba stání před odjezdem. Pro koncové zastávky očekávaná doba stání po příjezdu.]"..
        "tooltip[3.5,0;2.75,1;Kód dopravny: kód dopravny\\, kde má vlak zastavit. Vlak bude ignorovat\n"..
        "ARS pravidla a zastaví na první zastávkové koleji v dopravně pro odpovídající počet vagonů.\n"..
        "Kód dopravny se na lince může opakovat.]"..
        "tooltip[6.25,0;4.75,1;Režim zastávky: výchozí/normální - vždy zastaví\\;\n"..
        "na znamení: zastaví na znamení (zatím experimentální)\\;\n"..
        "skrytá – vždy zastaví\\, ale nezobrazí se v jízdních řádech\\;\n"..
        "vypnutá – nezastaví (použijte při výlukách nebo při zrušení zastávky)\\;\n"..
        "koncová – vždy zastaví a tím ukončí spoj\\, vlak se stane nelinkovým\\;\n"..
        "koncová (pokračuje) – jako koncová\\, ale vlak se může na odjezdu opět stát linkovým.]"..
        "tooltip[10.5,0;1.5,1;Kolej: nepovinný\\, orientační údaj do jízdních řádů – na které koleji\n"..
        "vlaky obvykle zastavují. Nepovinný údaj.]"..
        "tooltip[12.5,0;3.5,1;Omezení pozice: Zadávejte jen v případě potřeby.\n"..
        "Je-li zadáno\\, vlak v dané dopravně nezastaví na žádné jiné zastávkové koleji\n"..
        "než na té\\, která leží přesně na zadané pozici. Příklad platné hodnoty:\n123,7,-13]"..
        "container_end[]")

	-- if pinfo.role ~= "new" then
	return table.concat(formspec)
end

local mode_from_formspec_map = {MODE_NORMAL, MODE_REQUEST_STOP, MODE_HIDDEN, MODE_DISABLED, MODE_FINAL, MODE_FINAL_HIDDEN, MODE_FINAL_CONTINUE}
local mode_to_formspec_map = table.key_value_swap(mode_from_formspec_map)

local function mode_to_formspec(i, raw_mode)
    if i == 1 then
        return ifthenelse(raw_mode ~= nil and raw_mode == MODE_HIDDEN, 2, 1)
    elseif raw_mode == nil then
        return 1
    else
        return mode_to_formspec_map[raw_mode] or 1
    end
end

local function mode_from_formspec(i, fs_mode)
    if i == 1 then
        return ifthenelse(fs_mode == 2, MODE_HIDDEN, nil)
    else
        local result = mode_from_formspec_map[fs_mode]
        return ifthenelse(result ~= nil and result ~= MODE_NORMAL, result, nil)
    end
end

local function custom_state_set_selection_index(custom_state, new_selection_index)
    -- this will also refresh stops and resets the changes
    assert(custom_state.player_name)
    assert(custom_state.linevars)
    assert(new_selection_index)
    local current_linevar = custom_state.linevars[new_selection_index - 1]
    custom_state.selection_index = new_selection_index or 1
    local stops = custom_state.stops
    if stops == nil then
        stops = {}
        custom_state.stops = stops
    end
    local linevar_stops
    if current_linevar ~= nil then
        linevar_stops = current_linevar.stops
    else
        linevar_stops = {}
    end

    for i = 1, max_stations do
        local stop = linevar_stops[i]
        if stop ~= nil then
            stops[i] = {
                dep = tostring(assert(stop.dep)),
                wait = tostring(stop.wait or 10),
                stn = assert(stop.stn),
                mode = mode_to_formspec(i, stop.mode),
                track = stop.track or "",
                pos = stop.pos or "",
                label = "",
            }
        else
            stops[i] = {
                dep = ifthenelse(i == 1, "0", ""),
                wait = "10",
                stn = "",
                mode = 1,
                track = "",
                pos = "",
                label = "",
            }
        end
    end
    if current_linevar ~= nil then
        local lv_line, lv_stn, lv_rc = linevar_decompose(current_linevar.name)
        custom_state.line = lv_line or ""
        custom_state.rc = lv_rc or ""
        custom_state.train_name = current_linevar.train_name or ""
        custom_state.owner = assert(current_linevar.owner)
        custom_state.disable_linevar = ifthenelse(current_linevar.disabled, "true", "false")
        custom_state.continues = current_linevar.continue_line or ""
        if custom_state.continues ~= "" then
            custom_state.continues = custom_state.continues.."/"..(current_linevar.continue_rc or "")
        end
    else
        custom_state.line = ""
        custom_state.rc = ""
        custom_state.train_name = ""
        custom_state.owner = custom_state.player_name
        custom_state.disable_linevar = "false"
        custom_state.continues = ""
    end
    custom_state.owner = prihlasovaci_na_zobrazovaci(custom_state.owner)
    custom_state.compiled_linevar = nil
    custom_state.evl_scroll = 0
    custom_state.message = ""
end

local function num_transform(s)
    local prefix = s:match("^([0-9]+)/")
    if prefix == nil then
        return s
    end
    return string.format(" %020d%s", tonumber(prefix) or 0, s:sub(#prefix, -1))
end

local function linevars_sorter(a, b)
    return num_transform(a.name) < num_transform(b.name)
end

local function custom_state_refresh_linevars(custom_state, linevar_to_select)
    assert(custom_state.player_name)
    local linevars = {}
    for _, stdata in pairs(advtrains.lines.stations) do
        if stdata.linevars ~= nil then
            for _, linevar_def in pairs(stdata.linevars) do
                table.insert(linevars, linevar_def)
            end
        end
    end
    table.sort(linevars, linevars_sorter)
    custom_state.selection_index = nil
    custom_state.linevars = linevars
    custom_state.compiled_linevar = nil
    if linevar_to_select ~= nil then
        for i, linevar_def in ipairs(linevars) do
            if linevar_def.name == linevar_to_select then
                custom_state_set_selection_index(custom_state, i + 1)
                return true
            end
        end
        return false
    end
end

local function custom_state_compile_linevar(custom_state)
    local stations = advtrains.lines.stations
    local line = assert(custom_state.line)
    local stn = assert(custom_state.stops[1].stn)
    local rc = assert(custom_state.rc)
    local train_name = assert(custom_state.train_name)
    local owner = assert(custom_state.owner)
    local stops = {}
    if line == "" then
        return false, S("Line name must not be empty!")
    elseif line:find("[/|\\]") then
        return false, S("Line name must not contain characters '/', '|' or '\\'!")
    elseif line:len() > 256 then
        return false, S("Line name must not exceed 256 characters!")
    elseif stn == "" then
        return false, S("Starting station code must not be empty!")
    elseif rc:find("[/|\\]") then
        return false, S("Routing code must not contain '/', '|' or '\\'!")
    elseif owner == "" then
        return false, S("Line owner must not be empty!")
    elseif train_name:len() > 256 then
        return false, S("Train name must not exceed 256 characters!")
    elseif custom_state.continues:len() - custom_state.continues:gsub("/", ""):len() > 1 then
        return false, S("Field 'Continuation' must contain maximum 1 '/'!")
    end
    -- Zkontrolovat zastávky:
    local errcount = 0
    local finalcount = 0
    local dep_to_index = {}
    for i, stop in ipairs(assert(custom_state.stops)) do
        local good_label
        stop.label = ""
        if stop.dep == "" then
            -- přeskočit
        elseif not stop.dep:match("^[0-9][0-9]*$") then
            errcount = errcount + 1
            stop.label = color_red..S("Incorrect departure time format!")
        elseif tonumber(stop.dep) < 0 or tonumber(stop.dep) > 3600 then
            errcount = errcount + 1
            stop.label = color_red..S("Departure time must lie within 0-3600 seconds!")
        elseif dep_to_index[tonumber(stop.dep)] ~= nil then
            errcount = errcount + 1
            stop.label = color_red..S("Duplicate departure time!")
        else
            dep_to_index[tonumber(stop.dep)] = i
            if stop.stn == "" or stations[stop.stn] == nil or stations[stop.stn].name == nil then
                errcount = errcount + 1
                stop.label = color_red..S("Unknown station code!")
            elseif stop.stn:find("[/|\\]") then
                errcount = errcount + 1
                stop.label = color_red..S("Station code must not contain '/', '|' or '\\'!")
            elseif stop.track:len() > 16 then
                errcount = errcount + 1
                stop.label = color_red..S("Line name must not exceed 16 characters!")
            elseif stop.pos ~= "" and not stop.pos:match("^[-0-9][0-9]*,[-0-9][0-9]*,[-0-9][0-9]*$") then
                errcount = errcount + 1
                stop.label = color_red..S("Incorrect format for stop position!")
            elseif stop.pos:len() > 22 then
                errcount = errcount + 1
                stop.label = color_red..S("Stop position must not exceed 22 characters!")
            else
                -- v pořádku:
                local new_stop = {
                    stn = stop.stn,
                    dep = tonumber(stop.dep),
                }
                local new_mode = mode_from_formspec(i, stop.mode)
                if new_mode ~= nil then
                    new_stop.mode = new_mode
                    if i > 1 and (new_mode == MODE_FINAL or new_mode == MODE_FINAL_CONTINUE or new_mode == MODE_FINAL_HIDDEN) then
                        finalcount = finalcount + 1
                    end
                end
                if stop.pos ~= "" then
                    new_stop.pos = stop.pos
                end
                if stop.track ~= "" then
                    new_stop.track = stop.track
                end
                local new_wait = tonumber(stop.wait)
                if new_wait ~= nil and new_wait == math.floor(new_wait) and new_wait >= 0 and new_wait <= 3600 then
                    new_stop.wait = new_wait
                end
                table.insert(stops, new_stop)
                if stop.stn ~= "" then
                    stop.label = color_green.."= "..assert(stations[stop.stn].name)
                end
            end
        end
    end
    if errcount > 0 then
        return false, S("@1 errors while checking line setup!", errcount)
    end
    if finalcount == 0 then
        return false, S("Line setup must contain at least 1 terminus stop!")
    end
    table.sort(stops, function(a, b) return a.dep < b.dep end)

    local index_vychozi, index_cil
    for i, stop in ipairs(stops) do
        local mode = stop.mode or MODE_NORMAL
        if mode ~= MODE_DISABLED and mode ~= MODE_HIDDEN and mode ~= MODE_FINAL_HIDDEN then
            if index_vychozi == nil then
                index_vychozi = i
            end
            index_cil = i
        end
        if mode == MODE_FINAL or mode == MODE_FINAL_CONTINUE or mode == MODE_FINAL_HIDDEN then
            break
        end
    end

    custom_state.compiled_linevar = {
        name = line.."/"..stops[1].stn.."/"..rc,
        owner = owner, -- ch_core.jmeno_na_prihlasovaci(owner),
        stops = stops,
        continue_line = "",
        continue_rc = "",
        index_vychozi = index_vychozi,
        index_cil = index_cil,
    }
    if train_name ~= "" then
        custom_state.compiled_linevar.train_name = train_name
    end
    if custom_state.disable_linevar == "true" then
        custom_state.compiled_linevar.disabled = true
    end
    local continues_split = custom_state.continues:find("/")
    if continues_split == nil then
        custom_state.compiled_linevar.continue_line = custom_state.continues
    else
        custom_state.compiled_linevar.continue_line = custom_state.continues:sub(1, continues_split - 1)
        custom_state.compiled_linevar.continue_rc = custom_state.continues:sub(continues_split + 1, -1)
    end
    return true, nil
end

local function formspec_callback(custom_state, player, formname, fields)
    local reload_stations, update_formspec = false, false
    -- print("DEBUG: "..dump2({custom_state = custom_state, formname = formname, fields = fields}))

	if fields.quit then
		return
	end
    -- scrollbar:
    if fields.evl_scroll then
        local event = core.explode_scrollbar_event(fields.evl_scroll)
        if event.type == "CHG" then
            custom_state.evl_scroll = event.value
        end
    end
    -- checkbox:
    if fields.disable_linevar then
        custom_state.disable_linevar = fields.disable_linevar
    end
    -- dropdowns:
    for i = 1, max_stations do
        local id = string.format("s%02d_mode", i)
        local n = tonumber(fields[id])
        if n ~= nil then
            custom_state.stops[i].mode = n
        end
    end
    -- fields:
    for _, key in ipairs({"line", "rc", "train_name", "owner", "continues"}) do
        if fields[key] then
            custom_state[key] = fields[key]
        end
    end
    for i, stop in ipairs(custom_state.stops) do
        local prefix = string.format("s%02d_", i)
        for _, key in ipairs({"dep", "wait", "stn", "track", "pos"}) do
            local value = fields[prefix..key]
            if value then
                stop[key] = value
            end
        end
    end
    -- selection:
    if fields.linevar then
        local event = core.explode_table_event(fields.linevar)
        if event.type == "CHG" or event.type == "DCL" then
            custom_state_set_selection_index(custom_state, assert(tonumber(event.row)))
            update_formspec = true
        end
    end

    -- buttons:
    if fields.create then
        custom_state_set_selection_index(custom_state, 1)
        update_formspec = true
    elseif fields.reset then
        custom_state_set_selection_index(custom_state, custom_state.selection_index or 1)
        update_formspec = true
    elseif fields.save then
        local pinfo = ch_core.normalize_player(player)
        if pinfo.role == "new" or pinfo.role == "none" then
            core.log("error", "Access violation in line editor caused by '"..pinfo.player_name.."'!")
            return -- access violation!
        end
        if custom_state.compiled_linevar == nil then
            -- zkontrolovat a skompilovat
            local success, errmsg = custom_state_compile_linevar(custom_state)
            if success then
                -- TODO: zkontrolovat práva a možnost přepsání i zde!
                custom_state.message = color_green..S("No issues found, the line can now be saved!")
            else
                custom_state.message = color_red..S("Error in line setup: ")..(errmsg or S("Unknown reason"))
            end
            update_formspec = true
        else
            -- pokusit se uložit...
            custom_state.message = ""

            local selection_index = custom_state.selection_index or 1
            local selected_linevar, selected_linevar_def, selected_linevar_station
            local to_linevar, to_linevar_def, to_linevar_station
            local new_linevar, new_linevar_def, new_linevar_station

            -- NEW:
            new_linevar_def = custom_state.compiled_linevar
            new_linevar = new_linevar_def.name
            new_linevar_station = get_stn_from_linevar(new_linevar)

            -- SELECTED:
            if custom_state.selection_index > 1 and custom_state.linevars[selection_index - 1] ~= nil then
                selected_linevar_def, selected_linevar_station = try_get_linevar_def(custom_state.linevars[selection_index - 1].name)
                if selected_linevar_def ~= nil then
                    selected_linevar = selected_linevar_def.name
                end
            end

            -- TO OVERWRITE:
            to_linevar_def, to_linevar_station = try_get_linevar_def(new_linevar)
            if to_linevar_def ~= nil then
                to_linevar = to_linevar_def.name
            end

            local success, errmsg
            if selected_linevar == nil then
                if to_linevar == nil then
                    -- zcela nová varianta
                    core.log("action", "Will add a new linevar '"..new_linevar.."'")
                    success, errmsg = add_linevar(new_linevar_station, new_linevar_def)
                else
                    -- replace
                    core.log("action", "Will replace an existing linevar '"..new_linevar.."'")
                    success = check_rights(pinfo, to_linevar_def.owner)
                    if success then
                        success, errmsg = replace_linevar(new_linevar_station, new_linevar_def)
                    else
                        errmsg = S("Insufficient permissions to edit '@1'.", to_linevar)
                    end
                end
            elseif to_linevar == nil then
                -- delete and add
                core.log("action", "Will delete selected linevar '"..selected_linevar.."' and add new linevar '"..new_linevar.."'")
                success = check_rights(pinfo, selected_linevar_def.owner)
                if success then
                    success, errmsg = delete_linevar(selected_linevar_station, selected_linevar)
                    if success then
                        success, errmsg = add_linevar(new_linevar_station, new_linevar_def)
                    end
                else
                    errmsg = S("Insufficient permissions to edit '@1'.", selected_linevar)
                end
            elseif selected_linevar ~= to_linevar then
                -- delete and replace
                core.log("action", "Will add delete selected linevar '"..selected_linevar.."' and replace existing linevar '"..new_linevar.."'")
                success = check_rights(pinfo, to_linevar_def.owner)
                if success then
                    success = check_rights(pinfo, selected_linevar_def.owner)
                    if success then
                        success, errmsg = delete_linevar(selected_linevar_station, selected_linevar)
                        if success then
                            success, errmsg = replace_linevar(new_linevar_station, new_linevar_def)
                        end
                    else
                        errmsg = S("Insufficient permissions to edit '@1'.", selected_linevar)
                    end
                else
                    errmsg = S("Insufficient permissions to edit '@1'.", to_linevar)
                end
            else
                -- replace
                core.log("action", "Will replace existing linevar '"..new_linevar.."'")
                success = check_rights(pinfo, to_linevar_def.owner)
                if success then
                    success, errmsg = replace_linevar(new_linevar_station, new_linevar_def)
                else
                    errmsg = S("Insufficient permissions to edit '@1'.", to_linevar)
                end
            end

            if success then
                custom_state.message = color_green..S("Line '@1' successfully saved!", new_linevar)
                custom_state_refresh_linevars(custom_state, new_linevar)
            else
                custom_state.message = color_red..S("Error in line setup: ")..(errmsg or S("Unknown reason"))
            end
            update_formspec = true
        end

    elseif fields.delete then
        local pinfo = ch_core.normalize_player(player)
        if pinfo.role == "new" or pinfo.role == "none" then
            core.log("error", "Access violation in line editor caused by '"..pinfo.player_name.."'!")
            return -- access violation!
        end
        local selection_index = custom_state.selection_index or 1
        local selected_linevar, selected_linevar_def, selected_linevar_station
        if selection_index > 1 and custom_state.linevars[selection_index - 1] ~= nil then
            selected_linevar_def, selected_linevar_station = try_get_linevar_def(custom_state.linevars[selection_index - 1].name)
            if selected_linevar_def ~= nil then
                selected_linevar = selected_linevar_def.name
            end
            local success, errmsg
            success = check_rights(pinfo, selected_linevar_def.owner)
            if success then
                success, errmsg = delete_linevar(selected_linevar_station, selected_linevar)
            else
                errmsg = S("Insufficient permissions to edit '@1'.", selected_linevar)
            end
            if success then
                custom_state.message = S("Line '@1' successfully deleted!", selected_linevar)
                custom_state_refresh_linevars(custom_state)
                custom_state_set_selection_index(custom_state, 1)
            else
                custom_state.message = S("Deletion failed: ")..(errmsg or S("Unknown reason"))
            end
            update_formspec = true
        end
    elseif fields.last_passages then
        local selected_linevar_def = try_get_linevar_def(custom_state.linevars[(custom_state.selection_index or 1) - 1].name)
        if selected_linevar_def ~= nil then
            assert(selected_linevar_def.name)
            show_last_passages_formspec(player, selected_linevar_def, assert(selected_linevar_def.name))
            return
        end
    end

	if update_formspec then
		return get_formspec(custom_state)
	end
end

local function show_editor_formspec(player, linevar_to_select)
    if player == nil then return false end
	local custom_state = {
		player_name = assert(player:get_player_name()),
        evl_scroll = 0,
        message = "",
        continues = "",
	}
    if not custom_state_refresh_linevars(custom_state, linevar_to_select) then
        custom_state_set_selection_index(custom_state, 1)
    end
	ch_core.show_formspec(player, "advtrains_line_automation:editor_linek", get_formspec(custom_state), formspec_callback, custom_state, {})
end

-- make line editor globally available
advtrains.lines.open_line_editor = show_editor_formspec

local function lp_formspec_callback(custom_state, player, formname, fields)
    if fields.back then
        show_editor_formspec(player, custom_state.selected_linevar)
    elseif fields.update then
		local selected_linevar_def = try_get_linevar_def(custom_state.selected_linevar)
        if selected_linevar_def ~= nil then
            show_last_passages_formspec(player, selected_linevar_def, custom_state.selected_linevar)
        end
    end
end

show_last_passages_formspec = function(player, linevar_def, selected_linevar)
    local formspec = {
        "formspec_version[6]"..
        "size[20,10]"..
        "label[0.5,0.6;",
        S("Last passages of line @1", F(assert(linevar_def.name))),
        "]"..
        "tablecolumns[text;text;text,width=5;text,width=5;text,width=5;text,width=5;text,width=5;text,width=5;text,width=5;text,width=5;text,width=5;text,width=5]",
        "table[0.5,1.25;19,8;jizdy;",S("Code"),",",S("Station"),",1.j.,2.j.,3.j.,4.j.,5.j.,6.j.,7.j.,8.j.,9.j.,10.j."
    }
    local passages, stops = get_last_passages(linevar_def)
    local max_time = {}
    if passages ~= nil then
        for j = 1, 10 do
            max_time[j] = 0
            if passages[j] == nil then
                passages[j] = {}
            end
        end
        -- stání na výchozí zastávce:
        table.insert(formspec, ",,"..S("At start station:"))
        for j = 1, 10 do
            local wait = passages[j].wait
            if wait ~= nil then
                table.insert(formspec, ","..wait.." s")
            else
                table.insert(formspec, ",-")
            end
        end
        -- odjezd z výchozí zastávky:
        table.insert(formspec, ","..F(stops[1][1])..","..F(stops[1][2]).." "..S("(departed)"))
        for j = 1, 10 do
            local time = passages[j][1]
            if time ~= nil then
                table.insert(formspec, ",("..time..")")
                if max_time[j] < time then
                    max_time[j] = time
                end
            else
                table.insert(formspec, ",-")
            end
        end
        -- odjezdy z ostatních zasŧávek:
        for i = 2, #stops do -- i = index zastávky
            table.insert(formspec, ","..F(stops[i][1])..","..F(stops[i][2]))
            for j = 1, 10 do -- j = index jízdy
                local dep_vych = passages[j][1]
                local time = passages[j][i]
                if time ~= nil and dep_vych ~= nil then
                    table.insert(formspec, ","..(time - dep_vych))
                    if max_time[j] < time then
                        max_time[j] = time
                    end
                else
                    table.insert(formspec, ",-")
                end
            end
        end
        table.insert(formspec, ",,"..S("Travelling:"))
        for i = 1, 10 do
            if max_time[i] ~= 0 then
                table.insert(formspec, ",_"..(max_time[i] - passages[i][1]).."_")
            else
                table.insert(formspec, ",-")
            end
        end
    end
    table.insert(formspec, ";]"..
		"button[13.75,0.3;1.75,0.75;update;"..S("Update").."]"..
        "button[17.75,0.3;1.75,0.75;back;"..S("Back").."]"..
        "tooltip[jizdy;Časové údaje jsou v sekundách železničního času.]")
    formspec = table.concat(formspec)
    local custom_state = {
        player_name = player:get_player_name(),
        selected_linevar = selected_linevar,
    }
    ch_core.show_formspec(player, "advtrains_line_automation:posledni_jizdy", formspec, lp_formspec_callback, custom_state, {})
end

def = {
    -- params = "",
    description = S("Open line editor"),
    privs = {railway_operator = true},
    func = function(player_name, param) show_editor_formspec(minetest.get_player_by_name(player_name)) end,
}
core.register_chatcommand("line_editor", def)
