/*
 * ADOBE CONFIDENTIAL
 *
 * Copyright (c) 2015 Adobe Systems Incorporated. All rights reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 */

"use strict";

var _             = require("lodash"),
    EventEmitter  = require("events").EventEmitter;

var SessionManager  = require("preview-common/SessionManager"),
    Model           = require("./Model"),
    headlights      = require("../headlights"),
    logger          = require("../logger");

/**
 * Message types.
 */
var MESSAGE = {
        CONNECT: "connect",
        DISCONNECT: "disconnect",
        CONNECTION_REFUSED: "connectionRefused",
        IMAGE_RESULT: "imageResult",
        LOG_EVENT: "logEvent",
        SUBSCRIBE: "subscribe"
    },
    validMessages = _.values(MESSAGE);

/**
 * Object that represents messages and validates the creation of messages.
 */
function Message(payload, attachment) {
    if (!this instanceof Message || !payload) {
        throw new Error("must use createMessage to create messages");
    }

    if (_.isString(payload)) {
        this.payload = {
            messageType: payload
        };
    } else if (_.isPlainObject(payload)) {
        this.payload = payload;
    } else {
        throw new Error("invalid payload");
    }
    if (!this.payload.messageType) {
        throw new Error("must create a message with a valid message type");
    }

    if (attachment) {
        if (!this.payload.attachmentLength) {
            logger.logMessage("attachment ignored");
        } else {
            this.attachment = attachment;
        }
    }
}

/**
 * Add an attachment to the message.
 */
Message.prototype.setAttachment = function (attachmentLength, attachment) {
    if (!attachmentLength || !attachment) {
        throw new Error("must call setAttachment with attachemntLength and attachment");
    }

    this.payload.attachmentLength = attachmentLength;
    this.attachment = attachment;
};

/**
 * When a message is received from the device, this function will dispatch it to the event handlers.
 */
function _dispatchMessage(message) {
    if (!message || !message.sender || !message.payload || !message.payload.messageType) {
        throw new Error("must have a valid message a recipient and Payload");
    } else {
        var messageType = message.payload.messageType;

        if (!_.contains(validMessages, messageType)) {
            logger.logMessage("no message handler for " + messageType, logger.LOGLEVEL_ERROR);
        } else {
            exports.messages.emit(messageType, message);
        }
    }
}

/**
 * This function sends a message to the designated recipient.
 */
function sendMessage(message, recipient) {
    message.recipient = recipient || message.recipient;

    if (!message || !message.recipient || !message.payload.messageType) {
        throw new Error("must have a valid message with a valid recipient and Payload");
    }

    SessionManager.sendMessage(message);
}

function sendMessageToAllDevices(message) {
    SessionManager.getDeviceIds().forEach(function(deviceId) {
        sendMessage(message, deviceId);
    });
}

/**
 * Creates a new message.
 */
function createMessage(payload, attachment) {
    return new Message(payload, attachment);
}

/**
 * Used to close the a session on CONNECTION_REFUSED or DISCONNECT messages.
 */
function closeSession(message) {
    SessionManager.close(message.sender);
}

function disconnect(message) {
    var deviceId = message.sender;
    if (Model.devices.contains(deviceId)) {
        var device = Model.devices.get(deviceId);
        device.state = Model.DEVICE_STATE.DISCONNECTED;
    }
    closeSession(message);
}

function handleConnectionRefused(message) {
    var deviceId = message.sender;
    if (Model.devices.contains(deviceId)) {
        var device = Model.devices.get(deviceId);
        headlights.logConnectionFailure(device, message.payload.reason);
    }
    closeSession(message);
}

SessionManager.events.on("message", _dispatchMessage);

function logEventFromDevice(message) {
    headlights.logDeviceAction(message.payload.eventName);
}

function handleImageResult(message) {
    if (!Model.devices.contains(message.sender)) {
        return;
    }

    var device = Model.devices.get(message.sender);
    if (!device.usageData) {
        return;
    }

    if (!message.payload.error) {
        if (!device.usageData.renderSuccessLogged) {
            headlights.logRenderEvent(headlights.renderEvents.RENDERED_SUCCESS);
            device.usageData.renderSuccessLogged = true;
        }
    } else {
        if (!device.usageData.renderFailureLogged) {
            headlights.logRenderEvent(headlights.renderEvents.RENDERED_FAILURE);
            device.usageData.renderFailureLogged = true;
        }
    }
}

// for unit testing only
exports._dispatchMessage = _dispatchMessage;

// Public API

// Available messages from the device
exports.messages = new EventEmitter();
exports.messages.on(MESSAGE.CONNECTION_REFUSED, handleConnectionRefused);
exports.messages.on(MESSAGE.DISCONNECT, disconnect);
exports.messages.on(MESSAGE.LOG_EVENT, logEventFromDevice);
exports.messages.on(MESSAGE.IMAGE_RESULT, handleImageResult);

// Constants for the messages from the device
exports.MESSAGE = MESSAGE;

exports.createMessage = createMessage;
exports.sendMessage = sendMessage;
exports.sendMessageToAllDevices = sendMessageToAllDevices;
exports.Message = Message;
