local def
local F = minetest.formspec_escape
local ch_core = advtrains.lines.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)

-- Get current translator
local S = advtrains.lines.translate

-- Singularis-Merge: Emulate certain ch functionality
local function systemovy_kanal(pname, message)
	core.chat_send_player(pname, S("[Station Editor]")..message)
end
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 function load_stations()
	local result = {}
	local by_stn = {}
	local all_lines_set = {["*"] = true}
	local lines_set = {}
	for stn, data in pairs(advtrains.lines.stations) do
		local new_station = {
			stn = stn,
			name = data.name or "",
			owner = data.owner or "",
			tracks = {},
		}
		by_stn[stn] = new_station
		lines_set[stn] = {}
		table.insert(result, new_station)
	end
	for epos, data in pairs(advtrains.lines.stops) do
		if data.stn ~= nil and data.stn ~= "" and by_stn[data.stn] ~= nil then
			local st = by_stn[data.stn]
			local new_track = {
				epos = epos,
				pos = assert(advtrains.decode_pos(epos)),
				track = data.track or "",
			}
			table.insert(st.tracks, new_track)
			local ars = data.ars
			if ars ~= nil then
				if ars.default then
					lines_set[data.stn] = all_lines_set
				else
					local set = lines_set[data.stn]
					for _, rule in ipairs(ars) do
						if not rule.n and rule.ln ~= nil then
							set[rule.ln] = true
						end
					end
				end
			end
		end
	end
	for _, st in ipairs(result) do
		local set = lines_set[st.stn]
		if set["*"] then
			st.lines = {"*"}
		else
			local lines = {}
			for line, _ in pairs(set) do
				table.insert(lines, line)
			end
			table.sort(lines)
			st.lines = lines
		end
	end
	table.sort(result, function(a, b) return a.stn < b.stn end)
	return result
end

advtrains.lines.load_stations_for_formspec = load_stations

local function station_distance(player_pos, st)
	if st.tracks[1] == nil then
		return -- no tracks
	end
	local distance = vector.distance(player_pos, st.tracks[1].pos)
	for i = 2, #st.tracks do
		local d2 = vector.distance(player_pos, st.tracks[i].pos)
		if d2 < distance then
			distance = d2
		end
	end
	return distance
end

local function station_distance_s(player_pos, st)
	local result = station_distance(player_pos, st)
	if result ~= nil then
		return string.format("%d m", result)
	else
		return ""
	end
end


local function filter_all()
	return true
end

local function filter_mine(a, player_info)
	return a.owner == player_info.name
end

local function sort_by_distance(a, b, player_info)
	return (station_distance(player_info.pos, a) or 1.0e+20) < (station_distance(player_info.pos, b) or 1.0e+20)
end

local function sort_by_stn(a, b)
	return tostring(a.stn) < tostring(b.stn)
end

local function sort_by_name(a, b)
	local a_key, b_key = ch_core.utf8_radici_klic(a.name, false), ch_core.utf8_radici_klic(b.name, false)
	if a_key ~= b_key then
		return a_key < b_key
	else
		return sort_by_stn(a, b)
	end
end

local filters = {
	{
		description = S("By Distance"),
		-- filter = filter_all,
		sorter = sort_by_distance,
	},
	{
		description = S("Station Code (A-Z)"),
		-- filter = filter_all,
		sorter = sort_by_stn,
	},
	{
		description = S("Station Name (A-Z)"),
		-- filter = filter_all,
		sorter = sort_by_name,
	},
	{
		description = S("My Stations (Station Code A-Z)"),
		filter = filter_mine,
		sorter = sort_by_stn,
	},
	{
		description = S("My Stations (Station Name A-Z)"),
		filter = filter_mine,
		sorter = sort_by_name,
	},
	{
		description = S("My Stations (By Distance)"),
		filter = filter_mine,
		sorter = sort_by_distance,
	},
}


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 player_info = {
		name = assert(custom_state.player_name),
		pos = assert(custom_state.player_pos),
	}

	local stations = custom_state.stations
	if stations == nil then
		local filter = filters[custom_state.active_filter]
		local filter_func = filter.filter or filter_all
		local sorter_func = filter.sorter
		stations = {}
		for _, st in ipairs(load_stations()) do
			if filter_func(st, player_info) then
				table.insert(stations, st)
			end
		end
		table.sort(stations, function(a, b) return sorter_func(a, b, player_info) end)
		custom_state.stations = stations
		if custom_state.selection_index ~= nil and custom_state.selection_index > #stations + 1 then
			custom_state.selection_index = nil
		end
	end

	local selection_index = custom_state.selection_index
    local formspec = {
        ch_core.formspec_header({formspec_version = 6, size = {20, 10}, auto_background = true}),
		"label[0.5,0.6;Editor dopraven]",
    }

	table.insert(formspec, "tablecolumns[image,"..
		"0=ch_core_empty.png,"..
		"1=basic_materials_padlock.png\\^[resize:16x16"..
		";text;text,width=25;color,span=1;text,width=7;text;text]"..
		"table[0.5,1.25;19,5;dopravna;0,"..S("CODE")..","..S("NAME")..",#ffffff,"..S("OWNER")..","..S("DISTANCE")..","..S("INFO").."")
	for _, st in ipairs(stations) do
		local n_tracks = #st.tracks
		table.insert(formspec, ",0,"..F(st.stn)..","..F(st.name)..",#ffffff,"..F(prihlasovaci_na_zobrazovaci(st.owner))..","..
			F(station_distance_s(custom_state.player_pos, st))..", "..S("@1 tracks", n_tracks))
		if n_tracks > 0 then
			table.insert(formspec, "\\, "..S("lines").." " ..F(table.concat(st.lines, ",")))
		end
	end
	table.insert(formspec, ";"..(selection_index or "").."]")

	-- rozbalovací pole s volbou filtru a řazení
	table.insert(formspec, "dropdown[8,0.3;7,0.6;filter;"..F(assert(filters[1].description)))
	for i = 2, #filters do
		table.insert(formspec, ","..F(filters[i].description))
	end
	table.insert(formspec, ";"..custom_state.active_filter..";true]")

	table.insert(formspec, "button_exit[18.75,0.3;0.75,0.75;close;X]")

	local st = selection_index ~= nil and stations[selection_index - 1]
	local stn, nazev, spravuje
	if st then
		stn = F(st.stn)
		nazev = F(st.name)
		spravuje = F(prihlasovaci_na_zobrazovaci(st.owner))
	else
		stn, nazev, spravuje = "", "", F(pinfo.viewname)
	end
	table.insert(formspec,
		"field[0.5,7;2.5,0.75;stn;kód:;"..stn.."]"..
		"field[3.25,7;7,0.75;name;název:;"..nazev.."]"..
		ifthenelse(pinfo.role == "admin", "field[10.5,7;4,0.75;owner;spravuje:;", "label[10.5,6.75;spravuje:\n")..
		spravuje.."]")
	if pinfo.role ~= "new" then
		table.insert(formspec, "button[0.5,8;4.5,0.75;vytvorit;"..S("Create New").."]"..
			"button[10,8;4.5,0.75;jrad;jízdní řády...]")
		if st and (pinfo.role == "admin" or st.owner == pinfo.player_name) then
			table.insert(formspec, "button[5.25,8;4.5,0.75;ulozit;"..S("Save Changes").."]")
			if st.tracks[1] == nil then
				table.insert(formspec, "button[15.25,8;3,0.75;smazat;"..S("Delete").."]")
			end
			if custom_state.linevars[1] ~= nil then
				table.insert(formspec, "label[0.5,9.4;"..S("Assign Track").."]"..
					"field[2.75,9.1;1,0.6;"..S("Track")..";;]"..
					"label[3.9,9.4;"..S("Lines").."]"..
					"dropdown[5,9.1;5,0.6;klinevar;")
				for i, lvar in ipairs(custom_state.linevars) do
					if i ~= 1 then
						table.insert(formspec, ",")
					end
					table.insert(formspec, F(lvar.linevar.." | "..lvar.dep.." "..stn.." ["..lvar.track.."]"))
				end
				table.insert(formspec, ";"..custom_state.current_linevar..";true]"..
					"button[10.25,9;4.25,0.75;priradit_kolej;"..S("Assign").."]"..
					"tooltip[klinevar;")
				table.insert(formspec, F("Vysvětlení formátu:\n<linka>/<kód vých.dop.>/<sm.kód> | <odjezd> <kód dop.> [<stávající kolej>]"))
				table.insert(formspec, "]")
			end
		end
	end
	if st and st.tracks[1] ~= nil then
		table.insert(formspec, "textarea[14.75,7;4.75,2.5;;"..S("Positions of tracks:")..";"..F(minetest.pos_to_string(st.tracks[1].pos)))
		for i = 2, #st.tracks do
			table.insert(formspec, "\n"..F(minetest.pos_to_string(st.tracks[i].pos)))
		end
		table.insert(formspec, "]")
	end
	return table.concat(formspec)
end

local function formspec_callback(custom_state, player, formname, fields)
	local update_formspec = false
	if fields.vytvorit then
		local new_stn, new_name, new_owner = fields.stn, fields.name or "", fields.owner
		local pinfo = ch_core.normalize_player(player)
		if pinfo.role ~= "admin" or new_owner == nil or new_owner == "" then
			new_owner = pinfo.player_name
		else
			--new_owner = ch_core.jmeno_na_prihlasovaci(new_owner)
		end
		if new_stn == nil or new_stn == "" then
			systemovy_kanal(custom_state.player_name, S("ERROR: Code must not be empty!"))
			return
		end
		local als = advtrains.lines.stations
		if als[new_stn] ~= nil then
			systemovy_kanal(custom_state.player_name, S("ERROR: Station '@1' already exists!", new_stn))
			return
		end
		als[new_stn] = {name = assert(new_name), owner = assert(new_owner)}
		custom_state.stations = nil
		update_formspec = true
		systemovy_kanal(custom_state.player_name, S("Station successfully created."))
	elseif fields.ulozit then
		local new_stn, new_name, new_owner = fields.stn, fields.name or "", fields.owner
		local pinfo = ch_core.normalize_player(player)
		local st = custom_state.stations[(custom_state.selection_index or 0) - 1]
		if st == nil then
			systemovy_kanal(custom_state.player_name, S("ERROR: No such station!"))
			return
		end
		local change_stn, change_name = st.stn ~= new_stn, st.name ~= new_name
		local change_owner = pinfo.role == "admin" and fields.owner ~= nil and fields.owner ~= "" and
			fields.owner ~= st.owner -- ch_core.jmeno_na_prihlasovaci(fields.owner) ~= st.owner
		if not change_stn and not change_name and not change_owner then
			systemovy_kanal(custom_state.player_name, S("Nothing has changed."))
			return
		end
		local t = advtrains.lines.stations[st.stn]
		if t == nil then
			systemovy_kanal(custom_state.player_name, "CHYBA: zastávka k úpravě nebyla nalezena! Toto je pravděpodobně vnitřní chyba editoru.")
			return
		end
		if change_stn then
			-- zkontrolovat, že cílový kód je volný
			if advtrains.lines.stations[new_stn] ~= nil then
				systemovy_kanal(custom_state.player_name, S("ERROR: Station '@1' already exists!", new_stn))
				return
			end
		end
		if change_owner then
			t.owner = fields.owner -- ch_core.jmeno_na_prihlasovaci(fields.owner)
			systemovy_kanal(custom_state.player_name, S("Owner changed!"))
		end
		if change_name then
			t.name = new_name
			systemovy_kanal(custom_state.player_name, S("Name changed!"))
		end
		if change_stn then
			advtrains.lines.stations[new_stn] = t
			local stops = advtrains.lines.stops
			local count = 0
			for _, trackinfo in ipairs(st.tracks) do
				local stop = stops[trackinfo.epos]
				if stop == nil then
					minetest.log("error", "Expected track at position "..trackinfo.epos.." not found!")
				elseif stop.stn ~= st.stn then
					minetest.log("error", "Track at position "..trackinfo.epos.." has unexpected stn '"..tostring(stop.stn).."' instead of '"..st.stn.."'!")
				else
					stop.stn = new_stn
					count = count + 1
				end
			end
			advtrains.lines.stations[st.stn] = nil
			systemovy_kanal(custom_state.player_name, S("Code changed, @1 station tracks updated!", count))
		end
		custom_state.stations = nil
		update_formspec = true
	elseif fields.smazat then
		local pinfo = ch_core.normalize_player(player)
		local st = custom_state.stations[(custom_state.selection_index or 0) - 1]
		if st == nil then
			systemovy_kanal(custom_state.player_name, S("ERROR: No stop selected!"))
			return
		end
		if st.tracks[1] ~= nil then
			systemovy_kanal(custom_state.player_name, S("ERROR: There are station tracks assigned, cannot delete the station!"))
			return
		end
		local t = advtrains.lines.stations[st.stn]
		if t == nil then
			systemovy_kanal(custom_state.player_name, "CHYBA: zastávka k úpravě nebyla nalezena! Toto je pravděpodobně vnitřní chyba editoru.")
			return
		end
		advtrains.lines.stations[st.stn] = nil
		systemovy_kanal(custom_state.player_name, S("Station deleted!"))
		custom_state.stations = nil
		update_formspec = true
	elseif fields.jrad then
		local st = custom_state.stations[(custom_state.selection_index or 0) - 1]
		if st == nil then
			systemovy_kanal(custom_state.player_name, S("ERROR: No stop selected!"))
			return
		end
		advtrains.lines.show_jr_formspec(player, nil, assert(st.stn))
		return
	elseif fields.priradit_kolej then
		local st = custom_state.stations[(custom_state.selection_index or 0) - 1]
		if st == nil then
			systemovy_kanal(custom_state.player_name, S("ERROR: No stop selected!"))
			return
		end
		local linevar_to_change = custom_state.linevars[custom_state.current_linevar]
		if linevar_to_change == nil then
			systemovy_kanal(custom_state.player_name, "CHYBA: vnitřní chyba 1!")
			return
		end
		local linevar_def = advtrains.lines.try_get_linevar_def(linevar_to_change.linevar, linevar_to_change.stn)
		if linevar_def == nil then
			systemovy_kanal(custom_state.player_name, "CHYBA: vnitřní chyba 2!")
			return
		end
		local stop = linevar_def.stops[linevar_to_change.linevar_index]
		if stop == nil then
			systemovy_kanal(custom_state.player_name, "CHYBA: vnitřní chyba 3!")
			return
		end
		if stop.stn ~= st.stn then
			systemovy_kanal(custom_state.player_name, "CHYBA: vnitřní chyba 4!")
			return
		end
		stop.track = tostring(fields.kolej)
		linevar_to_change.track = stop.track
		systemovy_kanal(custom_state.player_name, S("Track successfully assigned!"))
		update_formspec = true
	elseif fields.quit then
		return
	end

	if fields.dopravna then
		local event = minetest.explode_table_event(fields.dopravna)
		if event.type == "CHG" then
			custom_state.selection_index = ifthenelse(event.row > 1, event.row, nil)
			local st = custom_state.stations[(custom_state.selection_index or 0) - 1]
			local new_linevars = {}
			custom_state.linevars = new_linevars
			custom_state.current_linevar = 1
			if st ~= nil then
				for _, r in ipairs(advtrains.lines.get_linevars_by_station(st.stn)) do
					local line, stn = advtrains.lines.linevar_decompose(r.linevar)
					for _, i in ipairs(r.indices) do
						local stop = r.linevar_def.stops[i]
						table.insert(new_linevars, {
							stn = stn,
							linevar = r.linevar,
							linevar_index = i,
							dep = assert(stop.dep),
							track = stop.track or "",
						})
					end
				end
			end
			update_formspec = true
		end
	end

	if fields.klinevar then
		local n = tonumber(fields.klinevar)
		if n ~= nil and custom_state.linevars[n] ~= nil then
			custom_state.current_linevar = n
		end
	end

	-- filter (must be the last)
	if fields.filter then
		local new_filter = tonumber(fields.filter)
		if filters[new_filter] ~= nil and new_filter ~= custom_state.active_filter then
			custom_state.active_filter = new_filter
			custom_state.stations = nil
			update_formspec = true
		end
	end
	if update_formspec then
		return get_formspec(custom_state)
	end
end

local function show_formspec(player)

	local custom_state = {
		player_name = assert(player:get_player_name()),
		player_pos = vector.round(player:get_pos()),
		active_filter = 1,
		-- selection_index = nil,
		linevars = {--[[
			{stn = string, linevar = string, linevar_index = int, track = string}...
		]]},
		current_linevar = 1,
	}
	-- ch_core.show_formspec(player_or_player_name, formname, formspec, formspec_callback, custom_state, options)
	ch_core.show_formspec(player, "advtrains_line_automation:editor_dopraven", get_formspec(custom_state), formspec_callback, custom_state, {})
end

advtrains.lines.open_station_editor = show_formspec

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


-- Jízdní řád

--[[
	custom_state = {
		pos = vector or nil, -- node position (will use metadata to determine the owner)
		player_name = string, -- player to whom the formspec is to be shown (will use this to determine privs)
		stn = int, -- station selection from stns
		stns = {
			{stn = "", fs = "(vyberte dopravnu)", name_fs = ""},
			{stn = string, fs = string, name_fs = string}...
		}, -- the list of stations to select from; the first must be "", which means no station
		track = int, -- selection of track from "tracks" list
		tracks = {"", string...}, -- the list of tracks to select from; the first must be "", which means all tracks
		linevar = int,
		linevars = {{
			stn = string, -- station from linevar
			linevar = string, -- linevar
			line_fs = string, -- line for formspec
			target_fs = string, -- terminus name for formspec
			track_fs = string, -- track info for formspec ("" if not available)
			status_fs = string, -- line status for formspec (including color column)
			train_name_fs = string, -- train name for formspec ("" if not available)
			linevar_index = int, -- index of the selected station (stn) in linevar_def.stops of this linevar
		}...},
		stop = int,
		stops = {{
			stn = string,
			name_fs = string,
			track_fs = string,
			dep = int,
			wait = int,
			linevar_index = int, -- index of this stop in linevar_def.stops
		}...},
		message = string, -- message for label[] on the bottom; "" to disable
	}
]]

local all_stations_record = {stn = "", fs = S("(select station)"), name_fs = ""}
local is_visible_mode = assert(advtrains.lines.is_visible_mode)

-- refresh custom_state.stops according to custom_state.linevar
local function jr_refresh_stops(custom_state, stn_to_select)
	if stn_to_select == "" then
		stn_to_select = nil
	end
	local linevar_info = custom_state.linevars[custom_state.linevar]
	local index_to_select = 0
	local result = {}
	if linevar_info ~= nil then
		local linevar_def = advtrains.lines.try_get_linevar_def(linevar_info.linevar, linevar_info.stn)
		if linevar_def == nil then
			-- invalid linevar => no stops
			core.log("error", "Player "..custom_state.player_name.." selected invalid linevar "..linevars[linevar].linevar.."!")
		else
			local stops = linevar_def.stops
			for linevar_index, stop_data in ipairs(linevar_def.stops) do
				if is_visible_mode(stop_data.mode) then
					local r = {
						stn = assert(stop_data.stn),
						name_fs = F(advtrains.lines.get_station_name(stop_data.stn)),
						track_fs = stop_data.track or "",
						dep = stop_data.dep,
						wait = stop_data.wait or 10,
						linevar_index = linevar_index,
					}
					if r.track_fs ~= "" then
						r.track_fs = F("["..r.track_fs.."]")
					end
					table.insert(result, r)
					if stn_to_select ~= nil and index_to_select == 0 and r.stn == stn_to_select then
						index_to_select = #result -- stop selected
					end
				end
			end
		end
	end
	if index_to_select == 0 and result[1] ~= nil then
		index_to_select = 1
	end
	custom_state.stop = index_to_select
	custom_state.stops = result
	custom_state.message = ""
end

local function get_all_linevars()
	local result = {}
	local empty_table = {}
	local trains_by_linevar = advtrains.lines.get_trains_by_linevar()
	for stn, stdata in pairs(advtrains.lines.stations) do
		for linevar, linevar_def in pairs(stdata.linevars or empty_table) do
			local line, stn = advtrains.lines.linevar_decompose(linevar)
			local target_fs = F(advtrains.lines.get_line_description(linevar_def, {line_number = false, last_stop = true, last_stop_prefix = "",
				last_stop_uppercase = false, train_name = false}))
			local status_fs
			if trains_by_linevar[linevar] ~= nil then
				status_fs = "#00ff00,"..S("in operation")
			else
				status_fs = "#999999,"..S("unknown state")
			end
			table.insert(result, {
				stn = stn,
				linevar = linevar,
				line_fs = F(line),
				target_fs = target_fs,
				track_fs = "",
				status_fs = status_fs,
				train_name_fs = F(linevar_def.train_name or ""),
				linevar_index = 1,
			})
		end
	end
	table.sort(result, function(a, b) return a.linevar < b.linevar end)
	return result
end

--[[
	stn_filter = string,
	track_filter = string or nil,
]]
local function get_linevars_by_filter(stn_filter, track_filter)
	local result = {}
	local empty_table = {}
	local line_description_options = {line_number = false, last_stop = true, last_stop_prefix = "", last_stop_uppercase = false, train_name = false}
	local trains_by_linevar = advtrains.lines.get_trains_by_linevar()
	assert(stn_filter)
	if track_filter == "" then
		track_filter = nil
	end
	for stn, stdata in pairs(advtrains.lines.stations) do
		for linevar, linevar_def in pairs(stdata.linevars or empty_table) do
			local last_stop_index = advtrains.lines.get_last_stop(linevar_def, false)
			if last_stop_index ~= nil then
				for linevar_index = 1, last_stop_index - 1 do -- NOTE: the last visible station is intentionally ignored here!
					local stop_data = linevar_def.stops[linevar_index]
					local initialized = false
					local line, stn, line_fs, target_fs, status_fs
					if
						stop_data.stn == stn_filter and
						is_visible_mode(stop_data.mode) and
						(track_filter == nil or track_filter == stop_data.track)
					then
						if not initialized then
							initialized = true
							line, stn = advtrains.lines.linevar_decompose(linevar)
							line_fs = F(line)
							target_fs = F(advtrains.lines.get_line_description(linevar_def, line_description_options))
							if trains_by_linevar[linevar] ~= nil then
								status_fs = "#00ff00,"..S("in operation")
							else
								status_fs = "#999999,"..S("unknown state")
							end
						end
						local track_fs = stop_data.track
						if track_fs == nil or track_fs == "" then
							track_fs = ""
						else
							track_fs = F("["..track_fs.."]")
						end
						table.insert(result, {
							stn = stn,
							linevar = linevar,
							line_fs = line_fs,
							target_fs = target_fs,
							track_fs = track_fs,
							status_fs = status_fs,
							train_name_fs = F(linevar_def.train_name or ""),
							linevar_index = assert(linevar_index),
						})
					end
				end
			end
		end
	end
	table.sort(result, function(a, b)
		if a.linevar ~= b.linevar then
			return a.linevar < b.linevar
		else
			return a.linevar_index < b.linevar_index
		end
	end)
	return result
end

local function is_jr_node_name(name)
	return core.get_item_group(name, "ch_jrad") ~= 0
end

-- refresh custom_state.linevars according to custom_state.stn and custom_state.track
local function jr_refresh_linevars(custom_state, linevar_to_select, linevar_index_to_select)
	if linevar_to_select == "" then
		linevar_to_select = nil
	end
	assert(linevar_index_to_select == nil or type(linevar_index_to_select) == "number")
	local stn = assert(custom_state.stn)
	local stn_info = assert(custom_state.stns[stn])
	local track = assert(custom_state.track)
	local tracks = assert(custom_state.tracks)

	local new_linevars
	if stn_info.stn == "" then
		new_linevars = get_all_linevars()
	else
		new_linevars = get_linevars_by_filter(stn_info.stn, assert(tracks[track]))
	end
	local index_to_select = 1
	if linevar_to_select ~= nil then
		for i, r in ipairs(new_linevars) do
			if r.linevar == linevar_to_select and (linevar_index_to_select == nil or r.linevar_index == linevar_index_to_select) then
				index_to_select = i
				break
			end
		end
	end
	if new_linevars[index_to_select] == nil then
		index_to_select = 0
	end
	custom_state.linevars = new_linevars
	custom_state.linevar = index_to_select
	custom_state.message = ""

	local pos = custom_state.pos
	if pos ~= nil and is_jr_node_name(core.get_node(pos).name) then
		local meta = core.get_meta(pos)
		meta:set_string("stn", custom_state.stns[custom_state.stn].stn)
		meta:set_string("track", custom_state.tracks[custom_state.track])
		local infotext = {"jízdní řád\n"}
		if stn_info.stn == "" then
			table.insert(infotext, "<všechny linky>")
		else
			table.insert(infotext, advtrains.lines.get_station_name(stn_info.stn))
			if custom_state.tracks[custom_state.track] ~= "" then
				table.insert(infotext, " ["..custom_state.tracks[custom_state.track].."]")
			end
		end
		if new_linevars[1] ~= nil then
			local prefix = "\n"
			local set = {[""] = true}
			for _, linevar_info in ipairs(new_linevars) do
				local line = advtrains.lines.linevar_decompose(linevar_info.linevar)
				if not set[line] then
					set[line] = true
					table.insert(infotext, prefix.."["..line.."]")
					prefix = " "
				end
			end
		end
		meta:set_string("infotext", table.concat(infotext))
	end
end

-- refresh custom_state.tracks according to custom_state.stn
-- and selects a specified track, if available
local function jr_refresh_tracks(custom_state, track_to_select)
	if track_to_select == "" then
		track_to_select = nil
	end
	local result = {""}
	local index_to_select = 1
	local current_stn = custom_state.stns[custom_state.stn].stn
	if current_stn ~= "" and advtrains.lines.stations[current_stn] ~= nil then
		local track_set = {[""] = true}
		-- search for tracks:
		for epos, stdata in pairs(advtrains.lines.stops) do
			if stdata.stn == current_stn and stdata.track ~= nil and not track_set[stdata.track] then
				track_set[stdata.track] = true
				table.insert(result, tostring(stdata.track))
			end
		end
		if #result > 1 then
			table.sort(result)
			assert(result[1] == "")
			if track_to_select ~= nil then
				for i, track in ipairs(result) do
					if track_to_select == track then
						index_to_select = i
					end
				end
			end
		end
	end
	custom_state.tracks = result
	custom_state.track = index_to_select
	custom_state.message = ""
end

--[[
	-- will refresh custom_state.stns[] and (optionally) select a wanted station if it's on the list,
	-- otherwise the default 'select station' option will be chosen
	custom_state = table,
	stn_to_select = string or nil,
]]
local function jr_refresh_stns(custom_state, stn_to_select)
	if stn_to_select == "" then
		stn_to_select = nil
	end
	local result = {all_stations_record}
	local index_to_select = 1
	for i, st in ipairs(load_stations()) do
		result[1 + i] = {
			stn = assert(st.stn),
			fs = F(st.stn.." | "..st.name),
			name_fs = F(st.name),
		}
		if stn_to_select ~= nil and st.stn == stn_to_select then
			index_to_select = 1 + i
		end
	end
	custom_state.stns = result
	custom_state.stn = index_to_select
	custom_state.message = ""
end

-- will refresh a departure message according to linevar + stop
local function jr_refresh_departure(custom_state)
	local linevar_info = custom_state.linevars[custom_state.linevar]
	local stop_info = custom_state.stops[custom_state.stop]
	if linevar_info == nil or stop_info == nil then
		custom_state.message = ""
		return
	end
	local linevar_def = advtrains.lines.try_get_linevar_def(linevar_info.linevar)
	if linevar_def == nil then
		custom_state.message = ""
		return
	end
	local rwtime = rwt.to_secs(rwt.get_time())
	local prediction = advtrains.lines.predict_station_departures(linevar_def, assert(stop_info.linevar_index), rwtime)
	if #prediction == 0 then
		custom_state.message = S("No departures for the selected line found in the near future")
		return
	end
	local deps = {}
	for i, pred in ipairs(prediction) do
		deps[i] = tostring(pred.dep - rwtime)
	end
	custom_state.message = S("next departures of the selected line in: ")..table.concat(deps, ", ").." s"
end

local function get_jr_formspec(custom_state)
	local formspec = {
		ch_core.formspec_header({
			formspec_version = 6,
			size = {20, 12},
			auto_background = true,
		})
	}
	local access_level = "player" -- player | owner | admin
	local node_owner
	if custom_state.pos ~= nil then
		node_owner = core.get_meta(custom_state.pos):get_string("owner")
		if node_owner == "" then
			node_owner = nil
		end
	end
	local stn, stn_owner
	if custom_state.stn > 1 and custom_state.stns[custom_state.stn] ~= nil then
		stn_owner = (advtrains.lines.stations[custom_state.stns[custom_state.stn].stn] or {}).owner -- may be nil
	end

	if not custom_state.force_unprivileged then
		if core.check_player_privs(custom_state.player_name, {protection_bypass=true}) then -- TODO make a lines admin privilege
			access_level = "admin"
		elseif custom_state.player_name == node_owner or custom_state.player_name == stn_owner then
			access_level = "owner"
		end
	end

	if node_owner ~= nil then
		if access_level ~= "player" then
			-- admin or owner:
			table.insert(formspec, "label[0.5,0.6;"..S("Timetables").."]"..
				"dropdown[5,0.3;10,0.6;dopravna;")
			for i, r in ipairs(custom_state.stns) do
				table.insert(formspec, ifthenelse(i == 1, r.fs, ","..r.fs))
			end
			table.insert(formspec, ";"..custom_state.stn..";true]"..
				"dropdown[15.25,0.3;3.5,0.6;kolej;")
			for i, r in ipairs(custom_state.tracks) do
				if i == 1 then
					table.insert(formspec, S("(all platforms)"))
				else
					table.insert(formspec, ","..F(r))
				end
			end
			table.insert(formspec, ";"..custom_state.track..";true]")
		else
			-- player (including 'new' players)
			local stn_info = custom_state.stns[custom_state.stn]
			if stn_info.stn == "" then
				table.insert(formspec, "label[0.5,0.6;"..S("Timetables (all lines)").."]")
			else
				local track = custom_state.tracks[custom_state.track]
				if track ~= "" then
					track = F(" ["..track.."]")
				end
				table.insert(formspec, "label[0.5,0.6;"..S("Timetables: ")..stn_info.name_fs..track.."]")
			end
		end
		if access_level ~= "admin" then
			-- player/owner
			table.insert(formspec, "label[0.5,1.65;"..S("Owner: "))
			table.insert(formspec, prihlasovaci_na_zobrazovaci(node_owner))
			if stn_owner ~= nil then
				table.insert(formspec, " | "..S("Station Operator: "))
				table.insert(formspec, prihlasovaci_na_zobrazovaci(stn_owner))
			end
			table.insert(formspec, "]")
		else
			-- admin only
			table.insert(formspec, "label[0.5,1.65;"..S("Owner: ").."]"..
				"field[2.75,1.25;5,0.75;owner;;")
			table.insert(formspec, prihlasovaci_na_zobrazovaci(node_owner))
			table.insert(formspec, "]button[8,1.25;3,0.75;setowner;"..S("Set").."]")
			if stn_owner ~= nil then
				table.insert(formspec, "label[11.25,1.65;"..S("Station Operator: ")..prihlasovaci_na_zobrazovaci(stn_owner).."]")
			end
		end
	else
		table.insert(formspec, "label[0.5,0.6;"..S("Timetable for current station (all lines)").."]")
	end

	table.insert(formspec, "tablecolumns[text,align=right,tooltip=Line;text,width=12,tooltip=Terminus;text,tooltip=Track;color,span=1;text,tooltip=Status;color,span=1;text,tooltip=Train Name]"..
		"table[0.5,2.25;11,5;linka;")
	for i, r in ipairs(custom_state.linevars) do
		if i > 1 then
			table.insert(formspec, ",")
		end
		table.insert(formspec, r.line_fs..","..r.target_fs..","..r.track_fs..","..r.status_fs..",#cccccc,"..r.train_name_fs)
	end
	table.insert(formspec, ifthenelse(custom_state.linevar > 0, ";"..custom_state.linevar.."]", ";]"))
	table.insert(formspec, "tablecolumns[text,align=right;text;text]"..
		"table[12.5,2.25;7,8.75;zastavka;")
	if custom_state.stops[1] ~= nil then
		local selected_stop_index = custom_state.stop
		if custom_state.stops[selected_stop_index] == nil then
			selected_stop_index = 1
		end
		local base_dep
		for i, r in ipairs(custom_state.stops) do
			if i > 1 then
				table.insert(formspec, ",")
			end
			if i >= selected_stop_index then
				if i == selected_stop_index then
					base_dep = assert(r.dep)
					table.insert(formspec, "0,")
				else
					table.insert(formspec, (r.dep - base_dep)..",")
				end
			else
				table.insert(formspec, "    ,")
			end
			table.insert(formspec, r.name_fs..","..r.track_fs)
		end
		table.insert(formspec, ";"..selected_stop_index.."]")
	else
		table.insert(formspec, ";]") -- empty list
	end
	table.insert(formspec, "button_exit[18.75,0.3;0.75,0.75;close;X]")
	if custom_state.message ~= "" then
		table.insert(formspec, "label[5.25,11.35;"..F(custom_state.message).."]")
	end
	table.insert(formspec, "button[0.5,11;4.5,0.75;refresh;"..S("Update next departures...").."]")
	return table.concat(formspec)
end

local function jr_formspec_callback(custom_state, player, formname, fields)
	if fields.dopravna then
		local new_stn = tonumber(fields.dopravna)
		if new_stn ~= nil and new_stn ~= custom_state.stn and custom_state.stns[new_stn] ~= nil then
			custom_state.stn = new_stn
			local current_track = custom_state.tracks[custom_state.track] or ""
			local current_linevar_info = custom_state.linevars[custom_state.linevar]
			jr_refresh_tracks(custom_state, current_track)
			if current_linevar_info ~= nil then
				jr_refresh_linevars(custom_state, current_linevar_info.linevar, current_linevar_info.linevar_index)
			else
				jr_refresh_linevars(custom_state)
			end
			jr_refresh_stops(custom_state, custom_state.stns[new_stn].stn)
			jr_refresh_departure(custom_state)
			return get_jr_formspec(custom_state)
		end
	end
	if fields.kolej then
		local new_track = tonumber(fields.kolej)
		if new_track ~= nil and new_track ~= custom_state.track and custom_state.tracks[new_track] ~= nil then
			custom_state.track = new_track
			local current_linevar_info = custom_state.linevars[custom_state.linevar]
			if current_linevar_info ~= nil then
				jr_refresh_linevars(custom_state, current_linevar_info.linevar, current_linevar_info.linevar_index)
			else
				jr_refresh_linevars(custom_state)
			end
			jr_refresh_stops(custom_state, custom_state.stns[custom_state.stn].stn)
			jr_refresh_departure(custom_state)
			return get_jr_formspec(custom_state)
		end
	end
	if fields.setowner and custom_state.pos ~= nil and is_jr_node_name(core.get_node(custom_state.pos).name) then
		local meta = core.get_meta(custom_state.pos)
		local jm = fields.owner -- ch_core.jmeno_na_existujici_prihlasovaci(fields.owner)
		if jm ~= nil then
			meta:set_string("owner", jm)
		else
			core.chat_send_player(custom_state.player_name, "*** Postava '"..fields.owner.."' neexistuje!")
		end
		return get_jr_formspec(custom_state)
	end
	if fields.linka then
		local event = core.explode_table_event(fields.linka)
		local new_line
		if event.type == "CHG" or event.type == "DCL" then
			new_line = tonumber(event.row)
		end
		if new_line ~= nil and new_line ~= custom_state.linevar then
			local new_linevar_info = custom_state.linevars[new_line]
			if new_linevar_info ~= nil then
				jr_refresh_linevars(custom_state, new_linevar_info.linevar, new_linevar_info.linevar_index)
			else
				jr_refresh_linevars(custom_state)
			end
			jr_refresh_stops(custom_state, custom_state.stns[custom_state.stn].stn)
			jr_refresh_departure(custom_state)
			return get_jr_formspec(custom_state)
		end
	end
	if fields.zastavka then
		local event = core.explode_table_event(fields.zastavka)
		if event.type == "CHG" or event.type == "DCL" then
			local new_stop = tonumber(event.row)
			if new_stop ~= nil and new_stop ~= custom_state.stop and custom_state.stops[new_stop] ~= nil then
				custom_state.stop = new_stop
				if event.type == "DCL" then
					jr_refresh_departure(custom_state)
				end
				return get_jr_formspec(custom_state)
			end
		end
	end
	if fields.refresh then
		jr_refresh_departure(custom_state)
		return get_jr_formspec(custom_state)
	end
end

function advtrains.lines.show_jr_formspec(player, pos, stn, track, linevar, stop_stn, force_unprivileged)
	assert(core.is_player(player))
	local custom_state = {
		player_name = player:get_player_name(),
		stn = 1,
		stns = {all_stations_record},
		track = 1,
		tracks = {""},
		linevar = 0,
		linevars = {},
		stop = 0,
		stops = {},
		message = "",
		force_unprivileged = force_unprivileged,
	}
	if pos ~= nil then
		custom_state.pos = pos
	end
	jr_refresh_stns(custom_state, stn)
	jr_refresh_tracks(custom_state, track)
	jr_refresh_linevars(custom_state, linevar)
	if stop_stn == nil then
		stop_stn = custom_state.stns[custom_state.stn].stn
	end
	jr_refresh_stops(custom_state, stop_stn)
	jr_refresh_departure(custom_state)

	-- show formspec:
	return ch_core.show_formspec(player, "advtrains_line_automation:jizdni_rad",
		get_jr_formspec(custom_state), jr_formspec_callback, custom_state, {})
end
