<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <title>Reactive Programming</title>
</head>

<body>
  
  <div>
    <h1>Links</h1>
    <div>
      <a href="http://reactivex.io/documentation/operators.html" target="_blank">Operator Decision Tree (scroll down)</a>
    </div>
    <div>
      <a href="https://github.com/Reactive-Extensions/RxJS/tree/master/doc">RxJS 4</a>
    </div>
  </div>

  <div>
    <h1>Exercise: Input</h1>
    <div>
      Check the input field
    </div>
    <div id="helloWorld">
      <input type="text" id="input1">
      <button id="stopLogging">Stop logging</button>
      <br>
      <input type="text" id="input2" disabled="true"> Reacts to input with more than 2 characters
      <br>
      <input type="text" id="input3" disabled="true"> Reacts to input after a short delay
      <br>
      <input type="text" id="input4" disabled="true"> Reacts to the first 3 events. Then stops
    </div>
  </div>

  <div>
    <h1>Exercise:Arrays & Objects</h1>
    <div>
      Try sort, map, group by and filtering
    </div>
    <div id="arraysObjects">
      <!-- The content for this will be generated dynamicaly -->
    </div>
  </div>


  <div>
    <h1>Exercise: Chat</h1>
      your name :
      <input type="text" name="" id="chatUsername" value="" />
      <button id="chatLoginButton">Login</button>
    <div id="chatMessages" style="height:200px; overflow:auto; border: 1px solid black;"></div>
    <div id="chatCurrentlyTyping"></div>
    <form action="" id="chatForm">
      <input id="chatInput" autocomplete="off" />
      <button id="chatSendButton" disabled="true">Send</button>
    </form>
    <div>
      All Users:
      <div id="chatAllUsers"></div>
    </div>
  </div>

  <div>
    <h1>Exercise: Typing of the Dead</h1>
    <div>
      You get a point every time you type the word correctly. If you make a typo you lose all your points!
    </div>
    <div id="TotDWord"></div>
    <input type="text" name="" id="TotDInput" value="" /><span>Highscore: </span><span id="TotDHighscore"></span>
    <div id="TotDScore"></div>
  </div>
  
   <div>
    <h1>Tweet Service</h1>
    <div>
      Get the newest infos straight from twitter!
    </div>
    <div id="tweetService">
      <!-- The content for this will be generated dynamicaly -->
    </div>
  </div>

  <div>
    <h1>Wikipedia Suggestions</h1>
    <div id="wikipedia">
      <input type="text" name="" id="wikipediaInput" value="" />
      <div id="wikipediaOutput"></div>
    </div>
  </div>

  <div>
    <h1>Multiclick</h1>
    <div id="multiclick">
      <button id="multiclickButton">Click me fast!</button>
    </div>
  </div>


  <script>
    console.clear();
  </script>

  <script data-require="jquery" data-semver="3.1.1" src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.6/rx.all.js"></script>
  <script src="https://cdn.socket.io/socket.io-1.3.5.js"></script>

  <script src="util/renderer.js"></script>

  <script src="references/3_1-TweetUI.js"></script>
  <script src="references/3_1-TweetService.js"></script>
  <script src="references/3_1-TweetApp.js"></script>
  <script src="references/3_2-Wikipedia.js"></script>
  <script src="references/3_3-Multiclick.js"></script>

  <script src="exercise/Input.js"></script>
  <script src="exercise/ArraysObjects.js"></script>
  <script src="exercise/ChatClient.js"></script>
  <script src="exercise/TypingOfTheDead.js"></script>

</body>
function render(data, containerId) {
  if (!containerId) containerId = 'content';
  const content = document.createElement('div');
  innerHtml = data;
  if (typeof data === 'object') innerHtml = JSON.stringify(data);
  content.innerHTML = innerHtml;
  document.getElementById(containerId).appendChild(content);
}

function renderHtml(html, containerId) {
  if (!containerId) containerId = 'content';
  document.getElementById(containerId).appendChild(html);
}

function createElement(containerId) {
  const ele = document.createElement('div');
  renderHtml(ele, containerId);
  return $(ele);
}
(() => {

    const hwInput = $('#input1')
    const hwOutputFilter = $('#input2')
    const hwOutputDelay = $('#input3')
    const hwOutputTake3 = $('#input4')
    const stopLoggingButton = $('#stopLogging');

    var input = Rx.Observable.fromEvent(hwInput, 'input')
      .map(event => event.target.value);

    // Filter out target values less than 3 characters long
    input.filter(text => text.length > 2)
      .subscribe(value => hwOutputFilter.val(value)); // "hel"

    // Delay the events
    input.delay(200)
      .subscribe(value => hwOutputDelay.val(value)); // "h" -200ms-> "e" -200ms-> "l" ...

    // Stop the stream of events after 3 events
    input.take(3)
      .subscribe(value => hwOutputTake3.val(value)); // "hel"

    // Passes through events until other observable triggers an event
    var stopStream = Rx.Observable.fromEvent(stopLoggingButton, 'click');
    input.takeUntil(stopStream)
      .subscribe(value => console.log(value)); // "hello" (click)
  }

)();
{
"myData":"got loaded"
}
class TweetService {
  
  constructor() {
    // Subject is an implementation of an Observable that allows us to manually emit an event via onNext()
    this.updateStream = new Rx.Subject();  
    // The tweetStream fetches new data, everytime the updateStream emits an event
    this.tweetStream = this.updateStream.flatMapLatest(this.fetchData);
  }
  
  // Returns a promise. Only the caller of the function will receive the new data
  getDataPromise() {
    return this.fetchData();
  }
  
  // Every caller of this function gets the same observable and therefore all of the updates
  getDataObservable() {
    return this.tweetStream;
  }

  // Trigger an update for the observable
  updateDataForObservable() {
    // Send an event to the update stream - this will cause the tweetStream to call the server
    this.updateStream.onNext();
  }
  
  // Make an HTTP call to get the data
  // Returns a promise
  fetchData() {
    // We get mock data instead of calling an actual server
    return $.get('mockData.json');
  }
}
// This class renders our tweets
class TweetDisplayer {
  
  constructor(tweetService) {
    this.service = tweetService;
    this.service.getDataObservable()
    .subscribe(tweets => {
      const data = 'At ' + new Date().toLocaleTimeString() + ' Trump tweeted something stupid!';
      render(data, 'tweetService');
    });

    // Creates a button that calls the update function on click
    createTweetUiButton('Update from the displayer class', () => this.update());
  }

  update() {
    this.service.updateDataForObservable();
  }
}

// This is some other class that can also trigger an update for Tweets - and the TweetDisplayer needs to react to these aswell
class AnotherClass {
  constructor(tweetService) {
    this.service = tweetService;
    // Creates a button that calls the update function on click
    createTweetUiButton('Update from another class', () => this.update());
  }

  update() {
    this.service.updateDataForObservable();
  }
}

function createTweetUiButton(text, onClick) {
  const button = document.createElement('button');
  button.innerHTML = text;
  button.onclick = onClick;
  renderHtml(button, 'tweetService');
}
(() => {
  
  // Create the service, displayer and another class. It's important that all UI elements get the same instance of the service!
  const service = new TweetService();
  const displayer = new TweetDisplayer(service);
  const otherThing = new AnotherClass(service);

})();
(() => {
  
  const myInputField = $('#wikipediaInput');
  const suggestionBox = $('#wikipediaOutput');

  const inputs = Rx.Observable.fromEvent(myInputField, 'input')
    .map(e => e.target.value);

  const newSearchTerm = inputs
    .filter(text => text.length > 2)
    .throttle(500);

  newSearchTerm.flatMapLatest(searchWikipedia)
    .subscribe(renderData, renderError);

  // Queries the wikipedia servers
  // returns a promise
  function searchWikipedia(term) {
    return $.ajax({
      // Change the URL to cause a 404 error
      url: 'https://en.wikipedia.org/w/api.php',
      dataType: 'jsonp',
      data: {
        action: 'opensearch',
        format: 'json',
        search: term
      }
    }).promise();
  }

  // ------------- RENDER FUNCTIONS ------------------
  // A function that displays the data in the UI - no need to know how it works
  function renderData(data) {
    var res = data[1];
    suggestionBox.empty();
    $.each(res, (_, value) => $('<li>' + value + '</li>').appendTo(suggestionBox));
  }

  function renderError(error) {
    suggestionBox.empty();
    $('<li>Error: ' + error.status + '</li>').appendTo(suggestionBox);
  }
  
})();
(() => {
  
const multiButton = $('#multiclickButton');

const clickStream = Rx.Observable.fromEvent(multiButton, 'click');

const multiClicks = clickStream
  .buffer(clickStream.debounce(250))
  .map(x => x.length)
  .filter(x => x>=2);

const messageBox = createElement('multiclick');

multiClicks.subscribe(x => {
  messageBox.empty();
  messageBox.append( x + ' Clicks!');
});

})();
/*******************************************************************************
 * Recommended order:
 * Send/receive message
 * Display current users
 * "Is Typing"
 * Send/receive PM (private message)
 ******************************************************************************/ 


(() => {
  // ----- UI Elements -----
  const loginButton = $('#chatLoginButton');
  const usernameInput = $('#chatUsername');
  const messages = $('#chatMessages');
  const input = $('#chatInput');
  const form = $('#chatForm');
  const messageInput = $('#chatInput');
  // Box to display who's currently typing
  const currentlyTyping = $('#chatCurrentlyTyping');
  // Box to display all current users
  const allUsers = $('#chatAllUsers');

  var username;

  // ----- Login ---------------------------------------------------------------
  loginButton.click(() => {
    username = usernameInput.val();
    if (!username) {
      alert('Please enter a username');
      return;
    }
    if(username.indexOf(' ') !== -1){
      alert('No spaces allowed in the username');
      return;
    }
    connect(username);
    loginButton.prop('disabled', true);
    usernameInput.prop('disabled', true);
    $('#chatSendButton').prop('disabled', false);
  });

  // ----- Chat ----------------------------------------------------------------
  function connect(username) {

    // ----- Connect to the server ----- 
    var socket = io.connect('http://141.60.170.61:3000', {
      transports: ['websocket'],
      query: 'name=' + username
    });

    // ----- Send message -----
    form.submit(() => {
      const message = input.val();
      if (!message) return false;
      
      /**
       * TODO: Send Message
       * Look at the ChatServer code to see how socket.io works.
       * You will use socket.emit(...) and socket.on(...) to interact with the server
       * Check the server API file in this plunker for infos
       */
       
      /**
       * TODO: Send PMs
       * Use the isPM function to check if a string conforms to the PM syntax
       * Use the createPM function to turn the user input string to an PM object
       */
       
      input.val('');
      return false;
    });

    // ----- Receive message ----- 
    /**
     * TODO
     * Use the renderMessage function to display them
     */

    // ----- Receive PM -----
    /**
     * TODO
     * Use the pmToMe and pmFromMe functions to render the PMs
     * Check the Server API for infos on the data model
     */

    // ----- "Is Typing" ----- 
    /**
     * TODO
     * Send a signal, when you are typing (don't worry about sending the same signal multiple times)
     * Send a signal, when you haven't typed anything for 3 seconds
     * Use the renderIsTyping function to display the names
     */

    // ----- Display current users -----
    /**
     * TODO
     * Use the renderUsers function to display the names
     */
  }
 // ----- Render functions ----------------------------------------------------

  // Renders an array of names of people who are typing
  function renderIsTyping(names) {
    currentlyTyping.empty();
    if (!names.length) return;
    currentlyTyping.append(names.join(', ') + ' is typing...');
  }

  // Renders an array of names in the all users section
  function renderUsers(users) {
    allUsers.empty();
    allUsers.append(users.join('<br>'))
  }

  // Render a string in the message box
  function renderMessage(msg) {
    render(msg, 'chatMessages')
    var foo = document.getElementById('chatMessages');
    foo.scrollTop = foo.scrollHeight;
  }

  // Render a PM object sent from someone to me
  function pmToMe(msg) {
    renderMessage(`From ${msg.from}: ${msg.message}`);
  }
  
  // Render a PM object sent from me to someone 
  function pmFromMe(msg) {
    renderMessage(`to ${msg.to}: ${msg.message}`);
  }

  // ----- Regex functions -----------------------------------------------------

  //  PMs look like this
  // "/pm" + username (no spaces) + message
  // "/pm Manuel Hey man, what's up?"
  const pmRegex = /\/pm\s+(\w+)\s+(.*)/;

  // returns true if the name is a PM.
  function isPM(s) {
    return s.match(pmRegex);
  }

  // Creates a PM object from a string that isPM()
  // Returns a object with a "to" and "message" property (which is what the server expects)
  function createPM(s) {
    const captureGroups = s.match(pmRegex);
    return {
      to: captureGroups[1],
      message: captureGroups[2]
    };
  }

})();
(() => {

  // --- UI Stuff, NO NEED TO TOUCH THESE --- //
  const wordField = $('#TotDWord');
  const inputField = $('#TotDInput');
  const scoreField = $('#TotDScore');
  const highscoreField = $('#TotDHighscore');
  // ----------------------------------------- //

  let score = 0;
  let highscore = 0;

  /**
   * TODO: create a stream from the input field, that returns the user's input as string
   * Hint: See the Wikipedia example
   */
  // const inputStream = ???

  // A stream that allows us to manually trigger that we need a new word
  // To make a Subject emit a new value call .onNext() function with the value (or nothing) as parameter.
  // Example: nextStream.onNext()
  const nextStream = new Rx.Subject();

  // When we want a new word we need to reset the users input
  nextStream.subscribe(() => {
    // Setting the value doesn't trigger a change event
    inputField.val('');
    // so we need to manually trigger the event
    inputField.trigger('input');
  });

  /**
   * TODO: Complete the statement below to create a stream that emits a random word everytime the nextStream emits an event.
   * The .startWith() is needed to load the first word
   * Look at the Wikipedia example on how to handle a function that returns a promise. Use the "getRandomWord" function defined at the end of this file.
   * Call ".share()" after the map function to cache the result and avoid multiple server calls
   */
  // const wordStream = nextStream.startWith('').???

  /**
   * TODO: Uncomment this code when you have the word stream
   * A random word should be displayed above the input box then.
   * If there is none, check the wordStream or ask for help.
   */

  // When there is a new word, we display it
  // wordStream.subscribe(word => {
  //   wordField.empty();
  //   wordField.append(word);
  // });

  /**
   * TODO: combine the latest values from wordStream and the inputStream.
   * Every time either of those stream emits a value this stream should emit a tuple with the latest values of both streams.
   * The tuple need to look like this: ['the word', 'the user input']
   * Call .share() after combining the stream to make sure every subscriber receives the same values - this is a bit more advanced so ask for help if you want to know why
   */
  // const checkStream = ???;


  // Uncomment this code to see if the output of the checkStream looks like it's supposed to:
  // If the word in the console doesn't match the word on the page you missed the caching in the wordStream!
  // checkStream.subscribe(x => console.log(x));


  /**
   * TODO: Create a typoStream that emits an event everytime the user makes a mistake
   * Hint: 
   * You can access the values of tuples with the index notation "myTupleVariableName[0]" and "myTupleVariableName[1]"
   * strings offer a ".startsWith(x)" function.
   */
  // const typoStream = ???

  /** 
   * TODO: Feed the typoStream into the nextStream
   * Now every typo should cause your input to reset & a new word to appear
   */

  /**
   * TODO: Create a stream that emits an event every time the user has entered the entire word correctly.
   */
  // const wordCompletedStream = ???

  /**
   * TODO: Feed the wordCompleteStream into the nextStream
   * Hint: Check the comment above the nextStream to learn how to make the stream emit a value
   * Now every time you complete a word it should cause your input to reset & a new word to appear
   */

  // When this stream emits "true" increase the score, when it emits "false" reset to zero
  const scoreUpdateStream = new Rx.Subject();
  
  // Increase or reset score - depending on the update sent
  scoreUpdateStream.subscribe(increase => score = increase ? score + 1 : 0);

  /**
   * TODO: Create the score stream that emits the current score whenever there is a score update.
   * If the scoreUpdateStream emits false set the score to zero
   * If the scoreUpdateStream emits true increase the score by 1
   * Use the variable "score" to persist the current score.
   * Make the scoreStream startWith (= emit from the beginngin) a value of 0
   */
  // const scoreStream = ???

  /**
   * TODO: Uncomment once you have the scoreStream
   */
  //scoreStream.subscribe(num => {
  //  scoreField.empty();
  //  scoreField.append(num);
  //});
  
  /**
   * TODO: Update the score
   * On a typo, reset the score
   * On a completed word increase the score
   * Hint: send values to the scoreUpdateStream
   */
  
  /**
   * TODO: Create a highscoreStream that emits the new highscore everytime the old one has been broken
   * Hint: just assume the current highscore is in the "highscore" variable - we will fill it later
   */
  // const highscoreStream = ???

  /**
   * TODO: Uncomment once you have the highscoreStream
   * Now you should have a working highscore field next to the input field
   */
  //highscoreStream.subscribe(num => {
  //  highscore = num;
  //  highscoreField.empty()
  //  highscoreField.append(num);
  //});

  /**
   * YOURE DONE!
   * Make sure all of the following are correct:
   * There's a word at the start.
   * When you type a word correctly the input field empties, a new word is generated and you get one point
   * When you make a typo the input field empties, a new word is generated and you lose all points
   * Your highscore is updated every time you have a new highscore
   */

  // Calls a server for a random word
  // returns a promise
  function getRandomWord() {
    return $.ajax({
      // Change the URL to cause a 404 error
      url: 'http://setgetgo.com/randomword/get.php'
    }).promise();
  }
})();
(() => {

  // --- UI Stuff, NO NEED TO TOUCH THESE --- //
  const wordField = $('#TotDWord');
  const inputField = $('#TotDInput');
  const scoreField = $('#TotDScore');
  const highscoreField = $('#TotDHighscore');
  // ----------------------------------------- //

  let score = 0;
  let highscore = -1;

  // A stream of the users string inputs
  const inputStream = Rx.Observable.fromEvent(inputField, 'input')
    .map(x => x.target.value);

  // A stream that allows us to manually trigger that we need a new word
  const nextStream = new Rx.Subject();

  // When we want the next word we need to reset the users input
  nextStream.subscribe(() => inputField.val('').trigger('input'));

  // This stream calls a server for a new random word every time the nextStream emits an event. We startWith a value to trigger the first word
  const wordStream = nextStream.startWith('')
    .flatMapLatest(getRandomWord)
    // share() to cache the result - otherwise every .map on wordStream would cause a new HTTP request (and therefore another random word)
    .share();

  // When there is a new word, we display it
  wordStream.subscribe(word => {
    wordField.empty();
    wordField.append(word);
  });

  // Checkstream combines the latest word with the latest userinput. It emits an array, like this ['the word', 'the user input'];
  // The share() is to make sure everybody receives the same data tuple (like the wordStream caching)
  const checkStream = wordStream.combineLatest(inputStream).share();

  // Emits an event if the user input is not correct
  const typoStream = checkStream.map(tuple => {
      const word = tuple[0];
      const input = tuple[1];
      return word.startsWith(input);
    })
    .filter(x => !x);

  // When there is a typo we need a new word
  typoStream.subscribe(nextStream);
  // This is a short version for
  // typoStream.subscribe(x => nextStream.onNext(x));

  // Emits an event when the user has entered the entire word correctly
  const wordCompletedStream = checkStream.filter(tuple => {
    const word = tuple[0];
    const input = tuple[1];
    return word == input;
  });

  typoStream.subscribe(x => console.log('wont get executed'));
  // Whenever the word is completed, request a new word
  wordCompletedStream.subscribe(nextStream);
  typoStream.subscribe(x => console.log('will get executed'));

  // When this stream emits "true" increase the score, when it emits "false" reset to zero
  const scoreUpdateStream = new Rx.Subject();

  scoreUpdateStream.subscribe(increase => {
    const newScore = increase ? ++score : 0;
    score = newScore;
  });

  const scoreStream = scoreUpdateStream.map(increase => score).startWith(0);

  scoreStream.subscribe(num => {
    scoreField.empty();
    scoreField.append(num);
  });

  typoStream.subscribe(() => scoreUpdateStream.onNext(false));
  wordCompletedStream.subscribe(() => scoreUpdateStream.onNext(true))

  const highscoreStream = scoreStream.filter(x => x > highscore);

  highscoreStream.subscribe(num => {
    highscore = num;
    highscoreField.empty()
    highscoreField.append(num);
  });

  // Calls a server for a random word
  // returns a promise
  function getRandomWord() {
    return $.ajax({
      // Change the URL to cause a 404 error
      url: 'http://setgetgo.com/randomword/get.php'
    }).promise();
  }
})();
(() => {
  // ----- UI Elements -----
  const loginButton = $('#chatLoginButton');
  const usernameInput = $('#chatUsername');
  const messages = $('#chatMessages');
  const input = $('#chatInput');
  const form = $('#chatForm');
  const messageInput = $('#chatInput');
  // Box to display who's currently typing
  const currentlyTyping = $('#chatCurrentlyTyping');
  // Box to display all current users
  const allUsers = $('#chatAllUsers');

  // ----- Login ---------------------------------------------------------------
  loginButton.click(() => {
    const username = usernameInput.val();
    if (!username) {
      alert('Please enter a username');
      return;
    }
    if (username.indexOf(' ') !== -1) {
      alert('No spaces allowed in the username');
      return;
    }
    connect(username);
    loginButton.prop('disabled', true);
    usernameInput.prop('disabled', true);
    $('#chatSendButton').prop('disabled', false);
  });

  // ----- Chat ----------------------------------------------------------------
  function connect(username) {

    // ----- Connect to the server ----- 
    var socket = io.connect('http://141.60.170.61:3000', {
      transports: ['websocket'],
      query: 'name=' + username
    });

    // ----- Send message ----- 
    form.submit((e) => {
      const message = input.val();
      if (!message) return false;
      if (isPM(message)) {
        socket.emit('pm', createPM(message));
      } else {
        socket.emit('chat message', message);
      }
      input.val('');
      e.preventDefault();
      e.stopPropagation();
      return false;
    });

    // ----- Receive message ----- 
    socket.on('chat message', renderMessage);
    
    // ----- Receive PM -----
    socket.on('pm', pm => {
      if(pm.from === username) pmFromMe(pm)
      else pmToMe(pm)
    });

    // ----- "Is Typing" ----- 
    const inputStream = Rx.Observable.fromEvent(messageInput, 'keyup')
      .map(x => x.target.value);
    inputStream.subscribe(x => socket.emit('start typing'));
    inputStream.buffer(inputStream.debounce(3000))
      .subscribe(x => socket.emit('stop typing'));
    socket.on('typing', renderIsTyping);

    // ----- Display current users -----
    socket.on('all users', renderUsers);
  }

   // ----- Render functions ----------------------------------------------------

  function renderIsTyping(names) {
    currentlyTyping.empty();
    if (!names.length) return;
    currentlyTyping.append(names.join(', ') + ' is typing...');
  }

  function renderUsers(users) {
    allUsers.empty();
    allUsers.append(users.join('<br>'))
  }

  function renderMessage(msg) {
    render(msg, 'chatMessages')
    var foo = document.getElementById('chatMessages');
    foo.scrollTop = foo.scrollHeight;
  }

  function pmToMe(msg) {
    renderMessage(`From ${msg.from}: ${msg.message}`);
  }

  function pmFromMe(msg) {
    renderMessage(`to ${msg.to}: ${msg.message}`);
  }

  // ----- Regex functions -----------------------------------------------------

  //  PMs look like this
  // "/pm" + username (no spaces) + message
  // "/pm Manuel Hey man, what's up?"
  const pmRegex = /\/pm\s+(\w+)\s+(.*)/;

  // returns true if the name is a PM.
  function isPM(s) {
    return s.match(pmRegex);
  }

  // Creates a PM object from a string that isPM()
  // Returns a object with a "to" and "message" property (which is what the server expects)
  function createPM(s) {
    const captureGroups = s.match(pmRegex);
    return {
      to: captureGroups[1],
      message: captureGroups[2]
    };
  }

})();
Chat server receives events:

name: 'chat message'
description: Send a message to the chat. Everybody will receive it
data: string

name: 'start typing'
description: Send this to the server when you start typing
data: no data needs to be sent

name: 'stop typing'
description: Send this to the server when you stop typing
data: no data needs to be sent

name: 'pm'
description: Send this to the server to send a message to only one person
data: Object 
  properties: "to" string, "message" string
  example: {to: "Peter", message: "Hi!"}



Chat server emits events:

name: 'chat message'
description: The server send a message to everybody connected. The string will be prefixed with the name of the sender
data: string

name: 'pm'
description: This event is sent to the sender and receiver of a PM
data: Object
  properties: "from" string, "to" string, "message" string
  example: {from: "Markus", to: "Peter", message: "Hey, what's up dude?"}

name: 'typing'
description: Emits an array of all users that are currently typing
data: Array of strings
  example: ['Manuel', 'Sonja']

name: 'all users'
description: Emits an array of all users that are currently online
data: Array of strings
  example: ['Manuel', 'Sonja']
/*******************************************************************************
 * THIS CODE IS ONLY A REFERENCE. THE CHAT SERVER DOESN'T ACTUALLY RUN IN YOUR
 * BROWSER. CHANING CODE HERE WON'T DO ANYTHING AT ALL.
 ******************************************************************************/

const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const port = process.env.PORT || 3000;

app.get('/', function (req, res) {
    res.sendFile(__dirname + '/index.html');
});

const allConnections = {};
const currentlyTyping = [];

io.on('connection', function (socket) {
    const name = socket.request._query['name'];
    allConnections[name] = socket;
    emitUsers();
    console.log(name + ' connected');

    socket.on('disconnect', function () {
        console.log(name + ' disconnected');
        delete allConnections[name];
        emitUsers();
    });

    socket.on('chat message', function (msg) {
        const message = name + ': ' + msg;
        console.log(message);
        io.emit('chat message', message);
    });

    socket.on('start typing', function () {
        if (currentlyTyping.indexOf(name) !== -1) return;
        currentlyTyping.push(name);
        emitTyping();
    });

    socket.on('stop typing', function () {
        if (currentlyTyping.indexOf(name) === -1) return;
        currentlyTyping.splice(currentlyTyping.indexOf(name), 1);
        emitTyping();
    });

    socket.on('pm', function (pm) {
        if (!pm || !allConnections[pm.to] || pm.to === name) return;
        console.log(`${name} to ${pm.to}: ${pm.message}`);
        let id = allConnections[pm.to].id;
        pm.from = name;
        socket.to(id).emit('pm', pm);
        socket.emit('pm', pm);
    });

    function emitTyping() {
        io.emit('typing', currentlyTyping);
    }

    function emitUsers() {
        io.emit('all users', Object.keys(allConnections));
    }
});

http.listen(port, function () {
    console.log('listening on *:' + port);
});
(() => {

    const aoOutputArray = document.createElement('input');
    aoOutputArray.disabled = true;
    renderHtml(aoOutputArray, 'arraysObjects');

    const hwBreakAO = document.createElement('br');
    renderHtml(hwBreakAO, 'arraysObjects');
    const aoOutputObject = document.createElement('textarea');




    /**************************************FIRST TASK*****************************************************/

    const myMixedArray = new Array("&", 1, 2, "n", 3, 4, 5, 12, 'u', 10, "r", 11, 6, 7, "Z", 8, "e", 9, "i", "c", "h", "e", "n");
    //TODO create Observable with source myMixedArray

    //TODO sort all characters of myMixedArray
    // use for output:  .subscribe(value => addValue(value));





    /**************************************SECOND TASK*****************************************************/
    //Second task - 

    const people = [{name: 'Anna', age: 25, city: 'Rosenheim'},{name: 'Johann', age: 34 , city: 'Bad Endorf'},
                    {name: 'Max', age: 26, city: 'Bad Aibling'}, {name: 'Sarah', age: 35, city: 'Rosenheim'},
                    {name: 'Klaus', age: 22, city: 'Bad Aibling'}, {name: 'Alfons', age: 21, city: 'Rosenheim'},
                    {name: 'Stefanie', age: 28, city: 'Bad Aibling'}, {name: 'Michael', age: 27, city: 'M√ľnchen'},
                    {name: 'Peter', age: 29, city: 'Traunstein'}, {name: 'Anna', age: 23, city: 'Rosenheim'}];

    //TODO create Observable with source myMixedArray

    //TODO group the following people by city and select those which are 25 or older


    // use for output:     .subscribe(val => outputObject( val));


    /********************************HELPER FUNCTION******************************************************************/
    function addValue(value) {
      aoOutputArray.setAttribute("value", (aoOutputArray.getAttribute("value") + " " + value));
    }

    function outputObject(value) {
      let data = aoOutputObject.getAttribute("value");

      function getData(arr) {
        for (let i = 0; i < arr.length; i++) {
          if (arr[i].length !== undefined && arr[i].length > 0) {
            getData(arr[i]);
          } else {
            if (arr[i] !== null && arr[i] != 0) {
              console.log(arr[i])
              $('<li>' + arr[i].name + " lives in " + arr[i].city + '</li>').appendTo(arraysObjects);
            }
          }
        }
      }
      getData(value);
    }

  }

)();
(() => {

    // ----- UI Elements -----
    const hwInput = $('#input1')
    const hwOutputFilter = $('#input2')
    const hwOutputDelay = $('#input3')
    const hwOutputTake3 = $('#input4')
    const stopLoggingButton = $('#stopLogging');
    const hwOutputStop = $('#input5');


    var input = Rx.Observable.fromEvent(hwInput, 'input')
      .map(event => event.target.value);

    //TODO Filter the input and show only characters that are longer than 3 characters

    // use to display .subscribe(value => hwOutputFilter.val(value)); 

    //TODO Delay the events  by 200ms

    // use to display .subscribe(value => hwOutputDelay.val(value)); 

    // TODO Stop the stream of events after 3 events

    // use to display  .subscribe(value => hwOutputTake3.val(value)); 

    /*TODO create a new observable based on the stop button, 
    when the button is clicked, the output into hwOutputStop should end
    */

    // use to display    .subscribe(value => hwOutputStop.val(value)); 

  }


)();