--------------------------------------------------------------------------------
-- Assign train IDs
--------------------------------------------------------------------------------
if not Metrostroi.WagonID then
    Metrostroi.WagonID = 1
end
function Metrostroi.NextWagonID()
    local id = Metrostroi.WagonID
    Metrostroi.WagonID = Metrostroi.WagonID + 1
    if Metrostroi.WagonID > 99 then Metrostroi.WagonID = 1 end
    return id
end

Metrostroi.UsedNumbers = Metrostroi.UsedNumbers or {}
hook.Add("EntityRemoved","WagonNumberRemove",Metrostroi.RemoveNumber)
function Metrostroi.RemoveNumber(ent)
    if IsValid(ent) and ent.WagonNumber then
        local typ = ent.SubwayTrain and ent.SubwayTrain.Type or ent:GetClass()
        local tbl = Metrostroi.UsedNumbers[typ]
        if not tbl then return end
        print(Format("Removed number %05d to %s(%04d %s)",ent.WagonNumber,ent:GetClass(),ent:EntIndex(),typ))
        tbl[ent.WagonNumber] = nil
    end
end
function Metrostroi.GenerateNumber(train,tbl,func,retry)
    Metrostroi.RemoveNumber(train)
    if not tbl or not IsValid(train) then return 0 end
    local typ = train.SubwayTrain and train.SubwayTrain.Type or train:GetClass()
    if not Metrostroi.UsedNumbers[typ] then Metrostroi.UsedNumbers[typ] = {} end
    for i=1,1000 do
        local range = train.NumberRangesID and tbl[train.NumberRangesID] or tbl[math.random(1,#tbl)]
        local number
        if range[1]==true then
            local tblN = range[2]
            number = tblN[math.random(1,#tblN)]
        else
            number = math.random(range[1],range[2])
        end
        if func then number = func(train,number) or number end
        if number and number~=true and not Metrostroi.UsedNumbers[typ][number] then
            Metrostroi.UsedNumbers[typ][number] = true
            print(Format("Assigned number %05d to %s(%04d %s)",number,train:GetClass(),train:EntIndex(),typ))
            return number,range[3]
        end
    end
    if retry then
        ErrorNoHalt(Format("Metrostroi: Error generating number for train %s! Second try was failed.\n",train:GetClass()))
        return 0
    else
        ErrorNoHalt(Format("Metrostroi: Error generating number for train %s! Clearing wagon numbers table...\n",train:GetClass()))
        Metrostroi.UsedNumbers = {}
        return Metrostroi.GenerateNumber(train,tbl,func,true)
    end
end

if not Metrostroi.EquipmentID then
    Metrostroi.EquipmentID = 1
end
function Metrostroi.NextEquipmentID()
    local id = Metrostroi.EquipmentID
    Metrostroi.EquipmentID = Metrostroi.EquipmentID + 1
    return id
end


--------------------------------------------------------------------------------
-- Custom drop to floor that only checks origin and not bounding box
--------------------------------------------------------------------------------
function Metrostroi.DropToFloor(ent)
    local result = util.TraceLine({
        start = ent:GetPos(),
        endpos = ent:GetPos() - Vector(0,0,256),
        mask = -1,
        filter = { ent },
    })
    if result.Hit then ent:SetPos(result.HitPos) end
end




--------------------------------------------------------------------------------
-- Get random number that is same over a period of 1 minute
--------------------------------------------------------------------------------
local prediods = {}
local periodNumbers = {}
local randomPeriodStart = 0
local randomPeriodNumber = math.random()
function Metrostroi.PeriodRandomNumber(typ)
    typ = typ or 0
    if not prediods[typ] or (CurTime() - prediods[typ]) > 60 then
        periodNumbers[typ] = math.random()
    end

    -- Refresh the period
    prediods[typ] = CurTime()

    -- Return number
    return periodNumbers[typ]
end




--------------------------------------------------------------------------------
-- Joystick controls
-- Author: HunterNL
--------------------------------------------------------------------------------
if not Metrostroi.JoystickValueRemap then
    Metrostroi.JoystickValueRemap = {}
    Metrostroi.JoystickSystemMap = {}
end

function Metrostroi.RegisterJoystickInput (uid,analog,desc,min,max)
    if not joystick then
        Error("Joystick Input registered without joystick addon installed, get it at https://github.com/MattJeanes/Joystick-Module")
    end
    --If this is only called in a JoystickRegister hook it should never even happen

    if #uid > 20 then
        print("Metrostroi Joystick UID too long, trimming")
        local uid = string.Left(uid,20)
    end


    local atype
    if analog then
        atype = "analog"
    else
        atype = "digital"
    end

    local temp = {
        uid = uid,
        type = atype,
        description = desc,
        category = "Metrostroi" --Just Metrostroi for now, seperate catagories for different trains later?
        --Catergory is also checked in subway base, don't just change
    }


    --Joystick addon's build-in remapping doesn't work so well, so we're doing this instead
    if min ~= nil and max ~= nil and analog then
        Metrostroi.JoystickValueRemap[uid]={min,max}
    end

    jcon.register(temp)
end

-- Wrapper around joystick get to implement our own remapping
function Metrostroi.GetJoystickInput(ply,uid)
    local remapinfo = Metrostroi.JoystickValueRemap[uid]
    local jvalue = joystick.Get(ply,uid)
    if remapinfo == nil then
        return jvalue
    elseif jvalue ~= nil then
        return math.Remap(joystick.Get(ply,uid),0,255,remapinfo[1],remapinfo[2])
    else
        return jvalue
    end
end




--------------------------------------------------------------------------------
-- Player meta table magic
-- Author: HunterNL
--------------------------------------------------------------------------------
local Player = FindMetaTable("Player")

function Player:CanDriveTrains()
    return IsValid(self:GetWeapon("train_kv_wrench")) or self:IsAdmin()
end

function Player:GetTrain()
    local seat = self:GetVehicle()
    if IsValid(seat) then
        return seat:GetNW2Entity("TrainEntity"),seat
    end
end

hook.Add("PlayerEnteredVehicle","MetrostroiPlayerTrain",function(ply,veh)
    ply.InMetrostroiTrain = IsValid(veh:GetNW2Entity("TrainEntity")) and veh:GetNW2Entity("TrainEntity")
end)
hook.Add("PlayerLeaveVehicle","MetrostroiPlayerTrain",function(ply,veh)
    ply.InMetrostroiTrain = false
end)


--------------------------------------------------------------------------------
-- Train count
--------------------------------------------------------------------------------
function Metrostroi.TrainCount(...)
    local classnames = {...}
    if #classnames == 1 then
        return #ents.FindByClass(classnames[1])
    end

    local N = 0
    for k,v in pairs(#classnames > 0 and classnames or Metrostroi.TrainClasses) do
        if not baseclass.Get(v).SubwayTrain then continue end
        N = N + #ents.FindByClass(v)
    end
    return N
end

function Metrostroi.TrainCountOnPlayer(ply ,...)
    local classnames = {...}
    local typ
    if type(classnames[1]) == "number" then
        typ = classnames[1]
        classnames = {}
    end
    if CPPI then
        local N = 0
        for k,v in pairs(#classnames > 0 and classnames or Metrostroi.TrainClasses) do
            if not baseclass.Get(v).SubwayTrain then continue end
            local ents = ents.FindByClass(v)
            for k2,v2 in pairs(ents) do
                if ply == v2:CPPIGetOwner() and (not typ or v2.SubwayTrain.WagType == typ) then
                    N = N + 1
                end
            end
        end
        return N
    end
    return 0
end

concommand.Add("metrostroi_train_count", function(ply, _, args)
    print("Trains on server: "..Metrostroi.TrainCount())
    if CPPI then
        local N = {}
        for k,v in pairs(Metrostroi.TrainClasses) do
            if  v == "gmod_subway_base" then continue end
            local ents = ents.FindByClass(v)
            for k2,v2 in pairs(ents) do
                N[v2:CPPIGetOwner() or "(disconnected)"] = (N[v2:CPPIGetOwner() or "(disconnected)"] or 0) + 1
            end
        end
        for k,v in pairs(N) do
            print(k,"Trains count: "..v)
        end
    end
end)




--------------------------------------------------------------------------------
-- Simple hack to get a driving schedule
--------------------------------------------------------------------------------
concommand.Add("metrostroi_schedule", function(ply, _, args)
    if not IsValid(ply) then return end
    local train = ply:GetTrain()
    local pos = Metrostroi.TrainPositions[train]
    --if pos and pos[1] then
        local line = tonumber(args[1])
        local path = tonumber(args[2]) or 1
        local starts = tonumber(args[3])
        local ends = tonumber(args[4])
        train.Schedule = Metrostroi.GenerateSchedule("Line"..line.."_Platform"..path,starts,ends)
        if train.Schedule then
            train:SetNW2Int("_schedule_id",train.Schedule.ScheduleID)
            train:SetNW2Int("_schedule_duration",train.Schedule.Duration)
            train:SetNW2Int("_schedule_interval",train.Schedule.Interval)
            train:SetNW2Int("_schedule_N",#train.Schedule)
            train:SetNW2Int("_schedule_path",path)
            for k,v in ipairs(train.Schedule) do
                train:SetNW2Int("_schedule_"..k.."_1",v[1])
                train:SetNW2Int("_schedule_"..k.."_2",v[2])
                train:SetNW2Int("_schedule_"..k.."_3",v[3])
                train:SetNW2Int("_schedule_"..k.."_4",v[4])
                train:SetNW2String("_schedule_"..k.."_5",Metrostroi.StationNames[v[1]] or v[1])
            end
        end
    --end
end)




--------------------------------------------------------------------------------
-- Failures related stuff
--------------------------------------------------------------------------------
concommand.Add("metrostroi_failures", function(ply, _, args)
    local i = 0
    for _,class in pairs(Metrostroi.TrainClasses) do
        local trains = ents.FindByClass(class)
        for _,train in pairs(trains) do
            timer.Simple(0.1+i*0.2,function()
                print("Failures for train "..train:EntIndex())
                train:TriggerInput("FailSimStatus",1)
            end)
            i = i + 1
        end
    end
end)

concommand.Add("metrostroi_fail", function(ply, _, args)
    local trainList = {}
    if not IsValid(ply) then
        for _,class in pairs(Metrostroi.TrainClasses) do
            local trains = ents.FindByClass(class)
            for _,train in pairs(trains) do
                table.insert(trainList,train)
            end
        end
    else
        local train = ply:GetTrain()
        if IsValid(train) then
            train:UpdateWagonList()
            for k,v in pairs(train.WagonList) do
                trainList[k] = v
            end
        end
    end

    local train = table.Random(trainList)
    if train then
        if IsValid(ply) then
            ply:PrintMessage(HUD_PRINTCONSOLE,"Generating random failure in your train!")
            print("Player "..tostring(ply).." generated random failure in train "..train:EntIndex())
        else
            print("Generating random failure in train "..train:EntIndex())
        end
        train:TriggerInput("FailSimFail",1)
    else
        if IsValid(ply) then
            ply:PrintMessage(HUD_PRINTCONSOLE,"You must be inside a train to generate a failure!")
        end
    end
end)

concommand.Add("metrostroi_fail_reset", function(ply, _, args)
    local trainList = {}
    if not IsValid(ply) then
        for _,class in pairs(Metrostroi.TrainClasses) do
            local trains = ents.FindByClass(class)
            for _,train in pairs(trains) do
                table.insert(trainList,train)
            end
        end
    else
        local train = ply:GetTrain()
        if IsValid(train) then
            train:UpdateWagonList()
            for k,v in pairs(train.WagonList) do
                trainList[k] = v
            end
        end
    end

    if #trainList > 0 then
        if IsValid(ply) then
            ply:PrintMessage(HUD_PRINTCONSOLE,"Reset all failures in your train!")
            print("Player "..tostring(ply).." reset all failures in train "..trainList[1]:EntIndex())
        else
            print("Reset all failures in train "..trainList[1]:EntIndex())
        end
        for _,v in pairs(trainList) do v:TriggerInput("FailSimReset") end
    else
        if IsValid(ply) then
            ply:PrintMessage(HUD_PRINTCONSOLE,"You must be inside a train to reset all failures!")
        end
    end
end)

concommand.Add("metrostroi_wire", function(ply, _, args)
    local trainList = {}
    if not IsValid(ply) then
        for _,class in pairs(Metrostroi.TrainClasses) do
            local trains = ents.FindByClass(class)
            for _,train in pairs(trains) do
                table.insert(trainList,train)
            end
        end
    else
        local train = ply:GetTrain()
        if IsValid(train) then
            --train:UpdateWagonList()
            for k,v in pairs(train.WagonList) do
                trainList[k] = v
            end
        end
    end

    local train = table.Random(trainList)
    if train then
        if IsValid(ply) then
            args[1] = tonumber(args[1])
            if not args[1] then ply:PrintMessage(HUD_PRINTCONSOLE,"Argument must be a number") return end
            ply:PrintMessage(HUD_PRINTCONSOLE,"sets outside power in train wire"..args[1].."!")
            print("Player "..tostring(ply).." sets outside power in train "..args[1].." wire failure in train "..train:EntIndex())
        else
            print("sets outside power in train wire "..train:EntIndex())
        end
        --if train.WriteTrainWire then train:WriteTrainWire(args[1],1) end
        train.TrainWireOutside[args[1]] = 1
        --if train.WriteTrainWire then train:WriteTrainWire(args[1],1) end
    else
        if IsValid(ply) then
            ply:PrintMessage(HUD_PRINTCONSOLE,"You must be inside a train!")
        end
    end
end)
concommand.Add("metrostroi_wire_reset", function(ply, _, args)
    local trainList = {}
    if not IsValid(ply) then
        for _,class in pairs(Metrostroi.TrainClasses) do
            local trains = ents.FindByClass(class)
            for _,train in pairs(trains) do
                table.insert(trainList,train)
            end
        end
    else
        local train = ply:GetTrain()
        if IsValid(train) then
            --train:UpdateWagonList()
            for k,v in pairs(train.WagonList) do
                trainList[k] = v
            end
        end
    end

    if #trainList > 0 then
        if IsValid(ply) then
            ply:PrintMessage(HUD_PRINTCONSOLE,"reset wire outside power in train!")
            print("Player "..tostring(ply).." reset outside power in train ")
        else
            print("Reset outside power in trains ")
        end
        for _,v in pairs(trainList) do v.TrainWireOutside = {} end
    else
        if IsValid(ply) then
            ply:PrintMessage(HUD_PRINTCONSOLE,"You must be inside a train!")
        end
    end
end)
concommand.Add("metrostroi_can", function(ply, _, args)
    if not IsValid(ply) or not IsValid(ply:GetVehicle()) then return end
    local train = ply:GetVehicle():GetNW2Entity("TrainEntity")
    if not IsValid(train) then return end
    if not args[4] then return end
    local system = args[1]
    local id = tonumber(args[2])
    local name = args[3]
    local value = args[4]
    if args[4] == "curtime" then value = CurTime() end
    if args[4] == "true" then value = true end
    if args[4] == "false" then value = false end
    if tonumber(args[4]) then value = tonumber(args[4]) end
    local srcid = tonumber(args[5])
    ply:PrintMessage(HUD_PRINTCONSOLE,"Hacking CAN!")
    print(Format("Player %s hack CAN(%s->%s:%s %s=%s)",ply,srcid,system,id,name,value))
    train:CANWrite("Hacker",srcid or train:GetWagonNumber(),system,id,name,value)
end)

--------------------------------------------------------------------------------
-- Electric consumption stats
--------------------------------------------------------------------------------
-- Load total kWh
timer.Create("Metrostroi_TotalkWhTimer",5.00,0,function()
    file.Write("metrostroi_data/total_kwh.txt",Metrostroi.TotalkWh or 0)
end)
Metrostroi.TotalkWh = Metrostroi.TotalkWh or tonumber(file.Read("metrostroi_data/total_kwh.txt") or "") or 0
Metrostroi.TotalRateWatts = Metrostroi.TotalRateWatts or 0
Metrostroi.Voltage = 750
Metrostroi.Voltages = Metrostroi.Voltages or {}
Metrostroi.Currents = Metrostroi.Currents or {}
Metrostroi.Current = 0
Metrostroi.PeopleOnRails = 0
Metrostroi.VoltageRestoreTimer = 0

local prevTime
hook.Add("Think", "Metrostroi_ElectricConsumptionThink", function()
    -- Change in time
    prevTime = prevTime or CurTime()
    local deltaTime = (CurTime() - prevTime)
    prevTime = CurTime()

    -- Calculate total rate
    Metrostroi.TotalRateWatts = 0
    Metrostroi.Current = 0
    for k,v in pairs(Metrostroi.Currents) do Metrostroi.Currents[k] = 0 end
    local bogeys = ents.FindByClass("gmod_train_bogey")
    for _,bogey in pairs(bogeys) do
        if bogey.Feeder then
            Metrostroi.Currents[bogey.Feeder] = Metrostroi.Currents[bogey.Feeder] + bogey.DropByPeople
        else
            Metrostroi.Current = Metrostroi.Current + bogey.DropByPeople
        end
    end
    for _,class in pairs(Metrostroi.TrainClasses) do
        local trains = ents.FindByClass(class)
        for _,train in pairs(trains) do
            if train.Electric then
                Metrostroi.TotalRateWatts = Metrostroi.TotalRateWatts + math.max(0,(train.Electric.EnergyChange or 0))
                local current = math.max(0,(train.Electric.Itotal or 0)) -  math.max(0,(train.Electric.Iexit or 0))
                local feeder = false
                local fB = train.FrontBogey
                local rB = train.RearBogey
                if IsValid(fB) then
                    if fB.Feeder then
                        Metrostroi.Currents[fB.Feeder] = Metrostroi.Currents[fB.Feeder] + fB.DropByPeople+current*0.4
                        feeder = true
                    else
                        Metrostroi.Current = Metrostroi.Current + fB.DropByPeople
                    end
                end
                if IsValid(rB) then
                    if rB.Feeder then
                        Metrostroi.Currents[rB.Feeder] = Metrostroi.Currents[rB.Feeder] + rB.DropByPeople+current*0.4
                        feeder = true
                    else
                        Metrostroi.Current = Metrostroi.Current + rB.DropByPeople
                    end
                end
                if not feeder then Metrostroi.Current = Metrostroi.Current + current*0.4 end
            end
        end
    end
    -- Ignore invalid values
    if Metrostroi.TotalRateWatts > 1e8 then Metrostroi.TotalRateWatts = 0 end
    if Metrostroi.TotalRateWatts > 0 then
        -- Calculate total kWh
        Metrostroi.TotalkWh = Metrostroi.TotalkWh + (Metrostroi.TotalRateWatts/(3.6e6))*deltaTime
    end
    -- Calculate total resistance of people on rails and current flowing through
    --local Rperson = 0.613
    --local Iperson = Metrostroi.Voltage / (Rperson/(Metrostroi.PeopleOnRails + 1e-9))
    --Metrostroi.Current = Metrostroi.Current + Iperson

    -- Check if exceeded global maximum current
    if Metrostroi.Current > GetConVar("metrostroi_current_limit"):GetInt() then
        Metrostroi.VoltageRestoreTimer = CurTime() + 7.0
        print(Format("[!] Power feed protection tripped: current peaked at %.1f A",Metrostroi.Current))
    end

    local voltage = math.max(0,GetConVar("metrostroi_voltage"):GetInt())

    -- Calculate new voltage
    local Rfeed = 0.03 --25
    Metrostroi.Voltage = voltage - Metrostroi.Current*Rfeed
    if CurTime() < Metrostroi.VoltageRestoreTimer then Metrostroi.Voltage = 0 end
    for i in pairs(Metrostroi.Voltages) do
        Metrostroi.Voltages[i] = math.max(0,voltage - Metrostroi.Currents[i]*Rfeed)
    end
    --print(Format("%5.1f v %.0f A",Metrostroi.Voltage,Metrostroi.Current))
end)

concommand.Add("metrostroi_electric", function(ply, _, args) -- (%.2f$) Metrostroi.GetEnergyCost(Metrostroi.TotalkWh),
    local m = Format("[%25s] %010.3f kWh, %.3f kW (%5.1f v, %4.0f A)","<total>",
        Metrostroi.TotalkWh,Metrostroi.TotalRateWatts*1e-3,
        Metrostroi.Voltage,Metrostroi.Current)
    if IsValid(ply)
    then ply:PrintMessage(HUD_PRINTCONSOLE,m)
    else print(m)
    end

    if CPPI then
        local U = {}
        local D = {}
        for _,class in pairs(Metrostroi.TrainClasses) do
            local trains = ents.FindByClass(class)
            for _,train in pairs(trains) do
                local owner = "(disconnected)"
                if train:CPPIGetOwner() then
                    owner = train:CPPIGetOwner():GetName()
                end
                if train.Electric then
                    U[owner] = (U[owner] or 0) + train.Electric.ElectricEnergyUsed
                    D[owner] = (D[owner] or 0) + train.Electric.ElectricEnergyDissipated
                end
            end
        end
        for player,_ in pairs(U) do --, n=%.0f%%
            --local m = Format("[%20s] %08.1f KWh (lost %08.1f KWh)",player,U[player]/(3.6e6),D[player]/(3.6e6)) --,100*D[player]/U[player]) --,D[player])
            local m = Format("[%25s] %010.3f kWh (%.2f$)",player,U[player]/(3.6e6),Metrostroi.GetEnergyCost(U[player]/(3.6e6)))
            if IsValid(ply)
            then ply:PrintMessage(HUD_PRINTCONSOLE,m)
            else print(m)
            end
        end
    end
end)

timer.Create("Metrostroi_ElectricConsumptionTimer",0.5,0,function()
    if CPPI then
        local U = {}
        local D = {}
        for _,class in pairs(Metrostroi.TrainClasses) do
            local trains = ents.FindByClass(class)
            for _,train in pairs(trains) do
                local owner = train:CPPIGetOwner()
                if owner and (train.Electric) then
                    U[owner] = (U[owner] or 0) + train.Electric.ElectricEnergyUsed
                    D[owner] = (D[owner] or 0) + train.Electric.ElectricEnergyDissipated
                end
            end
        end
        for player,_ in pairs(U) do
            if IsValid(player) then
                player:SetDeaths(10*U[player]/(3.6e6))
                player.MUsedEnergy = (player.MUsedEnergy or 0) + 10*U[player]/(3.6e6)
            end
        end
    end
end)

local function murder(v)
    local positions = Metrostroi.GetPositionOnTrack(v:GetPos())
    for k2,v2 in pairs(positions) do
        local y,z = v2.y,v2.z
        y = math.abs(y)

        local y1 = 0.91-0.10
        local y2 = 1.78 ---0.50
        if (y > y1) and (y < y2) and (z < -1.70) and (z > -1.72) and (Metrostroi.Voltage > 40) then
            local pos = v:GetPos()

            util.BlastDamage(v,v,pos,64,3.0*Metrostroi.Voltage)

            local effectdata = EffectData()
            effectdata:SetOrigin(pos + Vector(0,0,-16+math.random()*(40+0)))
            util.Effect("cball_explode",effectdata,true,true)

            sound.Play("ambient/energy/zap"..math.random(1,3)..".wav",pos,75,math.random(100,150),1.0)
            Metrostroi.PeopleOnRails = Metrostroi.PeopleOnRails + 1

            --if math.random() > 0.85 then
                --Metrostroi.VoltageRestoreTimer = CurTime() + 7.0
                --print("[!] Power feed protection tripped: "..(tostring(v) or "").." died on rails")
            --end
        end
    end
end
--[[
timer.Create("Metrostroi_PlayerKillTimer",0.1,0,function()
    if true then return end
    Metrostroi.PeopleOnRails = 0
    for k,v in pairs(player.GetAll()) do
        murder(v)
    end
    for k,v in pairs(ents.FindByClass("npc_*")) do
        murder(v)
    end
end)
]]
timer.Remove("Metrostroi_PlayerKillTimer")


--------------------------------------------------------------------------------
-- Does current map have any sort of metrostroi support
--------------------------------------------------------------------------------
function Metrostroi.MapHasFullSupport(typ)
    if not typ then
        return (#Metrostroi.Paths > 0)
    elseif typ=="ars" then
        return next(Metrostroi.SignalEntitiesByName)
    elseif typ=="auto" then
        return Metrostroi.HaveAuto
    elseif typ=="sbpp" then
        return Metrostroi.HaveSBPP
    elseif typ=="pa" then
        return next(Metrostroi.PAMConfTest)
    end
end

concommand.Add("metrostroi_insert_signs", function(ply,_,args)
    if IsValid(ply) then error("Metrostroi: This command can be run only from server console!") end
    local MAP_NAME = game.GetMap() --"gm_mus_loopline_a3"
    local MAP_VERSION = args and args[1] or ""

    local commands = {Format("session_begin %s %s",MAP_NAME,MAP_VERSION)}

    local function createSign(pos,ang,model)
        table.insert(commands,Format("entity_create prop_static %s",pos))
        table.insert(commands,Format("entity_set_keyvalue prop_static %s \"angles\" \"%s\"",pos,ang))--table.insert(commands,Format("entity_rotate_incremental prop_static %s %s",pos,ang))
        table.insert(commands,Format("entity_set_keyvalue prop_static %s \"model\" \"%s\"",pos,model))
    end

    for k,v in pairs(ents.FindByClass("gmod_track_signs")) do
        local data = v.SignModels[v.SignType-1]
        local left = v.Left
        local offset = Vector(0,v.YOffset,v.ZOffset)
        local model = data.model
        if left and not data.noleft then
            if model:find("_r.mdl") then
                model = model:Replace("_r.mdl","_l.mdl")
            else
                model = model:Replace("_l.mdl","_r.mdl")
            end
        end
        local RAND = math.random(-10,10)
        local pos = data.pos + offset
        local ang = data.angles
        if not data.noauto then pos = pos+Vector(0,0,RAND/5); ang = ang+Angle(0,0,RAND) end
        if left then pos = pos*Vector(1,-1,1) end
        if left and data.rotate then ang = ang-Angle(0,180,0) end

        createSign(v:LocalToWorld(pos),v:LocalToWorldAngles(ang),model)
    end

    table.insert(commands,"session_end")

    for k,v in pairs(commands) do
        local result = hammer.SendCommand(v)
        if result ~= "ok" then
            hammer.SendCommand("session_end")
            error(Format("Error \"%s\" on command %s(%d)",result,v,k))
        end
    end
end)
SafeRemoveEntity(Metrostroi.RTCamera)
function Metrostroi.GetCam()
    if not IsValid(Metrostroi.RTCamera) then
        Metrostroi.RTCamera = ents.Create( "point_camera" )
        Metrostroi.RTCamera:SetKeyValue( "GlobalOverride", 1 )
        Metrostroi.RTCamera:SetKeyValue( "fogEnable", 1 )
        Metrostroi.RTCamera:SetKeyValue( "fogStart", 1 )
        Metrostroi.RTCamera:SetKeyValue( "fogEnd", 4096  )
        Metrostroi.RTCamera:SetKeyValue( "fogColor", "0 0 0 127"     )
        Metrostroi.RTCamera:SetPos(Vector(0,0,-2^16))
        Metrostroi.RTCamera:SetNoDraw(true)
        Metrostroi.RTCamera:Activate()
        Metrostroi.RTCamera:Spawn()
        Metrostroi.RTCamera:Fire( "SetOff", "", 0.0 )
    end
    return Metrostroi.RTCamera
end
util.AddNetworkString("metrostroi_cam_update")
local function updateCam()
    timer.Simple(0,function()
        local cam = Metrostroi.GetCam()
        net.Start("metrostroi_cam_update")
            net.WriteUInt(cam:EntIndex(),16)
        net.Broadcast()
    end)
end
net.Receive("metrostroi_cam_update",updateCam)
updateCam()


function Metrostroi.FindNextStation(src,stationsPath,stations)
    -- Determine direction of travel
    --assert(src.path == dest.path)
    --local direction = src.x < dest.x
    print("start")

    local markersForNode = {}
    local sensorsForNode = {}
    local entities = ents.FindByClass("gmod_track_pa_marker")
    for k,v in pairs(entities) do
        if v.TrackPosition then
            local node = v.TrackPosition.node1
            if not markersForNode[node] then markersForNode[node] = {} end
            table.insert(markersForNode[node],v)
        end
    end

    local entities = ents.FindByClass("gmod_track_autodrive_plate")
    for k,v in pairs(entities) do
        --print(v.PlateType)
        if v.PlateType ~= METROSTROI_LSENSOR then continue end
        local results = Metrostroi.GetPositionOnTrack(v:GetPos(),v:GetAngles())
        --[[ for k,v in pairs(results) do
            print(k,v.x,v.node2.x)
        end--]]
        local pos = results[1]
        if pos then -- FIXME make it select proper path
            sensorsForNode[pos.node1] = sensorsForNode[pos.node1] or {}
            table.insert(sensorsForNode[pos.node1],v)

            -- A signal belongs only to a single track
            sensorsForNode[v] = pos
        end
    end
    -- Accumulate travel time
    local iter = 0
    local function scan(node,stations,path,trace,dist,branches)
        while node do
            local nextnode = path and node.next or not path and node.prev
            assert(iter < 1000000, "OH SHI~")
            iter = iter + 1
            if nextnode then
                local heightDist = (nextnode.pos.z-node.pos.z)*0.01905
                local slope = heightDist/node.length*1000
                if math.abs(slope)>2 then
                    if not trace.slopeCurrent then
                        trace.slopeCurrent = node.id
                        trace.slopeLength = node.length
                        trace.slope = {}
                    else
                        trace.slopeLength = trace.slopeLength+node.length
                    end
                    --print(dist,slope)
                    table.insert(trace.slope,{dist,slope})
                elseif trace.slopeCurrent then
                    local slm,slmx,slc = 0,0,0
                    --print(#trace.slope,trace.slopeLength)
                    local restbl = {}
                    for _,v in pairs(trace.slope) do
                        local sl = math.floor((v[2]+2.5)/5)*5
                        if slm~=sl then
                            local slmax = sl>0 and math.max(sl,slm) or math.min(sl,slm)
                            if (slc>2 or math.abs(slm-sl)>20) and (not restbl[#restbl] or restbl[#restbl][1] ~= slmax) then
                                table.insert(restbl,{slmax,slmx})
                                slmx = v[1]
                            end
                            if slmx==0 then slmx = v[1] end
                            slm = sl
                            slc = 0
                        else
                            slc = slc + 1
                        end
                        --print(k,v,)
                        --slc = slc+v[1]
                    end
                    if #restbl==0 and slm~=0  and math.abs(dist-slmx)>30 then table.insert(restbl,{slm,slmx,slc}) end
                    if #restbl~=0 then
                        table.insert(restbl,{0,dist})
                        for k,v in pairs(restbl) do
                            --print(k,v[1],v[2],v[3])
                            table.insert(trace.slopes,v)
                        end
                    end
                    --print("E---",node.pos)
                    trace.slopeCurrent = nil
                end
            end

            local targetStation = tonumber(stations[1])
            --local stationT = Metrostroi.Stations[tonumber(id)]
            if markersForNode[node] then
                for i,marker in ipairs(markersForNode[node]) do
                    if marker.PAType == 1 then
                        print("MAKR",marker.TrackX,marker.TrackPosition.x,marker.PAStationID,targetStation,marker.PAStationPath,stationsPath)
                        if marker.PAStationID ~= targetStation or tonumber(marker.PAStationPath) ~= stationsPath then return end
                        table.remove(stations,1)
                        --if marker.PAStationCorrection then print(targetStation) end
                        local distance = dist+(marker.TrackPosition.x-node.x)-(marker.PAStationCorrection or 0)
                        local lastSens,nearSens
                        for s=0,3 do
                            if not trace.sensors[#trace.sensors-s] or distance-trace.sensors[#trace.sensors-s] > 150 then break end
                            lastSens = #trace.sensors-s
                        end
                        table.insert(trace.stations,{
                            id=targetStation,
                            path=stationsPath,
                            pos=distance,
                            isHorlift = marker.PAStationHorlift,
                            hasSwitches = marker.PAStationHasSwtiches,
                            rightDoors = marker.PAStationRightDoors,
                            name = marker.PAStationName,
                            isLast = marker.PALastStation,
                            isInWrong = marker.PAWrongPath,
                            name_last = marker.PALastStationName,
                            dist_last_start = marker.PADeadlockStart and distance+marker.PADeadlockStart,
                            dist_last_end = marker.PADeadlockEnd and distance+marker.PADeadlockEnd,
                            linkedSensor = lastSens,
                        })
                        --print("MAKR GOOD",marker.TrackX,marker.TrackPosition.x,targetStation)
                        break
                    end
                end
            end
            if Metrostroi.SignalEntitiesForNode[node] then
                for k,signal in pairs(Metrostroi.SignalEntitiesForNode[node]) do
                    if signal.TrackDir ~= path then continue end
                    table.insert(trace.signals,{signal.Name,dist+(signal.TrackX-node.x)--[[ signal.TrackPosition.x--]] })
                end
            end
            if sensorsForNode[node] then
                local sensor = sensorsForNode[node][1]
                local x = sensorsForNode[sensor].x
                --print("SENS",node,dist,dist+(x-node.x))
                table.insert(trace.sensors,dist+(x-node.x)--[[ sensor.TrackPosition.x--]] )
            end
            dist = dist+node.length
            if node.branches then
                for k,v in pairs(node.branches) do
                    if branches[v[2]] or v[2].path == src.path then continue end
                    branches[v[2]] = true
                    local result = scan(v[2],table.Copy(stations),true,table.Copy(trace),dist,branches) or scan(v[2],table.Copy(stations),false,table.Copy(trace),dist,branches)
                    if result and #result[3] == 0 then return result end
                end
            end
            --[=[if node.branches and not branches[node.branches[1]] and node.branches[1][2].path ~= src.path then
                branches[node.branches[1]] = true
                local result = scan(node,table.Copy(stations),true,table.Copy(trace),dist,branches) or scan(node,table.Copy(stations),false,table.Copy(trace),dist,branches)
                if result and #stations == 0 then return result end
            end
            if node.branches and not node.branches[2] and branches[node.branches[2]] and node.branches[2][2].path ~= src.path then
                branches[node.branches[2]] = true
                local result = scan(node,table.Copy(stations),true,table.Copy(trace),dist,branches) or scan(node,table.Copy(stations),false,table.Copy(trace),dist,branches)
                if result and #stations == 0 then return result end
            end]=]
            node = nextnode
            if not node then break end
        end
        if #stations == 0 then print(debug.traceback()) end
        return #stations == 0 and {trace,dist,stations}
    end

    return scan(src,stations,true,{signals={},stations={},sensors={},slopes={}},0,{}) or scan(src,stations,false,{signals={},stations={},sensors={},slopes={}},0,{})
end
concommand.Add("metrostroi_pam_genconfig", function(ply, _, args)
    if not IsValid(ply) or not ply:IsAdmin() then return end

    if args[1] == "clear" then
        ply:PrintMessage(HUD_PRINTCONSOLE,"Cleared!")
        Metrostroi.PAMConfTest = {}
        return
    end

    local line = tonumber(table.remove(args,1) or false)
    local path = tonumber(table.remove(args,1) or false)
    if (not line or not path) or #args == 0 then
        ply:PrintMessage(HUD_PRINTCONSOLE,"Bad metrostroi_pam_genconfig use.\nmetrostroi_pam_genconfig line path station1 ... stationN-1 stationN")
        return
    end

    local badFind = false
    for k,id in pairs(args) do
        id = tonumber(id)
        if not id--[[  or not Metrostroi.Stations[id]--]]  then
            badFind = id
            break
        end
    end

    if badFind then
        ply:PrintMessage(HUD_PRINTCONSOLE,Format("Check station id %s!",badFind))
        return
    end

    -- Print interesting information
    local results = Metrostroi.GetPositionOnTrack(ply:GetPos(),ply:GetAimVector():Angle())
    for k,v in pairs(results) do
        --ply:PrintMessage(HUD_PRINTCONSOLE,Format("\t[%d] Path #%d, ID #%d: (%.2f x %.2f x %.2f) m  Facing %s",k,v.path.id,v.node1.id,v.x,v.y,v.z,v.forward and "forward" or "v.node1"))
        local result = Metrostroi.FindNextStation(v.node1,path,args)
        if not result then
            ply:PrintMessage(HUD_PRINTCONSOLE,Format("Config not generated! Can't find all stations"))
            return
        end
        PrintTable(result)
        if not Metrostroi.PAMConfTest then
            Metrostroi.PAMConfTest = {}
        end
        if not Metrostroi.PAMConfTest[line] then
            Metrostroi.PAMConfTest[line] = {}
        end
        --if not Metrostroi.PAMConfTest[line][path] then
            Metrostroi.PAMConfTest[line][path] = result
            ply:PrintMessage(HUD_PRINTCONSOLE,"Generated!")
        --end
        Metrostroi.PARebuildStations()
        --print(distance)
    end
end)
function Metrostroi.PARebuildStations()
    Metrostroi.PAMStations = {}
    Metrostroi.LineCount = 1
    for lineID,line in pairs(Metrostroi.PAMConfTest) do
        if type(lineID) ~= "number" then continue end
        Metrostroi.LineCount = math.max(Metrostroi.LineCount)
        for _,path in ipairs(line) do
            for _,station in ipairs(path[1].stations) do
                if not station.id then continue end
                if not Metrostroi.PAMStations[lineID] then Metrostroi.PAMStations[lineID] = {} end
                if not Metrostroi.PAMStations[lineID][station.id] then
                    Metrostroi.PAMStations[lineID][station.id] =table.insert(Metrostroi.PAMStations[lineID],station)
                end
            end
        end
    end
end

concommand.Add("metrostroi_pam_add_station", function(ply, _, args)
    if not IsValid(ply) or not ply:IsAdmin() then return end

    local line = tonumber(table.remove(args,1) or false)
    local path = tonumber(table.remove(args,1) or false)
    local station = tonumber(table.remove(args,1) or false)
    local dist = tonumber(table.remove(args,1) or false)
    if (not line or not path or not station) then
        ply:PrintMessage(HUD_PRINTCONSOLE,"Bad metrostroi_pam_add_station use.\nmetrostroi_pam_add_station line path station dist")
        return
    end
    if not Metrostroi.PAMConfTest[line][path] then
        ply:PrintMessage(HUD_PRINTCONSOLE,"Generate config first!")
        return
    end
    local PA
    if not dist then
        local train = ply:GetTrain()
        if IsValid(train) and train.PAM and train.PAM.Distance then
            dist = train.PAM.Distance
            PA = train.PAM
        end
    end
    if not dist then
        ply:PrintMessage(HUD_PRINTCONSOLE,"Bad metrostroi_pam_add_station use.\nCan't get dist, because it's not entered and not found train with PAM")
        return
    end
    local tbl = Metrostroi.PAMConfTest[line][path][1].stations
    local badFind = false
    for k,id in ipairs(tbl) do
        if id.id == station then
            if math.abs(id.pos-dist)>20 then
                ply:PrintMessage(HUD_PRINTCONSOLE,Format("Too big delta, pos %.1f station %d station pos %.1f delta %.1f",dist,station,id.pos,id.pos-dist))
                return
            end
            ply:PrintMessage(HUD_PRINTCONSOLE,Format("Set pos %.1f to station %d, prev dist %.1f",dist,station,id.pos))
            id.pos = dist
            if PA then
                PA.StationTable = PA:FindStation(PA.Line,PA.StationTable.path,PA.StationTable.id)
            end
            table.sort(tbl, function(a,b) return a.pos < b.pos end)
            return
        end
    end
    --[[ table.insert(Metrostroi.PAMConfTest[line][path][1].stations,{station,dist})
    table.sort(tbl, function(a,b) print(a,b,a[2],b[2]) return a[2] < b[2] end)
    Metrostroi.PARebuildStations()
    ply:PrintMessage(HUD_PRINTCONSOLE,"Setted!")--]]
end)
