(function(context,$) {

  "use strict";
  context.JK = context.JK || {};
  context.JK.TextMessageDialog = function(app) {
    var logger = context.JK.logger;
    var rest = context.JK.Rest();
    var $dialog = null;
    var $previousMessages = null;
    var $previousMessagesScroller = null;
    var $sendTextMessage = null;
    var $form = null;
    var $interactionBlocker = null;
    var $disconnectedMsg = null;
    var $textBox = null;
    var userLookup = null;
    var otherId = null;
    var offset = 0;
    var sendingMessage = false;
    var LIMIT = 20;
    var showing = false;
    var fullyInitialized = false;
    var user = null;
    var renderQueue = []; // to handle race condition between dialog showing and populated using REST, and messages coming in

    var remainingCap = 400;

    function reset() {
      fullyInitialized = false;
      renderQueue = [];
      sendingMessage = false;
      offset = 0;
      userLookup = {};
      $previousMessages.empty();
      $textBox.val('');
      $sendTextMessage.unbind('click');
    }

    function buildParams() {
      return { target_user_id: otherId, offset: offset, limit: LIMIT};
    }

    function buildMessage() {
      var message = {};

      message['message'] = $textBox.val();
      message['target_user_id'] = otherId;

      return message;
    }

    function sendMessage() {
      if(!context.JK.JamServer.connected) {
        return false;
      }

      var msg = $textBox.val();
      if(!msg || msg == '') {
        // don't bother the server with empty messages
        return false;
      }

      if(!sendingMessage) {
        sendingMessage = true;

        $sendTextMessage.text('SENDING...')

        rest.createTextMessage(buildMessage())
          .done(function() {
            $textBox.val('');
            renderMessage(msg, user.id, user.name, new Date().toISOString(), true);
          })
          .fail(function(jqXHR) {
            app.notifyServerError(jqXHR, 'Unable to Send Message');
          })
          .always(function() {
            sendingMessage = false;
            $sendTextMessage.text('SEND');
          });

      }
      return false;
    }

    function scrollToBottom(instant) {
      $previousMessagesScroller.animate({scrollTop: $previousMessagesScroller[0].scrollHeight}, instant ? 0 : 'slow');
    }

    function renderMessage(msg, senderId, senderName, sent, append) {
      var options = {
        msg: msg,
        sender: senderId == user.id ? 'me' : senderName,
        sent: sent
      };
      var txt = $(context._.template($('#template-previous-message').html(), options, { variable: 'data' }));
      txt.find('.timeago').timeago();
      if(append) {
        $previousMessages.append(txt);
        scrollToBottom();
      }
      else {
        $previousMessages.prepend(txt);
      }
    }

    function drainQueue() {
      context._.each(renderQueue, function(msg) {
        renderMessage(msg.msg, msg.senderId, msg.senderName, msg.sent, true);
      });
      renderQueue = [];
    }

    function formatTextMessage(msg, sender_id, sender_name, clipped_msg) {
      var markedUpMsg = $('<span><span class="sender-name"></span> says: <span class="text-message"></span><a href="#" class="more-text-available"></a></span>');
      markedUpMsg.find('.sender-name').text(sender_name)
      markedUpMsg.find('.text-message').text(clipped_msg ? msg + "... " : msg).attr('data-is-clipped', clipped_msg)
      var moreTextLink = markedUpMsg.find('.more-text-available').attr('data-sender-id', sender_id);

      if(clipped_msg) {
        moreTextLink.text('more').show();
        moreTextLink.click(function(e) {
          app.layout.showDialog('text-message', {d1: $(this).attr('data-sender-id')});
          return false;
        });
      }
      else {
        moreTextLink.hide();
      }
      return markedUpMsg;
    }

    // we handled the notification, meaning the dialog showed this message as a chat message
    function handledNotification(payload) {
      return showing && payload.description == "TEXT_MESSAGE" && payload.sender_id == otherId;
    }

    function isNoisyNotification(payload) {
      return handledNotification(payload);
    }

    function afterShow(args) {
      $textBox.focus();
    }

    function renderDialog() {
      app.user()
        .done(function (userDetail) {
          user = userDetail;

          showing = true;

          userLookup[user.id] = user;

          rest.getUserDetail({id: otherId})
            .done(function (otherUser) {

              userLookup[otherUser.id] = otherUser;
              $dialog.find('.receiver-name').text(otherUser.name);
              $dialog.find('textarea').attr('placeholder', 'enter a message to ' + otherUser.name + '...');
              $dialog.find('.offline-tip').text('An email will be sent if ' + otherUser.name + ' is offline');
              if (!context.JK.JamServer.connected) {
                renderNotConnected();
              }
              $sendTextMessage.click(sendMessage);

              rest.getTextMessages(buildParams())
                .done(function (response) {
                  context._.each(response, function (textMessage) {
                    renderMessage(textMessage.message, textMessage.source_user_id, userLookup[textMessage.source_user_id].name, textMessage.created_at);
                  })

                  scrollToBottom(true);
                  fullyInitialized = true;
                  drainQueue();
                })
                .fail(function (jqXHR) {
                  app.notifyServerError(jqXHR, 'Unable to Load Conversation')
                })
            })
            .fail(function (jqXHR) {
              app.notifyServerError(jqXHR, 'Unable to Load Other User')
            })
        })
    }

    function beforeShow(args) {

      app.layout.closeDialog('text-message') // ensure no others are showing. this is a singleton dialog

      var other = args.d1;

      if (!other) throw "other must be specified in TextMessageDialog"
      otherId = other;

      renderDialog();
    }

    function afterHide() {
      showing = false;
      reset();
    }

    function renderNotConnected() {
      logger.debug("text-message dialog: render not connected")
      $interactionBlocker.addClass('active');
      $disconnectedMsg.addClass('active');
    }

    function renderConnected() {
      logger.debug("text-message dialog: render connected")
      $interactionBlocker.removeClass('active');
      $disconnectedMsg.removeClass('active');
    }

    function beforeDisconnect() {
      renderNotConnected();
    }

    function afterConnect() {
      renderConnected();
      reset();
      renderDialog();
    }

    function pasteIntoInput(el, text) {
      el.focus();
      if (typeof el.selectionStart == "number"
        && typeof el.selectionEnd == "number") {
        var val = el.value;
        var selStart = el.selectionStart;
        el.value = val.slice(0, selStart) + text + val.slice(el.selectionEnd);
        el.selectionEnd = el.selectionStart = selStart + text.length;
      } else if (typeof document.selection != "undefined") {
        var textRange = document.selection.createRange();
        textRange.text = text;
        textRange.collapse(false);
        textRange.select();
      }
    }

    function handleEnter(evt) {
      if (evt.keyCode == 13 && evt.shiftKey) {
        pasteIntoInput(this, "\n");
        evt.preventDefault();
      }
      else if(evt.keyCode == 13 && !evt.shiftKey){
        sendMessage();
        return false;
      }
    }

    function events() {
      $form.submit(sendMessage)

      // http://stackoverflow.com/questions/6014702/how-do-i-detect-shiftenter-and-generate-a-new-line-in-textarea
      $textBox.keydown(handleEnter);
    }


    function respondTextInvitation(args) {
        app.layout.showDialog('text-message', {d1: args.sender_id}) ;
    }

    // called from sidebar when messages come in
    function messageReceived(payload) {
        if(showing && otherId == payload.sender_id) {
          if(fullyInitialized) {
            renderMessage(payload.msg, payload.sender_id, payload.sender_name, payload.created_at, true);
          }
          else {
            // the dialog caught a message as it was initializing... queue it for later once dialog is showing
            renderQueue.push({msg: payload.msg, senderId: payload.sender_id, senderName: payload.sender_name, sent: msg.created_at});
          }
        }
        else {
          payload.msg = formatTextMessage(payload.msg, payload.sender_id, payload.sender_name, payload.clipped_msg);

          app.notify({
            "title": "Message from " + payload.sender_name,
            "text": payload.msg,
            "icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
          }, [{
              id: "btn-reply",
              text: "REPLY",
              "layout-action": "close",
              href: "#",
              css: "button-orange",
              callback: respondTextInvitation,
              callback_args: {
                "sender_id": payload.sender_id,
                "notification_id": payload.notification_id
              }
            }]
          );
        }
    }

    function initialize() {
      var dialogBindings = {
        'beforeShow' : beforeShow,
        'afterShow' : afterShow,
        'afterHide': afterHide,
        'beforeDisconnect': beforeDisconnect,
        'afterConnect': afterConnect
      };


      app.bindDialog('text-message', dialogBindings);

      $dialog = $('#text-message-dialog');
      $previousMessagesScroller = $dialog.find('.previous-messages-scroller');
      $previousMessages = $dialog.find('.previous-messages');
      $sendTextMessage = $dialog.find('.btn-send-text-message');
      $form = $dialog.find('form');
      $textBox = $form.find('textarea');
      $interactionBlocker = $dialog.find('.interaction-blocker');
      $disconnectedMsg = $dialog.find('.disconnected-msg');

      events();
    }

    this.initialize = initialize;
    this.messageReceived = messageReceived;
    this.formatTextMessage = formatTextMessage;
    this.handledNotification = handledNotification;
  }

  return this;
})(window,jQuery);
