text: // data on the MATRX API
  var matrx_url = 'http://' + window.location.hostname,
      port = "3001",
      matrx_send_message_url = "send_message";
  
  var chat_blocked = true;
  
  /*********************************************************************
   * Simulation control buttons in toolbar (start/pause etc.)
   ********************************************************************/
  
  // Toolbar elements
  var start_button = document.getElementById("start_button"),
      pause_button = document.getElementById("pause_button"),
      stop_button = document.getElementById("stop_button");
  
  /**
   * Synchronizes the play/pause button with the current value of MATRX
   */
  function sync_play_button(matrx_paused) {
  
      console.log("syncing play/pause button, matrx_paused:", matrx_paused);
      // hide the play button and show the pause button
      if (!matrx_paused) {
          start_button.classList.add("hidden");
          pause_button.classList.remove("hidden");
  
          // vice versa
      } else {
          start_button.classList.remove("hidden");
          pause_button.classList.add("hidden");
      }
  }
  
  start_button.addEventListener("click", toggle_start, false);
  
  function toggle_start() {
      console.log("pressed play");
      // hide / unhide the correct button
      start_button.classList.toggle("hidden");
      pause_button.classList.toggle("hidden");
  
      // send API message to MATRX
      send_api_message("start");
  }
  
  pause_button.addEventListener("click", toggle_pause, false);
  
  function toggle_pause() {
      console.log("pressed pause");
      // hide / unhide the correct button
      start_button.classList.toggle("hidden");
      pause_button.classList.toggle("hidden");
  
      // send API message to MATRX
      send_api_message("pause");
  }
  
  
  stop_button.addEventListener("click", toggle_stop, false);
  
  function toggle_stop() {
      send_api_message("stop");
  }
  
  
  /**
   * Send a message to the MATRX API
   */
  function send_api_message(type) {
      var resp = $.ajax({
          method: "GET",
          url: matrx_url + ":" + port + "/" + type,
          contentType: "application/json; charset=utf-8",
          dataType: 'json'
      });
  }
  
  function send_matrx_api_post_message(type, post_data) {
      var resp = $.ajax({
          method: "POST",
          data: JSON.stringify(post_data),
          url: matrx_url + ":" + port + "/" + type,
          contentType: "application/json; charset=utf-8",
          dataType: 'json'
      });
  }
  
  /*********************************************************************
   * Agent menu
   ********************************************************************/
  
  /**
   * Populate the menu with links to the views of all agents
   */
  function populate_agent_menu(state) {
      agents = [];
      var dropdown = document.getElementById("agent_dropdown");
  
      // remove old agents
      while (dropdown.firstChild) {
          dropdown.removeChild(dropdown.firstChild);
      }
  
  
      // search for agents
      var objects_keys = Object.keys(state);
      objects_keys.forEach(function(objID) {
          // don't list ourselves
          if (objID == lv_agent_id) {
              return;
          }
  
          // fetch agent object from state
          var obj = state[objID];
  
          // save the agent
          if (obj.hasOwnProperty('isAgent')) {
              agents.push(obj);
          }
      })
  
      // show what the agent looks like
      agents.forEach(function(agent) {
          var agentType = agent["is_human_agent"] ? "human-agent" : "agent";
  
          // preview of the agent
          var agent_preview = document.createElement("div");
          agent_preview.classList.add("agent_menu_preview");
  
          // use the image as the agent preview
          if (Object.keys(agent).includes('img_name')) {
              var img = new Image();
              img.src = window.location.origin + fix_img_url(agent['img_name']);
              agent_preview.append(img);
  
              // otherwise, use the the agent shape and colour as a preview
          } else {
  
              // add the css for the corresponding agent shape
              switch (agent['visualization']['shape']) {
                  case 0:
                      agent_preview.setAttribute("style", "background-color: " + agent['visualization']['colour'] + ';');
                      break;
                  case 2:
                      agent_preview.setAttribute("style", 'background-color:' + agent['visualization']['colour'] + '; border-radius: 100%');
                      break;
                  case 1:
                      agent_preview.setAttribute("style", 'width: 0; height: 0; border-left: 15px solid transparent; border-right: 15px solid transparent;border-bottom: 24px solid' + agent['visualization']['colour'] + ';');
                      break;
              }
          }
  
          // create a new dropdown item and add the preview and agent name
          var list_item = document.createElement('a');
          list_item.classList.add('dropdown-item');
          list_item.append(agent_preview);
          list_item.appendChild(document.createTextNode(agentType + ": " + agent["obj_id"]));
          list_item.href = '/' + agentType + '/' + agent["obj_id"]
          list_item.setAttribute('target', '_blank'); // open in a new tab
  
          // add the agent to the dropdown list
          dropdown.append(list_item);
      });
  }
  
  
  
  /*********************************************************************
   * Chat
   ********************************************************************/
  var showing_chat = false;
  // which chatrooms are added and have a tab
  var active_chatrooms = [];
  // all the possible chatrooms
  var all_chatrooms = {};
  // offsets of the latest messages for each chatroom
  var chat_offsets = {};
  // the currently opened chatroom
  var current_chatwindow = {};
  // our database with messages, indexed by chat room name
  var messages = {};
  
  /*
   * Add a key listener to the input text box, such that the message will be sent when the user
   * presses enter.
   */
  var chat_input_box = document.getElementById("chat_form_input");
  if (chat_input_box != null) {
      chat_input_box.addEventListener("keyup", function(event) {
          event.preventDefault();
          if (event.keyCode === 13) {
              document.getElementById("chat_form_submit").click();
          }
      });
  }
  
  
  /*
   * Show the chatrooms which the user can open when the user clicks the "+"
   * chatroom button
   */
  function populate_new_chat_dropdown(matrx_chatrooms) {
  //    console.log("Repopulating new chat dropdown");
      // add to our chatroom database
      all_chatrooms = matrx_chatrooms;
  
      // get the dropdown from html
      var dropdown = document.getElementById('new_chat_dropdown');
  
      // remove old items
      while (dropdown.firstChild) {
          dropdown.removeChild(dropdown.firstChild);
      }
  
      // check if there are any new chatrooms that have to be added to the
      // dropdown chatroom list
      Object.keys(matrx_chatrooms).forEach(function(chatroom_ID) {
  
          // skip already active chatrooms
          if (active_chatrooms.includes(chatroom_ID)) {
              return;
          }
  
          chatroom = matrx_chatrooms[chatroom_ID];
  
          // create a new dropdown item and add the chatroom name
          var list_item = document.createElement('a');
          list_item.classList.add('dropdown-item');
          list_item.classList.add('new_chat_option');
          list_item.id = "new_chatroom_option_" + chatroom_ID;
          list_item.chat_name = chatroom['name'];
          list_item.chatroom_display_name = get_chatroom_display_name(chatroom);
          list_item.appendChild(document.createTextNode(list_item.chatroom_display_name)); // name
          list_item.chatroom_ID = chatroom_ID;
          list_item.room_type = chatroom['type'];
  
          // on click add this chat room
          list_item.addEventListener('click', function(event) {
  
              add_chatroom(event.currentTarget.chat_name, event.currentTarget.chatroom_display_name,
                  event.currentTarget.chatroom_ID, event.currentTarget.room_type);
              open_chatroom(event.currentTarget.chatroom_display_name, event.currentTarget.chatroom_ID,
                  event.currentTarget.room_type);
  
              // remove this chat from the new chat dropdown
              document.getElementById(event.currentTarget.id).remove();
          });
  
          // add the chatroom to the dropdown list
          dropdown.append(list_item);
  //        console.log("added item to list");
      });
  }
  
  
  /*
   * Get the name that should be displayed for a chatroom in the dropdown / chatroom list
   */
  function get_chatroom_display_name(chatroom) {
      // by default the display name is the same as the chatroom name
      var display_name = chatroom['name'];
  
      // special care for the displayed name of private chats. These chatroom names are in the
      // form of agentID1__agentID2. However that is not a very useful or nice chatroom name.
      // So we remove our own agent ID from the name
      if (chatroom['type'] == "private" && !(lv_agent_id == "god")) {
          // split the chatroom name at the double underscore agent ID divider
          var chat_agent_IDs = chatroom['name'].split("__");
  
          // agents can send messages to themselves, display it as such
          if (chat_agent_IDs[0] == chat_agent_IDs[1]) {
              return "messages to self"
          }
  
          // in a private chat with someone else, only display the ID of the other
          chat_agent_IDs.forEach(function(id) {
              if (id != lv_agent_id) {
                  display_name = id;
              }
          });
      }
  
      return display_name
  }
  
  
  /*
   * Hide or show the chat, when the chat button is pressed in the toolbar
   */
  function chatToggle() {
      showing_chat = !showing_chat;
      if (showing_chat) {
          document.getElementById("chat_button").className = "btn btn-secondary";
      } else {
          document.getElementById("chat_button").className = "btn btn-dark";
      }
  }
  
  /*
   * Add the chatroom to the visible chatrooms
   */
  function add_chatroom(chat_name, chatroom_display_name, chatroom_ID, room_type, set_active = true) {
  //    console.log("Adding", room_type, "chatroom:", chat_name, "chatroom_display_name:", chatroom_display_name,
  //        "chatID:", chatroom_ID);
  
      // add a new list that will keep track of the messages of this chatroom
      messages[chatroom_ID] = [];
  
      // set the offset to the first message
      chat_offsets[chatroom_ID] = null;
  
      // set the previously open chatroom to inactive
      if (set_active) {
          var active_contacts = document.getElementsByClassName("contact_active");
          if (active_contacts.length > 0) {
              active_contacts[0].className = "contact";
          }
      }
  
      var chatrooms = document.getElementById("chatrooms_list");
      var new_chat_button = document.getElementById("new_chat_button");
  
      // create a new item and add the chatroom to the list
      var new_chatroom = document.createElement('div');
      new_chatroom.chatroom_ID = chatroom_ID;
      new_chatroom.id = "chatroom_listitem_" + chatroom_ID;
      new_chatroom.room_type = room_type;
      new_chatroom.name = chat_name;
      new_chatroom.chatroom_display_name = chatroom_display_name;
      new_chatroom.classList.add('contact');
      if (set_active) {
          new_chatroom.classList.add('contact_active'); // set the clicked chatroom to active
      }
      new_chatroom.appendChild(document.createTextNode(chatroom_display_name)); // name
  
      // add a hidden (by default) notification circle, for when new messages have arrived in this
      // chat room while it is unopened
      var chatroom_notification = document.createElement('span');
      chatroom_notification.className = "chat-notification";
      chatroom_notification.id = "chatroom_" + chatroom_ID + "_notification";
      chatroom_notification.style.display = "none";
      new_chatroom.appendChild(chatroom_notification);
  
      // When the user clicks on an added chat room, open it and display the messages
      new_chatroom.addEventListener('click', chatroom_click);
  
      // add the new chatroom before the global chatroom
      chatrooms.insertBefore(new_chatroom, new_chat_button);
  
      // note that we opened this chatroom
      active_chatrooms.push(chatroom_ID);
  }
  
  /*
   * The user clicked on a chatroom, set it as a active and open the chatroom
   */
  function chatroom_click(event) {
  
      // set the previously open chatroom to inactive
      var active_contacts = document.getElementsByClassName("contact_active");
      if (active_contacts.length > 0) {
          active_contacts[0].className = "contact";
      }
  
      // set this one as active
      event.currentTarget.className = "contact contact_active";
  
      // open the chatroom
      open_chatroom(event.currentTarget.chatroom_display_name, event.currentTarget.chatroom_ID,
          event.currentTarget.room_type);
  }
  
  
  /*
   * Open a chat room and display the messages.
   */
  function open_chatroom(chatroom_display_name, chatroom_ID, chatroom_type) {
      // cleanup arguments
      if (chatroom_type == null) {
          chatroom_type = "global";
      }
  
      if (chat_blocked && lv_agent_type == "human-agent") {
          document.getElementById("chat_form_input").placeholder = "Type a message..";
          document.getElementById("chat_form_submit").disabled = false;
          chat_blocked = false;
      }
  
      // set the chat room as selected
      current_chatwindow = {
          'chatroom_ID': chatroom_ID,
          'name': chatroom_display_name,
          'type': chatroom_type,
          'receiver': null // by default null = addressed to everyone
      };
  
      // set the receiver for team and private messages
      if (chatroom_type == 'private' || chatroom_type == 'team') {
          current_chatwindow['receiver'] = chatroom_display_name;
      }
  
  //    console.log("Opening", chatroom_type, "chatroom: ", all_chatrooms[chatroom_ID]['name'], " displayed as ",
  //            chatroom_display_name, " with ID ", chatroom_ID);
  
      // hide the notification of new messages for our goal chat room
      document.getElementById("chatroom_" + chatroom_ID + "_notification").style.display = "none";
  
      // remove messages from the old chatroom
      var mssgs_container = document.getElementById("messages");
      while (mssgs_container.firstChild) {
          mssgs_container.removeChild(mssgs_container.firstChild);
      }
  
      // display messages of this chatroom, if any
      if (Object.keys(messages).includes(chatroom_ID)) {
          messages[chatroom_ID].forEach(function(message) {
              add_message(chatroom_ID, message);
          });
      }
  
      // scroll message container to bottom
      scrollToBottom(document.getElementById("messages"));
  
  }
  
  /*
   * Add 1 message to the currently opened chatroom
   */
  function add_message(chatroom_ID, mssg) {
      mssg_content = "";
      // cleanup and validate the input
      if (typeof mssg.content == "string") {
          mssg_content = mssg.content.trim();
      }
      // show objects as strings by default
      else {
          mssg_content = JSON.stringify(mssg.content);
      }
  
      var div = document.createElement("div");
      div.className = "message_you"; // by default assume we sent this message
  
      // check if sent or received this message
      if (mssg.from_id != lv_agent_id) {
          div.className = "message_other";
  
          // display the sender name
          // console.log("adding sender");
          var mssg_sender = document.createElement('span');
          mssg_sender.className = "chat-mssg-sender";
          mssg_sender.appendChild(document.createTextNode(mssg.from_id + ": "));
          div.appendChild(mssg_sender);
      }
  
      // add the message text to the message div
      div.appendChild(document.createTextNode(mssg_content));
  
      // add the message div
      var mssgs_container = document.getElementById("messages");
      mssgs_container.appendChild(div);
  
      // scroll to the new message
  //    scrollSmoothToBottom(mssgs_container)
      scrollToBottom(mssgs_container);
  }
  
  
  /**
   * Scroll smoothly to the end of a div
   */
  function scrollSmoothToBottom (div) {
     $(div).animate({
        scrollTop: div.scrollHeight - div.clientHeight
     }, 500);
  }
  
  /**
   * Scroll directly to the end of a div
   */
  function scrollToBottom (div) {
     div.scrollTo(0, div.scrollHeight);
  }
  
  /*
   * Send the message to MATRX
   */
  function send_message(event) {
      // get and validate message from input field
      var user_message = document.getElementById("chat_form_input").value;
      user_message = user_message.trim();
      if (!user_message || user_message.length === 0) {
          return;
      }
  
      // empty input field
      document.getElementById("chat_form_input").value = null;
  
      // send message to MATRX
      data = {"content":user_message, "sender": lv_agent_id, "receiver": current_chatwindow['receiver']}
      console.log("Sending message to matrx:", data);
      send_matrx_api_post_message(matrx_send_message_url, data);
  }
  
  
  
  /*
   * Reset the chat by removing all messages etc.
   */
  function reset_chat() {
      // remove the chat room buttons
      active_chatrooms.forEach(function(chatroom_ID) {
          document.getElementById("chatroom_" + chatroom_ID).remove();
      });
  
      // remove old messages
      var mssgs_container = document.getElementById("messages");
      while (mssgs_container.firstChild) {
          mssgs_container.removeChild(mssgs_container.firstChild);
      }
  
      // reset the vars
      active_chatrooms = [];
      all_chatrooms = {};
      chat_offsets = {};
      current_chatwindow = {};
      messages = {};
  }
  
  
  /*
   * Process the object containing messages of various types, received from MATRX, and process them
   * into individual messages.
   */
  function process_messages(new_messages, chatrooms) {
      // the messages are in a object with as key the chatroom ID in which they belong
      // Process the new messages for each chatroom
      Object.keys(new_messages).forEach(function(chatroom_ID) {
  
          // skip any chatrooms without messages
          if (new_messages[chatroom_ID].length == 0) {
  
              // repopulate the chatroom dropdown if it is new
              if (!Object.keys(all_chatrooms).includes(chatroom_ID)) {
                  populate_new_chat_dropdown(chatrooms);
              }
              // skip any further processing
              return
          }
  
          // add the chatroom to the active chat list if it is new and contains messages
          if (!active_chatrooms.includes(chatroom_ID)) {
              // fetch the chatroom data
              chatroom = chatrooms[chatroom_ID];
  //            console.log("adding chatroom", chatroom);
  
              // add the chatroom to our GUI (but don't show it yet)
              var chatroom_display_name = get_chatroom_display_name(chatroom);
              add_chatroom(chatroom['name'], chatroom_display_name, chatroom_ID, chatroom['type'], set_active=false);
  
              // repopulate the chatroom dropdown
              populate_new_chat_dropdown(chatrooms);
  //            console.log("all chatrooms:", all_chatrooms);
          }
  
          // fetch the messages for this chatroom
          new_chatroom_mssgs = new_messages[chatroom_ID]
  
          // the chatroom is currently active, so add and show the new messages
          if (current_chatwindow['chatroom_ID'] == chatroom_ID) {
  
              // add each message to our database and the GUI
              new_chatroom_mssgs.forEach(function(mssg) {
                  mssg = jQuery.parseJSON(mssg);
                  chat_offsets[chatroom_ID] = mssg['chat_mssg_count']; // memorize the last mssg index
                  messages[chatroom_ID].push(mssg); // add to db
                  add_message(chatroom_ID, mssg); // add to GUI
  //                console.log("Adding message:", mssg, " type ", all_chatrooms[chatroom_ID]['type'], " chatroom ", all_chatrooms[chatroom_ID]['name']);
  //                console.log("Latest message offset is:", mssg['chat_mssg_count']);
              });
  
  
          // only add messages to our database but don't show
          } else {
              new_chatroom_mssgs.forEach(function(mssg) {
                  mssg = jQuery.parseJSON(mssg);
                  chat_offsets[chatroom_ID] = mssg['chat_mssg_count']; // memorize the last mssg index
                  messages[chatroom_ID].push(mssg); // add to db
  //                console.log("Adding message:", mssg, " type ", all_chatrooms[chatroom_ID]['type'], " chatroom ", all_chatrooms[chatroom_ID]['name']);
  //                console.log("Latest message offset (hidden) is:", mssg['chat_mssg_count']);
              })
              if (new_chatroom_mssgs.length != 0 ) {
                  // show the notification for the chat room
                  document.getElementById("chatroom_" + chatroom_ID + "_notification").style.display = "inline-block";
              }
          }
      });
  
      // if there is no chat active, set the last chat to active
      if (jQuery.isEmptyObject(current_chatwindow) && active_chatrooms.length != 0) {
  
          // fetch the last activated chatroom
          chatroom_ID = active_chatrooms[active_chatrooms.length - 1]
          chatroom = all_chatrooms[chatroom_ID];
          // open it
          open_chatroom(get_chatroom_display_name(chatroom), chatroom_ID, chatroom['type'])
          // set it to active
          document.getElementById("chatroom_listitem_" + chatroom_ID).className = "contact contact_active";
      }
  
  }
  
  
  /*********************************************************************
   * Drawing tools
   ********************************************************************/
  // Called when draw button is clicked. Enables/disables draw function
  var draw_activated = false;
  
  // Called when erase button is clicked. Enables/disables erase function
  var erase_activated = false;
  
  /*
   * Change class of all tiles so that they are highlighted when hovered
   */
  function add_draw_erase_classes(class_name) {
      var tiles = document.getElementsByClassName("tile");
      for (var i = 0; i < tiles.length; i++) {
          tiles[i].className = "tile " + class_name;
      }
      var objects = document.getElementsByClassName("object");
      for (var i = 0; i < objects.length; i++) {
          objects[i].className = "object " + class_name;
      }
  }
  
  /*
   * Change class of all tiles so that they are no longer highlighted when hovered
   */
  function remove_draw_erase_classes() {
      var tiles = document.getElementsByClassName("tile");
      for (var i = 0; i < tiles.length; i++) {
          tiles[i].className = "tile";
      }
      var objects = document.getElementsByClassName("object");
      for (var i = 0; i < objects.length; i++) {
          objects[i].className = "object";
      }
  }
  
  /*
   * Toggle drawing mode when the button is pressed
   */
  function drawToggle() {
      if (erase_activated) { // If the erase function is active, disable it
          eraseToggle();
      }
      draw_activated = !draw_activated;
      if (draw_activated) {
          document.getElementById("draw_button").className = "btn btn-secondary";
          add_draw_erase_classes("draw_mode");
      } else {
          document.getElementById("draw_button").className = "btn btn-dark";
          remove_draw_erase_classes();
      }
  }
  
  /*
   * Toggle erase mode when the button is pressed
   */
  function eraseToggle() {
      if (draw_activated) { // If the draw function is active, disable it
          drawToggle();
      }
      erase_activated = !erase_activated;
      if (erase_activated) {
          document.getElementById("erase_button").className = "btn btn-secondary";
          add_draw_erase_classes("erase_mode");
      } else {
          document.getElementById("erase_button").className = "btn btn-dark";
          remove_draw_erase_classes();
      }
  }
  
  /*
   * Called when a tile is clicked. Draws/erases the tile and starts drag function that allows the
   * user to draw/erase multiple tiles while holding the left mouse button
   */
  function startDrawErase(tile_id) {
      if (draw_activated) {
          drawTile(tile_id);
          startDrawDrag();
      }
      if (erase_activated) {
          eraseTile(tile_id);
          startEraseDrag();
      }
  }
  
  /*
   * Determines what happens when a tile is selected for drawing / erasing
   */
  function drawTile(tile_id) {
      if (draw_activated) {
          var tile = document.getElementById(tile_id);
          tile.style.backgroundColor = "crimson";
      }
  }
  
  /*
   * Sets mouse event listeners for drawing  by dragging the mouse
   */
  function startDrawDrag() {
      var tiles = document.getElementsByClassName("tile");
      for (var i = 0; i < tiles.length; i++) {
          tiles[i].setAttribute("onmouseenter", "drawTile(id)");
      }
  }
  
  /*
   * Remove the mouse listeners for drawing / erasing by dragging the mouse
   */
  function stopDrag() {
      var tiles = document.getElementsByClassName("tile");
      for (var i = 0; i < tiles.length; i++) {
          tiles[i].setAttribute("onmouseenter", "");
      }
  }
  
  /*
   * Determine what happens when a tile is clicked for erasing
   */
  function eraseTile(tile_id) {
      if (erase_activated) {
          var tile = document.getElementById(tile_id);
          tile.style.backgroundColor = "";
      }
  }
  
  /*
   * Sets mouse event listeners for erasing by dragging the mouse
   */
  function startEraseDrag() {
      var tiles = document.getElementsByClassName("tile");
      for (var i = 0; i < tiles.length; i++) {
          tiles[i].setAttribute("onmouseenter", "eraseTile(id)");
      }
  }
  