/*
 * View framework for JamKazam.
 *
 * Processes proprietary attributes in markup to convert a set of HTML elements
 * into the JamKazam screen layout. This module is only responsible for size
 * and position. All other visual aspects should be elsewhere.
 *
 * See the layout-example.html file for a simple working example.
 */
(function(context,$) {

    "use strict";

    context.JK = context.JK || {};

    // Static function to hide the 'curtain' which hides the underlying
    // stuff until we can get it laid out. Called from both the main
    // client as well as the landing page.
    context.JK.hideCurtain = function(duration) {
        context.setTimeout(function() {
            $('.curtain').fadeOut(2*duration);
        }, duration);
    };

    context.JK.Layout = function() {

        // privates
        var logger = context.JK.logger;

        var me = null;  // Reference to this instance for context sanity.

        var opts = {
            headerHeight: 75,
            sidebarWidth: 300,
            notifyHeight: 150,
            notifyGutter: 10,
            collapsedSidebar: 30,
            panelHeaderHeight: 36,
            gutter: 60,                           // Margin around the whole UI
            screenMargin: 0,                     // Margin around screens (not headers/sidebar)
            gridOuterMargin: 6,                   // Outer margin on Grids (added to screenMargin if screen)
            gridPadding: 8,                       // Padding around grid cells. Added to outer margin.
            animationDuration: 400,
            allowBodyOverflow: false         // Allow tests to disable the body-no-scroll policy
        };

        var width = $(context).width();
        var height = $(context).height();
        var resizing = null;
        var sidebarVisible = true;
        var expandedPanel = null;
        var previousScreen = null;
        var currentScreen = null;

        var screenBindings = {};
        var dialogBindings = {};
        var wizardShowFunctions = {};

        function setup() {
            requiredStyles();
            hideAll();
            setInitialExpandedSidebarPanel();
            sizeScreens(width, height, '[layout="screen"]', true);
            positionOffscreens(width, height);
            $('[layout="sidebar"]').show();
            $('[layout="panel"]').show();
            layout();
        }

        function setInitialExpandedSidebarPanel() {
            expandedPanel = $('[layout="panel"]').first().attr("layout-id");
        }

        function layout() {
            width = $(context).width();
            height = $(context).height();
            // TODO
            // Work on naming. File is layout, class is Layout, this method
            // is layout and every other method starts with 'layoutX'. Perhaps
            // a little redundant?
            layoutCurtain(width, height);
            layoutDialogOverlay(width, height);
            layoutScreens(width, height);
            layoutSidebar(width, height);
            layoutHeader(width, height);
            layoutNotify(width, height);
            layoutFooter(width, height);
        }

        function layoutCurtain(screenWidth, screenHeight) {
            var curtainStyle = {
                position: 'absolute',
                width: screenWidth + 'px',
                height: screenHeight + 'px'
            };
            $('.curtain').css(curtainStyle);
        }

        function layoutDialogOverlay(screenWidth, screenHeight) {
            var style = {
                position: 'absolute',
                width: screenWidth + 'px',
                height: screenHeight + 'px'
            };
            $('.dialog-overlay').css(style);
        }

        function layoutScreens(screenWidth, screenHeight) {
            var previousScreenSelector = '[layout-id="' + previousScreen + '"]';
            var currentScreenSelector = '[layout-id="' + currentScreen + '"]';
            $(currentScreenSelector).show();

            var width = screenWidth - (2 * opts.gutter + 2 * opts.screenMargin);
            var left = -1 * width - 100;

            if (currentScreenSelector === previousScreenSelector) {
                left = $(currentScreenSelector).css("left");
                if (left) {
                    left = left.split("px")[0];
                }
            }
            $(previousScreenSelector).animate({left: left}, {duration: opts.animationDuration, queue: false});
            sizeScreens(screenWidth, screenHeight, '[layout="screen"]');
            positionOffscreens(screenWidth, screenHeight);
            positionOnscreen(screenWidth, screenHeight);
        }

        function sizeScreens(screenWidth, screenHeight, selector, immediate) {
            var duration = opts.animationDuration;
            if (immediate) {
                duration = 0;
            }

            var width = screenWidth - (2 * opts.gutter + 2 * opts.screenMargin);
            if (sidebarVisible) {
                width -= (opts.sidebarWidth + 2*opts.gridPadding);
            } else {
                width -= opts.collapsedSidebar + 2*opts.gridPadding;
                width += opts.gutter; // Add back in the right gutter width.
            }
            var height = screenHeight - opts.headerHeight - (2 * opts.gutter + 2 * opts.screenMargin);
            var css = {
                width: width,
                height: height
            };
            var $screens = $(selector);
            $screens.animate(css, {duration:duration, queue:false});
            layoutHomeScreen(width, height);
        }

        /**
        * Postition all screens that are not the current screen.
        */
        function positionOffscreens(screenWidth, screenHeight) {
            var top = opts.headerHeight + opts.gutter + opts.screenMargin;
            var left = -1 * (screenWidth + 2*opts.gutter);
            var $screens = $('[layout="screen"]').not('[layout-id="' + currentScreen + '"]');
            $screens.css({
                top: top,
                left: left
            });
        }

        /**
        * Position the current screen
        */
        function positionOnscreen(screenWidth, screenHeight, immediate) {
            var duration = opts.animationDuration;
            if (immediate) {
                duration = 0;
            }
            var top = opts.headerHeight + opts.gutter + opts.screenMargin;
            var left = opts.gutter + opts.screenMargin;
            var $screen = $('[layout-id="' + currentScreen + '"]');
            $screen.animate({
                top: top,
                left: left,
                overflow: 'auto'
            }, duration);
        }

        function layoutHomeScreen(homeScreenWidth, homeScreenHeight) {
            var $grid = $('[layout-grid]');
            var gridWidth = homeScreenWidth;
            var gridHeight = homeScreenHeight;
            $grid.css({width:gridWidth, height: gridHeight});
            var layout = $grid.attr('layout-grid');
            if (!layout)
                return;
            var gridRows = layout.split('x')[0];
            var gridCols = layout.split('x')[1];

            $grid.children().each(function() {
                var childPosition = $(this).attr("layout-grid-position");
                var childRow = childPosition.split(',')[1];
                var childCol = childPosition.split(',')[0];
                var childRowspan = $(this).attr("layout-grid-rows");
                var childColspan = $(this).attr("layout-grid-columns");
                var childLayout = me.getCardLayout(gridWidth, gridHeight, gridRows, gridCols,
                                               childRow, childCol, childRowspan, childColspan);

                $(this).animate({
                    width: childLayout.width,
                    height: childLayout.height,
                    top: childLayout.top,
                    left: childLayout.left
                }, opts.animationDuration);
            });
        }

        function layoutSidebar(screenWidth, screenHeight) {
            var width = opts.sidebarWidth;
            var expanderHeight = $('[layout-sidebar-expander]').height();
            var height = screenHeight - opts.headerHeight - 2 * opts.gutter + expanderHeight;
            var right = opts.gutter;
            if (!sidebarVisible) {
                // Negative right to hide most of sidebar
                right = (0 - opts.sidebarWidth) + opts.collapsedSidebar;
            }
            var top = opts.headerHeight + opts.gutter - expanderHeight;
            var css = {
                width: width,
                height: height,
                top: top,
                right: right
            };
            $('[layout="sidebar"]').animate(css, opts.animationDuration);
            layoutPanels(width, height);
            if (sidebarVisible) {
                $('[layout-panel="collapsed"]').hide();
                $('[layout-panel="expanded"]').show();
                $('[layout-sidebar-expander="hidden"]').hide();
                $('[layout-sidebar-expander="visible"]').show();
            } else {
                $('[layout-panel="collapsed"]').show();
                $('[layout-panel="expanded"]').hide();
                $('[layout-sidebar-expander="hidden"]').show();
                $('[layout-sidebar-expander="visible"]').hide();
            }
        }

        function layoutPanels(sidebarWidth, sidebarHeight) {
            // TODO - don't like the accordian - poor usability. Requires longest mouse
            // reach when switching panels. Probably better to do tabs.
            if (!sidebarVisible) {
                return;
            }
            var $expandedPanelContents = $('[layout-id="' + expandedPanel + '"] [layout-panel="contents"]');
            var combinedHeaderHeight = $('[layout-panel="contents"]').length * opts.panelHeaderHeight;
            var searchHeight = $('.sidebar .search').first().height();
            var expanderHeight = $('[layout-sidebar-expander]').height();
            var expandedPanelHeight = sidebarHeight - (combinedHeaderHeight + expanderHeight + searchHeight);
            $('[layout-panel="contents"]').hide();
            $('[layout-panel="contents"]').css({"height": "1px"});
            $expandedPanelContents.show();
            $expandedPanelContents.animate({"height": expandedPanelHeight + "px"}, opts.animationDuration);
        }

        function layoutHeader(screenWidth, screenHeight) {
            var width = screenWidth - 2*opts.gutter;
            var height = opts.headerHeight - opts.gutter;
            var top = opts.gutter;
            var left = opts.gutter;
            var css = {
                width: width + "px",
                height: height + "px",
                top: top + "px",
                left: left + "px"
            };
            $('[layout="header"]').css(css);
        }

        function layoutNotify(screenWidth, screenHeight) {
            var $notify = $('[layout="notify"]');
            var nHeight = $notify.height();
            var notifyStyle = {
                bottom: '0px',
                position: 'fixed'
            };
            $notify.css(notifyStyle);
        }

        function layoutFooter(screenWidth, screenHeight) {
            var $footer = $('#footer');
            var nHeight = $footer.height();
            var footerStyle = {
                top: (screenHeight - 80) + 'px'
            };
            var width = screenWidth - (2 * opts.gutter + 2 * opts.screenMargin);
            var left = -1 * width - 100;
            $footer.animate({ "left" : opts.gutter, "width" : width, "top": (screenHeight - 78) + "px"}, opts.animationDuration);
        }

        function requiredStyles() {
            var bodyStyle = {
                margin: '0px',
                padding: '0px',
                overflow: 'hidden'
            };
            if (opts.allowBodyOverflow) {
                delete bodyStyle.overflow;
            }
            $('body').css(bodyStyle);

            var layoutStyle = {
                position: 'absolute',
                margin: '0px',
                padding: '0px'
            };
            $('[layout]').css(layoutStyle);
            $('[layout="notify"]').css({"z-index": "9", "padding": "20px"});
            $('[layout="panel"]').css({position: 'relative'});
            $('[layout-panel="expanded"] [layout-panel="header"]').css({
                margin: "0px",
                padding: "0px",
                height: opts.panelHeaderHeight + "px"
            });
            $('[layout-grid]').css({
                position: "relative"
            });
            $('[layout-grid]').children().css({
                position: "absolute"
            });
            var curtainStyle = {
                position: "absolute",
                margin: '0px',
                padding: '0px',
                overflow: 'hidden',
                zIndex: 100
            };
            $('.curtain').css(curtainStyle);
        }

        function hideAll() {
            $('[layout]').hide();
            $('[layout="header"]').show();
        }

        function showSidebar() {
            sidebarVisible = true;
            layout();
        }

        function hideSidebar() {
            sidebarVisible = false;
            layout();
        }

        function toggleSidebar() {
            if (sidebarVisible) {
                hideSidebar();
            } else {
                showSidebar();
            }
        }

        function hideDialogs() {
            // TODO - may need dialogEvents here for specific dialogs.
            $('[layout="dialog"]').hide();
            $('.dialog-overlay').hide();
        }

        function tabClicked(evt) {
            evt.preventDefault();
            var destination = $(evt.currentTarget).attr('tab-target');
            $('[tab-target]').removeClass('selected');
            $(evt.currentTarget).addClass('selected');
            $('.tab').hide();
            $('[tab-id="' + destination + '"]').show();
        }

        function linkClicked(evt) {
            evt.preventDefault();

            // allow links to be disabled
            if($(evt.currentTarget).hasClass("disabled") ) {
                return;
            }

            var destination = $(evt.currentTarget).attr('layout-link');
            var destinationType = $('[layout-id="' + destination + '"]').attr("layout");
            if (destinationType === "screen") {
                context.location = '#/' + destination;
            } else if (destinationType === "dialog") {
                showDialog(destination);
            }
        }

        function close(evt) {
            var $target = $(evt.currentTarget).closest('[layout]');
            var layoutId = $target.attr('layout-id');
            var isDialog = ($target.attr('layout') === 'dialog');
            if (isDialog) {
                closeDialog(layoutId);
            } else {
                $target.hide();
            }
            return false;
        }

        function closeDialog(dialog) {
            var $dialog = $('[layout-id="' + dialog + '"]');
            dialogEvent(dialog, 'beforeHide');
            $('.dialog-overlay').hide();
            $dialog.hide();
            dialogEvent(dialog, 'afterHide');
        }

        function screenEvent(screen, evtName, data) {
            if (screen && screen in screenBindings) {
                if (evtName in screenBindings[screen]) {
                    screenBindings[screen][evtName].call(me, data);
                }
            }
        }

        function dialogEvent(dialog, evtName, data) {
            if (dialog && dialog in dialogBindings) {
                if (evtName in dialogBindings[dialog]) {
                    dialogBindings[dialog][evtName].call(me, data);
                }
            }
        }

        function changeToScreen(screen, data) {
            previousScreen = currentScreen;
            currentScreen = screen;

            screenEvent(previousScreen, 'beforeHide', data);
            screenEvent(currentScreen, 'beforeShow', data);

            // For now -- it seems we want it open always.
            // TODO - support user preference here? Remember how they left it?
            sidebarVisible = true;
            /*
            var openSidebarScreens = [
                'home', 'session', 'createSession',
                'findSession', 'searchResults'
            ];
            $.each(openSidebarScreens, function() {
                logger.debug("comparing " + this + " to " + currentScreen);
                if (this === currentScreen) {
                    sidebarVisible = true;
                    return false;
                }
            });
            */
            layout();

            screenEvent(previousScreen, 'afterHide', data);
            screenEvent(currentScreen, 'afterShow', data);

            // Show any requested dialog
            if ("d" in data) {
                showDialog(data.d);
            }
        }

        function showDialog(dialog) {
            dialogEvent(dialog, 'beforeShow');
            $('.dialog-overlay').show();
            centerDialog(dialog);
            $('[layout-id="' + dialog + '"]').show();
            dialogEvent(dialog, 'afterShow');
        }

        function centerDialog(dialog) {
            var $dialog = $('[layout-id="' + dialog + '"]');
            $dialog.css({
                left: width/2 - ($dialog.width()/2) + "px",
                top: height/2 - ($dialog.height()/2) + "px"
            });
        }


        function panelHeaderClicked(evt) {
            evt.preventDefault();
            expandedPanel = $(evt.currentTarget).closest('[layout="panel"]').attr("layout-id");
            layout();
            return false;
        }

        function wizardLinkClicked(evt) {
            evt.preventDefault();
            var targetStepId = $(evt.currentTarget).attr("layout-wizard-link");
            setWizardStep(targetStepId);
            return false;
        }

        function setWizardStep(targetStepId) {
            var selector = '[layout-wizard-step="' + targetStepId + '"]';
            var $targetStep = $(selector);
            var stepDialogTitle = $targetStep.attr("dialog-title");
            if (stepDialogTitle) {
                var $myDialog = $targetStep.closest('[layout="dialog"]');
                var $myTitle = $('.content-head h1', $myDialog);
                $myTitle.html(stepDialogTitle);
            }
            // Hide all steps:
            // Invoke the 'show' function, if present prior to actually showing.
            if (context._.contains(context._.keys(wizardShowFunctions), targetStepId)) {
                wizardShowFunctions[targetStepId]();
            }
            $('[layout-wizard-step]').hide();
            $targetStep.show();

            var ftuePurpose = $targetStep.attr("dialog-purpose");
            context.JK.GA.trackFTUECompletion(ftuePurpose, context.JK.detectOS());
        }

        function trackLocationChange(e) {
            context.JK.GA.virtualPageView(location.pathname + location.search  + location.hash);
        }

        function events() {
            $(context).resize(function() {
                if (resizing) {
                    context.clearTimeout(resizing);
                }
                resizing = context.setTimeout(layout, 80);
            });
            $('body').on('click', '[layout-link]', linkClicked);
            $('[layout-action="close"]').on('click', close);
            $('[layout-sidebar-expander]').on('click', toggleSidebar);
            $('[layout-panel="expanded"] [layout-panel="header"]').on('click', panelHeaderClicked);
            $('[layout-wizard-link]').on('click', wizardLinkClicked);
            $('[tab-target]').on('click', tabClicked);
            $(context).on('hashchange', trackLocationChange);
        }

        // public functions
        this.getOpts = function() {
            return opts;
        };

        // used for concurrent notifications
        var notifyQueue = [];
        var firstNotification = false;
        var notifyDetails;

        this.notify = function(message, descriptor) {
            var $notify = $('[layout="notify"]');
            
            if (notifyQueue.length === 0) {
                firstNotification = true;
                setNotificationInfo(message, descriptor);
            }

            notifyQueue.push({message: message, descriptor: descriptor});
            $notify.slideDown(2000)
                .delay(2000)
                .slideUp({
                    duration: 2000,
                    queue: true,
                    complete: function() {
                        notifyDetails = notifyQueue.shift();

                        // shift 1 more time if this is first notification being displayed
                        if (firstNotification) {
                            notifyDetails = notifyQueue.shift();
                            firstNotification = false;
                        }

                        if (notifyDetails !== undefined) {
                            setNotificationInfo(notifyDetails.message, notifyDetails.descriptor);
                        }
                    }
                });
        }

        function setNotificationInfo(message, descriptor) {
            var $notify = $('[layout="notify"]');
            $('h2', $notify).text(message.title);
            $('p', $notify).text(message.text);

            if (message.icon_url) {
                $('#avatar', $notify).attr('src', message.icon_url);
                $('#avatar', $notify).show();
            }
            else {
                $('#avatar', $notify).hide();
            }

            if (message.detail) {
                $('div.detail', $notify).html(message.detail).show();
            }
            else {
                $('div.detail', $notify).hide();
            }

            if (descriptor) {
                if (descriptor.ok_text) {
                    $('#ok-button', $notify).html(descriptor.ok_text);
                }
                else {
                    $('#ok-button', $notify).html("OKAY");
                }

                if (descriptor.ok_callback !== undefined) {
                    $('#ok-button', $notify).click(function() {
                        if (descriptor.ok_callback_args) {
                            logger.debug("descriptor.ok_callback_args=" + descriptor.ok_callback_args);
                            descriptor.ok_callback(descriptor.ok_callback_args);
                            return false;
                        }
                        else {
                            descriptor.ok_callback();
                            return false;
                        }
                    });
                }

                if (descriptor.cancel_text) {
                    $('#cancel-button', $notify).html(descriptor.cancel_text);
                }
                else {
                    if(descriptor.no_cancel) {
                        $('#cancel-button', $notify).hide();
                    }
                    else {
                        $('#cancel-button', $notify).html("CANCEL");
                    }
                }
            }
            else {
                $('#ok-button', $notify).html("OKAY");
                $('#cancel-button', $notify).html("CANCEL");
            }
        }

        this.setWizardStep = setWizardStep;

        this.changeToScreen = function(screen, data) {
            changeToScreen(screen, data);
        };

        this.showDialog = function(dialog) {
            showDialog(dialog);
        };

        this.close = function(evt) {
            close(evt);
        };

        this.closeDialog = closeDialog;

        /**
        * Given information on a grid, and a given card's grid settings, use the
        * margin options and return a list of [top, left, width, height]
        * for the cell.
        */
        this.getCardLayout = function(gridWidth, gridHeight, gridRows, gridCols,
                                      row, col, rowspan, colspan) {

            var _gridWidth = gridWidth + 3*opts.gridPadding;
            var _gridHeight = gridHeight + 3*opts.gridPadding;
            var cellWidth, cellHeight, top, left, width, height;

            cellWidth = Math.floor((_gridWidth-2*opts.gridOuterMargin) / gridCols);
            cellHeight = Math.floor((_gridHeight-2*opts.gridOuterMargin) / gridRows);
            width = colspan * cellWidth - 2*opts.gridPadding;
            height = rowspan * cellHeight - 2*opts.gridPadding;
            top = row * cellHeight;//  + opts.gridOuterMargin; // + opts.gridPadding;
            left = col * cellWidth;//  + opts.gridOuterMargin; // + opts.gridPadding;

            return {
                top: top,
                left: left,
                width: width,
                height: height
            };
        };

        this.bindScreen = function(screen, handler) {
            screenBindings[screen] = handler;
        };

        this.bindDialog = function(dialog, handler) {
            dialogBindings[dialog] = handler;
        };

        this.registerWizardStepFunction = function(stepId, showFunction) {
            wizardShowFunctions[stepId] = showFunction;
        };

        this.initialize = function(inOpts) {
            me = this;
            opts = $.extend(opts, inOpts);
            setup();
            events();
        };

        return this;

    };

}(window, jQuery));