/*
 * 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 net                 = require("net"),
    util                = require("util"),
    EventEmitter        = require("events").EventEmitter,
    SessionManager      = require("preview-common/SessionManager"),
    IPCMessage          = require("preview-common/IPCMessage"),
    IPCPipe             = require("preview-common/IPCPipe"),
    CommonConfiguration = require("preview-common/CommonConfiguration"),
    DeviceMonitor       = require("./DeviceMonitor"),
    PreviewService      = require("./PreviewService"),
    headlights          = require("../headlights"),
    Preferences         = require("./Preferences"),
    Model               = require("./Model"),
    _                   = require("lodash"),
    logger              = require("../logger");

var DELAY_STOP_CONNECTION_WATCH = 180000;   // ms to wait before stopping connection watch after CEP closes

var _generator,
    cepconn,
    isPanelOpen = false;

/**
 * Server that runs in Generator to listen to connections from CEP.
 */
function CEPconnectionServer() {
    this._server = null;
    this._pendingSessions = [];
    this._sessions = {};
    this._handleConnection = this._handleConnection.bind(this);
    this._handleClose = this._handleClose.bind(this);
    this._handleIdMessage = this._handleIdMessage.bind(this);
}

util.inherits(CEPconnectionServer, EventEmitter);

/**
 * Starts the server.
 */
CEPconnectionServer.prototype.start = function () {
    this._server = net.createServer(this._handleConnection);
    this._server.on("error", function (e) {
        if (e.code === "EADDRINUSE") {
            logger.logMessage("CEPconnectionServer start: Address in use.", logger.LOGLEVEL_ERROR);
        }
    });

    if (IPCPipe.enabled) {
        this._server.listen(IPCPipe.getPath());
    } else {
        this._server.listen(CommonConfiguration.port, "127.0.0.1");
    }
};

/**
 * When a connection is established, launch a session in the preview protocol.
 * Only accepts local connections, and only one connection at a time.
 */
CEPconnectionServer.prototype._handleConnection = function (conn) {
    logger.logMessage("CEP session started");

    // Only one CEP connection is maintained, so the newest one wins.
    // This is useful both in development and if the user is opening/closing the panel
    // and we don't happen to spot that the socket has closed.
    var session = new SessionManager.Session(conn, "CEP");

    session.on("message", this._handleIdMessage);

    session.on("close", this._handleClose);
    this._pendingSessions.push(session);
};

/**
 * If the socket closes, we clear out our session.
 */
CEPconnectionServer.prototype._handleClose = function (session) {
    _.pull(this._pendingSessions, session);
    delete this._sessions[session.deviceId];
};

/**
 * Handle the initial connection message from any of our panels
 */
CEPconnectionServer.prototype._handleIdMessage = function (msg, session) {
    var message = msg.payload;
    if (message.messageType !== IPCMessage.types.REGISTERCEP) {
        throw new Error("Did not receive REGISTERCEP message for routing of CEP traffic");
    }
    if ((message.extension !== IPCMessage.clients.CEPID_PSLOADER) && (message.extension !== IPCMessage.clients.CEPID_PSPANEL)) {
        throw new Error("Unknown CEP extension: " + message.extension);
    }

    _.pull(this._pendingSessions, session);

    session.deviceId = message.extension;

    session.removeListener("message", this._handleIdMessage);
    session.on("message", function (panelMessage) {
        this.emit(message.extension, panelMessage);
    }.bind(this));
    this._sessions[message.extension] = session;
};

/**
 * Send a message to CEP.
 */
CEPconnectionServer.prototype.sendMessage = function (extension, payload) {
    if (this._sessions[extension]) {
        this._sessions[extension].sendMessage({
            recipient: {
                deviceId: "CEP"
            },
            payload: payload
        });
    }
};

/**
 * Starts the server to listen for a connection from CEP.
 */
function startServer() {
    if (cepconn) {
        throw new Error("startServer called with server already running!");
    }
    cepconn = new CEPconnectionServer();
    cepconn.start();
    return cepconn;
}

/**
 * Sends the full list of connected devices to the CEP, as well as those that have errors.
 */
function sendDeviceListToCEP() {
    cepconn.sendMessage(IPCMessage.clients.CEPID_PSPANEL, {
        messageType: IPCMessage.types.DEVICELIST,
        deviceList: Model.devices.filter(function (device) {
            return (device.state === Model.DEVICE_STATE.CONNECTED ||
                    device.state === Model.DEVICE_STATE.ERROR);
        })
    });
}

/**
 * Handle a device state change event
 */
function handleDeviceStateChange(device, oldState, newState) {
    // anytime a device connects or disconnects, send the entire device list to the CEP panel
    if (isPanelOpen && ((newState === Model.DEVICE_STATE.CONNECTED) || (oldState === Model.DEVICE_STATE.CONNECTED))) {
        sendDeviceListToCEP();
    }
    // when a device connects, notify the CEP
    if (newState === Model.DEVICE_STATE.CONNECTED) {
        cepconn.sendMessage(IPCMessage.clients.CEPID_PSLOADER, {
            messageType: IPCMessage.types.DEVICECONNECTED
        });
        cepconn.sendMessage(IPCMessage.clients.CEPID_PSPANEL, {
            messageType: IPCMessage.types.DEVICECONNECTED
        });
    }
}

/**
 * Handle a message sent from the CEP panel
 */
function handleCEPMessage(message) {
    switch(message.payload.messageType) {
        case IPCMessage.types.PANELOPENED:
            // CEP opened and wants to connect.  Reply w/ ack that we've received message.
            cepconn.sendMessage(IPCMessage.clients.CEPID_PSPANEL, {
                messageType: IPCMessage.types.PANELOPENEDACK
            });

            headlights.logAction(headlights.actions.PANEL_OPEN_CLICK);
            
            PreviewService.queryForClientList();    // manually request an updated client list from Preview Service

            // don't start watching for devices until the CEP panel is opened
            if (!DeviceMonitor.isMonitoring) {
                DeviceMonitor.start();
            }

            isPanelOpen = true;
            break;

        case IPCMessage.types.REQUESTDEVICELIST:
            // CEP has requested a list of devices.  Send them what we have now
            sendDeviceListToCEP();
            break;

        case IPCMessage.types.PANELCLOSED:
            isPanelOpen = false;
            // CEP closing.  Stop watching for connections after a certain delay
            if (!Preferences.getPref("watchImmediate")) {
                setTimeout(function() {
                    DeviceMonitor.stop();
                }, DELAY_STOP_CONNECTION_WATCH);
            }

            headlights.logAction(headlights.actions.PANEL_CLOSED);
            break;

        case IPCMessage.types.PREVIEWSERVICESTARTED:
            PreviewService.init(_generator, message.payload.accountGuid, message.payload.accessToken);
            break;

        case IPCMessage.types.DISCONNECT_USB:
            DeviceMonitor.disconnectUSBDevices();
            break;

        case IPCMessage.types.GETAPPLINKCLICK:
            headlights.logAction(headlights.actions.GET_APP_LINK_CLICK);
            break;

        case IPCMessage.types.LEARNMORELINKCLICK:
            headlights.logAction(headlights.actions.LEARN_MORE_LINK_CLICK);
            break;
            
        case IPCMessage.types.REQUESTCHECKFORDEVICES:
            logger.logMessage("User requested to check for new devices");
            headlights.logAction(headlights.actions.PANEL_REFRESH_CLICKED);
            PreviewService.queryForClientList();    // manually request an updated client list from Preview Service
            break;

        default:
            // unknown message type
            logger.logMessage("received unknown PSPanel message: " + JSON.stringify(message.payload.messageType), logger.LOGLEVEL_ERROR);
    }
}

/**
 * Handle a message sent from the loader panel
 */
function handleLoaderMessage(message) {
    switch(message.payload.messageType) {
        case IPCMessage.types.PREVIEWSERVICESTARTED:
            PreviewService.init(_generator, message.payload.accountGuid, message.payload.accessToken);
            break;
            
        case IPCMessage.types.PANELOPENEDARTBOARD:
            // PSLoader detected first-run artboard activity and is auto-opening PSPanel
            headlights.logAction(headlights.actions.PANEL_OPEN_ARTBOARD);
            break;

        default:
            // unknown message type
            logger.logMessage("received unknown PSLoader message: " + JSON.stringify(message.payload.messageType), logger.LOGLEVEL_ERROR);
    }
}

function handleGeneratorClosed() {
    IPCPipe.shutdown();
}

function init(generator) {
    _generator = generator;
    _generator.on("close", handleGeneratorClosed);

    // add event handlers to re-send the device list if a device is added or removed.
    Model.devices.on(Model.DEVICE_EVENT.STATE_CHANGED, handleDeviceStateChange);
    Model.devices.on(Model.EVENT.DEVICE_REMOVED, sendDeviceListToCEP);
    Model.devices.on(Model.EVENT.DEVICE_ERROR, sendDeviceListToCEP);
    
    // send an updated device list just as soon as we receive one from the Preview Service
    PreviewService.events.on("clientList", sendDeviceListToCEP);

    // Make sure the pipe is clear
    IPCPipe.init();

    // connect to CEP panel
    cepconn = startServer();

    // event handler to receive messages from the CEP panel
    cepconn.on(IPCMessage.clients.CEPID_PSPANEL, handleCEPMessage);
    // event handler to receive messages from the hidden CEP
    cepconn.on(IPCMessage.clients.CEPID_PSLOADER, handleLoaderMessage);

    // either immediately watch for device connections, or wait until the panel opens
    if (Preferences.getPref("watchImmediate")) {
        DeviceMonitor.start();
    }
}

exports.init            = init;

// API for Testing
exports.handleCEPMessage = handleCEPMessage;
exports.sendDeviceListToCEP = sendDeviceListToCEP;
exports.startServer = startServer;
