/*
 * spa.chat.js
 * Moduł funkcji czatu dla aplikacji SPA.
*/

/*jslint         browser : true, continue : true,
  devel  : true, indent  : 2,    maxerr   : 50,
  newcap : true, nomen   : true, plusplus : true,
  regexp : true, sloppy  : true, vars     : false,
  white  : true
*/

/*global $, spa */

spa.chat = (function () {
  'use strict';
  //---------------- ROZPOCZĘCIE SEKCJI ZMIENNYCH ZAKRESU MODUŁU --------------
  var
    configMap = {
      main_html : String()
        + '<div class="spa-chat">'
          + '<div class="spa-chat-head">'
            + '<div class="spa-chat-head-toggle">+</div>'
            + '<div class="spa-chat-head-title">'
              + 'Czat'
            + '</div>'
          + '</div>'
          + '<div class="spa-chat-closer">x</div>'
          + '<div class="spa-chat-sizer">'
            + '<div class="spa-chat-list">'
              + '<div class="spa-chat-list-box"></div>'
            + '</div>'
            + '<div class="spa-chat-msg">'
              + '<div class="spa-chat-msg-log"></div>'
              + '<div class="spa-chat-msg-in">'
                + '<form class="spa-chat-msg-form">'
                  + '<input type="text"/>'
                  + '<input type="submit" style="display:none"/>'
                  + '<div class="spa-chat-msg-send">'
                    + 'send'
                  + '</div>'
                + '</form>'
              + '</div>'
            + '</div>'
          + '</div>'
        + '</div>',

      settable_map : {
        slider_open_time    : true,
        slider_close_time   : true,
        slider_opened_em    : true,
        slider_closed_em    : true,
        slider_opened_title : true,
        slider_closed_title : true,

        chat_model      : true,
        people_model    : true,
        set_chat_anchor : true
      },

      slider_open_time     : 250,
      slider_close_time    : 250,
      slider_opened_em     : 18,
      slider_closed_em     : 2,
      slider_opened_title  : 'Kliknij, aby zamknąć',
      slider_closed_title  : 'Kliknij, aby otworzyć',
      slider_opened_min_em : 10,
      window_height_min_em : 20,

      chat_model      : null,
      people_model    : null,
      set_chat_anchor : null
    },
    stateMap  = {
      $append_target   : null,
      position_type    : 'closed',
      px_per_em        : 0,
      slider_hidden_px : 0,
      slider_closed_px : 0,
      slider_opened_px : 0
    },
    jqueryMap = {},

    setJqueryMap,  setPxSizes,   scrollChat,
    writeChat,     writeAlert,   clearChat,
    setSliderPosition,
    onTapToggle,   onSubmitMsg,  onTapList,
    onSetchatee,   onUpdatechat, onListchange,
    onLogin,       onLogout,
    configModule,  initModule,
    removeSlider,  handleResize;
  //----------------- ZAKOŃCZENIE SEKCJI ZMIENNYCH ZAKRESU MODUŁU ---------------

  //------------------- ROZPOCZĘCIE SEKCJI METOD NARZĘDZIOWYCH ------------------
  //-------------------- ZAKOŃCZENIE SEKCJI METOD NARZĘDZIOWYCH  -------------------

  //--------------------- ROZPOCZĘCIE SEKCJI METOD DOM --------------------
  // Rozpoczęcie metody DOM /setJqueryMap/.
  setJqueryMap = function () {
    var
      $append_target = stateMap.$append_target,
      $slider        = $append_target.find( '.spa-chat' );

    jqueryMap = {
      $slider   : $slider,
      $head     : $slider.find( '.spa-chat-head' ),
      $toggle   : $slider.find( '.spa-chat-head-toggle' ),
      $title    : $slider.find( '.spa-chat-head-title' ),
      $sizer    : $slider.find( '.spa-chat-sizer' ),
      $list_box : $slider.find( '.spa-chat-list-box' ),
      $msg_log  : $slider.find( '.spa-chat-msg-log' ),
      $msg_in   : $slider.find( '.spa-chat-msg-in' ),
      $input    : $slider.find( '.spa-chat-msg-in input[type=text]'),
      $send     : $slider.find( '.spa-chat-msg-send' ),
      $form     : $slider.find( '.spa-chat-msg-form' ),
      $window   : $(window)
    };
  };
  // Zakończenie metody DOM /setJqueryMap/.

  // Rozpoczęcie metody DOM /setPxSizes/.
  setPxSizes = function () {
    var px_per_em, window_height_em, opened_height_em;

    px_per_em = spa.util_b.getEmSize( jqueryMap.$slider.get(0) );
    window_height_em = Math.floor(
      ( jqueryMap.$window.height() / px_per_em ) + 0.5
    );

    opened_height_em
      = window_height_em > configMap.window_height_min_em
      ? configMap.slider_opened_em
      : configMap.slider_opened_min_em;

    stateMap.px_per_em        = px_per_em;
    stateMap.slider_closed_px = configMap.slider_closed_em * px_per_em;
    stateMap.slider_opened_px = opened_height_em * px_per_em;
    jqueryMap.$sizer.css({
      height : ( opened_height_em - 2 ) * px_per_em
    });
  };
  // Zakończenie metody DOM /setPxSizes/.

  // Rozpoczęcie metody publicznej /setSliderPosition/.
  // Przykład: spa.chat.setSliderPosition( 'closed' );
  // Cel: przesunięcie suwaka czatu do żądanej pozycji.
  // Argumenty:
  //   * position_type — enum('closed', 'opened' lub 'hidden');
  // * callback — opcjonalne wywołanie zwrotne, które ma być uruchomione na koniec
  //     animacji suwaka. Wywołanie zwrotne otrzymuje kolekcję jQuery
  //     reprezentującą kontener div suwaka jako jego pojedynczy argument.
  //
  // Akcja:
  // ta metoda przesuwa suwak do żądanej pozycji.
  // Jeśli żądaną pozycją jest aktualna pozycja,
  // zwraca true bez podejmowania dalszych działań.
  // Zwraca:
  // * true — żądana pozycja została osiągnięta;
  // * false — żądana pozycja nie została osiągnięta.
  // Rzuca: nic.
  //
  setSliderPosition = function ( position_type, callback ) {
    var
      height_px, animate_time, slider_title, toggle_text;

    // Typ pozycji 'opened' nie jest dozwolony dla użytkownika anonimowego,
    // dlatego po prostu zwracana jest wartość false; powłoka naprawi URI
    // i wykona kolejną próbę. 
    if ( position_type === 'opened'
      && configMap.people_model.get_user().get_is_anon()
    ){ return false; }

    // Zwraca true, jeśli suwak jest już w żądanej pozycji.
    if ( stateMap.position_type === position_type ){
      if ( position_type === 'opened' ) {
        jqueryMap.$input.focus();
      }
      return true;
    }

    // Przygotowanie parametrów animacji.
    switch ( position_type ){
      case 'opened' :
        height_px    = stateMap.slider_opened_px;
        animate_time = configMap.slider_open_time;
        slider_title = configMap.slider_opened_title;
        toggle_text  = '=';
        jqueryMap.$input.focus();
      break;

      case 'hidden' :
        height_px    = 0;
        animate_time = configMap.slider_open_time;
        slider_title = '';
        toggle_text  = '+';
      break;

      case 'closed' :
        height_px    = stateMap.slider_closed_px;
        animate_time = configMap.slider_close_time;
        slider_title = configMap.slider_closed_title;
        toggle_text  = '+';
      break;

      // Rozwiązanie dla nieznanego argumentu position_type.
      default : return false;
    }

    // Animacja zmiany pozycji suwaka.
    stateMap.position_type = '';
    jqueryMap.$slider.animate(
      { height : height_px },
      animate_time,
      function () {
        jqueryMap.$toggle.prop( 'title', slider_title );
        jqueryMap.$toggle.text( toggle_text );
        stateMap.position_type = position_type;
        if ( callback ) { callback( jqueryMap.$slider ); }
      }
    );
    return true;
  };
  // Zakończenie metody publicznej DOM /setSliderPosition/.

  // Rozpoczęcie prywatnych metod DOM do zarządzania komunikatami czatu.
  scrollChat = function() {
    var $msg_log = jqueryMap.$msg_log;
    $msg_log.animate(
      { scrollTop : $msg_log.prop( 'scrollHeight' )
        - $msg_log.height()
      },
      150
    );
  };

  writeChat = function ( person_name, text, is_user ) {
    var msg_class = is_user
      ? 'spa-chat-msg-log-me' : 'spa-chat-msg-log-msg';

    jqueryMap.$msg_log.append(
      '<div class="' + msg_class + '">'
      + spa.util_b.encodeHtml(person_name) + ': '
      + spa.util_b.encodeHtml(text) + '</div>'
    );

    scrollChat();
  };

  writeAlert = function ( alert_text ) {
    jqueryMap.$msg_log.append(
      '<div class="spa-chat-msg-log-alert">'
        + spa.util_b.encodeHtml(alert_text)
      + '</div>'
    );
    scrollChat();
  };

  clearChat = function () { jqueryMap.$msg_log.empty(); };
  // Zakończenie prywatnych metod DOM do zarządzania komunikatami czatu.
  //---------------------- ZAKOŃCZENIE SEKCJI METOD DOM ---------------------

  //------------------- ROZPOCZĘCIE SEKCJI PROCEDUR OBSŁUGI ZDARZEŃ -------------------
  onTapToggle = function ( event ) {
    var set_chat_anchor = configMap.set_chat_anchor;
    if ( stateMap.position_type === 'opened' ) {
      set_chat_anchor( 'closed' );
    }
    else if ( stateMap.position_type === 'closed' ){
      set_chat_anchor( 'opened' );
    }
    return false;
  };

  onSubmitMsg = function ( event ) {
    var msg_text = jqueryMap.$input.val();
    if ( msg_text.trim() === '' ) { return false; }
    configMap.chat_model.send_msg( msg_text );
    jqueryMap.$input.focus();
    jqueryMap.$send.addClass( 'spa-x-select' );
    setTimeout(
      function () { jqueryMap.$send.removeClass( 'spa-x-select' ); },
      250
    );
    return false;
  };

  onTapList = function ( event ) {
    var $tapped  = $( event.elem_target ), chatee_id;
    if ( ! $tapped.hasClass('spa-chat-list-name') ) { return false; }

    chatee_id = $tapped.attr( 'data-id' );
    if ( ! chatee_id ) { return false; }

    configMap.chat_model.set_chatee( chatee_id );
    return false;
  };

  onSetchatee = function ( event, arg_map ) {
    var
      new_chatee = arg_map.new_chatee,
      old_chatee = arg_map.old_chatee;

    jqueryMap.$input.focus();
    if ( ! new_chatee ) {
      if ( old_chatee ) {
        writeAlert( old_chatee.name + 'opuścił czat' );
      }
      else {
        writeAlert( 'Twój przyjaciel opuścił czat' );
      }
      jqueryMap.$title.text( 'Czat' );
      return false;
    }

    jqueryMap.$list_box
      .find( '.spa-chat-list-name' )
      .removeClass( 'spa-x-select' )
      .end()
      .find( '[data-id=' + arg_map.new_chatee.id + ']' )
      .addClass( 'spa-x-select' );

    writeAlert( 'Rozmawiasz teraz z ' + arg_map.new_chatee.name );
    jqueryMap.$title.text( 'Czat z  ' + arg_map.new_chatee.name );
    return true;
  };

  onListchange = function ( event ) {
    var
      list_html = String(),
      people_db = configMap.people_model.get_db(),
      chatee    = configMap.chat_model.get_chatee();


    people_db().each( function ( person, idx ) {
      var select_class = '';

      if ( person.get_is_anon() || person.get_is_user()
      ) { return true;}

      if ( chatee && chatee.id === person.id ) {
        select_class=' spa-x-select';
      }
      list_html
        += '<div class="spa-chat-list-name'
        +  select_class + '" data-id="' + person.id + '">'
        +  spa.util_b.encodeHtml( person.name ) + '</div>';
    });

    if ( ! list_html ) {
      list_html = String()
        + '<div class="spa-chat-list-note">'
        + 'Samotne rozmowy są losem wszystkich wielkich dusz...<br><br>'
        + 'Nikt nie jest w trybie online'
        + '</div>';
      clearChat();
    }
    // jqueryMap.$list_box.html( list_html );
    jqueryMap.$list_box.html( list_html );
  };

  onUpdatechat = function ( event, msg_map ) {
    var
      is_user,
      sender_id = msg_map.sender_id,
      msg_text  = msg_map.msg_text,
      chatee    = configMap.chat_model.get_chatee() || {},
      sender    = configMap.people_model.get_by_cid( sender_id );

    if ( ! sender ) {
      writeAlert( msg_text );
      return false;
    }

    is_user = sender.get_is_user();

    if ( ! ( is_user || sender_id === chatee.id ) ) {
      configMap.chat_model.set_chatee( sender_id );
    }

    writeChat( sender.name, msg_text, is_user );

    if ( is_user ) {
      jqueryMap.$input.val( '' );
      jqueryMap.$input.focus();
    }
  };

  onLogin = function ( event, login_user ) {
    configMap.set_chat_anchor( 'opened' );
  };

  onLogout = function ( event, logout_user ) {
    configMap.set_chat_anchor( 'closed' );
    jqueryMap.$title.text( 'Czat' );
    clearChat();
  };

  //-------------------- ZAKOŃCZENIE SEKCJI PROCEDUR OBSŁUGI ZDARZEŃ --------------------

  //------------------- ROZPOCZĘCIE SEKCJI METOD PUBLICZNYCH -------------------
  // Rozpoczęcie metody publicznej /configModule/.
  // Przykład: spa.chat.configModule({ slider_open_em : 18 });
  // Cel: konfiguracja modułu przed inicjowaniem.
  // Argumenty:
  //   * set_chat_anchor — wywołanie zwrotne do zmiany kotwicy adresu URI,
  //     aby wskazać stan opened (otwarty) lub closed (zamknięty). To wywołanie zwrotne
  //     musi zwracać wartość false (fałsz), jeśli żądany stan nie może zostać ustawiony.
  //   * chat_model — obiekt modelu czatu, który zapewnia metody
  //       do interakcji z naszym komunikatorem internetowym.
  //   * people_model — obiekt modelu użytkowników, który zapewnia
  //       metody do interakcji z listą użytkowników utrzymywaną przez Model.
  //   * slider_* ustawienia. Są to opcjonalne skalary.
  //       Pełną listę znajdziesz w configMap.settable_map.
  //       Przykład: slider_opened_em określa wysokość otwarcia w jednostkach em.
  // Akcja:
  //   wewnętrzna struktura danych konfiguracyjnych (configMap) jest
  //   aktualizowana za pomocą dostarczonych argumentów. Nie są podejmowane żadne inne działania.
  // Zwraca: true.
  // Rzuca: obiekt błędu JavaScript i ślad stosu w przypadku
  //        niedopuszczalnych lub brakujących argumentów.
  //
  configModule = function ( input_map ) {
    spa.util.setConfigMap({
      input_map    : input_map,
      settable_map : configMap.settable_map,
      config_map   : configMap
    });
    return true;
  };
  // Zakończenie metody publicznej /configModule/.

  // Rozpoczęcie metody publicznej /initModule/.
  // Przykład: spa.chat.initModule( $('#div_id') );
  // Cel:
  //   wskazuje Czatowi, aby zaoferował swoje możliwości użytkownikowi.
  // Argumenty:
  //   * $append_target (example: $('#div_id')).
  //   Kolekcja jQuery, która powinna reprezentować pojedynczy kontener DOM.
  //
  // Akcja:
  //   dołącza suwak czatu do dostarczonego kontenera i wypełnia
  //   go zawartością HTML. Następnie inicjuje elementy,
  //   zdarzenia oraz procedury obsługi, aby zapewnić użytkownikowi interfejs
  //   czatroomu.
  // Zwraca: true w przypadku powodzenia, false w przypadku niepowodzenia.
  // Rzuca: nic.
  //
  initModule = function ( $append_target ) {
    var $list_box;

    // Ładowanie zawartości HTML i pamięci podręcznej jQuery suwaka czatu.
    stateMap.$append_target = $append_target;
    $append_target.append( configMap.main_html );
    setJqueryMap();
    setPxSizes();

    // Inicjowanie suwaka czatu z domyślnym tytułem i w domyślnym stanie.
    jqueryMap.$toggle.prop( 'tytuł', configMap.slider_closed_title );
    stateMap.position_type = 'closed';

    // $list_box subskrybuje globalne zdarzenia jQuery.
    $list_box = jqueryMap.$list_box;
    $.gevent.subscribe( $list_box, 'spa-listchange', onListchange );
    $.gevent.subscribe( $list_box, 'spa-setchatee',  onSetchatee  );
    $.gevent.subscribe( $list_box, 'spa-updatechat', onUpdatechat );
    $.gevent.subscribe( $list_box, 'spa-login',      onLogin      );
    $.gevent.subscribe( $list_box, 'spa-logout',     onLogout     );

    // Wiązanie zdarzeń wprowadzania danych przez użytkownika.
    jqueryMap.$head.bind(     'utap', onTapToggle );
    jqueryMap.$list_box.bind( 'utap', onTapList   );
    jqueryMap.$send.bind(     'utap', onSubmitMsg );
    jqueryMap.$form.bind(   'submit', onSubmitMsg );
  };
  // Zakończenie metody publicznej /initModule/.

  // Rozpoczęcie metody publicznej /removeSlider/.
  // Cel:
  //   * usunięcie elementu DOM chatSlider;
  //   * przywrócenie stanu początkowego;
  //   * usunięcie wskaźników do wywołań zwrotnych oraz innych danych.
  // Argumenty: brak.
  // Zwraca: true.
  // Rzuca: nic.
  //
  removeSlider = function () {
    // Wycofuje inicjowanie i stan. 
    // Usuwa kontener DOM; usuwa także wiązania zdarzeń.
    if ( jqueryMap.$slider ) {
      jqueryMap.$slider.remove();
      jqueryMap = {};
    }
    stateMap.$append_target = null;
    stateMap.position_type  = 'closed';

    // Wycofanie kluczowych konfiguracji.
    configMap.chat_model      = null;
    configMap.people_model    = null;
    configMap.set_chat_anchor = null;

    return true;
  };
  // Zakończenie metody publicznej /removeSlider/.

  // Rozpoczęcie metody publicznej /handleResize /.
  // Cel:
  //   przy danym zdarzeniu zmiany rozmiaru okna
  //   dostosowanie w razie potrzeby dostarczanej przez ten moduł prezentacji.
  // Akcje:
  //   jeśli wysokość lub szerokość okna spadnie poniżej
  //   danej wartości granicznej, zmieniamy rozmiar suwaka czatu
  //   dla zmniejszonego rozmiaru okna.
  // Zwraca wartość boolean:
  //   * false — nie uwzględniono zmiany rozmiaru;
  //   * true — uwzględniono zmianę rozmiaru.
  // Rzuca: nic.
  //
  handleResize = function () {
    // Nic nie rób, jeśli nie mamy kontenera suwaka.
    if ( ! jqueryMap.$slider ) { return false; }

    setPxSizes();
    if ( stateMap.position_type === 'opened' ){
      jqueryMap.$slider.css({ height : stateMap.slider_opened_px });
    }
    return true;
  };
  // Zakończenie metody publicznej /handleResize/.

  // Zwracanie metod publicznych.
  return {
    setSliderPosition : setSliderPosition,
    configModule      : configModule,
    initModule        : initModule,
    removeSlider      : removeSlider,
    handleResize      : handleResize
  };
  //------------------- ZAKOŃCZENIE SEKCJI METOD PUBLICZNYCH ---------------------
}());
