/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var loop = loop || {};
loop.store = loop.store || {};

loop.store.TextChatStore = (function() {
  "use strict";

  var sharedActions = loop.shared.actions;

  var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES = {
    RECEIVED: "recv",
    SENT: "sent",
    SPECIAL: "special"
  };

  var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES = {
    CONTEXT: "chat-context",
    TEXT: "chat-text",
    ROOM_NAME: "room-name"
  };

  /**
   * A store to handle text chats. The store has a message list that may
   * contain different types of messages and data.
   */
  var TextChatStore = loop.store.createStore({
    actions: [
      "dataChannelsAvailable",
      "receivedTextChatMessage",
      "sendTextChatMessage",
      "updateRoomInfo"
    ],

    /**
     * Initializes the store.
     *
     * @param  {Object} options An object containing options for this store.
     *                          It should consist of:
     *                          - sdkDriver: The sdkDriver to use for sending
     *                                       messages.
     */
    initialize: function(options) {
      options = options || {};

      if (!options.sdkDriver) {
        throw new Error("Missing option sdkDriver");
      }

      this._sdkDriver = options.sdkDriver;
    },

    /**
     * Returns initial state data for this active room.
     */
    getInitialStoreState: function() {
      return {
        textChatEnabled: false,
        // The messages currently received. Care should be taken when updating
        // this - do not update the in-store array directly, but use a clone or
        // separate array and then use setStoreState().
        messageList: [],
        length: 0
      };
    },

    /**
     * Handles information for when data channels are available - enables
     * text chat.
     *
     * @param {sharedActions.DataChannelsAvailable} actionData
     */
    dataChannelsAvailable: function(actionData) {
      this.setStoreState({ textChatEnabled: actionData.available });

      if (actionData.available) {
        window.dispatchEvent(new CustomEvent("LoopChatEnabled"));
      }
    },

    /**
     * Appends a message to the store, which may be of type 'sent' or 'received'.
     *
     * @param {CHAT_MESSAGE_TYPES} type
     * @param {Object} messageData Data for this message. Options are:
     * - {CHAT_CONTENT_TYPES} contentType
     * - {String}             message     The message detail.
     * - {Object}             extraData   Extra data associated with the message.
     */
    _appendTextChatMessage: function(type, messageData) {
      // We create a new list to avoid updating the store's state directly,
      // which confuses the views.
      var message = {
        type: type,
        contentType: messageData.contentType,
        message: messageData.message,
        extraData: messageData.extraData,
        sentTimestamp: messageData.sentTimestamp,
        receivedTimestamp: messageData.receivedTimestamp
      };
      var newList = [].concat(this._storeState.messageList);
      var isContext = message.contentType === CHAT_CONTENT_TYPES.CONTEXT;
      if (isContext) {
        var contextUpdated = false;
        for (var i = 0, l = newList.length; i < l; ++i) {
          // Replace the current context message with the provided update.
          if (newList[i].contentType === CHAT_CONTENT_TYPES.CONTEXT) {
            newList[i] = message;
            contextUpdated = true;
            break;
          }
        }
        if (!contextUpdated) {
          newList.push(message);
        }
      } else {
        newList.push(message);
      }
      this.setStoreState({ messageList: newList });

      // Notify MozLoopService if appropriate that a message has been appended
      // and it should therefore check if we need a different sized window or not.
      if (message.contentType != CHAT_CONTENT_TYPES.ROOM_NAME) {
        window.dispatchEvent(new CustomEvent("LoopChatMessageAppended"));
      }
    },

    /**
     * Handles received text chat messages.
     *
     * @param {sharedActions.ReceivedTextChatMessage} actionData
     */
    receivedTextChatMessage: function(actionData) {
      // If we don't know how to deal with this content, then skip it
      // as this version doesn't support it.
      if (actionData.contentType != CHAT_CONTENT_TYPES.TEXT) {
        return;
      }

      this._appendTextChatMessage(CHAT_MESSAGE_TYPES.RECEIVED, actionData);
    },

    /**
     * Handles sending of a chat message.
     *
     * @param {sharedActions.SendTextChatMessage} actionData
     */
    sendTextChatMessage: function(actionData) {
      this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SENT, actionData);
      this._sdkDriver.sendTextChatMessage(actionData);
    },

    /**
     * Handles receiving information about the room - specifically the room name
     * so it can be added to the list.
     *
     * @param  {sharedActions.UpdateRoomInfo} actionData
     */
    updateRoomInfo: function(actionData) {
      // XXX When we add special messages to desktop, we'll need to not post
      // multiple changes of room name, only the first. Bug 1171940 should fix this.
      if (actionData.roomName) {
        this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SPECIAL, {
          contentType: CHAT_CONTENT_TYPES.ROOM_NAME,
          message: actionData.roomName
        });
      }

      // Append the context if we have any.
      if (("urls" in actionData) && actionData.urls && actionData.urls.length) {
        // We only support the first url at the moment.
        var urlData = actionData.urls[0];

        this._appendTextChatMessage(CHAT_MESSAGE_TYPES.SPECIAL, {
          contentType: CHAT_CONTENT_TYPES.CONTEXT,
          message: urlData.description,
          extraData: {
            location: urlData.location,
            thumbnail: urlData.thumbnail
          }
        });
      }
    }
  });

  return TextChatStore;
})();
