/*
Date: Tue Feb  1 17:49:55 2022

Model author: Joeri Exelmans
Model name: client
Model description:
Statechart model implementing much of the 'client' part of our protocol for synchronous collaboration sessions (joining, leaving, auto-reconnecting, broadcasting and receiving operations, and heartbeats to monitor connection health).
*/


// package client
const {
                RuntimeClassBase,
                StatechartSemantics,
                ObjectManagerBase,
                EventLoopControllerBase,
                State,
                ParallelState,
                Transition,
                Event,
                Association,
            } = require("statecharts_core");

let Main = function(controller, uiState, myId, getMyName) {
    RuntimeClassBase.call(this, controller);
    
    this.inports["ack"] = controller.addInputPort("ack", this);
    this.inports["socket"] = controller.addInputPort("socket", this);
    
    this.semantics.bigStepMaximality = StatechartSemantics.TakeMany;
    this.semantics.internalEventLifeline = StatechartSemantics.NextComboStep;
    this.semantics.inputEventLifeline = StatechartSemantics.FirstComboStep;
    this.semantics.priority = StatechartSemantics.SourceParent;
    this.semantics.concurrency = StatechartSemantics.Single;
    
    // build Statechart structure
    this.buildStatechartStructure();
    
    // call user defined constructor
    Main.prototype.userDefinedConstructor.call(this, uiState, myId, getMyName);
};
Main.prototype = new Object();
(function() {
    var proto = new RuntimeClassBase();
    for (let prop in proto) {
        Main.prototype[prop] = proto[prop];
    }
})();

Main.prototype.userDefinedConstructor = function(uiState, myId, getMyName) {
    this.uiState = uiState;
    this.myId = myId;
    this.getMyName = getMyName;
    
    this.connected = false;
};

Main.prototype.userDefinedDestructor = function() {
};


// builds Statechart structure
Main.prototype.buildStatechartStructure = function() {
    
    // state <root>
    this.states[""] = new State(0, "", this);
    
    // state /p
    this.states["/p"] = new ParallelState(1, "/p", this);
    
    // state /p/response_handler
    this.states["/p/response_handler"] = new State(2, "/p/response_handler", this);
    this.states["/p/response_handler"].setEnter(this.PResponseHandlerEnter);
    
    // state /p/response_handler/default
    this.states["/p/response_handler/default"] = new State(3, "/p/response_handler/default", this);
    
    // state /p/mode
    this.states["/p/mode"] = new State(4, "/p/mode", this);
    
    // state /p/mode/uninitialized
    this.states["/p/mode/uninitialized"] = new State(5, "/p/mode/uninitialized", this);
    
    // state /p/mode/async_disconnected
    this.states["/p/mode/async_disconnected"] = new State(6, "/p/mode/async_disconnected", this);
    this.states["/p/mode/async_disconnected"].setEnter(this.PModeAsyncDisconnectedEnter);
    
    // state /p/mode/async_connected
    this.states["/p/mode/async_connected"] = new State(7, "/p/mode/async_connected", this);
    this.states["/p/mode/async_connected"].setEnter(this.PModeAsyncConnectedEnter);
    
    // state /p/mode/can_leave
    this.states["/p/mode/can_leave"] = new State(8, "/p/mode/can_leave", this);
    this.states["/p/mode/can_leave"].setExit(this.PModeCanLeaveExit);
    
    // state /p/mode/can_leave/waiting_for_new_share_ack
    this.states["/p/mode/can_leave/waiting_for_new_share_ack"] = new State(9, "/p/mode/can_leave/waiting_for_new_share_ack", this);
    this.states["/p/mode/can_leave/waiting_for_new_share_ack"].setEnter(this.PModeCanLeaveWaitingForNewShareAckEnter);
    this.states["/p/mode/can_leave/waiting_for_new_share_ack"].setExit(this.PModeCanLeaveWaitingForNewShareAckExit);
    
    // state /p/mode/can_leave/reshare_when_online
    this.states["/p/mode/can_leave/reshare_when_online"] = new State(10, "/p/mode/can_leave/reshare_when_online", this);
    this.states["/p/mode/can_leave/reshare_when_online"].setEnter(this.PModeCanLeaveReshareWhenOnlineEnter);
    
    // state /p/mode/can_leave/session_set
    this.states["/p/mode/can_leave/session_set"] = new State(11, "/p/mode/can_leave/session_set", this);
    this.states["/p/mode/can_leave/session_set"].setEnter(this.PModeCanLeaveSessionSetEnter);
    this.states["/p/mode/can_leave/session_set"].setExit(this.PModeCanLeaveSessionSetExit);
    
    // state /p/mode/can_leave/session_set/waiting_for_join_ack
    this.states["/p/mode/can_leave/session_set/waiting_for_join_ack"] = new State(12, "/p/mode/can_leave/session_set/waiting_for_join_ack", this);
    this.states["/p/mode/can_leave/session_set/waiting_for_join_ack"].setEnter(this.PModeCanLeaveSessionSetWaitingForJoinAckEnter);
    this.states["/p/mode/can_leave/session_set/waiting_for_join_ack"].setExit(this.PModeCanLeaveSessionSetWaitingForJoinAckExit);
    
    // state /p/mode/can_leave/session_set/joined
    this.states["/p/mode/can_leave/session_set/joined"] = new ParallelState(13, "/p/mode/can_leave/session_set/joined", this);
    
    // state /p/mode/can_leave/session_set/joined/region_sendreceive
    this.states["/p/mode/can_leave/session_set/joined/region_sendreceive"] = new State(14, "/p/mode/can_leave/session_set/joined/region_sendreceive", this);
    
    // state /p/mode/can_leave/session_set/joined/region_sendreceive/default
    this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"] = new State(15, "/p/mode/can_leave/session_set/joined/region_sendreceive/default", this);
    this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"].setEnter(this.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefaultEnter);
    
    // state /p/mode/can_leave/session_set/joined/region_cursor
    this.states["/p/mode/can_leave/session_set/joined/region_cursor"] = new State(16, "/p/mode/can_leave/session_set/joined/region_cursor", this);
    
    // state /p/mode/can_leave/session_set/joined/region_cursor/nodelay
    this.states["/p/mode/can_leave/session_set/joined/region_cursor/nodelay"] = new State(17, "/p/mode/can_leave/session_set/joined/region_cursor/nodelay", this);
    
    // state /p/mode/can_leave/session_set/joined/region_cursor/delay
    this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay"] = new State(18, "/p/mode/can_leave/session_set/joined/region_cursor/delay", this);
    this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay"].setEnter(this.PModeCanLeaveSessionSetJoinedRegionCursorDelayEnter);
    this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay"].setExit(this.PModeCanLeaveSessionSetJoinedRegionCursorDelayExit);
    
    // state /p/mode/can_leave/session_set/joined/region_cursor/delay/inner
    this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay/inner"] = new State(19, "/p/mode/can_leave/session_set/joined/region_cursor/delay/inner", this);
    
    // state /p/mode/can_leave/session_set/rejoin_when_online
    this.states["/p/mode/can_leave/session_set/rejoin_when_online"] = new State(20, "/p/mode/can_leave/session_set/rejoin_when_online", this);
    this.states["/p/mode/can_leave/session_set/rejoin_when_online"].setEnter(this.PModeCanLeaveSessionSetRejoinWhenOnlineEnter);
    
    // state /p/socket_region
    this.states["/p/socket_region"] = new State(21, "/p/socket_region", this);
    
    // state /p/socket_region/disconnected
    this.states["/p/socket_region/disconnected"] = new State(22, "/p/socket_region/disconnected", this);
    
    // state /p/socket_region/connecting_or_connected
    this.states["/p/socket_region/connecting_or_connected"] = new State(23, "/p/socket_region/connecting_or_connected", this);
    
    // state /p/socket_region/connecting_or_connected/connecting
    this.states["/p/socket_region/connecting_or_connected/connecting"] = new State(24, "/p/socket_region/connecting_or_connected/connecting", this);
    this.states["/p/socket_region/connecting_or_connected/connecting"].setEnter(this.PSocketRegionConnectingOrConnectedConnectingEnter);
    
    // state /p/socket_region/connecting_or_connected/connected
    this.states["/p/socket_region/connecting_or_connected/connected"] = new ParallelState(25, "/p/socket_region/connecting_or_connected/connected", this);
    this.states["/p/socket_region/connecting_or_connected/connected"].setEnter(this.PSocketRegionConnectingOrConnectedConnectedEnter);
    this.states["/p/socket_region/connecting_or_connected/connected"].setExit(this.PSocketRegionConnectingOrConnectedConnectedExit);
    
    // state /p/socket_region/connecting_or_connected/connected/send_receive_region
    this.states["/p/socket_region/connecting_or_connected/connected/send_receive_region"] = new State(26, "/p/socket_region/connecting_or_connected/connected/send_receive_region", this);
    
    // state /p/socket_region/connecting_or_connected/connected/send_receive_region/ready
    this.states["/p/socket_region/connecting_or_connected/connected/send_receive_region/ready"] = new State(27, "/p/socket_region/connecting_or_connected/connected/send_receive_region/ready", this);
    
    // state /p/socket_region/connecting_or_connected/connected/connection_monitor_region
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region"] = new State(28, "/p/socket_region/connecting_or_connected/connected/connection_monitor_region", this);
    
    // state /p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good"] = new ParallelState(29, "/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good", this);
    
    // state /p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings"] = new State(30, "/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings", this);
    
    // state /p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings/waiting
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings/waiting"] = new State(31, "/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings/waiting", this);
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings/waiting"].setEnter(this.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodSendPingsWaitingEnter);
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings/waiting"].setExit(this.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodSendPingsWaitingExit);
    
    // state /p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs"] = new State(32, "/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs", this);
    
    // state /p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs/waiting
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs/waiting"] = new State(33, "/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs/waiting", this);
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs/waiting"].setEnter(this.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodReceivePongsWaitingEnter);
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs/waiting"].setExit(this.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodReceivePongsWaitingExit);
    
    // state /p/socket_region/connecting_or_connected/connected/connection_monitor_region/timeout
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/timeout"] = new State(34, "/p/socket_region/connecting_or_connected/connected/connection_monitor_region/timeout", this);
    
    // state /p/socket_region/connecting_or_connected/wait_reconnect
    this.states["/p/socket_region/connecting_or_connected/wait_reconnect"] = new State(35, "/p/socket_region/connecting_or_connected/wait_reconnect", this);
    this.states["/p/socket_region/connecting_or_connected/wait_reconnect"].setEnter(this.PSocketRegionConnectingOrConnectedWaitReconnectEnter);
    this.states["/p/socket_region/connecting_or_connected/wait_reconnect"].setExit(this.PSocketRegionConnectingOrConnectedWaitReconnectExit);
    
    // add children
    this.states[""].addChild(this.states["/p"]);
    this.states["/p"].addChild(this.states["/p/response_handler"]);
    this.states["/p"].addChild(this.states["/p/mode"]);
    this.states["/p"].addChild(this.states["/p/socket_region"]);
    this.states["/p/response_handler"].addChild(this.states["/p/response_handler/default"]);
    this.states["/p/mode"].addChild(this.states["/p/mode/uninitialized"]);
    this.states["/p/mode"].addChild(this.states["/p/mode/async_disconnected"]);
    this.states["/p/mode"].addChild(this.states["/p/mode/async_connected"]);
    this.states["/p/mode"].addChild(this.states["/p/mode/can_leave"]);
    this.states["/p/mode/can_leave"].addChild(this.states["/p/mode/can_leave/waiting_for_new_share_ack"]);
    this.states["/p/mode/can_leave"].addChild(this.states["/p/mode/can_leave/reshare_when_online"]);
    this.states["/p/mode/can_leave"].addChild(this.states["/p/mode/can_leave/session_set"]);
    this.states["/p/mode/can_leave/session_set"].addChild(this.states["/p/mode/can_leave/session_set/waiting_for_join_ack"]);
    this.states["/p/mode/can_leave/session_set"].addChild(this.states["/p/mode/can_leave/session_set/joined"]);
    this.states["/p/mode/can_leave/session_set"].addChild(this.states["/p/mode/can_leave/session_set/rejoin_when_online"]);
    this.states["/p/mode/can_leave/session_set/joined"].addChild(this.states["/p/mode/can_leave/session_set/joined/region_sendreceive"]);
    this.states["/p/mode/can_leave/session_set/joined"].addChild(this.states["/p/mode/can_leave/session_set/joined/region_cursor"]);
    this.states["/p/mode/can_leave/session_set/joined/region_sendreceive"].addChild(this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"]);
    this.states["/p/mode/can_leave/session_set/joined/region_cursor"].addChild(this.states["/p/mode/can_leave/session_set/joined/region_cursor/nodelay"]);
    this.states["/p/mode/can_leave/session_set/joined/region_cursor"].addChild(this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay"]);
    this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay"].addChild(this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay/inner"]);
    this.states["/p/socket_region"].addChild(this.states["/p/socket_region/disconnected"]);
    this.states["/p/socket_region"].addChild(this.states["/p/socket_region/connecting_or_connected"]);
    this.states["/p/socket_region/connecting_or_connected"].addChild(this.states["/p/socket_region/connecting_or_connected/connecting"]);
    this.states["/p/socket_region/connecting_or_connected"].addChild(this.states["/p/socket_region/connecting_or_connected/connected"]);
    this.states["/p/socket_region/connecting_or_connected"].addChild(this.states["/p/socket_region/connecting_or_connected/wait_reconnect"]);
    this.states["/p/socket_region/connecting_or_connected/connected"].addChild(this.states["/p/socket_region/connecting_or_connected/connected/send_receive_region"]);
    this.states["/p/socket_region/connecting_or_connected/connected"].addChild(this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region"]);
    this.states["/p/socket_region/connecting_or_connected/connected/send_receive_region"].addChild(this.states["/p/socket_region/connecting_or_connected/connected/send_receive_region/ready"]);
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region"].addChild(this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good"]);
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region"].addChild(this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/timeout"]);
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good"].addChild(this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings"]);
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good"].addChild(this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs"]);
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings"].addChild(this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings/waiting"]);
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs"].addChild(this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs/waiting"]);
    this.states[""].fixTree();
    this.states[""].defaultState = this.states["/p"];
    this.states["/p/response_handler"].defaultState = this.states["/p/response_handler/default"];
    this.states["/p/mode"].defaultState = this.states["/p/mode/uninitialized"];
    this.states["/p/mode/can_leave"].defaultState = this.states["/p/mode/can_leave/session_set"];
    this.states["/p/mode/can_leave/session_set"].defaultState = this.states["/p/mode/can_leave/session_set/waiting_for_join_ack"];
    this.states["/p/mode/can_leave/session_set/joined/region_sendreceive"].defaultState = this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"];
    this.states["/p/mode/can_leave/session_set/joined/region_cursor"].defaultState = this.states["/p/mode/can_leave/session_set/joined/region_cursor/nodelay"];
    this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay"].defaultState = this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay/inner"];
    this.states["/p/socket_region"].defaultState = this.states["/p/socket_region/disconnected"];
    this.states["/p/socket_region/connecting_or_connected"].defaultState = this.states["/p/socket_region/connecting_or_connected/connecting"];
    this.states["/p/socket_region/connecting_or_connected/connected/send_receive_region"].defaultState = this.states["/p/socket_region/connecting_or_connected/connected/send_receive_region/ready"];
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region"].defaultState = this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good"];
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings"].defaultState = this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings/waiting"];
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs"].defaultState = this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs/waiting"];
    
    // transition /p/response_handler/default
    var PResponseHandlerDefault_0 = new Transition(this, this.states["/p/response_handler/default"], [this.states["/p/response_handler/default"]]);
    PResponseHandlerDefault_0.setAction(this.PResponseHandlerDefault_0Exec);
    PResponseHandlerDefault_0.setTrigger(new Event("received", null));
    this.states["/p/response_handler/default"].addTransition(PResponseHandlerDefault_0);
    var PResponseHandlerDefault_1 = new Transition(this, this.states["/p/response_handler/default"], [this.states["/p/response_handler/default"]]);
    PResponseHandlerDefault_1.setAction(this.PResponseHandlerDefault_1Exec);
    PResponseHandlerDefault_1.setTrigger(new Event("disconnected", null));
    this.states["/p/response_handler/default"].addTransition(PResponseHandlerDefault_1);
    
    // transition /p/mode/uninitialized
    var PModeUninitialized_0 = new Transition(this, this.states["/p/mode/uninitialized"], [this.states["/p/mode/async_disconnected"]]);
    PModeUninitialized_0.setTrigger(new Event("init_offline", this.getInPortName("in")));
    this.states["/p/mode/uninitialized"].addTransition(PModeUninitialized_0);
    var PModeUninitialized_1 = new Transition(this, this.states["/p/mode/uninitialized"], [this.states["/p/mode/can_leave/session_set/rejoin_when_online"]]);
    PModeUninitialized_1.setAction(this.PModeUninitialized_1Exec);
    PModeUninitialized_1.setTrigger(new Event("init_join", this.getInPortName("in")));
    this.states["/p/mode/uninitialized"].addTransition(PModeUninitialized_1);
    
    // transition /p/mode/async_disconnected
    var PModeAsyncDisconnected_0 = new Transition(this, this.states["/p/mode/async_disconnected"], [this.states["/p/mode/async_connected"]]);
    PModeAsyncDisconnected_0.setTrigger(new Event("connected", null));
    this.states["/p/mode/async_disconnected"].addTransition(PModeAsyncDisconnected_0);
    
    // transition /p/mode/async_connected
    var PModeAsyncConnected_0 = new Transition(this, this.states["/p/mode/async_connected"], [this.states["/p/mode/async_disconnected"]]);
    PModeAsyncConnected_0.setTrigger(new Event("disconnected", null));
    this.states["/p/mode/async_connected"].addTransition(PModeAsyncConnected_0);
    var PModeAsyncConnected_1 = new Transition(this, this.states["/p/mode/async_connected"], [this.states["/p/mode/can_leave/session_set/waiting_for_join_ack"]]);
    PModeAsyncConnected_1.setAction(this.PModeAsyncConnected_1Exec);
    PModeAsyncConnected_1.setTrigger(new Event("join", this.getInPortName("in")));
    this.states["/p/mode/async_connected"].addTransition(PModeAsyncConnected_1);
    var PModeAsyncConnected_2 = new Transition(this, this.states["/p/mode/async_connected"], [this.states["/p/mode/can_leave/waiting_for_new_share_ack"]]);
    PModeAsyncConnected_2.setAction(this.PModeAsyncConnected_2Exec);
    PModeAsyncConnected_2.setTrigger(new Event("new_share", this.getInPortName("in")));
    this.states["/p/mode/async_connected"].addTransition(PModeAsyncConnected_2);
    
    // transition /p/mode/can_leave/waiting_for_new_share_ack
    var PModeCanLeaveWaitingForNewShareAck_0 = new Transition(this, this.states["/p/mode/can_leave/waiting_for_new_share_ack"], [this.states["/p/mode/can_leave/reshare_when_online"]]);
    PModeCanLeaveWaitingForNewShareAck_0.setTrigger(new Event("disconnected", null));
    this.states["/p/mode/can_leave/waiting_for_new_share_ack"].addTransition(PModeCanLeaveWaitingForNewShareAck_0);
    var PModeCanLeaveWaitingForNewShareAck_1 = new Transition(this, this.states["/p/mode/can_leave/waiting_for_new_share_ack"], [this.states["/p/mode/can_leave/session_set/joined"]]);
    PModeCanLeaveWaitingForNewShareAck_1.setAction(this.PModeCanLeaveWaitingForNewShareAck_1Exec);
    PModeCanLeaveWaitingForNewShareAck_1.setTrigger(new Event("ack_new_share", this.getInPortName("ack")));
    this.states["/p/mode/can_leave/waiting_for_new_share_ack"].addTransition(PModeCanLeaveWaitingForNewShareAck_1);
    
    // transition /p/mode/can_leave/reshare_when_online
    var PModeCanLeaveReshareWhenOnline_0 = new Transition(this, this.states["/p/mode/can_leave/reshare_when_online"], [this.states["/p/mode/can_leave/waiting_for_new_share_ack"]]);
    PModeCanLeaveReshareWhenOnline_0.setTrigger(new Event("connected", null));
    this.states["/p/mode/can_leave/reshare_when_online"].addTransition(PModeCanLeaveReshareWhenOnline_0);
    
    // transition /p/mode/can_leave/session_set/waiting_for_join_ack
    var PModeCanLeaveSessionSetWaitingForJoinAck_0 = new Transition(this, this.states["/p/mode/can_leave/session_set/waiting_for_join_ack"], [this.states["/p/mode/can_leave/session_set/joined"]]);
    PModeCanLeaveSessionSetWaitingForJoinAck_0.setAction(this.PModeCanLeaveSessionSetWaitingForJoinAck_0Exec);
    PModeCanLeaveSessionSetWaitingForJoinAck_0.setTrigger(new Event("ack_join", this.getInPortName("ack")));
    this.states["/p/mode/can_leave/session_set/waiting_for_join_ack"].addTransition(PModeCanLeaveSessionSetWaitingForJoinAck_0);
    var PModeCanLeaveSessionSetWaitingForJoinAck_1 = new Transition(this, this.states["/p/mode/can_leave/session_set/waiting_for_join_ack"], [this.states["/p/mode/can_leave/session_set/rejoin_when_online"]]);
    PModeCanLeaveSessionSetWaitingForJoinAck_1.setTrigger(new Event("disconnected", null));
    this.states["/p/mode/can_leave/session_set/waiting_for_join_ack"].addTransition(PModeCanLeaveSessionSetWaitingForJoinAck_1);
    
    // transition /p/mode/can_leave/session_set/joined/region_sendreceive/default
    var PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_0 = new Transition(this, this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"], [this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"]]);
    PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_0.setAction(this.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_0Exec);
    PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_0.setTrigger(new Event("received", null));
    PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_0.setGuard(this.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_0Guard);
    this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"].addTransition(PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_0);
    var PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_1 = new Transition(this, this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"], [this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"]]);
    PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_1.setAction(this.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_1Exec);
    PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_1.setTrigger(new Event("new_edit", this.getInPortName("in")));
    this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"].addTransition(PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_1);
    var PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_2 = new Transition(this, this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"], [this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"]]);
    PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_2.setAction(this.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_2Exec);
    PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_2.setTrigger(new Event("request_lock", this.getInPortName("in")));
    this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"].addTransition(PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_2);
    var PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_3 = new Transition(this, this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"], [this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"]]);
    PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_3.setAction(this.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_3Exec);
    PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_3.setTrigger(new Event("release_lock", this.getInPortName("in")));
    this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"].addTransition(PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_3);
    var PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_4 = new Transition(this, this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"], [this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"]]);
    PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_4.setAction(this.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_4Exec);
    PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_4.setTrigger(new Event("received", null));
    PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_4.setGuard(this.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_4Guard);
    this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"].addTransition(PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_4);
    var PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_5 = new Transition(this, this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"], [this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"]]);
    PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_5.setAction(this.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_5Exec);
    PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_5.setTrigger(new Event("broadcast_selection", null));
    this.states["/p/mode/can_leave/session_set/joined/region_sendreceive/default"].addTransition(PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_5);
    
    // transition /p/mode/can_leave/session_set/joined/region_cursor/nodelay
    var PModeCanLeaveSessionSetJoinedRegionCursorNodelay_0 = new Transition(this, this.states["/p/mode/can_leave/session_set/joined/region_cursor/nodelay"], [this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay"]]);
    PModeCanLeaveSessionSetJoinedRegionCursorNodelay_0.setAction(this.PModeCanLeaveSessionSetJoinedRegionCursorNodelay_0Exec);
    PModeCanLeaveSessionSetJoinedRegionCursorNodelay_0.setTrigger(new Event("broadcast_cursor", this.getInPortName("in")));
    this.states["/p/mode/can_leave/session_set/joined/region_cursor/nodelay"].addTransition(PModeCanLeaveSessionSetJoinedRegionCursorNodelay_0);
    
    // transition /p/mode/can_leave/session_set/joined/region_cursor/delay/inner
    var PModeCanLeaveSessionSetJoinedRegionCursorDelayInner_0 = new Transition(this, this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay/inner"], [this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay/inner"]]);
    PModeCanLeaveSessionSetJoinedRegionCursorDelayInner_0.setAction(this.PModeCanLeaveSessionSetJoinedRegionCursorDelayInner_0Exec);
    PModeCanLeaveSessionSetJoinedRegionCursorDelayInner_0.setTrigger(new Event("broadcast_cursor", this.getInPortName("in")));
    this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay/inner"].addTransition(PModeCanLeaveSessionSetJoinedRegionCursorDelayInner_0);
    
    // transition /p/mode/can_leave/session_set/rejoin_when_online
    var PModeCanLeaveSessionSetRejoinWhenOnline_0 = new Transition(this, this.states["/p/mode/can_leave/session_set/rejoin_when_online"], [this.states["/p/mode/can_leave/session_set/waiting_for_join_ack"]]);
    PModeCanLeaveSessionSetRejoinWhenOnline_0.setTrigger(new Event("connected", null));
    this.states["/p/mode/can_leave/session_set/rejoin_when_online"].addTransition(PModeCanLeaveSessionSetRejoinWhenOnline_0);
    
    // transition /p/socket_region/disconnected
    var PSocketRegionDisconnected_0 = new Transition(this, this.states["/p/socket_region/disconnected"], [this.states["/p/socket_region/connecting_or_connected"]]);
    PSocketRegionDisconnected_0.setAction(this.PSocketRegionDisconnected_0Exec);
    PSocketRegionDisconnected_0.setTrigger(new Event("connect", this.getInPortName("in")));
    this.states["/p/socket_region/disconnected"].addTransition(PSocketRegionDisconnected_0);
    
    // transition /p/socket_region/connecting_or_connected/connecting
    var PSocketRegionConnectingOrConnectedConnecting_0 = new Transition(this, this.states["/p/socket_region/connecting_or_connected/connecting"], [this.states["/p/socket_region/connecting_or_connected/connected"]]);
    PSocketRegionConnectingOrConnectedConnecting_0.setTrigger(new Event("open", this.getInPortName("socket")));
    this.states["/p/socket_region/connecting_or_connected/connecting"].addTransition(PSocketRegionConnectingOrConnectedConnecting_0);
    var PSocketRegionConnectingOrConnectedConnecting_1 = new Transition(this, this.states["/p/socket_region/connecting_or_connected/connecting"], [this.states["/p/socket_region/connecting_or_connected/wait_reconnect"]]);
    PSocketRegionConnectingOrConnectedConnecting_1.setAction(this.PSocketRegionConnectingOrConnectedConnecting_1Exec);
    PSocketRegionConnectingOrConnectedConnecting_1.setTrigger(new Event("error", this.getInPortName("socket")));
    this.states["/p/socket_region/connecting_or_connected/connecting"].addTransition(PSocketRegionConnectingOrConnectedConnecting_1);
    
    // transition /p/socket_region/connecting_or_connected/connected/send_receive_region/ready
    var PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_0 = new Transition(this, this.states["/p/socket_region/connecting_or_connected/connected/send_receive_region/ready"], [this.states["/p/socket_region/connecting_or_connected/connected/send_receive_region/ready"]]);
    PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_0.setAction(this.PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_0Exec);
    PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_0.setTrigger(new Event("send", null));
    this.states["/p/socket_region/connecting_or_connected/connected/send_receive_region/ready"].addTransition(PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_0);
    var PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_1 = new Transition(this, this.states["/p/socket_region/connecting_or_connected/connected/send_receive_region/ready"], [this.states["/p/socket_region/connecting_or_connected/connected/send_receive_region/ready"]]);
    PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_1.setAction(this.PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_1Exec);
    PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_1.setTrigger(new Event("message", this.getInPortName("socket")));
    PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_1.setGuard(this.PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_1Guard);
    this.states["/p/socket_region/connecting_or_connected/connected/send_receive_region/ready"].addTransition(PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_1);
    
    // transition /p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings/waiting
    var PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodSendPingsWaiting_0 = new Transition(this, this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings/waiting"], [this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings/waiting"]]);
    PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodSendPingsWaiting_0.setTrigger(new Event("send", this.getInPortName("in")));
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings/waiting"].addTransition(PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodSendPingsWaiting_0);
    var PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodSendPingsWaiting_1 = new Transition(this, this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings/waiting"], [this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings/waiting"]]);
    PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodSendPingsWaiting_1.setAction(this.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodSendPingsWaiting_1Exec);
    PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodSendPingsWaiting_1.setTrigger(new Event("event1After"));
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/send_pings/waiting"].addTransition(PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodSendPingsWaiting_1);
    
    // transition /p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs/waiting
    var PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodReceivePongsWaiting_0 = new Transition(this, this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs/waiting"], [this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs/waiting"]]);
    PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodReceivePongsWaiting_0.setTrigger(new Event("message", this.getInPortName("socket")));
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs/waiting"].addTransition(PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodReceivePongsWaiting_0);
    var PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodReceivePongsWaiting_1 = new Transition(this, this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs/waiting"], [this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs/waiting"]]);
    PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodReceivePongsWaiting_1.setAction(this.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodReceivePongsWaiting_1Exec);
    PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodReceivePongsWaiting_1.setTrigger(new Event("event2After"));
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good/receive_pongs/waiting"].addTransition(PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodReceivePongsWaiting_1);
    
    // transition /p/socket_region/connecting_or_connected/connected/connection_monitor_region/timeout
    var PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionTimeout_0 = new Transition(this, this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/timeout"], [this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good"]]);
    PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionTimeout_0.setAction(this.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionTimeout_0Exec);
    PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionTimeout_0.setTrigger(new Event("message", this.getInPortName("socket")));
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/timeout"].addTransition(PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionTimeout_0);
    
    // transition /p/socket_region/connecting_or_connected/wait_reconnect
    var PSocketRegionConnectingOrConnectedWaitReconnect_0 = new Transition(this, this.states["/p/socket_region/connecting_or_connected/wait_reconnect"], [this.states["/p/socket_region/connecting_or_connected/connecting"]]);
    PSocketRegionConnectingOrConnectedWaitReconnect_0.setTrigger(new Event("event3After"));
    this.states["/p/socket_region/connecting_or_connected/wait_reconnect"].addTransition(PSocketRegionConnectingOrConnectedWaitReconnect_0);
    
    // transition /p/mode/can_leave
    var PModeCanLeave_0 = new Transition(this, this.states["/p/mode/can_leave"], [this.states["/p/mode/async_connected"]]);
    PModeCanLeave_0.setTrigger(new Event("leave", this.getInPortName("in")));
    PModeCanLeave_0.setGuard(this.PModeCanLeave_0Guard);
    this.states["/p/mode/can_leave"].addTransition(PModeCanLeave_0);
    var PModeCanLeave_1 = new Transition(this, this.states["/p/mode/can_leave"], [this.states["/p/mode/async_disconnected"]]);
    PModeCanLeave_1.setTrigger(new Event("leave", this.getInPortName("in")));
    PModeCanLeave_1.setGuard(this.PModeCanLeave_1Guard);
    this.states["/p/mode/can_leave"].addTransition(PModeCanLeave_1);
    
    // transition /p/mode/can_leave/session_set/joined
    var PModeCanLeaveSessionSetJoined_0 = new Transition(this, this.states["/p/mode/can_leave/session_set/joined"], [this.states["/p/mode/can_leave/session_set/rejoin_when_online"]]);
    PModeCanLeaveSessionSetJoined_0.setTrigger(new Event("disconnected", null));
    this.states["/p/mode/can_leave/session_set/joined"].addTransition(PModeCanLeaveSessionSetJoined_0);
    
    // transition /p/mode/can_leave/session_set/joined/region_cursor/delay
    var PModeCanLeaveSessionSetJoinedRegionCursorDelay_0 = new Transition(this, this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay"], [this.states["/p/mode/can_leave/session_set/joined/region_cursor/nodelay"]]);
    PModeCanLeaveSessionSetJoinedRegionCursorDelay_0.setAction(this.PModeCanLeaveSessionSetJoinedRegionCursorDelay_0Exec);
    PModeCanLeaveSessionSetJoinedRegionCursorDelay_0.setTrigger(new Event("event0After"));
    this.states["/p/mode/can_leave/session_set/joined/region_cursor/delay"].addTransition(PModeCanLeaveSessionSetJoinedRegionCursorDelay_0);
    
    // transition /p/socket_region/connecting_or_connected
    var PSocketRegionConnectingOrConnected_0 = new Transition(this, this.states["/p/socket_region/connecting_or_connected"], [this.states["/p/socket_region/disconnected"]]);
    PSocketRegionConnectingOrConnected_0.setAction(this.PSocketRegionConnectingOrConnected_0Exec);
    PSocketRegionConnectingOrConnected_0.setTrigger(new Event("disconnect", this.getInPortName("in")));
    this.states["/p/socket_region/connecting_or_connected"].addTransition(PSocketRegionConnectingOrConnected_0);
    
    // transition /p/socket_region/connecting_or_connected/connected
    var PSocketRegionConnectingOrConnectedConnected_0 = new Transition(this, this.states["/p/socket_region/connecting_or_connected/connected"], [this.states["/p/socket_region/connecting_or_connected/wait_reconnect"]]);
    PSocketRegionConnectingOrConnectedConnected_0.setTrigger(new Event("close", this.getInPortName("socket")));
    this.states["/p/socket_region/connecting_or_connected/connected"].addTransition(PSocketRegionConnectingOrConnectedConnected_0);
    
    // transition /p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good
    var PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGood_0 = new Transition(this, this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good"], [this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/timeout"]]);
    PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGood_0.setAction(this.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGood_0Exec);
    PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGood_0.setTrigger(new Event("timeout", null));
    this.states["/p/socket_region/connecting_or_connected/connected/connection_monitor_region/all_good"].addTransition(PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGood_0);
};

Main.prototype.PResponseHandlerEnter = function() {
    this.reqHandlers = new Map();
    this.reqCounter = 0;
    this.sendReq = (obj, handler) => {
      const reqId = this.reqCounter++;
      const req = {
        reqId,
        ...obj,
      };
      if (handler !== undefined) {
        this.reqHandlers.set(reqId, handler);
      }
      console.log("Sending req", req);
      this.addEvent(new Event("send", null, [req]));
    
      // allow to cancel the request handler
      return () => {
        this.reqHandlers.delete(reqId);
      };
    }
};

Main.prototype.PModeCanLeaveExit = function() {
    this.sendReq({type:"leave"});
};

Main.prototype.PModeCanLeaveSessionSetEnter = function() {
    if (!this.sessionId) {
      throw new Error("DEBUG: no session id");
    }
    this.uiState.setSession(this.sessionId);
};

Main.prototype.PModeCanLeaveSessionSetExit = function() {
    this.uiState.unsetSession();
};

Main.prototype.PModeCanLeaveSessionSetJoinedRegionCursorDelayEnter = function() {
    this.cursorState = null; 
    this.addTimer(0, 0.1);
};

Main.prototype.PModeCanLeaveSessionSetJoinedRegionCursorDelayExit = function() {
    this.removeTimer(0);
};

Main.prototype.PSocketRegionConnectingOrConnectedConnectedEnter = function() {
    this.raiseInternalEvent(new Event("connected", null, []));
    this.connected = true; 
};

Main.prototype.PSocketRegionConnectingOrConnectedConnectedExit = function() {
    this.raiseInternalEvent(new Event("disconnected", null, []));
    this.connected = false; 
};

Main.prototype.PModeAsyncDisconnectedEnter = function() {
    this.uiState.setOfflineDisconnected();
};

Main.prototype.PModeAsyncConnectedEnter = function() {
    this.uiState.setOfflineConnected();
};

Main.prototype.PModeCanLeaveWaitingForNewShareAckEnter = function() {
    this.uiState.setCreatingShare();
    this.deleteNewShareHandler = this.sendReq(
      {type: "new_share", ops: this.ops},
      res => {
        // called during transition (as action code), so no need to 'wake up' the controller like we do with 'addInput':
        this.addEvent(new Event("ack_new_share", "ack", [res.sessionId]));
      });
};

Main.prototype.PModeCanLeaveWaitingForNewShareAckExit = function() {
    this.deleteNewShareHandler();
};

Main.prototype.PModeCanLeaveReshareWhenOnlineEnter = function() {
    this.uiState.setReconnecting();
};

Main.prototype.PModeCanLeaveSessionSetWaitingForJoinAckEnter = function() {
    this.uiState.setJoining();
    
    this.deleteJoinHandler = this.sendReq(
      {type: "join", sessionId: this.sessionId},
      res => {
        // called during transition (as action code), so no need to 'wake up' the controller like we do with 'addInput':
        this.addEvent(new Event("ack_join", "ack", [res.ops]));
      });
};

Main.prototype.PModeCanLeaveSessionSetWaitingForJoinAckExit = function() {
    this.deleteJoinHandler();
};

Main.prototype.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefaultEnter = function() {
    this.uiState.setOnline();
};

Main.prototype.PModeCanLeaveSessionSetRejoinWhenOnlineEnter = function() {
    this.uiState.setReconnecting();
};

Main.prototype.PSocketRegionConnectingOrConnectedConnectingEnter = function() {
    console.log("connecting...");
    
    this.socket = new WebSocket(this.addr);
    
    // Translate socket events to statechart events:
    
    this.socket.onopen = event => {
      this.controller.addInput("open", this.inports["socket"], [], this.controller.wallclockToSimtime());
    };
    
    this.socket.onmessage = event => {
      let parsed;
      try {
        parsed = JSON.parse(event.data);
      } catch (e) {
        console.log("received unparsable message", e);
        return;
      }
    
      this.controller.addInput("message", this.inports["socket"], [parsed], this.controller.wallclockToSimtime());
    };
    
    this.socket.onerror = event => {
      // From what I see, this event only happens when the socket could not connect.
      // An 'error' happening after the connection is established, will be indicated by a close event.
      this.controller.addInput("error", this.inports["socket"], [], this.controller.wallclockToSimtime());
    };
    
    this.socket.onclose = event => {
      this.controller.addInput("close", this.inports["socket"], [], this.controller.wallclockToSimtime());
    };
};

Main.prototype.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodSendPingsWaitingEnter = function() {
    this.addTimer(1, 1);
};

Main.prototype.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodSendPingsWaitingExit = function() {
    this.removeTimer(1);
};

Main.prototype.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodReceivePongsWaitingEnter = function() {
    this.addTimer(2, 5);
};

Main.prototype.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodReceivePongsWaitingExit = function() {
    this.removeTimer(2);
};

Main.prototype.PSocketRegionConnectingOrConnectedWaitReconnectEnter = function() {
    this.addTimer(3, 1);
};

Main.prototype.PSocketRegionConnectingOrConnectedWaitReconnectExit = function() {
    this.removeTimer(3);
};

Main.prototype.PModeCanLeave_0Guard = function(parameters) {
    return this.connected;
};

Main.prototype.PModeCanLeave_1Guard = function(parameters) {
    return !this.connected;
};

Main.prototype.PModeCanLeaveSessionSetJoinedRegionCursorDelay_0Exec = function(parameters) {
    if (this.cursorState !== null) {
      this.addEvent(new Event("send", null, [{
        type:"broadcast",
        msg: {
          type:"update_cursor",
          userId: this.myId,
          name: this.getMyName(),
          ...this.cursorState,
        },
      }]));
    }
};

Main.prototype.PSocketRegionConnectingOrConnected_0Exec = function(parameters) {
    this.socket.close();
};

Main.prototype.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGood_0Exec = function(parameters) {
    this.raiseInternalEvent(new Event("timeout", null, []));
};

Main.prototype.PResponseHandlerDefault_0Exec = function(parameters) {
    var parsed = parameters[0];
    if (parsed.type === 'ack' && parsed.reqId !== undefined) {
      const handler = this.reqHandlers.get(parsed.reqId);
      if (handler) {
        handler(parsed);
      }
    }
};

Main.prototype.PResponseHandlerDefault_1Exec = function(parameters) {
    // no way we will get a response for our pending requests
    this.reqHandlers.clear();
    this.reqCounter = 0;
};

Main.prototype.PModeUninitialized_1Exec = function(parameters) {
    var sessionId = parameters[0];
    this.sessionId = sessionId;
};

Main.prototype.PModeAsyncConnected_1Exec = function(parameters) {
    var sessionId = parameters[0];
    this.sessionId = sessionId; 
};

Main.prototype.PModeAsyncConnected_2Exec = function(parameters) {
    var ops = parameters[0];
    // console.log("new share event, ops=", ops);
    this.ops = ops;
};

Main.prototype.PModeCanLeaveWaitingForNewShareAck_1Exec = function(parameters) {
    var sessionId = parameters[0];
    this.sessionId = sessionId;
    this.bigStep.outputEvent(new Event("ack_new_share", this.getOutPortName("out"), [sessionId]));
};

Main.prototype.PModeCanLeaveSessionSetWaitingForJoinAck_0Exec = function(parameters) {
    var ops = parameters[0];
    this.bigStep.outputEvent(new Event("ack_join", this.getOutPortName("out"), [ops]));
};

Main.prototype.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_0Exec = function(parameters) {
    var parsed = parameters[0];
    if (parsed.sessionId !== this.sessionId) {
      throw new Error("Unexpected: received edit for another session:" +  parsed.sessionId);
    }
    this.bigStep.outputEvent(new Event("received_op", this.getOutPortName("out"), [parsed.op]));
};

Main.prototype.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_0Guard = function(parameters) {
    var parsed = parameters[0];
    return parsed.type === 'pub_edit';
};

Main.prototype.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_1Exec = function(parameters) {
    var serialized = parameters[0];
    this.sendReq(
      {type:"new_edit", sessionId: this.sessionId, op: serialized},
      res => {
        // this.unacknowledged.delete(serialized.id);
      });
    // this.unacknowledged.set(serialized.id, serialized);
};

Main.prototype.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_2Exec = function(parameters) {
    var cellIds = parameters[0];
    var acquiredCallback = parameters[1];
    var deniedCallback = parameters[2];
    this.sendReq(
      {type:"request_lock", sessionId: this.sessionId, cellIds},
      res => {
        if (res.success) {
          acquiredCallback();
        } else {
          deniedCallback();
        }
      });
};

Main.prototype.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_3Exec = function(parameters) {
    var cellIds = parameters[0];
    this.sendReq(
      {type:"release_lock", sessionId: this.sessionId, cellIds},
      res => {},
    );
};

Main.prototype.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_4Exec = function(parameters) {
    var parsed = parameters[0];
    this.bigStep.outputEvent(new Event("broadcast", this.getOutPortName("out"), [parsed.sessionId, parsed.msg]));
};

Main.prototype.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_4Guard = function(parameters) {
    var parsed = parameters[0];
    return parsed.type === 'broadcast';
};

Main.prototype.PModeCanLeaveSessionSetJoinedRegionSendreceiveDefault_5Exec = function(parameters) {
    var selectedIds = parameters[0];
    if (this.cursorState !== null) {
      this.addEvent(new Event("send", null, [{
        type:"broadcast",
        msg: {
          type:"update_selection",
          userId: this.myId,
          selectedIds,
          ...this.cursorState,
        },
      }]));
    }
};

Main.prototype.PModeCanLeaveSessionSetJoinedRegionCursorNodelay_0Exec = function(parameters) {
    var x = parameters[0];
    var y = parameters[1];
    this.raiseInternalEvent(new Event("send", null, [{type:"broadcast", msg: {type:"update_cursor", name: this.getMyName(), x, y, userId: this.myId}}]));
};

Main.prototype.PModeCanLeaveSessionSetJoinedRegionCursorDelayInner_0Exec = function(parameters) {
    var x = parameters[0];
    var y = parameters[1];
    this.cursorState = {x,y};
};

Main.prototype.PSocketRegionDisconnected_0Exec = function(parameters) {
    var addr = parameters[0];
    console.log("received connect", addr)
    this.addr = addr;
};

Main.prototype.PSocketRegionConnectingOrConnectedConnecting_1Exec = function(parameters) {
    this.raiseInternalEvent(new Event("error", null, []));
};

Main.prototype.PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_0Exec = function(parameters) {
    var json = parameters[0];
    this.socket.send(JSON.stringify(json));
};

Main.prototype.PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_1Exec = function(parameters) {
    var parsed = parameters[0];
    this.raiseInternalEvent(new Event("received", null, [parsed]));
};

Main.prototype.PSocketRegionConnectingOrConnectedConnectedSendReceiveRegionReady_1Guard = function(parameters) {
    var parsed = parameters[0];
    return parsed.type !== 'pong';
};

Main.prototype.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodSendPingsWaiting_1Exec = function(parameters) {
    this.socket.send(JSON.stringify({type:"ping"}));
};

Main.prototype.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionAllGoodReceivePongsWaiting_1Exec = function(parameters) {
    this.raiseInternalEvent(new Event("timeout", null, []));
};

Main.prototype.PSocketRegionConnectingOrConnectedConnectedConnectionMonitorRegionTimeout_0Exec = function(parameters) {
    this.raiseInternalEvent(new Event("timeout_recovered", null, []));
};

Main.prototype.initializeStatechart = function() {
    // enter default state
    this.defaultTargets = this.states["/p"].getEffectiveTargetStates();
    RuntimeClassBase.prototype.initializeStatechart.call(this);
};

module.exports.Main = Main;

let ObjectManager = function(controller) {
    ObjectManagerBase.call(this, controller);
};
ObjectManager.prototype = new Object();
(function() {
    var proto = new ObjectManagerBase();
    for (let prop in proto) {
        ObjectManager.prototype[prop] = proto[prop];
    }
})();

ObjectManager.prototype.instantiate = function(className, constructParams) {
    if (className === "Main") {
        var instance = new Main(this.controller, constructParams[0], constructParams[1], constructParams[2]);
        instance.associations = {};
        instance.associations["socket"] = new Association("Socket", 1, 1);
    } else  {
        throw new Error("Cannot instantiate class " + className);
    }
    return instance;
};

module.exports.ObjectManager = ObjectManager;

let Controller = function(uiState, myId, getMyName, eventLoopCallbacks, finishedCallback, behindScheduleCallback) {
    if (finishedCallback === undefined) finishedCallback = null;
    if (behindScheduleCallback === undefined) behindScheduleCallback = null;
    EventLoopControllerBase.call(this, new ObjectManager(this), eventLoopCallbacks, finishedCallback, behindScheduleCallback);
    this.addInputPort("in");
    this.addOutputPort("out");
    this.objectManager.createInstance("Main", [uiState, myId, getMyName]);
};
Controller.prototype = new Object();
(function() {
    var proto = new EventLoopControllerBase();
    for (let prop in proto) {
        Controller.prototype[prop] = proto[prop];
    }
})();

module.exports.Controller = Controller;