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

var DATA_GROUP = "Preview_Connection";

var _actionCheck, _renderCheck, _dataFieldCheck, _disconnectMethodCheck, _connectionTypeCheck,
    _requiredDataFields, _allowedDataFields;

/**
 * Logs an action.
 *
 * @param {string} eventName Name of the action performed (must be present in exports.actions)
 */
function logAction(eventName) {
    if (!logger.assert(_actionCheck[eventName], "Unknown action event: ", eventName)) {
        return;
    }
    JSXRunner.runJSX("logHeadlights", {
        subcategory: exports.subcategories.PREVIEW_PANEL,
        eventName: eventName
    });
}

function logDeviceAction(eventName) {
    JSXRunner.runJSX("logHeadlights", {
        subcategory: exports.subcategories.PREVIEW_APP,
        eventName: eventName
    });
}

/**
 * Logs a render event. We should log these once per connection.
 *
 * @param {string} eventName See `exports.renderEvents`
 */
function logRenderEvent(eventName) {
    if (!logger.assert(_renderCheck[eventName], "Unknown render event: ", eventName)) {
        return;
    }
    JSXRunner.runJSX("logHeadlights", {
        subcategory: exports.subcategories.PREVIEW_RENDERING,
        eventName: eventName
    });
}

/**
 * @private
 *
 * Validates the connection data provided (ensures that we don't submit bogus data to highbeam...
 * garbage in, garbage out, as they say!)
 *
 * @param {Object} data Connection data to be validated
 * @return {null|string} null for no error, a string error message otherwise.
 */
function _validateConnectionData(data) {
    if (!data) {
        return "No data given";
    }
    var fieldsProvided = _.keys(data),
        missingFields = _.difference(_requiredDataFields, fieldsProvided);

    if (missingFields.length) {
        return "Missing required fields: " + missingFields.join(", ");
    }

    var unknownFields = _.difference(fieldsProvided, _allowedDataFields);

    if (unknownFields.length) {
        return "Unknown fields: " + unknownFields.join(", ");
    }

    if (_connectionTypeCheck.indexOf(data[exports.dataFields.CONNECTION_TYPE]) === -1) {
        return "Unknown connection type: " + data[exports.dataFields.CONNECTION_TYPE];
    }

    if (_disconnectMethodCheck.indexOf(data[exports.dataFields.DISCONNECT_METHOD]) === -1) {
        return "Unknown disconnect method: " + data[exports.dataFields.DISCONNECT_METHOD];
    }

    return null;
}

/**
 * Logs connection data. This should be done once per connection. See exports.dataFields
 * for the available keys on data. Every value is expected to be a string.
 *
 * @param {Object} data Connection data to log
 */
function logConnectionData(data) {
    var validationResult = _validateConnectionData(data);
    if (!logger.assert(validationResult === null, "Invalid connection data", validationResult)) {
        return;
    }
    JSXRunner.runJSX("logConnectionInfo", {
        groupName: DATA_GROUP,
        data: data
    });
}

// CONSTANTS

exports.subcategories = {
    PREVIEW_PANEL: "Preview_Panel",
    PREVIEW_RENDERING: "Preview_Rendering",
    PREVIEW_APP: "Preview_App"
};

var actions = exports.actions = {
    PANEL_OPEN_ARTBOARD: "Panel_Open_Artboard",
    PANEL_OPEN_CLICK: "Panel_Open_Click",
    PANEL_CC_CONNECTED: "Panel_CC_Connected",
    PANEL_CC_POLLING: "Panel_CC_Polling",
    PANEL_REFRESH_CLICKED: "Panel_Refresh_Clicked",
    CONNECTION_START_USB: "Connection_Start_USB",
    CONNECTION_START_WIFI: "Connection_Start_WIFI",
    CONNECTION_SUCCESS_USB: "Connection_Success_USB",
    CONNECTION_SUCCESS_WIFI: "Connection_Success_WIFI",
    CONNECTION_FAILURE_USB: "Connection_Failure_USB",
    CONNECTION_FAILURE_WIFI: "Connection_Failure_WIFI",
    CONNECTION_DISCONNECT: "Connection_Disconnect",
    CONNECTION_DISCONNECT_DROP: "Connection_Disconnect_Drop",
    PANEL_CLOSED: "Panel_Closed",
    GET_APP_LINK_CLICK: "Get_App_Link_Click",
    LEARN_MORE_LINK_CLICK: "Learn_More_Link_Click"
};

exports.renderEvents = {
    SEND_TO_DEVICE: "Send_to_Device",
    RENDERED_SUCCESS: "Rendered_Success",
    RENDERED_FAILURE: "Rendered_Failure"
};

var dataFields = exports.dataFields = {
    DEVICE_ID: "Device_ID",
    CONNECTION_ERROR_MESSAGE: "Connection_Error_Msg",
    DISCONNECT_METHOD: "Disconnect_Method",
    RENDERED_ERROR: "Rendered_Error",
    CONNECTION_TYPE: "Connection_Type",
    RENDER_FAILURES: "Render_Failures",
    SESSION_LENGTH: "Session_Length",
    IMAGES_RENDERED: "Images_Rendered",
    BYTES_RENDERED: "Bytes_Rendered"
};

exports.disconnectMethods = {
    APP_EJECT: "app_eject",
    DROP: "drop"
};

exports.connectionTypes = {
    USB: "usb",
    WIFI: "wifi"
};

function saveSessionData(device, disconnectMethod) {
    // Look to see if this device's data has already been reported
    if (!device.usageData) {
        return;
    }
    logAction(actions.CONNECTION_DISCONNECT);
    var data = {};
    data[dataFields.DEVICE_ID] = device.guid;
    data[dataFields.CONNECTION_TYPE] = device.connectionType;
    data[dataFields.SESSION_LENGTH] = Math.floor((new Date().getTime() - device.usageData.startTime) / 1000);
    data[dataFields.BYTES_RENDERED] = device.usageData.bytesRendered;
    data[dataFields.IMAGES_RENDERED] = device.usageData.imagesRendered;
    data[dataFields.RENDER_FAILURES] = device.usageData.renderFailures;
    data[dataFields.RENDERED_ERROR] = device.usageData.renderedError;
    data[dataFields.DISCONNECT_METHOD] = disconnectMethod;
    logConnectionData(data);

    device.usageData = null;
}

function handleDeviceStateChange(device, oldState, newState) {
    if (newState === Model.DEVICE_STATE.SOCKET_AVAILABLE) {
        if (device.connectionType === Model.CONNECTION_TYPE.USB) {
            logAction(actions.CONNECTION_START_USB);
        }
    } else if (newState === Model.DEVICE_STATE.CONNECTED) {
        device.usageData = {
            imagesRendered: 0,
            bytesRendered: 0,
            renderFailures: 0,
            renderedError: "",
            startTime: new Date().getTime()
        };
        if (device.connectionType === Model.CONNECTION_TYPE.USB) {
            logAction(actions.CONNECTION_SUCCESS_USB);
        } else {
            logAction(actions.CONNECTION_SUCCESS_WIFI);
        }
    } else if (oldState === Model.DEVICE_STATE.CONNECTED) {
        saveSessionData(device, newState === Model.DEVICE_STATE.DISCONNECTED ?
                                                exports.disconnectMethods.APP_EJECT :
                                                exports.disconnectMethods.DROP);
    }
}

function handleDeviceRemoved(device) {
    saveSessionData(device, exports.disconnectMethods.DROP);
}

Model.devices.on(Model.DEVICE_EVENT.STATE_CHANGED, handleDeviceStateChange);
Model.devices.on(Model.EVENT.DEVICE_REMOVED, handleDeviceRemoved);

function logConnectionFailure(device, reason) {
    if (device.connectionType === Model.CONNECTION_TYPE.USB) {
        logAction(actions.CONNECTION_FAILURE_USB);
    } else {
        logAction(actions.CONNECTION_FAILURE_WIFI);
    }

    var data = {};
    data[dataFields.DEVICE_ID] = device.guid;
    data[dataFields.CONNECTION_ERROR_MESSAGE] = reason;

    JSXRunner.runJSX("logConnectionInfo", {
        groupName: DATA_GROUP,
        data: data
    });

}

// Set up some variables based on the values above to make validation quick and easy.
_actionCheck = _.invert(exports.actions);
_renderCheck = _.invert(exports.renderEvents);
_dataFieldCheck = _.invert(exports.dataFields);
_disconnectMethodCheck = _.values(exports.disconnectMethods);
_connectionTypeCheck = _.values(exports.connectionTypes);
_requiredDataFields = [
    exports.dataFields.DEVICE_ID,
    exports.dataFields.CONNECTION_TYPE,
    exports.dataFields.DISCONNECT_METHOD,
    exports.dataFields.SESSION_LENGTH
];
_allowedDataFields = _.keys(_dataFieldCheck);


// Public functions
exports.logAction             = logAction;
exports.logDeviceAction       = logDeviceAction;
exports.logRenderEvent        = logRenderEvent;
exports.logConnectionData     = logConnectionData;
exports.logConnectionFailure  = logConnectionFailure;
