import Action from './action';
import {PusherEvent} from './message-types';
/**
 * Provides functions for handling Pusher protocol-specific messages.
 */

/**
 * Decodes a message in a Pusher format.
 *
 * The MessageEvent we receive from the transport should contain a pusher event
 * (https://pusher.com/docs/pusher_protocol#events) serialized as JSON in the
 * data field
 *
 * The pusher event may contain a data field too, and it may also be
 * serialised as JSON
 *
 * Throws errors when messages are not parse'able.
 *
 * @param  {MessageEvent} messageEvent
 * @return {PusherEvent}
 */
export var decodeMessage = function(messageEvent : MessageEvent) : PusherEvent {
  try {
    var messageData = JSON.parse(messageEvent.data);
    var pusherEventData = messageData.data;
    if (typeof pusherEventData === 'string') {
      try {
        pusherEventData = JSON.parse(messageData.data)
      } catch (e) {}
    }
    var pusherEvent: PusherEvent = {
      event: messageData.event,
      channel: messageData.channel,
      data: pusherEventData,
    }
    if (messageData.user_id) {
      pusherEvent.user_id = messageData.user_id
    }
    return pusherEvent;
  } catch (e) {
    throw { type: 'MessageParseError', error: e, data: messageEvent.data};
  }
};

/**
 * Encodes a message to be sent.
 *
 * @param  {PusherEvent} event
 * @return {String}
 */
export var encodeMessage = function(event : PusherEvent) : string {
  return JSON.stringify(event);
};

/** Processes a handshake message and returns appropriate actions.
 *
 * Returns an object with an 'action' and other action-specific properties.
 *
 * There are three outcomes when calling this function. First is a successful
 * connection attempt, when pusher:connection_established is received, which
 * results in a 'connected' action with an 'id' property. When passed a
 * pusher:error event, it returns a result with action appropriate to the
 * close code and an error. Otherwise, it raises an exception.
 *
 * @param {String} message
 * @result Object
 */
export var processHandshake = function(messageEvent : MessageEvent) : Action {
  var message = decodeMessage(messageEvent);

  if (message.event === "pusher:connection_established") {
    if (!message.data.activity_timeout) {
      throw "No activity timeout specified in handshake";
    }
    return {
      action: "connected",
      id: message.data.socket_id,
      activityTimeout: message.data.activity_timeout * 1000
    };
  } else if (message.event === "pusher:error") {
    // From protocol 6 close codes are sent only once, so this only
    // happens when connection does not support close codes
    return {
      action: this.getCloseAction(message.data),
      error: this.getCloseError(message.data)
    };
  } else {
    throw "Invalid handshake";
  }
};

/**
 * Dispatches the close event and returns an appropriate action name.
 *
 * See:
 * 1. https://developer.mozilla.org/en-US/docs/WebSockets/WebSockets_reference/CloseEvent
 * 2. http://pusher.com/docs/pusher_protocol
 *
 * @param  {CloseEvent} closeEvent
 * @return {String} close action name
 */
export var getCloseAction = function(closeEvent) : string {
  if (closeEvent.code < 4000) {
    // ignore 1000 CLOSE_NORMAL, 1001 CLOSE_GOING_AWAY,
    //        1005 CLOSE_NO_STATUS, 1006 CLOSE_ABNORMAL
    // ignore 1007...3999
    // handle 1002 CLOSE_PROTOCOL_ERROR, 1003 CLOSE_UNSUPPORTED,
    //        1004 CLOSE_TOO_LARGE
    if (closeEvent.code >= 1002 && closeEvent.code <= 1004) {
      return "backoff";
    } else {
      return null;
    }
  } else if (closeEvent.code === 4000) {
    return "tls_only";
  } else if (closeEvent.code < 4100) {
    return "refused";
  } else if (closeEvent.code < 4200) {
    return "backoff";
  } else if (closeEvent.code < 4300) {
    return "retry";
  } else {
    // unknown error
    return "refused";
  }
};

/**
 * Returns an error or null basing on the close event.
 *
 * Null is returned when connection was closed cleanly. Otherwise, an object
 * with error details is returned.
 *
 * @param  {CloseEvent} closeEvent
 * @return {Object} error object
 */
export var getCloseError = function(closeEvent) : any {
  if (closeEvent.code !== 1000 && closeEvent.code !== 1001) {
    return {
      type: 'PusherError',
      data: {
        code: closeEvent.code,
        message: closeEvent.reason || closeEvent.message
      }
    };
  } else {
    return null;
  }
};
