"use strict";
/* global Tablet, Script */
//
//  libraries/appUi.js
//
//  Created by Howard Stearns on 3/20/18.
//  Copyright 2018 High Fidelity, Inc.
//
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//

function AppUi(properties) {
    var request = Script.require('request').request;
    /* Example development order:
       1. var AppUi = Script.require('appUi');
       2. Put appname-i.svg, appname-a.svg in graphicsDirectory (where non-default graphicsDirectory can be added in #3).
       3. ui = new AppUi({buttonName: "APPNAME", home: "qml-or-html-path"});
          (And if converting an existing app,
            define var tablet = ui.tablet, button = ui.button; as needed.
            remove button.clicked.[dis]connect and tablet.remove(button).)
       4. Define onOpened and onClosed behavior in #3, if any.
          (And if converting an existing app, remove screenChanged.[dis]connect.)
       5. Define onMessage and sendMessage in #3, if any. onMessage is wired/unwired on open/close. If you
          want a handler to be "always on", connect it yourself at script startup.
          (And if converting an existing app, remove code that [un]wires that message handling such as
          fromQml/sendToQml or webEventReceived/emitScriptEvent.)
       6. (If converting an existing app, cleanup stuff that is no longer necessary, like references to button, tablet,
           and use isOpen, open(), and close() as needed.)
       7. lint!
     */
    var that = this;
    function defaultButton(name, suffix) {
        var base = that[name] || (that.buttonPrefix + suffix);
        that[name] = (base.indexOf('/') >= 0) ? base : (that.graphicsDirectory + base); // poor man's merge
    }

    // Defaults:
    that.tabletName = "com.highfidelity.interface.tablet.system";
    that.inject = "";
    that.graphicsDirectory = "icons/tablet-icons/"; // Where to look for button svgs. See below.
    that.additionalAppScreens = [];
    that.checkIsOpen = function checkIsOpen(type, tabletUrl) { // Are we active? Value used to set isOpen.
        // Actual url may have prefix or suffix.
        return that.currentVisibleUrl &&
            ((that.home.indexOf(that.currentVisibleUrl) > -1) ||
            (that.additionalAppScreens.indexOf(that.currentVisibleUrl) > -1));
    };
    that.setCurrentVisibleScreenMetadata = function setCurrentVisibleScreenMetadata(type, url) {
        that.currentVisibleScreenType = type;
        that.currentVisibleUrl = url;
    };
    that.open = function open(optionalUrl, optionalInject) { // How to open the app.
        var url = optionalUrl || that.home;
        var inject = optionalInject || that.inject;

        if (that.isQMLUrl(url)) {
            that.tablet.loadQMLSource(url);
        } else {
            that.tablet.gotoWebScreen(url, inject);
        }
    };
    // Opens some app on top of the current app (on desktop, opens new window)
    that.openNewAppOnTop = function openNewAppOnTop(url, optionalInject) {
        var inject = optionalInject || "";
        if (that.isQMLUrl(url)) {
            that.tablet.loadQMLOnTop(url);
        } else {
            that.tablet.loadWebScreenOnTop(url, inject);
        }
    };
    that.close = function close() { // How to close the app.
        that.currentVisibleUrl = "";
        // for toolbar-mode: go back to home screen, this will close the window.
        that.tablet.gotoHomeScreen();
    };
    that.buttonActive = function buttonActive(isActive) { // How to make the button active (white).
        that.button.editProperties({isActive: isActive});
    };
    that.isQMLUrl = function isQMLUrl(url) {
        var type = /.qml$/.test(url) ? 'QML' : 'Web';
        return type === 'QML';
    };
    that.isCurrentlyOnQMLScreen = function isCurrentlyOnQMLScreen() {
        return that.currentVisibleScreenType === 'QML';
    };

    //
    // START Notification Handling Defaults
    //
    that.messagesWaiting = function messagesWaiting(isWaiting) { // How to indicate a message light on button.
        // Note that waitingButton doesn't have to exist unless someone explicitly calls this with isWaiting true.
        that.button.editProperties({
            icon: isWaiting ? that.normalMessagesButton : that.normalButton,
            activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton
        });
    };
    that.notificationPollTimeout = [false];
    that.notificationPollTimeoutMs = [60000];
    that.notificationPollEndpoint = [false];
    that.notificationPollStopPaginatingConditionMet = [false];
    that.notificationDataProcessPage = function (data) {
        return data;
    };
    that.notificationPollCallback = [that.ignore];
    that.notificationPollCaresAboutSince = [false];
    that.notificationInitialCallbackMade = [false];
    that.notificationDisplayBanner = function (message) {
        if (!that.isOpen) {
            Window.displayAnnouncement(message);
        }
    };
    //
    // END Notification Handling Defaults
    //

    // Handlers
    that.onScreenChanged = function onScreenChanged(type, url) {
        // Set isOpen, wireEventBridge, set buttonActive as appropriate,
        // and finally call onOpened() or onClosed() IFF defined.
        that.setCurrentVisibleScreenMetadata(type, url);

        if (that.checkIsOpen(type, url)) {
            that.wireEventBridge(true);
            if (!that.isOpen) {
                that.buttonActive(true);
                if (that.onOpened) {
                    that.onOpened();
                }
                that.isOpen = true;
            }
        } else {
            // A different screen is now visible, or the tablet has been closed.
            // Tablet visibility is controlled separately by `tabletShownChanged()`
            that.wireEventBridge(false);
            if (that.isOpen) {
                that.buttonActive(false);
                if (that.onClosed) {
                    that.onClosed();
                }
                that.isOpen = false;
            }
        }
    };

    // Overwrite with the given properties:
    Object.keys(properties).forEach(function (key) {
        that[key] = properties[key];
    });

    //
    // START Notification Handling
    //

    var currentDataPageToRetrieve = [];
    var concatenatedServerResponse = [];
    for (var i = 0; i < that.notificationPollEndpoint.length; i++) {
        currentDataPageToRetrieve[i] = 1;
        concatenatedServerResponse[i] = new Array();
    }

    var MAX_LOG_LENGTH_CHARACTERS = 300;
    function requestCallback(error, response, optionalParams) {
        var indexOfRequest = optionalParams.indexOfRequest;
        var urlOfRequest = optionalParams.urlOfRequest;

        if (error || (response.status !== 'success')) {
            print("Error: unable to complete request from URL. Error:", error || response.status);
            startNotificationTimer(indexOfRequest);
            return;
        }

        if (!that.notificationPollStopPaginatingConditionMet[indexOfRequest] ||
            that.notificationPollStopPaginatingConditionMet[indexOfRequest](response)) {
            startNotificationTimer(indexOfRequest);

            var notificationData;
            if (concatenatedServerResponse[indexOfRequest].length) {
                notificationData = concatenatedServerResponse[indexOfRequest];
            } else {
                notificationData = that.notificationDataProcessPage[indexOfRequest](response);
            }
            console.debug(that.buttonName,
                'truncated notification data for processing:',
                JSON.stringify(notificationData).substring(0, MAX_LOG_LENGTH_CHARACTERS));
            that.notificationPollCallback[indexOfRequest](notificationData);
            that.notificationInitialCallbackMade[indexOfRequest] = true;
            currentDataPageToRetrieve[indexOfRequest] = 1;
            concatenatedServerResponse[indexOfRequest] = new Array();
        } else {
            concatenatedServerResponse[indexOfRequest] =
                concatenatedServerResponse[indexOfRequest].concat(that.notificationDataProcessPage[indexOfRequest](response));
            currentDataPageToRetrieve[indexOfRequest]++;
            request({
                json: true,
                uri: (urlOfRequest + "&page=" + currentDataPageToRetrieve[indexOfRequest])
            }, requestCallback, optionalParams);
        }
    }


    var METAVERSE_BASE = Account.metaverseServerURL;
    var MS_IN_SEC = 1000;
    that.notificationPoll = function (i) {
        if (!that.notificationPollEndpoint[i]) {
            return;
        }

        // User is "appearing offline" or is not logged in
        if (GlobalServices.findableBy === "none" || Account.username === "Unknown user") {
            // The notification polling will restart when the user changes their availability
            // or when they log in, so it's not necessary to restart a timer here.
            console.debug(that.buttonName + " Notifications: User is appearing offline or not logged in. " +
                that.buttonName + " will poll for notifications when user logs in and has their availability " +
                "set to not appear offline.");
            return;
        }

        var url = METAVERSE_BASE + that.notificationPollEndpoint[i];

        var settingsKey = "notifications/" + that.notificationPollEndpoint[i] + "/lastPoll";
        var currentTimestamp = new Date().getTime();
        var lastPollTimestamp = Settings.getValue(settingsKey, currentTimestamp);
        if (that.notificationPollCaresAboutSince[i]) {
            url = url + "&since=" + lastPollTimestamp / MS_IN_SEC;
        }
        Settings.setValue(settingsKey, currentTimestamp);

        request({
            json: true,
            uri: url
        },
        requestCallback,
        {
            indexOfRequest: i,
            urlOfRequest: url
        });
    };

    // This won't do anything if there isn't a notification endpoint set
    for (i = 0; i < that.notificationPollEndpoint.length; i++) {
        that.notificationPoll(i);
    }

    function startNotificationTimer(indexOfRequest) {
        that.notificationPollTimeout[indexOfRequest] = Script.setTimeout(function () {
            that.notificationPoll(indexOfRequest);
        }, that.notificationPollTimeoutMs[indexOfRequest]);
    }

    function restartNotificationPoll() {
        for (var j = 0; j < that.notificationPollEndpoint.length; j++) {
            that.notificationInitialCallbackMade[j] = false;
            if (that.notificationPollTimeout[j]) {
                Script.clearTimeout(that.notificationPollTimeout[j]);
                that.notificationPollTimeout[j] = false;
            }
            that.notificationPoll(j);
        }
    }
    //
    // END Notification Handling
    //

    // Properties:
    that.tablet = Tablet.getTablet(that.tabletName);
    // Must be after we gather properties.
    that.buttonPrefix = that.buttonPrefix || that.buttonName.toLowerCase() + "-";
    defaultButton('normalButton', 'i.svg');
    defaultButton('activeButton', 'a.svg');
    defaultButton('normalMessagesButton', 'i-msg.svg');
    defaultButton('activeMessagesButton', 'a-msg.svg');
    var buttonOptions = {
        icon: that.normalButton,
        activeIcon: that.activeButton,
        text: that.buttonName
    };
    // `TabletScriptingInterface` looks for the presence of a `sortOrder` key.
    // What it SHOULD do is look to see if the value inside that key is defined.
    // To get around the current code, we do this instead.
    if (that.sortOrder) {
        buttonOptions.sortOrder = that.sortOrder;
    }
    that.button = that.tablet.addButton(buttonOptions);
    that.ignore = function ignore() { };
    that.hasOutboundEventBridge = false;
    that.hasInboundQmlEventBridge = false;
    that.hasInboundHtmlEventBridge = false;
    // HTML event bridge uses strings, not objects. Here we abstract over that.
    // (Although injected javascript still has to use JSON.stringify/JSON.parse.)
    that.sendToHtml = function (messageObject) {
        that.tablet.emitScriptEvent(JSON.stringify(messageObject));
    };
    that.fromHtml = function (messageString) {
        var parsedMessage = JSON.parse(messageString);
        parsedMessage.messageSrc = "HTML";
        that.onMessage(parsedMessage);
    };
    that.sendMessage = that.ignore;
    that.wireEventBridge = function wireEventBridge(on) {
        // Uniquivocally sets that.sendMessage(messageObject) to do the right thing.
        // Sets has*EventBridge and wires onMessage to the proper event bridge as appropriate, IFF onMessage defined.
        var isCurrentlyOnQMLScreen = that.isCurrentlyOnQMLScreen();
        // Outbound (always, regardless of whether there is an inbound handler).
        if (on) {
            that.sendMessage = isCurrentlyOnQMLScreen ? that.tablet.sendToQml : that.sendToHtml;
            that.hasOutboundEventBridge = true;
        } else {
            that.sendMessage = that.ignore;
            that.hasOutboundEventBridge = false;
        }

        if (!that.onMessage) {
            return;
        }

        // Inbound
        if (on) {
            if (isCurrentlyOnQMLScreen && !that.hasInboundQmlEventBridge) {
                console.debug(that.buttonName, 'connecting', that.tablet.fromQml);
                that.tablet.fromQml.connect(that.onMessage);
                that.hasInboundQmlEventBridge = true;
            } else if (!isCurrentlyOnQMLScreen && !that.hasInboundHtmlEventBridge) {
                console.debug(that.buttonName, 'connecting', that.tablet.webEventReceived);
                that.tablet.webEventReceived.connect(that.fromHtml);
                that.hasInboundHtmlEventBridge = true;
            }
        } else {
            if (that.hasInboundQmlEventBridge) {
                console.debug(that.buttonName, 'disconnecting', that.tablet.fromQml);
                that.tablet.fromQml.disconnect(that.onMessage);
                that.hasInboundQmlEventBridge = false;
            }
            if (that.hasInboundHtmlEventBridge) {
                console.debug(that.buttonName, 'disconnecting', that.tablet.webEventReceived);
                that.tablet.webEventReceived.disconnect(that.fromHtml);
                that.hasInboundHtmlEventBridge = false;
            }
        }
    };
    that.isOpen = false;
    // To facilitate incremental development, only wire onClicked to do something when "home" is defined in properties.
    that.onClicked = that.home
        ? function onClicked() {
            // Call open() or close(), and reset type based on current home property.
            if (that.isOpen) {
                that.close();
            } else {
                that.open();
            }
        } : that.ignore;
    that.onScriptEnding = function onScriptEnding() {
        // Close if necessary, clean up any remaining handlers, and remove the button.
        GlobalServices.myUsernameChanged.disconnect(restartNotificationPoll);
        GlobalServices.findableByChanged.disconnect(restartNotificationPoll);
        that.tablet.screenChanged.disconnect(that.onScreenChanged);
        if (that.isOpen) {
            that.close();
            that.onScreenChanged("", "");
        }
        if (that.button) {
            if (that.onClicked) {
                that.button.clicked.disconnect(that.onClicked);
            }
            that.tablet.removeButton(that.button);
        }
        for (var i = 0; i < that.notificationPollTimeout.length; i++) {
            if (that.notificationPollTimeout[i]) {
                Script.clearInterval(that.notificationPollTimeout[i]);
                that.notificationPollTimeout[i] = false;
            }
        }
    };
    // Set up the handlers.
    that.tablet.screenChanged.connect(that.onScreenChanged);
    that.button.clicked.connect(that.onClicked);
    Script.scriptEnding.connect(that.onScriptEnding);
    GlobalServices.findableByChanged.connect(restartNotificationPoll);
    GlobalServices.myUsernameChanged.connect(restartNotificationPoll);
    if (that.buttonName === Settings.getValue("startUpApp")) {
        Settings.setValue("startUpApp", "");
        Script.setTimeout(function () {
            that.open();
        }, 1000);
    }
}
module.exports = AppUi;