﻿ // declare global metadata host
if (!window.shapeTracingMetadataHost) {
    window.shapeTracingMetadataHost = {};
    window.shapeTracingMetadataHost.placement = {
        'n/a': 'n/a'
    };
}

jQuery(function ($) {

    // default shape window height when first opened
    var defaultHeight = 200;

    // preload main objects
    var shapeTracingContainer = $('#shape-tracing-container');
    var shapeTracingResizeHandle = $('#shape-tracing-resize-handle');
    var shapeTracingToolbar = $('#shape-tracing-toolbar');
    var shapeTracingToolbarSwitch = $('#shape-tracing-toolbar-switch');
    var shapeTracingWindow = $('#shape-tracing-window');
    var shapeTracingWindowTree = $('#shape-tracing-window-tree');
    var shapeTracingWindowContent = $('#shape-tracing-window-content');
    var shapeTracingGhost = $('#shape-tracing-container-ghost');
    var shapeTracingOverlay = $('#shape-tracing-overlay');
    var shapeTracingTabs = $('#shape-tracing-tabs');
    var shapeTracingTabsShape = $('#shape-tracing-tabs-shape');
    var shapeTracingTabsModel = $('#shape-tracing-tabs-model');
    var shapeTracingTabsPlacement = $('#shape-tracing-tabs-placement');
    var shapeTracingTabsTemplate = $('#shape-tracing-tabs-template');
    var shapeTracingTabsHtml = $('#shape-tracing-tabs-html');
    var shapeTracingBreadcrumb = $('#shape-tracing-breadcrumb');
    var shapeTracingMetaContent = $('#shape-tracing-meta-content');
    var shapeTracingEnabled = false;

    // store the size of the container when it is closed (default in css)
    var initialContainerSize = shapeTracingContainer.height();
    var previousSize = 0;

    // represents the arrow to add to any collpasible container
    var glyph = '<span class="expando-glyph-container closed"><span class="expando-glyph"></span>&#8203;</span>';

    // ensure the ghost has always the same size as the container
    // and the container is always positionned correctly
    var syncResize = function () {
        var _window = $(window);
        var containerHeight = shapeTracingContainer.outerHeight();
        var containerWidth = shapeTracingContainer.outerWidth();
        var toolbarHeight = shapeTracingToolbar.outerHeight();
        var resizeHandleHeight = shapeTracingResizeHandle.outerHeight();

        shapeTracingGhost.height(containerHeight);

        var windowHeight = _window.height();
        var scrollTop = _window.scrollTop();
        var containerWindowHeight = containerHeight - toolbarHeight - resizeHandleHeight;

        shapeTracingContainer.offset({ top: windowHeight - containerHeight + scrollTop, left: 0 });
        shapeTracingWindow.height(containerWindowHeight);
        shapeTracingWindowTree.height(containerWindowHeight);
        shapeTracingWindowContent.height(containerWindowHeight);
        shapeTracingContainer.width('100%');

        syncResizeMeta();
    };

    // forces the content meta zone's height to enable scrollbar
    var syncResizeMeta = function () {
        var containerHeight = shapeTracingContainer.outerHeight();
        var containerWidth = shapeTracingContainer.outerWidth();
        var toolbarHeight = shapeTracingToolbar.outerHeight();
        var resizeHandleHeight = shapeTracingResizeHandle.outerHeight();

        var tabsHeight = shapeTracingTabs.outerHeight();
        var breadcrumbHeight = shapeTracingBreadcrumb.outerHeight();
        if (tabsHeight) {
            padding = parseInt(shapeTracingMetaContent.css('padding-bottom') + shapeTracingMetaContent.css('padding-top'));
            shapeTracingMetaContent.height(containerHeight - toolbarHeight - resizeHandleHeight - tabsHeight - breadcrumbHeight - padding);
        }
    };

    // ensure the size/position is correct whenver the container or the browser is resized
    shapeTracingContainer.resize(syncResize);
    $(window).resize(syncResize);
    $(window).resize();

    // removes the position flickering by hiding it first, then showing when ready
    shapeTracingContainer.show();

    // expand/collapse behavior
    // ensure the container has always a valid size when expanded
    shapeTracingToolbarSwitch.click(function () {
        var _this = $(this);
        _this.toggleClass('expanded');
        if (_this.hasClass('expanded')) {
            shapeTracingContainer.height(Math.max(previousSize, defaultHeight, shapeTracingContainer.height()));
            enableShapeTracing();
        }
        else {
            // save previous height
            previousSize = shapeTracingContainer.height();
            shapeTracingContainer.height(initialContainerSize);
            disableShapeTracing();
        }

        syncResize();
    });

    var disableShapeTracing = function () {
        shapeTracingEnabled = false;
        selectShape();
    }

    var enableShapeTracing = function () {
        shapeTracingEnabled = true;
    }

    // add a resizable handle to the container
    $('#shape-tracing-resize-handle').addClass('ui-resizable-handle ui-resizable-n');
    shapeTracingContainer.resizable({
        handles: { n: '#shape-tracing-resize-handle' },
        grid: 20, // mitigates the number of calls to syncResize()
        resize: function () { shapeTracingEnabled = false },
        stop: function () { shapeTracingEnabled = true }
    });

    var shapeNodes = {}; // represents the main index of shape nodes, indexed by id

    // projects the shape ids to each DOM element
    var startShapeTracingBeacons = $('.shape-tracing-wrapper[shape-id]');
    startShapeTracingBeacons.each(function () {
        var _this = $(this);

        var shapeNode = {
            id: _this.attr('shape-id'),
            type: _this.attr('shape-type'),
            hint: _this.attr('shape-hint'),
            parent: null,
            children: {}
        };

        // register the new shape node into the main shape nodes index
        shapeNodes[shapeNode.id] = shapeNode;

        // assign the shape-id attribute to all direct children, except wrappers themselves (it would erase their own shape-id)
        var found = false;
        _this
            .nextUntil('[end-of="' + shapeNode.id + '"]') // all elements between the script beacons
            .find(':not(.shape-tracing-wrapper)') // all children but not inner beacons
            .andSelf() // add the first level items
            .attr('shape-id', shapeNode.id) // add the shape-id attribute
            .each(function () {
                // assign a shapeNode instance to the DOM element
                this.shapeNode = shapeNode;
                found = true;
            });

        // if the shape is empty, add a hint
        if (!found) {
            shapeNode.hint = 'empty';
        }
        this.shapeNode = shapeNode;
    });

    // construct the shape tree based on all current nodes
    // for each start beacon, search for the first parent beacon, and create nodes if they don't exist
    startShapeTracingBeacons.each(function () {
        var _this = $(this);
        var shapeNode = this.shapeNode;
        var parent = _this.parents('[shape-id!=' + shapeNode.id + ']').get(0);

        shapeNodes[shapeNode.id] = shapeNode;

        if (parent.shapeNode) {
            var parentShapeNode = parent.shapeNode;
            shapeNodes[parentShapeNode.id] = parentShapeNode;
            shapeNode.parent = parentShapeNode;
            parentShapeNode.children[shapeNode.id] = shapeNode;
        }
    });

    // removes all beacons as we don't need them anymore
    $('.shape-tracing-wrapper').remove();

    // add first level shapes tree nodes
    var shapes = $('<ul></ul>');
    for (var shapeId in shapeNodes) {
        if (!shapeNodes[shapeId].parent) {
            shapes.append(createTreeNode(shapeNodes[shapeId]));
        }
    }

    shapeTracingWindowTree.append(shapes);

    // add the expand/collapse logic to the shapes tree
    shapeTracingWindowTree.find('li:has(ul:has(li))').prepend($(glyph));

    // collapse all sub uls
    shapeTracingWindowTree.find('ul ul').toggle(false);

    // expands a list of shapes in the tree
    var openExpando = function (expando) {
        if (expando.hasClass("closed")) {
            expando.siblings('ul').toggle(true);
            expando.removeClass("closed").addClass("open");
        }
    }

    // collapses a list of shapes in the tree
    var closeExpando = function (expando) {
        if (expando.hasClass("open")) {
            expando.siblings('ul').toggle(false);
            expando.removeClass("open").addClass("closed");
        }
    }

    shapeTracingWindow.add(shapeTracingResizeHandle).hover(function () {
        shapeTracingOverlay.hide();
    }, function () {
        shapeTracingOverlay.show();
    }
    );

    //create an overlay on shapes' descendants
    var overlayTarget = null;
    $('[shape-id]').add(shapeTracingOverlay).mousemove(
        function (event) {
            if (!shapeTracingEnabled) {
                return;
            }

            event.stopPropagation();

            if ($(this).get(0) == shapeTracingOverlay.get(0)) {
                shapeTracingOverlay.hide();
            }

            var element = document.elementFromPoint(event.pageX - $(window).scrollLeft(), event.pageY - $(window).scrollTop());
            shapeTracingOverlay.show();


            while (element && !element.shapeNode)
                element = element.parentNode;

            if (!element || (overlayTarget != null && overlayTarget.get(0) == element)) {
                return;
            }

            element = $(element);
            shapeTracingOverlay.offset(element.offset());
            shapeTracingOverlay.width(element.outerWidth()); // include border and padding 
            shapeTracingOverlay.height(element.outerHeight()); // include border and padding 

            overlayTarget = element;
        }
    );

    var currentShape;

    // selects a specific shape in the tree, highlight its elements, and display the current tab
    var selectShape = function (shapeId) {
        // remove current tab content
        shapeTracingMetaContent.children().remove();

        // remove selection ?
        if (!shapeId) {
            currentShape = null;
            shapeTracingOverlay.hide();
            $('.shape-tracing-selected').removeClass('shape-tracing-selected');
            shapeTracingWindowTree.find('.shape-tracing-selected').removeClass('shape-tracing-selected');
            return;
        }

        currentShape = shapeId;

        $('.shape-tracing-selected').removeClass('shape-tracing-selected');
        $('li[tree-shape-id="' + shapeId + '"] > div').add('[shape-id="' + shapeId + '"]').addClass('shape-tracing-selected');
        shapeTracingOverlay.hide();

        defaultTab();
    }

    // select shapes when clicked
    shapeTracingOverlay.click(function () {
        var shapeNode = overlayTarget.get(0).shapeNode;
        selectShape(shapeNode.id);

        var lastExpanded = null;
        // open the tree until the selected element
        $('li[tree-shape-id="' + shapeNode.id + '"]').parents('li').andSelf().find('> .expando-glyph-container').each(function () {
            openExpando($(this));
        }).each(function () {
            shapeTracingWindowTree.scrollTo(this, 0, { margin: true });
        });

        return false;
    });

    //create an overlay on shape tree nodes
    shapeTracingWindowTree.find('[tree-shape-id] > div')
    .hover(
        function () {
            var _this = $(this);
            $('.shape-tracing-overlay').removeClass('shape-tracing-overlay');
            _this.addClass('shape-tracing-overlay');
        },
        function () {
            $('.shape-tracing-overlay').removeClass('shape-tracing-overlay');
        })
    .click(function (event) {
        var shapeId = $(this).parent().get(0).shapeNode.id;
        selectShape(shapeId);

        var element = $('[shape-id="' + shapeId + '"]').get(0);
        // there might be no DOM element if the shape was empty, or is not displayed
        if (element) {
            $(window).scrollTo(element, 500, { margin: true });
        }

        event.stopPropagation();
    });

    // move all shape tracing meta blocks to the content window
    $("[shape-id-meta]").detach().prependTo(shapeTracingWindowContent);

    // add the expand/collapse logic to the shape model
    shapeTracingWindowContent.find('li:has(ul:has(li))').prepend($(glyph));

    // collapse all sub uls
    shapeTracingWindowContent.find('ul ul').toggle(false);

    // Shape tab
    var displayTabShape = function () {
        // toggle the selected class
        shapeTracingTabs.children('.selected').removeClass('selected');
        shapeTracingTabsShape.addClass('selected');

        // remove old content
        shapeTracingMetaContent.children().remove();

        // render the template
        if (currentShape && shapeTracingMetadataHost[currentShape]) {
            $("#shape-tracing-tabs-shape-template").tmpl(shapeTracingMetadataHost[currentShape]).appendTo(shapeTracingMetaContent);
        }

        shapeTracingBreadcrumb.text('');

        // create collapsible containers
        shapeTracingMetaContent.find('li:has(ul:has(li))').prepend($(glyph));
        shapeTracingMetaContent.find('ul ul').toggle(false);
        shapeTracingMetaContent.find('.expando-glyph-container').click(expandCollapseExpando);

        $('#activeTemplate').click(function () {
            displayTabTemplate();
        });

        defaultTab = displayTabShape;
    };

    var defaultTab = displayTabShape;

    shapeTracingTabsShape.click(function () {
        displayTabShape();
    });

    // Model tab
    var displayTabModel = function () {
        // toggle the selected class
        shapeTracingTabs.children('.selected').removeClass('selected');
        shapeTracingTabsModel.addClass('selected');

        // remove old content
        shapeTracingMetaContent.children().remove();

        // render the template
        if (currentShape && shapeTracingMetadataHost[currentShape]) {
            $("#shape-tracing-tabs-model-template").tmpl(shapeTracingMetadataHost[currentShape].shape.model).appendTo(shapeTracingMetaContent);
        }

        shapeTracingBreadcrumb.text('');

        // create collapsible containers
        shapeTracingMetaContent.find('li:has(ul:has(li))').prepend($(glyph));
        shapeTracingMetaContent.find('ul ul').toggle(false);
        shapeTracingMetaContent.find('.expando-glyph-container').click(expandCollapseExpando);

        shapeTracingMetaContent.find('.model div.name')
        .hover(
            function () {
                var _this = $(this);
                $('.shape-tracing-overlay').removeClass('shape-tracing-overlay');
                _this.addClass('shape-tracing-overlay');
            },
            function () {
                $('.shape-tracing-overlay').removeClass('shape-tracing-overlay');
            })
        .click(function (event) {
            // model node is selected
            var _this = $(this);
            shapeTracingWindowContent.find('.shape-tracing-selected').removeClass('shape-tracing-selected');
            _this.addClass('shape-tracing-selected');

            // display breadcrumb
            var breadcrumb = null;
            _this.parentsUntil('.model').children('.name').each(function () {
                if (breadcrumb != null) {
                    breadcrumb = $(this).text() + '.' + breadcrumb;
                }
                else {
                    breadcrumb = $(this).text();
                }
            });

            // fix enumerable properties display
            breadcrumb = breadcrumb.replace('.[', '[');

            shapeTracingBreadcrumb.text('@' + breadcrumb);
            event.stopPropagation();
        });

        // open the root node (Model)
        shapeTracingMetaContent.find('.expando-glyph-container:first').click();

        defaultTab = displayTabModel;
    };

    shapeTracingTabsModel.click(function () {
        displayTabModel();
    });

    // Placement tab
    var displayTabPlacement = function () {
        // toggle the selected class
        shapeTracingTabs.children('.selected').removeClass('selected');
        shapeTracingTabsPlacement.addClass('selected');

        // remove old content
        shapeTracingMetaContent.children().remove();

        // render the template
        if (currentShape && shapeTracingMetadataHost[currentShape]) {
            var placementSource = shapeTracingMetadataHost[currentShape].shape.placement;
            shapeTracingBreadcrumb.text(placementSource);
            $("#shape-tracing-tabs-placement-template").tmpl(shapeTracingMetadataHost.placement[placementSource]).appendTo(shapeTracingMetaContent);
        }
        else {
            shapeTracingBreadcrumb.text('');
        }

        enableCodeMirror(shapeTracingMetaContent);
        defaultTab = displayTabPlacement;
    };

    shapeTracingTabsPlacement.click(function () {
        displayTabPlacement();
    });

    // Template tab
    var displayTabTemplate = function () {
        // toggle the selected class
        shapeTracingTabs.children('.selected').removeClass('selected');
        shapeTracingTabsTemplate.addClass('selected');

        // remove old content
        shapeTracingMetaContent.children().remove();

        // render the template
        if (currentShape && shapeTracingMetadataHost[currentShape]) {
            shapeTracingBreadcrumb.text(shapeTracingMetadataHost[currentShape].shape.template);
            $("#shape-tracing-tabs-template-template").tmpl(shapeTracingMetadataHost[currentShape].shape.templateContent).appendTo(shapeTracingMetaContent);
        }
        else {
            shapeTracingBreadcrumb.text('');
        }

        enableCodeMirror(shapeTracingMetaContent);
        defaultTab = displayTabTemplate;
    };

    shapeTracingTabsTemplate.click(function () {
        displayTabTemplate();
    });

    // HTML tab
    var displayTabHtml = function () {
        // toggle the selected class
        shapeTracingTabs.children('.selected').removeClass('selected');
        shapeTracingTabsHtml.addClass('selected');

        // remove old content
        shapeTracingMetaContent.children().remove();

        // render the template
        if (currentShape && shapeTracingMetadataHost[currentShape]) {
            $("#shape-tracing-tabs-html-template").tmpl(shapeTracingMetadataHost[currentShape].shape.html).appendTo(shapeTracingMetaContent);
        }

        shapeTracingBreadcrumb.text('');

        enableCodeMirror(shapeTracingMetaContent);
        defaultTab = displayTabHtml;
    };

    shapeTracingTabsHtml.click(function () {
        displayTabHtml();
    });

    // activates codemirror on specific textareas
    var enableCodeMirror = function (target) {
        // if there is a script, and colorization is not enabled yet, turn it on
        // code mirror seems to work only if the textarea is visible
        target.find('textarea:visible').each(function () {
            if ($(this).next('.CodeMirror').length == 0) {
                CodeMirror.fromTextArea(this, { mode: "razor", tabMode: "indent", readOnly: true, lineNumbers: true });
            }
        });
    }

    // hooks the click event on expandos
    var expandCollapseExpando = function () {
        var _this = $(this);
        if (_this.hasClass("closed")) {
            openExpando(_this);
        }
        else {
            closeExpando(_this);
        }

        return false;
    };

    // automatically expand or collapse shapes in the tree
    shapeTracingWindowTree.find('.expando-glyph-container').click(expandCollapseExpando);

    // recursively create a node for the shapes tree
    function createTreeNode(shapeNode) {
        var node = $('<li></li>');
        node.attr('tree-shape-id', shapeNode.id);
        node.get(0).shapeNode = shapeNode;

        var text = shapeNode.type;
        // add the hint to the tree node if available
        if (shapeNode.hint != '') {
            text += ' [' + shapeNode.hint + ']';
        }

        node.append('<div>' + text + '</div>');
        var list = $('<ul></ul>');
        node.append(list);
        if (shapeNode.children) {
            for (var shapeId in shapeNode.children) {
                var child = createTreeNode(shapeNode.children[shapeId]);
                list.append(child);
            }
        }
        return node;
    }

});

