/*
 * 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 MessageDispatcher = require("./MessageDispatcher"),
    ImageGenerator    = require("./ImageGenerator"),
    Model             = require("./Model"),
    headlights        = require("../headlights"),
    logger            = require("../logger"),
    _                 = require("lodash");

/*
 * Generates the image and sends it to the device
 */
function generateAndSendImage(sub) {
    var options = {
    };

    if (sub.artboardId) {
        options.generateForArtboard = true;
        options.artboardId = sub.artboardId;
    }

    logger.logMessage("generating preview image");
    var timerId = logger.timerStart("generateAndSendImage generatePreviewPNG");
    return ImageGenerator.generatePreviewPNG(options).then(
        function (imageBuffer) {
            logger.timerEnd(timerId);
            Model.devices.getAllForSubscription(sub).forEach(function (device) {
                var imageSize = imageBuffer.length;
                device.trackImageRendered(imageSize);
                if (device.usageData && !device.usageData.sendToDeviceLogged) {
                    headlights.logRenderEvent(headlights.renderEvents.SEND_TO_DEVICE);
                    device.usageData.sendToDeviceLogged = true;
                }

                logger.logMessage("sending image to device (id: " + device.id + ")");
                var imageMessage = MessageDispatcher.createMessage({
                    messageType: "image",
                    documentId: sub.documentId,
                    documentName: Model.docInfo.current.name,
                    artboardId: sub.artboardId,
                    attachmentLength: imageSize
                }, imageBuffer);
                MessageDispatcher.sendMessage(imageMessage, device.id);
            });
        }, function (err) {
            logger.timerEnd(timerId);
            logger.logMessage("Error generating preview image: " + err, logger.LOGLEVEL_ERROR);
        }
    );
}

var renderQueue = {};

/**
 * Adds a new entry to the queue for a subscription that needs to be rendered.
 */
function addQueueEntry(queue, key, subscription) {
    queue[key] = {
        subscription: subscription,
        needToRender: true,
        renderInProgress: false
    };
}

/**
 * Adds a new subscription to the queue if it's not already there. If it's
 * not in the queue, it will be scheduled for rendering.
 */
function addSubscriptionToQueue(queue, subscription) {
    var key = Model.getSubscriptionKey(subscription),
        existing = queue[key];

    // When adding a new subscription, if the subscription is already in the queue
    // we know it will be processed.
    if (existing) {
        return false;
    }

    addQueueEntry(queue, key, subscription);

    return true;
}

/**
 * Marks all subscriptions as needing to be rerendered.
 */
function needToRerender(queue, subscriptions) {
    var result = false;
    subscriptions.forEach(function (sub) {
        var subkey = Model.getSubscriptionKey(sub),
            status = queue[subkey];

        if (status) {
            if (status.needToRender) {
                // reset render flags to re-render
                status.renderInProgress = false;
                result = true;
            } else {
                status.needToRender = true;

                // Rendering only needs to be done when a render is not in progress for this sub
                if (!status.renderInProgress) {
                    result = true;
                }
            }
        } else {
            addQueueEntry(queue, subkey, sub);
            result = true;
        }
    });
    return result;
}

/**
 * Marks the given subscription as having been rendered. Returns true if another render is
 * required.
 */
function renderCompleted(queue, sub) {
    var subkey = Model.getSubscriptionKey(sub),
        status = queue[subkey];

    if (status && status.needToRender) {
        status.renderInProgress = false;
        return true;
    }

    delete queue[subkey];
    return false;
}

/**
 * This is intended to be called passing in the result from addSubscriptionToQueue, needToRerender or renderCompleted.
 * If renderNow is true, this function will scan through the queued render jobs and generate images for anything that
 * has needsToRender set but is not already in progress.
 */
function renderFromQueue(renderNow) {
    if (!renderNow) {
        return;
    }

    _.each(renderQueue, function (job) {
        if (job.needToRender && !job.renderInProgress) {
            job.needToRender = false;
            job.renderInProgress = true;
            var sub = job.subscription;

            // Commented out until the device either handles this message or is okay
            // with unknown messages.
//            Model.devices.getAllForSubscription(sub).forEach(function (device) {
//                var imageRenderingMessage = MessageDispatcher.createMessage({
//                    messageType: "imageRendering"
//                });
//                MessageDispatcher.sendMessage(imageRenderingMessage, device.id);
//            });

            generateAndSendImage(sub).then(function () {
                // There could be another render required, so we'll check for that condition upon completing the render.
                renderFromQueue(renderCompleted(renderQueue, sub));
            }).catch(function () {
                renderFromQueue(renderCompleted(renderQueue, sub));
            });
        }
    });
}

/*
 * Clean up queue by removing any entries that are for a doc other than the passed docId.
 */
function cleanupQueue(queue, docId) {
    var keys =  _.keys(queue);
    _.each(keys, function (key) {
        if (key.search("documentId: " + docId) === -1) {
            delete queue[key];
        }
    });
    
}

/*
 * handles subscribe messages
 * These contain a documentId and optional artboardId
 */
function handleSubscribe(message) {
    if (Model.docInfo.current === undefined || message.payload.documentId !== Model.docInfo.current.id) {
        logger.logMessage("Attempting to subscribe to an unknown document: " + message.payload.documentId, logger.LOGLEVEL_ERROR);
        return;
    }

    var deviceId = message.sender;

    if (!Model.devices.contains(deviceId)) {
        logger.logMessage("Subscribe message received for unknown device: " + deviceId, logger.LOGLEVEL_ERROR);
        return;
    }

    var device = Model.devices.get(deviceId),
        subscription = _.clone(message.payload);

    delete subscription.messageType;
    device.subscription = subscription;

    renderFromQueue(addSubscriptionToQueue(renderQueue, subscription));
}

/*
 * Handles documentChanged notification from the model
 * which cancels all subscriptions
 */
function handleDocumentChange() {
    Model.devices.cancelAllSubscriptions();
    var docInfo = Model.docInfo.current;
    logger.logMessage("Subscription Manager is waiting for subscriptions to " + (docInfo ? (docInfo.file || docInfo.name) : "nothing"));

    if (docInfo && docInfo.id) {
        cleanupQueue(renderQueue, docInfo.id);
    }
}

/*
 * Handles an imageChanged notification from the model
 * which sends a new image to all of devices
 */

function handleImageChange() {
    logger.logMessage("Subscription Manager is notifying all subscribers of image change");
    renderFromQueue(needToRerender(renderQueue, Model.devices.getAllSubscriptions()));
}

function init() {
    MessageDispatcher.messages.on(MessageDispatcher.MESSAGE.SUBSCRIBE, handleSubscribe);
    Model.docInfo.on(Model.EVENT.DOCUMENT_CHANGED, handleDocumentChange);
    Model.docInfo.on(Model.EVENT.IMAGE_CHANGED, handleImageChange);
}

// Private API for testing
exports._addSubscriptionToQueue = addSubscriptionToQueue;
exports._needToRerender = needToRerender;
exports._renderCompleted = renderCompleted;
exports._cleanupQueue = cleanupQueue;

// Public API
exports.init = init;
