/*
 * 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 EventEmitter  = require("events").EventEmitter,
    https         = require("https"),
    Q             = require("q"),
    io            = require("socket.io-client"),
    later         = require("later"),
    _             = require("lodash"),
    logger        = require("../logger");

var PreviewServiceConfiguration = require("./PreviewServiceConfiguration");

var events = new EventEmitter();

var apiPollTimerWrapper,
    connectRetryWrapper,
    apiWebSocket;

var jsonMimeType = "application/json",
    apiMimeType = "application/vnd.adobe.previewpairing-operation+json";

var accountGuid;

var previewServiceApi = {
    accessToken: undefined,

    isHttpOK: function (code) {
        return (Math.floor(code / 200) === 1);
    },

    createRequestCommon: function (options, body) {
        return _.extend(options, {
            hostname: PreviewServiceConfiguration.apiHost,
            port: "443",
            headers: {
                "Content-Type": jsonMimeType,
                "Accept": apiMimeType,
                "Authorization": "Bearer " + previewServiceApi.accessToken,
                "Content-Length": body ? body.length : 0
            }
        });
    },

    createFetchRequest: function () {
        return previewServiceApi.createRequestCommon({
            path: PreviewServiceConfiguration.apiFetchPath,
            method: "GET"
        });
    },

    callRemoteFunction: function (createRequest, body) {
        var result = Q.defer(),
            options = createRequest();

        var req = https.request(options, function(res) {
            res.setEncoding("utf8");

            if (previewServiceApi.isHttpOK(res.statusCode)) {
                var data = "";
                res.on("data", function (chunk) {
                    data += chunk;
                });
                res.on("end", function() {
                    result.resolve(JSON.parse(data));
                });
            } else {
                logger.logMessage(res.statusCode.toString(), logger.LOGLEVEL_ERROR);
                result.reject(res.statusCode);
            }
        });

        req.on("error", function (e) {
            logger.logMessage(e.toString(), logger.LOGLEVEL_ERROR);
            result.reject(e);
        });

        if (body) {
            req.write(body);
        }

        req.end();

        return result.promise;
    },

    fetchConnectedClients: function() {
        var deferred = Q.defer();

        previewServiceApi.callRemoteFunction(previewServiceApi.createFetchRequest).then(
            function(result) {
                deferred.resolve(result);
            },
            function(err) {
                logger.logMessage("unable to fetchConnectedClients: " + err.toString(), logger.LOGLEVEL_ERROR);
                deferred.reject(err);
            }
        );

        return deferred.promise;
    }
};


function fetchConnectedClients() {
    var result = Q.defer();
    logger.logMessage("fetchingConnectedClients");
    previewServiceApi.fetchConnectedClients().then(
        function(clientList) {
            logger.logMessage("Received client list" + clientList);
            events.emit("clientList", clientList);
            result.resolve();
        },
        function () {
            result.reject();
        }
    );
    return result.promise;
}

function createWebSocket() {
    logger.logMessage("OPENING SOCKET" + PreviewServiceConfiguration.apiWebSocketUri);
    logger.logMessage("query: consumerKey: photoshop:" + accountGuid);
    return io(PreviewServiceConfiguration.apiWebSocketUri, {
        query: {
            consumerKey: "photoshop:" + accountGuid
    }});
}

function handleWebSocketMessage (data) {
    logger.logMessage("message received on channel " + (data && data.toString()));
    if (data && data.type === "create") {
        events.emit("clientList", [data.data]);
    }
}

function handleWebSocketError () {
    logger.logMessage("could not reach reach push notification server.", logger.LOGLEVEL_ERROR);
    events.emit("orphaned");
}

function handleWebSocketClosed () {
    logger.logMessage("websocket connection was interrupted. ", logger.LOGLEVEL_ERROR);
    apiWebSocket = undefined;
    events.emit("abandoned");
}

function removeWebSocketListeners() {
    apiWebSocket.removeListener("message", handleWebSocketMessage);
    apiWebSocket.removeListener("error", handleWebSocketError);
    apiWebSocket.removeListener("close", handleWebSocketClosed);
}

function disconnectWebSocket() {
    if (apiWebSocket) {
        removeWebSocketListeners();
        apiWebSocket.disconnect();
        apiWebSocket = undefined;
    }
    if (apiPollTimerWrapper) {
        apiPollTimerWrapper.clear();
        apiPollTimerWrapper = undefined;
    }
}


function switchToPollingMode() {
    // we're already in polling mode
    if (apiPollTimerWrapper) {
        logger.logMessage("trying to switch to polling mode when we're already in polling mode...");
        return;
    }

    // disconnect the websocket listener
    if (apiWebSocket) {
        removeWebSocketListeners();
        apiWebSocket.close();
        apiWebSocket = undefined;
    }

    // we can't pull if we don't have an access token
    if (!previewServiceApi.accessToken) {
        logger.logMessage("trying to switch to polling mode when we don't have an auth token...");
        return;
    }

    logger.logMessage("polling API gateway every 30 seconds for changes");

    apiWebSocket = undefined;
    apiPollTimerWrapper = later.setInterval(fetchConnectedClients, later.parse.text("every 30 seconds"));
}

function setupWebSocket() {
    if (apiWebSocket) {
        logger.logMessage("attempting to install a push notificatiton handler when one is already installed");
        return;
    }

    logger.logMessage("installing push notification handler");
    apiWebSocket = createWebSocket();

    [
        "connect",
        "disconnect",
        "reconnect",
        "reconnect_attempt",
        "reconnecting",
        "reconnect_error",
        "reconnect_failed",
        "message"
    ].forEach(function (type) {
        apiWebSocket.on(type, function () {
            if (logger.loggingLevel >= logger.LOGLEVEL_INFO) {
                // Log the event name and all arguments
                var args = Array.prototype.slice.call(arguments);
                args.unshift("[info:preview.generate] [event " + type + "]");
                console.log.apply(console, args);
            }
        });
    });

    apiWebSocket.on("message", handleWebSocketMessage);
    apiWebSocket.on("error", handleWebSocketError);
    apiWebSocket.on("close", handleWebSocketClosed);
}

function connect () {
    if (connectRetryWrapper) {
        logger.logMessage("Trying to connect when we're in a connect retry loop");
        return;
    }
    function tryConnect() {
        if (previewServiceApi.accessToken) {
            fetchConnectedClients().then(
                function () {
                    if (connectRetryWrapper) {
                        connectRetryWrapper.clear();
                        connectRetryWrapper = undefined;
                    }
                    setupWebSocket();
                },
                function () {
                    logger.logMessage("Could not reach API gateway...trying again in 1 minute");

                    if (!connectRetryWrapper) {
                        connectRetryWrapper = later.setInterval(
                            function () {
                                tryConnect();
                            }, later.parse.text("every 1 mins")
                        );
                    }
                }
            );
        } else {
            setupWebSocket();
        }
    }
    tryConnect();
}

function init(userId, accessToken) {
    if (accountGuid) {
        return;
    }

    events.on("abandoned", function () {
        logger.logMessage("...trying to connect again");
        disconnectWebSocket();
        connect();
    });

    events.on("orphaned", function () {
        logger.logMessage("...switching to polling mode");
        switchToPollingMode();
    });
    
    previewServiceApi.accessToken = accessToken;
    accountGuid = userId;

    connect();
}

function shutdown() {
    disconnectWebSocket();

    if (connectRetryWrapper) {
        connectRetryWrapper.clear();
        connectRetryWrapper = undefined;
    }

}

exports.events = events;
exports.init = init;
exports.shutdown = shutdown;
