/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/

/*
    MaSzyna EU07 locomotive simulator
    Copyright (C) 2001-2004  Marcin Wozniak and others

*/

#include "stdafx.h"

#include "event.h"
#include "simulation.h"
#include "Globals.h"
#include "Timer.h"
#include "Logs.h"
#include "World.h"
#include "messaging.h"

TEvent::TEvent( std::string const &m ) :
                       asNodeName( m )
{
    if( false == m.empty() ) {
        // utworzenie niejawnego odczytu komórki pamięci w torze
        Type = tp_GetValues;
    }
    for( int i = 0; i < 13; ++i ) {
        Params[ i ].asPointer = nullptr;
    }
};

TEvent::~TEvent() {

    switch (Type)
    { // sprzątanie
    case tp_Multiple:
        // SafeDeleteArray(Params[9].asText); //nie usuwać - nazwa obiektu powiązanego zamieniana na wskaźnik
        if (iFlags & conditional_memstring) // o ile jest łańcuch do porównania w memcompare
            SafeDeleteArray(Params[10].asText);
        break;
    case tp_UpdateValues:
    case tp_AddValues:
        SafeDeleteArray(Params[0].asText);
        if (iFlags & conditional_memstring) // o ile jest łańcuch do porównania w memcompare
            SafeDeleteArray(Params[10].asText);
        break;
    case tp_Animation: // nic
		break;
    case tp_GetValues: // nic
        break;
	case tp_PutValues: // params[0].astext stores the token
		SafeDeleteArray( Params[ 0 ].asText );
		break;
    default:
        break;
    }
    evJoined = nullptr; // nie usuwać podczepionych tutaj

};

void TEvent::Conditions(cParser *parser, std::string s)
{ // przetwarzanie warunków, wspólne dla Multiple i UpdateValues
    if (s == "condition")
    { // jesli nie "endevent"
        std::string token, str;
        if (!asNodeName.empty())
        { // podczepienie łańcucha, jeśli nie jest pusty
			// BUG: source of a memory leak -- the array never gets deleted. fix the destructor
            Params[9].asText = new char[asNodeName.size() + 1]; // usuwane i zamieniane na wskaźnik
            strcpy(Params[9].asText, asNodeName.c_str());
        }
        parser->getTokens();
        *parser >> token;
		str = token;
        if (str == "trackoccupied")
            iFlags |= conditional_trackoccupied;
        else if (str == "trackfree")
            iFlags |= conditional_trackfree;
        else if (str == "propability")
        {
            iFlags |= conditional_propability;
            parser->getTokens();
            *parser >> Params[10].asdouble;
        }
        else if (str == "memcompare")
        {
            iFlags |= conditional_memcompare;
            parser->getTokens(1, false); // case sensitive
            *parser >> token;
			str = token;
            if (str != "*") //"*" - nie brac command pod uwage
            { // zapamiętanie łańcucha do porównania
                Params[10].asText = new char[str.size() + 1];
                strcpy(Params[10].asText, str.c_str());
                iFlags |= conditional_memstring;
            }
            parser->getTokens();
            *parser >> token;
			str = token;
            if (str != "*") //"*" - nie brac val1 pod uwage
            {
                Params[11].asdouble = atof(str.c_str());
                iFlags |= conditional_memval1;
            }
            parser->getTokens();
            *parser >> token;
            str = token;
            if (str != "*") //"*" - nie brac val2 pod uwage
            {
                Params[12].asdouble = atof(str.c_str());
                iFlags |= conditional_memval2;
            }
        }
        parser->getTokens();
        *parser >> token;
        s = token; // ewentualnie dalej losowe opóźnienie
    }
    if (s == "randomdelay")
    { // losowe opóźnienie
        std::string token;
        parser->getTokens();
        *parser >> fRandomDelay; // Ra 2014-03-11
        parser->getTokens();
        *parser >> token; // endevent
    }
};

void TEvent::Load(cParser *parser, Math3D::vector3 const &org)
{
    std::string token;

    bEnabled = true; // zmieniane na false dla eventów używanych do skanowania sygnałów

    parser->getTokens();
    *parser >> token;
    asName = ToLower(token); // użycie parametrów może dawać wielkie

    parser->getTokens();
    *parser >> token;
    // str = AnsiString(token.c_str());

    if (token == "exit")
        Type = tp_Exit;
    else if (token == "updatevalues")
        Type = tp_UpdateValues;
    else if (token == "getvalues")
        Type = tp_GetValues;
    else if (token == "putvalues")
        Type = tp_PutValues;
    else if (token == "disable")
        Type = tp_Disable;
    else if (token == "sound")
        Type = tp_Sound;
    else if (token == "velocity")
        Type = tp_Velocity;
    else if (token == "animation")
        Type = tp_Animation;
    else if (token == "lights")
        Type = tp_Lights;
    else if (token == "visible")
        Type = tp_Visible; // zmiana wyświetlania obiektu
    else if (token == "switch")
        Type = tp_Switch;
    else if (token == "dynvel")
        Type = tp_DynVel;
    else if (token == "trackvel")
        Type = tp_TrackVel;
    else if (token == "multiple")
        Type = tp_Multiple;
    else if (token == "addvalues")
        Type = tp_AddValues;
    else if (token == "copyvalues")
        Type = tp_CopyValues;
    else if (token == "whois")
        Type = tp_WhoIs;
    else if (token == "logvalues")
        Type = tp_LogValues;
    else if (token == "voltage")
        Type = tp_Voltage; // zmiana napięcia w zasilaczu (TractionPowerSource)
    else if (token == "message")
        Type = tp_Message; // wyświetlenie komunikatu
    else if (token == "friction")
        Type = tp_Friction; // zmiana tarcia na scenerii
    else
        Type = tp_Unknown;

    parser->getTokens();
    *parser >> fDelay;

    parser->getTokens();
    *parser >> token;

    if (token != "none")
        asNodeName = token; // nazwa obiektu powiązanego

    if (asName.substr(0, 5) == "none_")
        m_ignored = true; // Ra: takie są ignorowane

    switch (Type)
    {
    case tp_AddValues:
        iFlags = update_memadd; // dodawanko
    case tp_UpdateValues:
        // if (Type==tp_UpdateValues) iFlags=0; //co modyfikować
        parser->getTokens(1, false); // case sensitive
        *parser >> token;
        Params[0].asText = new char[token.size() + 1];
        strcpy(Params[0].asText, token.c_str());
        if (token != "*") // czy ma zostać bez zmian?
            iFlags |= update_memstring;
        parser->getTokens();
        *parser >> token;
        if (token != "*") // czy ma zostać bez zmian?
        {
            Params[1].asdouble = atof(token.c_str());
            iFlags |= update_memval1;
        }
        else
            Params[1].asdouble = 0;
        parser->getTokens();
        *parser >> token;
        if (token != "*") // czy ma zostać bez zmian?
        {
            Params[2].asdouble = atof(token.c_str());
            iFlags |= update_memval2;
        }
        else
            Params[2].asdouble = 0;
        parser->getTokens();
        *parser >> token;
        Conditions(parser, token); // sprawdzanie warunków
        break;
    case tp_CopyValues: {
        Params[9].asText = nullptr;
        iFlags = update_memstring | update_memval1 | update_memval2; // normalanie trzy
        int paramidx { 0 };
        parser->getTokens();
        *parser >> token; // nazwa drugiej komórki (źródłowej)
        while (token.compare("endevent") != 0) {

            switch (++paramidx)
            { // znaczenie kolejnych parametrów
            case 1: // nazwa drugiej komórki (źródłowej)
                Params[9].asText = new char[token.size() + 1]; // usuwane i zamieniane na wskaźnik
                strcpy(Params[9].asText, token.c_str());
                break;
            case 2: // maska wartości
                iFlags = stol_def(token, (update_memstring | update_memval1 | update_memval2));
                break;
            }
            parser->getTokens();
            *parser >> token;
        }
        break;
    }
    case tp_WhoIs: {
        iFlags = update_memstring | update_memval1 | update_memval2; // normalanie trzy
        int paramidx { 0 };
        parser->getTokens();
        *parser >> token; // nazwa drugiej komórki (źródłowej)
        while( token.compare( "endevent" ) != 0 ) {
            switch( ++paramidx ) { // znaczenie kolejnych parametrów
                case 1: // maska wartości
                    iFlags = stol_def( token, ( update_memstring | update_memval1 | update_memval2 ) );
                    break;
                default:
                    break;
            }
            parser->getTokens();
            *parser >> token;
        }
        break;
    }
    case tp_GetValues:
    case tp_LogValues:
        parser->getTokens(); //"endevent"
        *parser >> token;
        break;
    case tp_PutValues:
        parser->getTokens(3);
        *parser >> Params[3].asdouble >> Params[4].asdouble >> Params[5].asdouble; // położenie
        // X,Y,Z
        if ( !(org == Math3D::vector3()) )
        { // przesunięcie
            // tmp->pCenter.RotateY(aRotate.y/180.0*M_PI); //Ra 2014-11: uwzględnienie rotacji
            Params[3].asdouble += org.x; // współrzędne w scenerii
            Params[4].asdouble += org.y;
            Params[5].asdouble += org.z;
        }
        // Params[12].asInt=0;
        parser->getTokens(1, false); // komendy 'case sensitive'
        *parser >> token;
        // str = AnsiString(token.c_str());
        if (token.substr(0, 19) == "PassengerStopPoint:")
        {
            if (token.find('#') != std::string::npos)
				token.erase(token.find('#')); // obcięcie unikatowości
            win1250_to_ascii( token ); // get rid of non-ascii chars
            bEnabled = false; // nie do kolejki (dla SetVelocity też, ale jak jest do toru
            // dowiązany)
            Params[6].asCommand = cm_PassengerStopPoint;
        }
        else if (token == "SetVelocity")
        {
            bEnabled = false;
            Params[6].asCommand = cm_SetVelocity;
        }
        else if (token == "RoadVelocity")
        {
            bEnabled = false;
            Params[6].asCommand = cm_RoadVelocity;
        }
        else if (token == "SectionVelocity")
        {
            bEnabled = false;
            Params[6].asCommand = cm_SectionVelocity;
        }
        else if (token == "ShuntVelocity")
        {
            bEnabled = false;
            Params[6].asCommand = cm_ShuntVelocity;
        }
        //else if (str == "SetProximityVelocity")
        //{
        //    bEnabled = false;
        //    Params[6].asCommand = cm_SetProximityVelocity;
        //}
        else if (token == "OutsideStation")
        {
            bEnabled = false; // ma być skanowny, aby AI nie przekraczało W5
            Params[6].asCommand = cm_OutsideStation;
        }
        else
            Params[6].asCommand = cm_Unknown;
        Params[0].asText = new char[token.size() + 1];
        strcpy(Params[0].asText, token.c_str());
        parser->getTokens();
        *parser >> token;
        // str = AnsiString(token.c_str());
        if (token == "none")
            Params[1].asdouble = 0.0;
        else
            try
            {
                Params[1].asdouble = atof(token.c_str());
            }
            catch (...)
            {
                Params[1].asdouble = 0.0;
                WriteLog("Error: number expected in PutValues event, found: " + token);
            }
        parser->getTokens();
        *parser >> token;
        // str = AnsiString(token.c_str());
        if (token == "none")
            Params[2].asdouble = 0.0;
        else
            try
            {
                Params[2].asdouble = atof(token.c_str());
            }
            catch (...)
            {
                Params[2].asdouble = 0.0;
                WriteLog("Error: number expected in PutValues event, found: " + token);
            }
        parser->getTokens();
        *parser >> token;
        break;
    case tp_Lights: {
        int paramidx { 0 };
        do {
            parser->getTokens();
            *parser >> token;
            if( token.compare( "endevent" ) != 0 ) {

                if( paramidx < 8 ) {
                    Params[ paramidx ].asdouble = atof( token.c_str() ); // teraz może mieć ułamek
                    ++paramidx;
                }
                else {
                    ErrorLog( "Bad event: lights event \"" + asName + "\" with more than 8 parameters" );
                }
            }
        } while( token.compare( "endevent" ) != 0 );
        break;
    }
    case tp_Visible: // zmiana wyświetlania obiektu
        parser->getTokens();
        *parser >> token;
        Params[0].asInt = atoi(token.c_str());
        parser->getTokens();
        *parser >> token;
        break;
    case tp_Velocity:
        parser->getTokens();
        *parser >> token;
        Params[0].asdouble = atof(token.c_str()) * 0.28;
        parser->getTokens();
        *parser >> token;
        break;
    case tp_Sound:
        parser->getTokens();
        *parser >> Params[0].asInt; // 0: wylaczyc, 1: wlaczyc; -1: wlaczyc zapetlone
        Params[1].asdouble = 0.0;
        parser->getTokens();
        if( parser->peek() != "endevent" ) {
            // optional parameter, radio channel to receive/broadcast the sound
            *parser >> Params[1].asdouble;
            parser->getTokens();
        }
        *parser >> token;
        break;
    case tp_Exit:
        asNodeName = ExchangeCharInString( asNodeName, '_', ' ' );
        parser->getTokens();
        *parser >> token;
        break;
    case tp_Disable:
        parser->getTokens();
        *parser >> token;
        break;
    case tp_Animation:
        parser->getTokens();
        *parser >> token;
        Params[0].asInt = 0; // na razie nieznany typ
        if (token.compare("rotate") == 0)
        { // obrót względem osi
            parser->getTokens();
            *parser >> token;
            Params[9].asText = new char[token.size() + 1]; // nazwa submodelu
            std::strcpy(Params[9].asText, token.c_str());
            Params[0].asInt = 1;
            parser->getTokens(4);
            *parser
				>> Params[1].asdouble
				>> Params[2].asdouble
				>> Params[3].asdouble
				>> Params[4].asdouble;
        }
        else if (token.compare("translate") == 0)
        { // przesuw o wektor
            parser->getTokens();
            *parser >> token;
            Params[9].asText = new char[token.size() + 1]; // nazwa submodelu
            std::strcpy(Params[9].asText, token.c_str());
            Params[0].asInt = 2;
            parser->getTokens(4);
            *parser
				>> Params[1].asdouble
				>> Params[2].asdouble
				>> Params[3].asdouble
				>> Params[4].asdouble;
        }
        else if (token.compare("digital") == 0)
        { // licznik cyfrowy
            parser->getTokens();
            *parser >> token;
            Params[9].asText = new char[token.size() + 1]; // nazwa submodelu
            std::strcpy(Params[9].asText, token.c_str());
            Params[0].asInt = 8;
            parser->getTokens(4); // jaki ma być sens tych parametrów?
            *parser
				>> Params[1].asdouble
				>> Params[2].asdouble
				>> Params[3].asdouble
				>> Params[4].asdouble;
        }
        else if (token.substr(token.length() - 4, 4) == ".vmd") // na razie tu, może będzie inaczej
        { // animacja z pliku VMD
//			TFileStream *fs = new TFileStream( "models/" + AnsiString( token.c_str() ), fmOpenRead );
			{
				std::ifstream file( "models/" + token, std::ios::binary | std::ios::ate ); file.unsetf( std::ios::skipws );
				auto size = file.tellg();   // ios::ate already positioned us at the end of the file
				file.seekg( 0, std::ios::beg ); // rewind the caret afterwards
				Params[ 7 ].asInt = size;
				Params[8].asPointer = new char[size];
				file.read( static_cast<char*>(Params[ 8 ].asPointer), size ); // wczytanie pliku
			}
			parser->getTokens();
            *parser >> token;
            Params[9].asText = new char[token.size() + 1]; // nazwa submodelu
            std::strcpy(Params[9].asText, token.c_str());
            Params[0].asInt = 4; // rodzaj animacji
            parser->getTokens(4);
            *parser
				>> Params[1].asdouble
				>> Params[2].asdouble
				>> Params[3].asdouble
				>> Params[4].asdouble;
        }
        parser->getTokens();
        *parser >> token;
        break;
    case tp_Switch:
        parser->getTokens();
        *parser >> Params[0].asInt;
        parser->getTokens();
        *parser >> token;
        // str = AnsiString(token.c_str());
        if (token != "endevent")
        {
            Params[1].asdouble = atof(token.c_str()); // prędkość liniowa ruchu iglic
            parser->getTokens();
            *parser >> token;
            // str = AnsiString(token.c_str());
        }
        else
            Params[1].asdouble = -1.0; // użyć domyślnej
        if (token != "endevent")
        {
            Params[2].asdouble =
                atof(token.c_str()); // dodatkowy ruch drugiej iglicy (zamknięcie nastawnicze)
            parser->getTokens();
            *parser >> token;
        }
        else
            Params[2].asdouble = -1.0; // użyć domyślnej
        break;
    case tp_DynVel:
        parser->getTokens();
        *parser >> Params[0].asdouble; // McZapkie-090302 *0.28;
        parser->getTokens();
        *parser >> token;
        break;
    case tp_TrackVel:
        parser->getTokens();
        *parser >> Params[0].asdouble; // McZapkie-090302 *0.28;
        parser->getTokens();
        *parser >> token;
        break;
    case tp_Multiple: {
        int paramidx { 0 };
        bool ti { false }; // flaga dla else
        parser->getTokens();
        *parser >> token;

        while( ( token != "endevent" )
            && ( token != "condition" )
            && ( token != "randomdelay" ) ) {

            if( token != "else" ) {
                if( token.substr( 0, 5 ) != "none_" ) {
                    // eventy rozpoczynające się od "none_" są ignorowane
                    if( paramidx < 8 ) {
                        Params[ paramidx ].asText = new char[ token.size() + 1 ];
                        strcpy( Params[ paramidx ].asText, token.c_str() );
                        if( ti ) {
                            // oflagowanie dla eventów "else"
                            iFlags |= conditional_else << paramidx;
                        }
                        ++paramidx;
                    }
                    else {
                        ErrorLog( "Bad event: multi-event \"" + asName + "\" with more than 8 events; discarding link to event \"" + token + "\"" );
                    }
                }
                else {
                    WriteLog( "Multi-event \"" + asName + "\" ignored link to event \"" + token + "\"" );
                }
            }
            else {
                // zmiana flagi dla słowa "else"
                ti = !ti;
            }
            parser->getTokens();
            *parser >> token;
        }
        Conditions(parser, token); // sprawdzanie warunków
        break;
    }
    case tp_Voltage: // zmiana napięcia w zasilaczu (TractionPowerSource)
    case tp_Friction: // zmiana przyczepnosci na scenerii
        parser->getTokens();
        *parser >> Params[0].asdouble; // Ra 2014-01-27
        parser->getTokens();
        *parser >> token;
        break;
    case tp_Message: // wyświetlenie komunikatu
        do
        {
            parser->getTokens();
            *parser >> token;
            // str = AnsiString(token.c_str());
        } while (token != "endevent");
        break;
//    case tp_Ignored: // ignorowany
    case tp_Unknown: // nieznany
        do
        {
            parser->getTokens();
            *parser >> token;
            // str = AnsiString(token.c_str());
        } while (token != "endevent");
        WriteLog("Bad event: \"" + asName +
                 (Type == tp_Unknown ? "\" has unknown type." : "\" is ignored."));
        break;
    }
};

void TEvent::AddToQuery( TEvent *Event, TEvent *&Start ) {

    TEvent *target( Start );
    TEvent *previous( nullptr );
    while( ( Event->fStartTime >= target->fStartTime )
        && ( target->evNext != nullptr ) ) {
        previous = target;
        target = target->evNext;
    }
    // the new event will be either before or after the one we located
    if( Event->fStartTime >= target->fStartTime ) {
        assert( target->evNext == nullptr );
        target->evNext = Event;
        // if we have resurrected event land at the end of list, the link from previous run could potentially "add" unwanted events to the queue
        Event->evNext = nullptr;
    }
    else {
        if( previous != nullptr ) {
            previous->evNext = Event;
            Event->evNext = target;
        }
        else {
            // special case, we're inserting our event before the provided start point
            Event->evNext = Start;
            Start = Event;
        }
    }
}

//---------------------------------------------------------------------------

std::string TEvent::CommandGet()
{ // odczytanie komendy z eventu
    switch (Type)
    { // to się wykonuje również składu jadącego bez obsługi
    case tp_GetValues:
        return std::string(Params[9].asMemCell->Text());
    case tp_PutValues:
        return std::string(Params[0].asText);
    }
    return ""; // inne eventy się nie liczą
};

TCommandType TEvent::Command()
{ // odczytanie komendy z eventu
    switch (Type)
    { // to się wykonuje również dla składu jadącego bez obsługi
    case tp_GetValues:
        return Params[9].asMemCell->Command();
    case tp_PutValues:
        return Params[6].asCommand; // komenda zakodowana binarnie
    }
    return cm_Unknown; // inne eventy się nie liczą
};

double TEvent::ValueGet(int n)
{ // odczytanie komendy z eventu
    n &= 1; // tylko 1 albo 2 jest prawidłowy
    switch (Type)
    { // to się wykonuje również składu jadącego bez obsługi
    case tp_GetValues:
        return n ? Params[9].asMemCell->Value1() : Params[9].asMemCell->Value2();
    case tp_PutValues:
        return Params[2 - n].asdouble;
    }
    return 0.0; // inne eventy się nie liczą
};

glm::dvec3 TEvent::PositionGet() const
{ // pobranie współrzędnych eventu
    switch (Type)
    { //
    case tp_GetValues:
        return Params[9].asMemCell->location(); // współrzędne podłączonej komórki pamięci
    case tp_PutValues:
        return glm::dvec3(Params[3].asdouble, Params[4].asdouble, Params[5].asdouble);
    }
    return glm::dvec3(0, 0, 0); // inne eventy się nie liczą
};

bool TEvent::StopCommand()
{ //
    if (Type == tp_GetValues)
        return Params[9].asMemCell->StopCommand(); // info o komendzie z komórki
    return false;
};

void TEvent::StopCommandSent()
{
    if (Type == tp_GetValues)
        Params[9].asMemCell->StopCommandSent(); // komenda z komórki została wysłana
};

void TEvent::Append(TEvent *e)
{ // doczepienie kolejnych z tą samą nazwą
    if (evJoined)
        evJoined->Append(e); // rekurencja! - góra kilkanaście eventów będzie potrzebne
    else
    {
        evJoined = e;
        e->bEnabled = true; // ten doczepiony może być tylko kolejkowany
    }
};



event_manager::~event_manager() {

    for( auto *event : m_events ) {
        delete event;
    }
}

// adds specified event launcher to the list of global launchers
void
event_manager::queue( TEventLauncher *Launcher ) {

    m_launcherqueue.emplace_back( Launcher );
}

// legacy method, updates event queues
void
event_manager::update() {

    // process currently queued events
    CheckQuery();
    // test list of global events for possible new additions to the queue
    for( auto *launcher : m_launcherqueue ) {

        if( true == launcher->check_conditions() ) {
            // NOTE: we're presuming global events aren't going to use event2
            WriteLog( "Eventlauncher " + launcher->name() );
            if( launcher->Event1 ) {
                AddToQuery( launcher->Event1, nullptr );
            }
        }
    }
}

// adds provided event to the collection. returns: true on success
// TODO: return handle instead of pointer
bool
event_manager::insert( TEvent *Event ) {

    if( Event->Type == tp_Unknown ) { return false; }

    // najpierw sprawdzamy, czy nie ma, a potem dopisujemy
    auto lookup = m_eventmap.find( Event->asName );
    if( lookup != m_eventmap.end() ) {
        // duplicate of already existing event
        auto const size = Event->asName.size();
        // zawsze jeden znak co najmniej jest
        if( Event->asName[ 0 ] == '#' ) {
            // utylizacja duplikatu z krzyżykiem
            return false;
        }
        // tymczasowo wyjątki:
        else if( ( size > 8 )
              && ( Event->asName.substr( 0, 9 ) == "lineinfo:" ) ) {
            // tymczasowa utylizacja duplikatów W5
            return false;
        }
        else if( ( size > 8 )
              && ( Event->asName.substr( size - 8 ) == "_warning" ) ) {
            // tymczasowa utylizacja duplikatu z trąbieniem
            return false;
        }
        else if( ( size > 4 )
              && ( Event->asName.substr( size - 4 ) == "_shp" ) ) {
            // nie podlegają logowaniu
            // tymczasowa utylizacja duplikatu SHP
            return false;
        }

        auto *duplicate = m_events[ lookup->second ];
        if( Global::bJoinEvents ) {
            // doczepka (taki wirtualny multiple bez warunków)
            duplicate->Append( Event );
        }
        else {
            // NOTE: somewhat convoluted way to deal with 'replacing' events without leaving dangling pointers
            // can be cleaned up if pointers to events were replaced with handles
            ErrorLog( "Bad event: encountered duplicated event, \"" + Event->asName + "\"" );
            duplicate->Append( Event ); // doczepka (taki wirtualny multiple bez warunków)
            duplicate->m_ignored = true; // dezaktywacja pierwotnego - taka proteza na wsteczną zgodność
        }
    }

    m_events.emplace_back( Event );
    if( lookup == m_eventmap.end() ) {
        // if it's first event with such name, it's potential candidate for the execution queue
        m_eventmap.emplace( Event->asName, m_events.size() - 1 );
        if( ( Event->m_ignored != true )
         && ( Event->asName.find( "onstart" ) != std::string::npos ) ) {
            // event uruchamiany automatycznie po starcie
            AddToQuery( Event, nullptr );
        }
    }

    return true;
}

// legacy method, returns pointer to specified event, or null
TEvent *
event_manager::FindEvent( std::string const &Name ) {

    if( Name.empty() ) { return nullptr; }

    auto const lookup = m_eventmap.find( Name );
    return (
        lookup != m_eventmap.end() ?
            m_events[ lookup->second ] :
            nullptr );
}

// legacy method, inserts specified event in the event query
bool
event_manager::AddToQuery( TEvent *Event, TDynamicObject *Owner, double delay ) {

    if( ( false == Event->m_ignored ) && ( true == Event->bEnabled ) ) {
        // jeśli może być dodany do kolejki (nie używany w skanowaniu)
        if( !Event->iQueued ) // jeśli nie dodany jeszcze do kolejki
        { // kolejka eventów jest posortowana względem (fStartTime)
            Event->Activator = Owner;
            if( ( Event->Type == tp_AddValues )
             && ( Event->fDelay == 0.0 ) ) {
                // eventy AddValues trzeba wykonywać natychmiastowo, inaczej kolejka może zgubić jakieś dodawanie
                // Ra: kopiowanie wykonania tu jest bez sensu, lepiej by było wydzielić funkcję
                // wykonującą eventy i ją wywołać
                if( EventConditon( Event ) ) { // teraz mogą być warunki do tych eventów
                    Event->Params[ 5 ].asMemCell->UpdateValues(
                        Event->Params[ 0 ].asText, Event->Params[ 1 ].asdouble,
                        Event->Params[ 2 ].asdouble, Event->iFlags );
                    if( Event->Params[ 6 ].asTrack ) { // McZapkie-100302 - updatevalues oprocz zmiany wartosci robi putcommand dla
                        // wszystkich 'dynamic' na danym torze
                        for( auto dynamic : Event->Params[ 6 ].asTrack->Dynamics ) {
                            Event->Params[ 5 ].asMemCell->PutCommand(
                                dynamic->Mechanik,
                                Event->Params[ 4 ].asLocation );
                        }
                        //if (DebugModeFlag)
                        WriteLog(
                            "EVENT EXECUTED" + ( Owner ? ( " by " + Owner->asName ) : "" ) + ": AddValues & Track command - ["
                            + std::string{ Event->Params[ 0 ].asText } + "] ["
                            + to_string( Event->Params[ 1 ].asdouble, 2 ) + "] ["
                            + to_string( Event->Params[ 2 ].asdouble, 2 ) + " ]" );
                    }
                    //else if (DebugModeFlag)
                    WriteLog(
                        "EVENT EXECUTED" + ( Owner ? ( " by " + Owner->asName ) : "" ) + ": AddValues - ["
                        + std::string( Event->Params[ 0 ].asText ) + "] ["
                        + to_string( Event->Params[ 1 ].asdouble, 2 ) + "] ["
                        + to_string( Event->Params[ 2 ].asdouble, 2 ) + "]" );
                }
                // jeśli jest kolejny o takiej samej nazwie, to idzie do kolejki (and if there's no joint event it'll be set to null and processing will end here)
                do {
                    Event = Event->evJoined;
                    // NOTE: we could've received a new event from joint event above, so we need to check conditions just in case and discard the bad events
                    // TODO: refactor this arrangement, it's hardly optimal
                } while( ( Event != nullptr )
                    && ( ( false == Event->bEnabled )
                      || ( Event->iQueued > 0 ) ) );
            }
            if( Event != nullptr ) {
                // standardowe dodanie do kolejki
                ++Event->iQueued; // zabezpieczenie przed podwójnym dodaniem do kolejki
                WriteLog( "EVENT ADDED TO QUEUE" + ( Owner ? ( " by " + Owner->asName ) : "" ) + ": " + Event->asName );
                Event->fStartTime = delay + std::abs( Event->fDelay ) + Timer::GetTime(); // czas od uruchomienia scenerii
                if( Event->fRandomDelay > 0.0 ) {
                    // doliczenie losowego czasu opóźnienia
                    Event->fStartTime += Event->fRandomDelay * Random( 10000 ) * 0.0001;
                }
                if( QueryRootEvent != nullptr ) {
                    TEvent::AddToQuery( Event, QueryRootEvent );
                }
                else {
                    QueryRootEvent = Event;
                    QueryRootEvent->evNext = nullptr;
                }
            }
        }
    }
    return true;
}

// legacy method, executes queued events
bool
event_manager::CheckQuery() {

    TLocation loc;
    int i;
    while( ( QueryRootEvent != nullptr )
        && ( QueryRootEvent->fStartTime < Timer::GetTime() ) )
    { // eventy są posortowana wg czasu wykonania
        m_workevent = QueryRootEvent; // wyjęcie eventu z kolejki
        if (QueryRootEvent->evJoined) // jeśli jest kolejny o takiej samej nazwie
        { // to teraz on będzie następny do wykonania
            QueryRootEvent = QueryRootEvent->evJoined; // następny będzie ten doczepiony
            QueryRootEvent->evNext = m_workevent->evNext; // pamiętając o następnym z kolejki
            QueryRootEvent->fStartTime = m_workevent->fStartTime; // czas musi być ten sam, bo nie jest aktualizowany
            QueryRootEvent->Activator = m_workevent->Activator; // pojazd aktywujący
            QueryRootEvent->iQueued = 1;
            // w sumie można by go dodać normalnie do kolejki, ale trzeba te połączone posortować wg czasu wykonania
        }
        else // a jak nazwa jest unikalna, to kolejka idzie dalej
            QueryRootEvent = QueryRootEvent->evNext; // NULL w skrajnym przypadku
        if( ( false == m_workevent->m_ignored ) && ( true == m_workevent->bEnabled ) ) {
            // w zasadzie te wyłączone są skanowane i nie powinny się nigdy w kolejce znaleźć
            --m_workevent->iQueued; // teraz moze być ponownie dodany do kolejki
            WriteLog( "EVENT LAUNCHED" + ( m_workevent->Activator ? ( " by " + m_workevent->Activator->asName ) : "" ) + ": " + m_workevent->asName );
            switch (m_workevent->Type)
            {
            case tp_CopyValues: // skopiowanie wartości z innej komórki
                m_workevent->Params[5].asMemCell->UpdateValues(
                    m_workevent->Params[9].asMemCell->Text(),
                    m_workevent->Params[9].asMemCell->Value1(),
                    m_workevent->Params[9].asMemCell->Value2(),
                    m_workevent->iFlags // flagi określają, co ma być skopiowane
                    );
            // break; //żeby się wysłało do torów i nie było potrzeby na AddValues * 0 0
            case tp_AddValues: // różni się jedną flagą od UpdateValues
            case tp_UpdateValues:
                if (EventConditon(m_workevent))
                { // teraz mogą być warunki do tych eventów
                    if (m_workevent->Type != tp_CopyValues) // dla CopyValues zrobiło się wcześniej
                        m_workevent->Params[5].asMemCell->UpdateValues(
                            m_workevent->Params[0].asText,
                            m_workevent->Params[1].asdouble,
                            m_workevent->Params[2].asdouble,
                            m_workevent->iFlags);
                    if (m_workevent->Params[6].asTrack)
                    { // McZapkie-100302 - updatevalues oprocz zmiany wartosci robi putcommand dla
                        // wszystkich 'dynamic' na danym torze
                        for( auto dynamic : m_workevent->Params[ 6 ].asTrack->Dynamics ) {
                            m_workevent->Params[ 5 ].asMemCell->PutCommand(
                                dynamic->Mechanik,
                                m_workevent->Params[ 4 ].asLocation );
                        }
                        //if (DebugModeFlag)
                        WriteLog("Type: UpdateValues & Track command - [" +
                            m_workevent->Params[5].asMemCell->Text() + "] [" +
                            to_string( m_workevent->Params[ 5 ].asMemCell->Value1(), 2 ) + "] [" +
                            to_string( m_workevent->Params[ 5 ].asMemCell->Value2(), 2 ) + "]" );
                    }
                    else //if (DebugModeFlag) 
                        WriteLog("Type: UpdateValues - [" +
                            m_workevent->Params[5].asMemCell->Text() + "] [" +
                            to_string( m_workevent->Params[ 5 ].asMemCell->Value1(), 2 ) + "] [" +
                            to_string( m_workevent->Params[ 5 ].asMemCell->Value2(), 2 ) + "]" );
                }
                break;
            case tp_GetValues: {
                if( m_workevent->Activator ) {
                    // TODO: re-enable when messaging module is in place
                    if( Global::iMultiplayer ) {
                        // potwierdzenie wykonania dla serwera (odczyt semafora już tak nie działa)
                        multiplayer::WyslijEvent( m_workevent->asName, m_workevent->Activator->name() );
                    }
                    m_workevent->Params[ 9 ].asMemCell->PutCommand(
                        m_workevent->Activator->Mechanik,
                        m_workevent->Params[ 8 ].asLocation );
                }
                WriteLog( "Type: GetValues" );
                break;
            }
            case tp_PutValues: {
                if (m_workevent->Activator) {
                    // zamiana, bo fizyka ma inaczej niż sceneria
                    loc.X = -m_workevent->Params[3].asdouble;
                    loc.Y =  m_workevent->Params[5].asdouble;
                    loc.Z =  m_workevent->Params[4].asdouble;
                    if( m_workevent->Activator->Mechanik ) {
                        // przekazanie rozkazu do AI
                        m_workevent->Activator->Mechanik->PutCommand(
                            m_workevent->Params[0].asText, m_workevent->Params[1].asdouble,
                            m_workevent->Params[2].asdouble, loc);
                    }
                    else {
                        // przekazanie do pojazdu
                        m_workevent->Activator->MoverParameters->PutCommand(
                            m_workevent->Params[0].asText, m_workevent->Params[1].asdouble,
                            m_workevent->Params[2].asdouble, loc);
                    }
                    WriteLog("Type: PutValues - [" +
                        std::string(m_workevent->Params[0].asText) + "] [" +
                        to_string( m_workevent->Params[ 1 ].asdouble, 2 ) + "] [" +
                        to_string( m_workevent->Params[ 2 ].asdouble, 2 ) + "]" );
                }
                break;
            }
            case tp_Lights: {
                if( m_workevent->Params[ 9 ].asModel ) {
                    for( i = 0; i < iMaxNumLights; ++i ) {
                        if( m_workevent->Params[ i ].asdouble >= 0 ) {
                            // -1 zostawia bez zmiany
                            m_workevent->Params[ 9 ].asModel->LightSet(
                                i,
                                m_workevent->Params[ i ].asdouble );
                        }
                    }
                }
                break;
            }
            case tp_Visible: {
                if( m_workevent->Params[ 9 ].asEditorNode )
                    m_workevent->Params[ 9 ].asEditorNode->visible( m_workevent->Params[ i ].asInt > 0 );
                break;
            }
            case tp_Velocity: {
                Error( "Not implemented yet :(" );
                break;
            }
            case tp_Exit: {
                Global::iTextMode = -1; // wyłączenie takie samo jak sekwencja F10 -> Y
                return false;
            }
            case tp_Sound: {
				if (m_workevent->Params[ 9 ].tsTextSound == nullptr)
					break;
                switch( m_workevent->Params[ 0 ].asInt ) {
                    // trzy możliwe przypadki:
                    case 0: {
                        m_workevent->Params[ 9 ].tsTextSound->stop();
                        break;
                    }
                    case 1: {
                        if( m_workevent->Params[ 1 ].asdouble > 0.0 ) {
                            Global::pWorld->radio_message(
                                m_workevent->Params[ 9 ].tsTextSound,
                                static_cast<int>( m_workevent->Params[ 1 ].asdouble ) );
                        }
                        else {
                            m_workevent->Params[ 9 ].tsTextSound->play( sound_flags::exclusive | sound_flags::event );
                        }
                        break;
                    }
                    case -1: {
                        m_workevent->Params[ 9 ].tsTextSound->play( sound_flags::exclusive | sound_flags::looping | sound_flags::event );
                        break;
                    }
                    default: {
                        break;
                    }
                }
                break;
            }
            case tp_Disable:
                Error("Not implemented yet :(");
                break;
            case tp_Animation: {
                switch( m_workevent->Params[ 0 ].asInt ) {
                    case 1: {
                        m_workevent->Params[ 9 ].asAnimContainer->SetRotateAnim(
                            Math3D::vector3 {
                                m_workevent->Params[ 1 ].asdouble,
                                m_workevent->Params[ 2 ].asdouble,
                                m_workevent->Params[ 3 ].asdouble },
                            m_workevent->Params[ 4 ].asdouble );
                        break;
                    }
                    case 2: {
                        m_workevent->Params[ 9 ].asAnimContainer->SetTranslateAnim(
                            Math3D::vector3 {
                                m_workevent->Params[ 1 ].asdouble,
                                m_workevent->Params[ 2 ].asdouble,
                                m_workevent->Params[ 3 ].asdouble },
                            m_workevent->Params[ 4 ].asdouble );
                        break;
                    }
                    case 4: {
                        m_workevent->Params[ 9 ].asModel->AnimationVND(
                            m_workevent->Params[ 8 ].asPointer,
                            m_workevent->Params[ 1 ].asdouble, // tu mogą być dodatkowe parametry, np. od-do
                            m_workevent->Params[ 2 ].asdouble,
                            m_workevent->Params[ 3 ].asdouble,
                            m_workevent->Params[ 4 ].asdouble );
                        break;
                    }
                    default: {
                        break;
                    }
                }
                break;
            }
            case tp_Switch: {
                if( m_workevent->Params[ 9 ].asTrack ) {
                    m_workevent->Params[ 9 ].asTrack->Switch(
                        m_workevent->Params[ 0 ].asInt,
                        m_workevent->Params[ 1 ].asdouble,
                        m_workevent->Params[ 2 ].asdouble );
                }
                if( Global::iMultiplayer ) {
                    // dajemy znać do serwera o przełożeniu
                    multiplayer::WyslijEvent( m_workevent->asName, "" ); // wysłanie nazwy eventu przełączajacego
                }
                // Ra: bardziej by się przydała nazwa toru, ale nie ma do niej stąd dostępu
                break;
            }
            case tp_TrackVel:
                if (m_workevent->Params[9].asTrack) {
                    // prędkość na zwrotnicy może być ograniczona z góry we wpisie, większej się nie ustawi eventem
                    m_workevent->Params[9].asTrack->VelocitySet(m_workevent->Params[0].asdouble);
                    // wyświetlana jest ta faktycznie ustawiona
                    WriteLog( "Type: TrackVel - ["
                        + to_string( m_workevent->Params[ 0 ].asdouble, 2 ) + "]"
                        + ( DebugModeFlag ?
                            ", actual [ " + to_string( m_workevent->Params[ 9 ].asTrack->VelocityGet(), 2 ) + "]" :
                            "" ) );
                }
                break;
            case tp_DynVel:
                Error("Event \"DynVel\" is obsolete");
                break;
            case tp_Multiple: {
                auto const bCondition = EventConditon(m_workevent);
                if( ( bCondition )
                 || ( m_workevent->iFlags & conditional_anyelse ) ) {
                    // warunek spelniony albo było użyte else
                    WriteLog("Type: Multi-event");
                    for (i = 0; i < 8; ++i) {
                        // dodawane do kolejki w kolejności zapisania
                        if( m_workevent->Params[ i ].asEvent ) {
                            if( bCondition != ( ( ( m_workevent->iFlags & ( conditional_else << i ) ) != 0 ) ) ) {
                                if( m_workevent->Params[ i ].asEvent != m_workevent )
                                    AddToQuery( m_workevent->Params[ i ].asEvent, m_workevent->Activator ); // normalnie dodać
                                else {
                                    // jeśli ma być rekurencja to musi mieć sensowny okres powtarzania
                                    if( m_workevent->fDelay >= 5.0 ) {
                                        AddToQuery( m_workevent, m_workevent->Activator );
                                    }
                                }
                            }
                        }
                    }
                    if( Global::iMultiplayer ) {
                        // dajemy znać do serwera o wykonaniu
                        if( ( m_workevent->iFlags & conditional_anyelse ) == 0 ) {
                            // jednoznaczne tylko, gdy nie było else
                            if( m_workevent->Activator ) {
                                multiplayer::WyslijEvent( m_workevent->asName, m_workevent->Activator->name() );
                            }
                            else {
                                multiplayer::WyslijEvent( m_workevent->asName, "" );
                            }
                        }
                    }
                }
                break;
            }
            case tp_WhoIs: {
                // pobranie nazwy pociągu do komórki pamięci
                if (m_workevent->iFlags & update_load) {
                    // jeśli pytanie o ładunek
                    if( m_workevent->iFlags & update_memadd ) {
                        // jeśli typ pojazdu
                        // TODO: define and recognize individual request types
                        auto const owner = (
                            ( ( m_workevent->Activator->Mechanik != nullptr ) && ( m_workevent->Activator->Mechanik->Primary() ) ) ?
                                m_workevent->Activator->Mechanik :
                                m_workevent->Activator->ctOwner );
                        auto const consistbrakelevel = (
                            owner != nullptr ?
                                owner->fReady :
                                -1.0 );
                        auto const collisiondistance = (
                            owner != nullptr ?
                                owner->TrackBlock() :
                                -1.0 );

                        m_workevent->Params[ 9 ].asMemCell->UpdateValues(
                            m_workevent->Activator->MoverParameters->TypeName, // typ pojazdu
                            consistbrakelevel,
                            collisiondistance,
                            m_workevent->iFlags & ( update_memstring | update_memval1 | update_memval2 ) );

                        WriteLog(
                              "Type: WhoIs (" + to_string( m_workevent->iFlags ) + ") - "
                            + "[name: " + m_workevent->Activator->MoverParameters->TypeName + "], "
                            + "[consist brake level: " + to_string( consistbrakelevel, 2 ) + "], "
                            + "[obstacle distance: " + to_string( collisiondistance, 2 ) + " m]" );
                    }
                    else {
                        // jeśli parametry ładunku
                        m_workevent->Params[ 9 ].asMemCell->UpdateValues(
                            m_workevent->Activator->MoverParameters->LoadType, // nazwa ładunku
                            m_workevent->Activator->MoverParameters->Load, // aktualna ilość
                            m_workevent->Activator->MoverParameters->MaxLoad, // maksymalna ilość
                            m_workevent->iFlags & ( update_memstring | update_memval1 | update_memval2 ) );

                        WriteLog(
                              "Type: WhoIs (" + to_string( m_workevent->iFlags ) + ") - "
                            + "[load type: " + m_workevent->Activator->MoverParameters->LoadType + "], "
                            + "[current load: " + to_string( m_workevent->Activator->MoverParameters->Load, 2 ) + "], "
                            + "[max load: " + to_string( m_workevent->Activator->MoverParameters->MaxLoad, 2 ) + "]" );
                    }
                }
                else if (m_workevent->iFlags & update_memadd)
                { // jeśli miejsce docelowe pojazdu
                    m_workevent->Params[ 9 ].asMemCell->UpdateValues(
                        m_workevent->Activator->asDestination, // adres docelowy
                        m_workevent->Activator->DirectionGet(), // kierunek pojazdu względem czoła składu (1=zgodny,-1=przeciwny)
                        m_workevent->Activator->MoverParameters->Power, // moc pojazdu silnikowego: 0 dla wagonu
                        m_workevent->iFlags & (update_memstring | update_memval1 | update_memval2));

                        WriteLog(
                              "Type: WhoIs (" + to_string( m_workevent->iFlags ) + ") - "
                            + "[destination: " + m_workevent->Activator->asDestination + "], "
                            + "[direction: " + to_string( m_workevent->Activator->DirectionGet() ) + "], "
                            + "[engine power: " + to_string( m_workevent->Activator->MoverParameters->Power, 2 ) + "]" );
                }
                else if (m_workevent->Activator->Mechanik)
                    if (m_workevent->Activator->Mechanik->Primary())
                    { // tylko jeśli ktoś tam siedzi - nie powinno dotyczyć pasażera!
                        m_workevent->Params[ 9 ].asMemCell->UpdateValues(
							m_workevent->Activator->Mechanik->TrainName(),
                            m_workevent->Activator->Mechanik->StationCount() - m_workevent->Activator->Mechanik->StationIndex(), // ile przystanków do końca
                            m_workevent->Activator->Mechanik->IsStop() ?
                                1 :
                                0, // 1, gdy ma tu zatrzymanie
                            m_workevent->iFlags);
                        WriteLog("Train detected: " + m_workevent->Activator->Mechanik->TrainName());
                    }
                break;
            }
            case tp_LogValues: {
                // zapisanie zawartości komórki pamięci do logu
                if( m_workevent->Params[ 9 ].asMemCell ) {
                    // jeśli była podana nazwa komórki
                    WriteLog( "Memcell \"" + m_workevent->asNodeName + "\": ["
                        + m_workevent->Params[ 9 ].asMemCell->Text() + "] ["
                        + to_string( m_workevent->Params[ 9 ].asMemCell->Value1(), 2 ) + "] ["
                        + to_string( m_workevent->Params[ 9 ].asMemCell->Value2(), 2 ) + "]" );
                }
                else {
                    // lista wszystkich
                    simulation::Memory.log_all();
                }
                break;
            }
            case tp_Voltage: // zmiana napięcia w zasilaczu (TractionPowerSource)
                if (m_workevent->Params[9].psPower)
                { // na razie takie chamskie ustawienie napięcia zasilania
                    WriteLog("Type: Voltage");
                    m_workevent->Params[9].psPower->VoltageSet(m_workevent->Params[0].asdouble);
                }
            case tp_Friction: // zmiana tarcia na scenerii
            { // na razie takie chamskie ustawienie napięcia zasilania
                WriteLog("Type: Friction");
                Global::fFriction = (m_workevent->Params[0].asdouble);
            }
            break;
            case tp_Message: // wyświetlenie komunikatu
                break;
			case tp_Lua:
				((lua::eventhandler_t)m_workevent->Params[0].asPointer)(m_workevent, m_workevent->Activator);
				break;
            } // switch (tmpEvent->Type)
        } // if (tmpEvent->bEnabled)
    } // while
    return true;
}

// legacy method, initializes events after deserialization from scenario file
void
event_manager::InitEvents() {

    //łączenie eventów z pozostałymi obiektami
    for( auto *event : m_events ) {

        switch( event->Type ) {

        case tp_AddValues: // sumowanie wartości
        case tp_UpdateValues: { // zmiana wartości
            auto *cell = simulation::Memory.find( event->asNodeName ); // nazwa komórki powiązanej z eventem
            if( cell != nullptr ) { // McZapkie-100302
                if( event->iFlags & ( conditional_trackoccupied | conditional_trackfree ) ) {
                    // jeśli chodzi o zajetosc toru (tor może być inny, niż wpisany w komórce)
                    // nazwa toru ta sama, co nazwa komórki
                    event->Params[ 9 ].asTrack = simulation::Paths.find( event->asNodeName );
                    if( event->Params[ 9 ].asTrack == nullptr ) {
                        ErrorLog( "Bad event: track \"" + event->asNodeName + "\" referenced in event \"" + event->asName + "\" doesn't exist" );
                    }
                }
                event->Params[ 4 ].asLocation = &( cell->location() );
                event->Params[ 5 ].asMemCell = cell; // komórka do aktualizacji
                if( event->iFlags & ( conditional_memcompare ) ) {
                    // komórka do badania warunku
                    event->Params[ 9 ].asMemCell = cell;
                }
                if( false == cell->asTrackName.empty() ) {
                    // tor powiązany z komórką powiązaną z eventem
                    // tu potrzebujemy wskaźnik do komórki w (tmp)
                    event->Params[ 6 ].asTrack = simulation::Paths.find( cell->asTrackName );
                    if( event->Params[ 6 ].asTrack == nullptr ) {
                        ErrorLog( "Bad memcell: track \"" + cell->asTrackName + "\" referenced in memcell \"" + cell->name() + "\" doesn't exist" );
                    }
                }
                else {
                    event->Params[ 6 ].asTrack = nullptr;
                }
            }
            else {
                // nie ma komórki, to nie będzie działał poprawnie
                event->m_ignored = true; // deaktywacja
                ErrorLog( "Bad event: event \"" + event->asName + "\" cannot find memcell \"" + event->asNodeName + "\"" );
            }
            break;
        }
        case tp_LogValues: {
            // skojarzenie z memcell
            if( event->asNodeName.empty() ) { // brak skojarzenia daje logowanie wszystkich
                event->Params[ 9 ].asMemCell = nullptr;
                break;
            }
        }
        case tp_GetValues:
        case tp_WhoIs: {
            auto *cell = simulation::Memory.find( event->asNodeName );
            if( cell != nullptr ) {
                event->Params[ 8 ].asLocation = &( cell->location() );
                event->Params[ 9 ].asMemCell = cell;
                if( ( event->Type == tp_GetValues )
                 && ( cell->IsVelocity() ) ) {
                    // jeśli odczyt komórki a komórka zawiera komendę SetVelocity albo ShuntVelocity
                    // to event nie będzie dodawany do kolejki
                    event->bEnabled = false;
                }
            }
            else {
                // nie ma komórki, to nie będzie działał poprawnie
                event->m_ignored = true; // deaktywacja
                ErrorLog( "Bad event: event \"" + event->asName + "\" cannot find memcell \"" + event->asNodeName + "\"" );
            }
            break;
        }
        case tp_CopyValues: {
            // skopiowanie komórki do innej
            auto *cell = simulation::Memory.find( event->asNodeName ); // komórka docelowa
            if( cell != nullptr ) {
                event->Params[ 4 ].asLocation = &( cell->location() );
                event->Params[ 5 ].asMemCell = cell; // komórka docelowa
                if( false == cell->asTrackName.empty() ) {
                    // tor powiązany z komórką powiązaną z eventem
                    // tu potrzebujemy wskaźnik do komórki w (tmp)
                    event->Params[ 6 ].asTrack = simulation::Paths.find( cell->asTrackName );
                    if( event->Params[ 6 ].asTrack == nullptr ) {
                        ErrorLog( "Bad memcell: track \"" + cell->asTrackName + "\" referenced in memcell \"" + cell->name() + "\" doesn't exists" );
                    }
                }
                else {
                    event->Params[ 6 ].asTrack = nullptr;
                }
            }
            else {
                ErrorLog( "Bad event: copyvalues event \"" + event->asName + "\" cannot find memcell \"" + event->asNodeName + "\"" );
            }
            std::string const cellastext { event->Params[ 9 ].asText };
            cell = simulation::Memory.find( event->Params[ 9 ].asText ); // komórka źódłowa
            SafeDeleteArray( event->Params[ 9 ].asText ); // usunięcie nazwy komórki
            if( cell != nullptr ) {
                event->Params[ 8 ].asLocation = &( cell->location() );
                event->Params[ 9 ].asMemCell = cell; // komórka źródłowa
            }
            else {
                ErrorLog( "Bad event: copyvalues event \"" + event->asName + "\" cannot find memcell \"" + cellastext + "\"" );
            }
            break;
        }
        case tp_Animation: {
            // animacja modelu
            // retrieve target name parameter
            std::string const cellastext = event->Params[ 9 ].asText;
            SafeDeleteArray( event->Params[ 9 ].asText );
            // egzemplarz modelu do animowania
            auto *instance = simulation::Instances.find( event->asNodeName );
            if( instance != nullptr ) {
                if( event->Params[ 0 ].asInt == 4 ) {
                    // model dla całomodelowych animacji
                    event->Params[ 9 ].asModel = instance;
                }
                else {
                    // standardowo przypisanie submodelu
                    event->Params[ 9 ].asAnimContainer = instance->GetContainer( cellastext ); // submodel
                    if( event->Params[ 9 ].asAnimContainer ) {
                        event->Params[ 9 ].asAnimContainer->WillBeAnimated(); // oflagowanie animacji
                        if( event->Params[ 9 ].asAnimContainer->Event() == nullptr ) {
                            // nie szukać, gdy znaleziony
                            event->Params[ 9 ].asAnimContainer->EventAssign(
                                FindEvent( event->asNodeName + "." + cellastext + ":done" ) );
                        }
                    }
                }
            }
            else {
                ErrorLog( "Bad event: animation event \"" + event->asName + "\" cannot find model instance \"" + event->asNodeName + "\"" );
            }
            event->asNodeName = "";
            break;
        }
        case tp_Lights: {
            // zmiana świeteł modelu
            auto *instance = simulation::Instances.find( event->asNodeName );
            if( instance != nullptr )
                event->Params[ 9 ].asModel = instance;
            else
                ErrorLog( "Bad event: lights event \"" + event->asName + "\" cannot find model instance \"" + event->asNodeName + "\"" );
            event->asNodeName = "";
            break;
        }
        case tp_Visible: {
            // ukrycie albo przywrócenie obiektu
            editor::basic_node *node = simulation::Instances.find( event->asNodeName ); // najpierw model
            if( node == nullptr ) {
                // albo tory?
                node = simulation::Paths.find( event->asNodeName );
            }
            if( node == nullptr ) {
                // może druty?
                node = simulation::Traction.find( event->asNodeName );
            }
            if( node != nullptr )
                event->Params[ 9 ].asEditorNode = node;
            else
                ErrorLog( "Bad event: visibility event \"" + event->asName + "\" cannot find item \"" + event->asNodeName + "\"" );
            event->asNodeName = "";
            break;
        }
        case tp_Switch: {
            // przełożenie zwrotnicy albo zmiana stanu obrotnicy
            auto *track = simulation::Paths.find( event->asNodeName );
            if( track != nullptr ) {
                // dowiązanie toru
                if( track->iAction == 0 ) {
                    // jeśli nie jest zwrotnicą ani obrotnicą to będzie się zmieniał stan uszkodzenia
                    track->iAction |= 0x100;
                }
                event->Params[ 9 ].asTrack = track;
                if( ( event->Params[ 0 ].asInt == 0 )
                 && ( event->Params[ 2 ].asdouble >= 0.0 ) ) {
                    // jeśli przełącza do stanu 0 & jeśli jest zdefiniowany dodatkowy ruch iglic
                    // przesłanie parametrów
                    event->Params[ 9 ].asTrack->Switch(
                        event->Params[ 0 ].asInt,
                        event->Params[ 1 ].asdouble,
                        event->Params[ 2 ].asdouble );
                }
            }
            else {
                ErrorLog( "Bad event: switch event \"" + event->asName + "\" cannot find track \"" + event->asNodeName + "\"" );
            }
            event->asNodeName = "";
            break;
        }
        case tp_Sound: {
            // odtworzenie dźwięku
            auto *sound = simulation::Sounds.find( event->asNodeName );
            if( sound != nullptr )
                event->Params[ 9 ].tsTextSound = sound;
            else
                ErrorLog( "Bad event: sound event \"" + event->asName + "\" cannot find static sound \"" + event->asNodeName + "\"" );
            event->asNodeName = "";
            break;
        }
        case tp_TrackVel: {
            // ustawienie prędkości na torze
            if( false == event->asNodeName.empty() ) {
                auto *track = simulation::Paths.find( event->asNodeName );
                if( track != nullptr ) {
                    // flaga zmiany prędkości toru jest istotna dla skanowania
                    track->iAction |= 0x200;
                    event->Params[ 9 ].asTrack = track;
                }
                else {
                    ErrorLog( "Bad event: track velocity event \"" + event->asName + "\" cannot find track \"" + event->asNodeName + "\"" );
                }
            }
            event->asNodeName = "";
            break;
        }
        case tp_DynVel: {
            // komunikacja z pojazdem o konkretnej nazwie
            if( event->asNodeName == "activator" )
                event->Params[ 9 ].asDynamic = nullptr;
            else {
                auto *vehicle = simulation::Vehicles.find( event->asNodeName );
                if( vehicle != nullptr )
                    event->Params[ 9 ].asDynamic = vehicle;
                else
                    ErrorLog( "Bad event: vehicle velocity event \"" + event->asName + "\" cannot find vehicle \"" + event->asNodeName + "\"" );
            }
            event->asNodeName = "";
            break;
        }
        case tp_Multiple: {
            std::string cellastext;
            if( event->Params[ 9 ].asText != nullptr ) { // przepisanie nazwy do bufora
                cellastext = event->Params[ 9 ].asText;
                SafeDeleteArray( event->Params[ 9 ].asText );
                event->Params[ 9 ].asPointer = nullptr; // zerowanie wskaźnika, aby wykryć brak obeiktu
            }
            if( event->iFlags & ( conditional_trackoccupied | conditional_trackfree ) ) {
                // jeśli chodzi o zajetosc toru
                event->Params[ 9 ].asTrack = simulation::Paths.find( cellastext );
                if( event->Params[ 9 ].asTrack == nullptr ) {
                    ErrorLog( "Bad event: multi-event \"" + event->asName + "\" cannot find track \"" + cellastext + "\"" );
                    event->iFlags &= ~( conditional_trackoccupied | conditional_trackfree ); // zerowanie flag
                }
            }
            else if( event->iFlags & ( conditional_memstring | conditional_memval1 | conditional_memval2 ) ) {
                // jeśli chodzi o komorke pamieciową
                event->Params[ 9 ].asMemCell = simulation::Memory.find( cellastext );
                if( event->Params[ 9 ].asMemCell == nullptr ) {
                    ErrorLog( "Bad event: multi-event \"" + event->asName + "\" cannot find memory cell \"" + cellastext + "\"" );
                    event->iFlags &= ~( conditional_memstring | conditional_memval1 | conditional_memval2 );
                }
            }
            for( int i = 0; i < 8; ++i ) {
                if( event->Params[ i ].asText != nullptr ) {
                    cellastext = event->Params[ i ].asText;
                    SafeDeleteArray( event->Params[ i ].asText );
                    event->Params[ i ].asEvent = FindEvent( cellastext );
                    if( event->Params[ i ].asEvent == nullptr ) {
                        // Ra: tylko w logu informacja o braku
                        ErrorLog( "Bad event: multi-event \"" + event->asName + "\" cannot find event \"" + cellastext + "\"" );
                    }
                }
            }
            break;
        }
        case tp_Voltage: {
            // zmiana napięcia w zasilaczu (TractionPowerSource)
            if( false == event->asNodeName.empty() ) {
                auto *powersource = simulation::Powergrid.find( event->asNodeName ); // podłączenie zasilacza
                if( powersource != nullptr )
                    event->Params[ 9 ].psPower = powersource;
                else
                    ErrorLog( "Bad event: voltage event \"" + event->asName + "\" cannot find power source \"" + event->asNodeName + "\"" );
            }
            event->asNodeName = "";
            break;
        }
        case tp_Message: {
            // wyświetlenie komunikatu
            break;
        }

        } // switch

        if( event->fDelay < 0 ) { AddToQuery( event, nullptr ); }
    }
}

// legacy method, initializes event launchers after deserialization from scenario file
void
event_manager::InitLaunchers() {

    for( auto *launcher : m_launchers.sequence() ) {

        if( launcher->iCheckMask != 0 ) {
            if( launcher->asMemCellName != "none" ) {
                // jeśli jest powiązana komórka pamięci
                launcher->MemCell = simulation::Memory.find( launcher->asMemCellName );
                if( launcher->MemCell == nullptr ) {
                    ErrorLog( "Bad scenario: event launcher \"" + launcher->name() + "\" cannot find memcell \"" + launcher->asMemCellName + "\"" );
                }
            }
            else {
                launcher->MemCell = nullptr;
            }
        }

        if( launcher->asEvent1Name != "none" ) {
            launcher->Event1 = simulation::Events.FindEvent( launcher->asEvent1Name );
            if( launcher->Event1 == nullptr ) {
                ErrorLog( "Bad scenario: event launcher \"" + launcher->name() + "\" cannot find event \"" + launcher->asEvent1Name + "\"" );
            }
        }

        if( launcher->asEvent2Name != "none" ) {
            launcher->Event2 = simulation::Events.FindEvent( launcher->asEvent2Name );
            if( launcher->Event2 == nullptr ) {
                ErrorLog( "Bad scenario: event launcher \"" + launcher->name() + "\" cannot find event \"" + launcher->asEvent2Name + "\"" );
            }
        }
    }
}

// legacy method, verifies condition for specified event
bool
event_manager::EventConditon( TEvent *Event ) {

    if (Event->iFlags <= update_only)
        return true; // bezwarunkowo

    if (Event->iFlags & conditional_trackoccupied)
        return (!Event->Params[9].asTrack->IsEmpty());
    else if (Event->iFlags & conditional_trackfree)
        return (Event->Params[9].asTrack->IsEmpty());
    else if (Event->iFlags & conditional_propability)
    {
        double rprobability = Random();
        WriteLog( "Random integer: " + std::to_string( rprobability ) + " / " + std::to_string( Event->Params[ 10 ].asdouble ) );
        return (Event->Params[10].asdouble > rprobability);
    }
    else if( Event->iFlags & conditional_memcompare ) {
        // porównanie wartości
        if( nullptr == Event->Params[9].asMemCell ) {

            ErrorLog( "Event " + Event->asName + " trying conditional_memcompare with nonexistent memcell" );
            return true; // though this is technically error, we report success to maintain backward compatibility
        }
        auto const comparisonresult =
            m_workevent->Params[ 9 ].asMemCell->Compare(
                ( Event->Params[ 10 ].asText != nullptr ?
                    Event->Params[ 10 ].asText :
                    "" ),
                Event->Params[ 11 ].asdouble,
                Event->Params[ 12 ].asdouble,
                Event->iFlags );

        std::string comparisonlog = "Type: MemCompare - ";

        comparisonlog +=
               "[" + Event->Params[ 9 ].asMemCell->Text() + "]"
            + " [" + to_string( Event->Params[ 9 ].asMemCell->Value1(), 2 ) + "]"
            + " [" + to_string( m_workevent->Params[ 9 ].asMemCell->Value2(), 2 ) + "]";

        comparisonlog += (
            true == comparisonresult ?
                " == " :
                " != " );

        comparisonlog += (
            TestFlag( Event->iFlags, conditional_memstring ) ?
                "[" + std::string( m_workevent->Params[ 10 ].asText ) + "]" :
                "[*]" );
        comparisonlog += (
            TestFlag( m_workevent->iFlags, conditional_memval1 ) ?
                " [" + to_string( m_workevent->Params[ 11 ].asdouble, 2 ) + "]" :
                " [*]" );
        comparisonlog += (
            TestFlag( m_workevent->iFlags, conditional_memval2 ) ?
                " [" + to_string( m_workevent->Params[ 12 ].asdouble, 2 ) + "]" :
                " [*]" );

        WriteLog( comparisonlog );
        return comparisonresult;
    }
    // unrecognized request
    return false;
}
