"use strict";

//
//  progress.js
//  examples
//
//  Created by David Rowe on 29 Jan 2015.
//  Copyright 2015 High Fidelity, Inc.
//
//  This script displays a progress download indicator when downloads are in progress.
//
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//

(function () { // BEGIN LOCAL_SCOPE
    function debug() {
        //print.apply(null, arguments);
    }

    Script.include("/~/system/libraries/globals.js");
    var rawProgress = 100, // % raw value.
        displayProgress = 100, // % smoothed value to display.
        alpha = 0.0,
        alphaDelta = 0.0, // > 0 if fading in; < 0 if fading out.
        ALPHA_DELTA_IN = 0.15,
        ALPHA_DELTA_OUT = -0.02,
        fadeTimer = null,
        FADE_INTERVAL = 30, // ms between changes in alpha.
        fadeWaitTimer = null,
        FADE_OUT_WAIT = 1000, // Wait before starting to fade out after progress 100%.
        visible = false,

        BAR_DESKTOP_2K_WIDTH = 2240, // Width of SVG image in pixels. Sized for 1920 x 1080 display with 6 visible repeats.
        BAR_DESKTOP_2K_REPEAT = 320,  // Length of repeat in bar = 2240 / 7.
        BAR_DESKTOP_2K_HEIGHT = 3,  // Display height of SVG
        BAR_DESKTOP_2K_URL = Script.resolvePath("assets/images/progress-bar-2k.svg"),

        BAR_DESKTOP_4K_WIDTH = 4480, // Width of SVG image in pixels. Sized for 4096 x 1920 display with 6 visible repeats.
        BAR_DESKTOP_4K_REPEAT = 640,  // Length of repeat in bar = 2240 / 7.
        BAR_DESKTOP_4K_HEIGHT = 6,  // Display height of SVG
        BAR_DESKTOP_4K_URL = Script.resolvePath("assets/images/progress-bar-4k.svg"),

        BAR_HMD_WIDTH = 2240,  // Desktop image works with HMD well.
        BAR_HMD_REPEAT = 320,
        BAR_HMD_HEIGHT = 3,
        BAR_HMD_URL = Script.resolvePath("assets/images/progress-bar-2k.svg"),

        BAR_Y_OFFSET_DESKTOP = 0, // Offset of progress bar while in desktop mode
        BAR_Y_OFFSET_HMD = -100, // Offset of progress bar while in HMD

        ANIMATION_SECONDS_PER_REPEAT = 4,  // Speed of bar animation

        TEXT_HEIGHT = 32,
        TEXT_WIDTH = 256,
        TEXT_URL = Script.resolvePath("assets/images/progress-bar-text.svg"),
        windowWidth = 0,
        windowHeight = 0,
        barDesktop = {},
        barHMD = {},
        textDesktop = {},  // Separate desktop and HMD overlays because can't change text size after overlay created.
        textHMD = {},
        SCALE_TEXT_DESKTOP = 0.6,
        SCALE_TEXT_HMD = 1.0,
        isHMD = false,

        // Max seen since downloads started. This is reset when all downloads have completed.
        maxSeen = 0,

        // Progress is defined as: (pending_downloads + active_downloads) / max_seen
        // We keep track of both the current progress (rawProgress) and the
        // best progress we've seen (bestRawProgress). As you are downloading, you may
        // encounter new assets that require downloads, increasing the number of
        // pending downloads and thus decreasing your overall progress.
        bestRawProgress = 0,

        // True if we have known active downloads
        isDownloading = false,

        // Entities are streamed to users, so you don't receive them all at once; instead, you
        // receive them over a period of time. In many cases we end up in a situation where
        //
        // The initial delay cooldown keeps us from tracking progress before the allotted time
        // has passed.
        INITIAL_DELAY_COOLDOWN_TIME = 1000,
        initialDelayCooldown = 0,

        isInInterstitialMode = false;

    function fade() {

        alpha = alpha + alphaDelta;

        if (alpha < 0) {
            alpha = 0;
        } else if (alpha > 1) {
            alpha = 1;
        }

        if (alpha === 0 || alpha === 1) { // Finished fading in or out
            alphaDelta = 0;
            Script.clearInterval(fadeTimer);
        }

        if (alpha === 0) { // Finished fading out
            visible = false;
        }

        Overlays.editOverlay(barDesktop.overlay, {
            alpha: alpha,
            visible: visible && !isHMD
        });
        Overlays.editOverlay(barHMD.overlay, {
            alpha: alpha,
            visible: visible && isHMD
        });
        Overlays.editOverlay(textDesktop.overlay, {
            alpha: alpha,
            visible: visible && !isHMD
        });
        Overlays.editOverlay(textHMD.overlay, {
            alpha: alpha,
            visible: visible && isHMD
        });
    }

    Window.domainChanged.connect(function () {
        isDownloading = false;
        bestRawProgress = 100;
        rawProgress = 100;
        displayProgress = 100;
    });

    function onDownloadInfoChanged(info) {

        debug("PROGRESS: Download info changed ", info.downloading.length, info.pending, maxSeen);

        // Update raw progress value
        if (info.downloading.length + info.pending === 0) {
            isDownloading = false;
            rawProgress = 100;
            bestRawProgress = 100;
            initialDelayCooldown = INITIAL_DELAY_COOLDOWN_TIME;
        } else {
            var count = info.downloading.length + info.pending;
            if (!isDownloading) {
                isDownloading = true;
                bestRawProgress = 0;
                rawProgress = 0;
                initialDelayCooldown = INITIAL_DELAY_COOLDOWN_TIME;
                displayProgress = 0;
                maxSeen = count;
            }
            if (count > maxSeen) {
                maxSeen = count;
            }
            if (initialDelayCooldown <= 0) {
                rawProgress = ((maxSeen - count) / maxSeen) * 100;

                if (rawProgress > bestRawProgress) {
                    bestRawProgress = rawProgress;
                }
            }
        }
        debug("PROGRESS:", rawProgress, bestRawProgress, maxSeen);
    }

    function createOverlays() {
        barDesktop.overlay = Overlays.addOverlay("image", {
            imageURL: barDesktop.url,
            subImage: {
                x: 0,
                y: 0,
                width: barDesktop.width - barDesktop.repeat,
                height: barDesktop.height
            },
            width: barDesktop.width,
            height: barDesktop.height,
            visible: false,
            alpha: 0.0
        });
        barHMD.overlay = Overlays.addOverlay("image", {
            imageURL: BAR_HMD_URL,
            subImage: {
                x: 0,
                y: 0,
                width: BAR_HMD_WIDTH - BAR_HMD_REPEAT,
                height: BAR_HMD_HEIGHT
            },
            width: barHMD.width,
            height: barHMD.height,
            visible: false,
            alpha: 0.0
        });
        textDesktop.overlay = Overlays.addOverlay("image", {
            imageURL: TEXT_URL,
            width: textDesktop.width,
            height: textDesktop.height,
            visible: false,
            alpha: 0.0
        });
        textHMD.overlay = Overlays.addOverlay("image", {
            imageURL: TEXT_URL,
            width: textHMD.width,
            height: textHMD.height,
            visible: false,
            alpha: 0.0
        });
    }

    function deleteOverlays() {
        Overlays.deleteOverlay(barDesktop.overlay);
        Overlays.deleteOverlay(barHMD.overlay);
        Overlays.deleteOverlay(textDesktop.overlay);
        Overlays.deleteOverlay(textHMD.overlay);
    }

    function updateProgressBarLocation() {
        var viewport = Controller.getViewportDimensions();

        windowWidth = viewport.x;
        windowHeight = viewport.y;
        isHMD = HMD.active;

        if (isHMD) {

            Overlays.editOverlay(barHMD.overlay, {
                x: windowWidth / 2 - barHMD.width / 2,
                y: windowHeight - 2 * barHMD.height + BAR_Y_OFFSET_HMD
            });

            Overlays.editOverlay(textHMD.overlay, {
                x: windowWidth / 2 - textHMD.width / 2,
                y: windowHeight - 2 * barHMD.height - textHMD.height + BAR_Y_OFFSET_HMD
            });

        } else {

            Overlays.editOverlay(barDesktop.overlay, {
                x: windowWidth / 2 - barDesktop.width / 2,
                y: windowHeight - 2 * barDesktop.height + BAR_Y_OFFSET_DESKTOP,
                width: barDesktop.width
            });

            Overlays.editOverlay(textDesktop.overlay, {
                x: windowWidth / 2 - textDesktop.width / 2,
                y: windowHeight - 2 * barDesktop.height - textDesktop.height + BAR_Y_OFFSET_DESKTOP
            });
        }
    }

    function update() {
        var viewport, diff, x, gpuTextures;

        initialDelayCooldown -= 30;

        if (displayProgress < rawProgress) {
            diff = rawProgress - displayProgress;
            if (diff < 0.5) {
                displayProgress = rawProgress;
            } else {
                displayProgress += diff * 0.05;
            }
        }

        gpuTextures = Render.getConfig("Stats").texturePendingGPUTransferCount;

        // Update state
        if (!visible) { // Not visible because no recent downloads
            if ((displayProgress < 100 || gpuTextures > 0) && !isInInterstitialMode && !isInterstitialOverlaysVisible) { // Have started downloading so fade in
                visible = true;
                alphaDelta = ALPHA_DELTA_IN;
                fadeTimer = Script.setInterval(fade, FADE_INTERVAL);
            }
        } else if (alphaDelta !== 0.0) { // Fading in or out
            if (alphaDelta > 0) {
                if (rawProgress === 100 && gpuTextures === 0) { // Was downloading but now have finished so fade out
                    alphaDelta = ALPHA_DELTA_OUT;
                }
            } else {
                if (displayProgress < 100 || gpuTextures > 0) { // Was finished downloading but have resumed so fade in
                    alphaDelta = ALPHA_DELTA_IN;
                }
            }
        } else { // Fully visible because downloading or recently so
            if (fadeWaitTimer === null) {
                if (rawProgress === 100 && gpuTextures === 0) { // Was downloading but have finished so fade out soon
                    fadeWaitTimer = Script.setTimeout(function () {
                        alphaDelta = ALPHA_DELTA_OUT;
                        fadeTimer = Script.setInterval(fade, FADE_INTERVAL);
                        fadeWaitTimer = null;
                    }, FADE_OUT_WAIT);
                }
            } else {
                if (displayProgress < 100 || gpuTextures > 0) { // Was finished and waiting to fade out but have resumed so
                                                                // don't fade out
                    Script.clearInterval(fadeWaitTimer);
                    fadeWaitTimer = null;
                }
            }
        }

        if (visible) {
            x = ((Date.now() / 1000) % ANIMATION_SECONDS_PER_REPEAT) / ANIMATION_SECONDS_PER_REPEAT;
            if (!isHMD) {
                x = x * barDesktop.repeat;
            } else {
                x = x * BAR_HMD_REPEAT;
            }
            if (isInInterstitialMode || isInterstitialOverlaysVisible) {
                visible = false;
            }

            // Update progress bar
            Overlays.editOverlay(barDesktop.overlay, {
                visible: !isHMD && visible,
                bounds: {
                    x: barDesktop.repeat - x,
                    y: windowHeight - barDesktop.height,
                    width: barDesktop.width - barDesktop.repeat,
                    height: barDesktop.height
                }
            });

            Overlays.editOverlay(barHMD.overlay, {
                visible: isHMD && visible,
                bounds: {
                    x: BAR_HMD_REPEAT - x,
                    y: windowHeight - BAR_HMD_HEIGHT,
                    width: BAR_HMD_WIDTH - BAR_HMD_REPEAT,
                    height: BAR_HMD_HEIGHT
                }
            });

            Overlays.editOverlay(textDesktop.overlay, {
                visible: !isHMD && visible
            });

            Overlays.editOverlay(textHMD.overlay, {
                visible: isHMD && visible
            });

            // Update 2D overlays to maintain positions at bottom middle of window
            viewport = Controller.getViewportDimensions();

            if (viewport.x !== windowWidth || viewport.y !== windowHeight || isHMD !== HMD.active) {
                updateProgressBarLocation();
            }
        }
    }

    function interstitialModeChanged(inMode) {
        isInInterstitialMode = inMode;
    }

    function setUp() {
        var is4k = Window.innerWidth > 3000;

        isHMD = HMD.active;

        barDesktop.width = is4k ? BAR_DESKTOP_4K_WIDTH - BAR_DESKTOP_4K_REPEAT : BAR_DESKTOP_2K_WIDTH - BAR_DESKTOP_2K_REPEAT;
        barDesktop.height = is4k ? BAR_DESKTOP_4K_HEIGHT : BAR_DESKTOP_2K_HEIGHT;
        barDesktop.repeat = is4k ? BAR_DESKTOP_4K_REPEAT : BAR_DESKTOP_2K_REPEAT;
        barDesktop.url = is4k ? BAR_DESKTOP_4K_URL : BAR_DESKTOP_2K_URL;
        barHMD.width = BAR_HMD_WIDTH - BAR_HMD_REPEAT;
        barHMD.height = BAR_HMD_HEIGHT;

        textDesktop.width = SCALE_TEXT_DESKTOP * TEXT_WIDTH;
        textDesktop.height = SCALE_TEXT_DESKTOP * TEXT_HEIGHT;
        textHMD.width = SCALE_TEXT_HMD * TEXT_WIDTH;
        textHMD.height = SCALE_TEXT_HMD * TEXT_HEIGHT;

        createOverlays();
    }

    function tearDown() {
        deleteOverlays();
    }

    setUp();
    Window.interstitialModeChanged.connect(interstitialModeChanged);
    GlobalServices.downloadInfoChanged.connect(onDownloadInfoChanged);
    GlobalServices.updateDownloadInfo();
    Script.setInterval(update, 1000 / 60);
    Script.scriptEnding.connect(tearDown);

}()); // END LOCAL_SCOPE