; (function (trv, $, window, document, undefined) {
    "use strict";
    
    var utils = trv.utils;
    if (!utils) {
        throw "Missing telerikReportViewer.utils";
    }

    var defaultOptions = {
        controller: null,
        loadingPageHtml: "<div style='width: 300px; padding: 5px; '><span class='k-loading' style='padding-left: 30px;'></span>Loading Page #{pagenumber}...</div>",
    };

    var constants = {
        pageLoadingClassName: "loading",
    };

    var scaleModes = {
        FIT_PAGE_WIDTH: "FIT_PAGE_WIDTH",
        FIT_PAGE: "FIT_PAGE",
        SPECIFIC: "SPECIFIC"
    };

    var ReportViewerArea = function (placeholder, options) {
        options = $.extend({}, defaultOptions, options);

        var controller = options.controller;

        if (!controller)
            throw "No controller (telerikReportViewer.reportViewerController) has been specified.";

        var $viewerArea = $("<div class='trv-viewer-area'></div>"),
            $errorPane = $("<div class='trv-error-pane'></div>"),
            $errorMessage = $("<div class='trv-error-message k-tooltip-validation k-widget'></div>"),
            $pageContainer = $("<div class='trv-page-container'></div>"),
            pageContainer = $pageContainer[0]
        ;

        $errorMessage.appendTo($errorPane);

        $viewerArea
            .append($pageContainer)
            .append($errorPane)
            .appendTo(placeholder);

        var pageCount = -1,
            reportReady = false,
            lastScrollTop = 0,
            currentPageIndex = -1,
            pendingBookmark,
            inScroll = false,
            pageScaleMode = scaleModes.SPECIFIC,
            pageScale = 1.0,
            minPageScale = 0.1 /* 10% */,
            maxPageScale = 8.0 /* 800% */
        ;

        $(window).on("resize", function (event, args) {
            if (shouldAutosizePage()) {
                updatePageDimensions();
            }
        });
        
        function touchDelta(touch1, touch2) {
            var x1 = touch1.x.location,
                y1 = touch1.y.location,
                x2 = touch2.x.location,
                y2 = touch2.y.location,
                dx = x1 - x2,
                dy = y1 - y2;

            return {
                center: {
                    x: (x1 + x2) / 2,
                    y: (y1 + y2) / 2
                },

                distance: Math.sqrt(dx * dx + dy * dy)
            };
        }
        
        if (typeof $.fn.kendoTouch === 'function') {
            var startDistance;
            $pageContainer.kendoTouch({
                multiTouch: true,
                enableSwipe: true,
                swipe: function (e) {
                    var current = controller.currentPageNumber();
                    if (e.direction == 'left') {
                        controller.navigateToPage(current + 1);
                    } else {
                        controller.navigateToPage(current - 1);
                    }
                },
                
                touchstart : function(e) {
                },
                
                gesturestart: function(e) {
                    startDistance = touchDelta(e.touches[0], e.touches[1]).distance;
                },
                
                gestureend: function (e) {
                },
                
                gesturechange: function(e) {
                    var current = touchDelta(e.touches[0], e.touches[1]).distance;
                    if (pageScaleMode == trv.ScaleModes.SPECIFIC) {
                        var f = current / startDistance;
                        setPageScale({ scale: pageScale * f });
                    }
                    startDistance = current;
                }

            });
        }

        controller
            .beforeLoadReport(function () {
                clear();
                showError("Rendering started...");
            })

            .beginLoadReport(function (event, args) {
                showError("Loading page #1");

                controller.navigateToPage(1);
            })
        
            .reportLoadProgress(function(event, args) {
                showError(utils.stringFormat("Loading report pages in progress ({0} pages loaded so far).", [args.pageCount]));
            })
        
            .reportLoadComplete(function (event, args) {
                enableInteractivity();

                showError(utils.stringFormat("Report loaded. ({0} pages in total)", [args.pageCount]));
                setTimeout(function () { showError(); }, 1000);
            })

            .navigateToPage(function (event, pageNumber, bookmark) {
                navigateToPage(pageNumber, bookmark); //navigateToPage(args.pageNumber, args.bookmark);
            })

            .pageReady(function (event, page) {
                setPageContent(page.pageNumber, page.pageContent);
                // tryLoadNextPage();
            })

            .error(function (event, error) {
                showError(error);
            })

            .exportStarted(function (event, args) {
                showError("Preparing document to download. Please wait...");
            })

            .exportReady(function (event, args) {
                showError();
            })

            .printStarted(function (event, args) {
                showError("Preparing document to print. Please wait...");
            })

            .printReady(function (event, args) {
                showError();
            })

            .viewModeChanged(function (event, viewMode) {
                if (viewMode == trv.ViewMode.Interactive) {
                    $viewerArea.removeClass("printpreview");
                    $viewerArea.addClass("interactive");
                } else {
                    $viewerArea.removeClass("interactive");
                    $viewerArea.addClass("printpreview");
                }
            })
        
            .scale(function(event, args) {
                setPageScale(args);
            })
        
            .getScale(function (event, args) {
                args.scale = pageScale;
                args.scaleMode = pageScaleMode;
            })
        ;
        
        function shouldAutosizePage() {
            return -1 != [scaleModes.FIT_PAGE, scaleModes.FIT_PAGE_WIDTH].indexOf(pageScaleMode);
        }

        function updatePageDimensions() {
            for (var i = 0, children = pageContainer.childNodes, len = children.length; i < len; i++) {
                setPageDimensions(children[i], pageScaleMode, pageScale);
            }
        }
        
        function setPageScale(options) {
            pageScaleMode = options.scaleMode || pageScaleMode;

            var scale = pageScale;
            if ('scale' in options) {
                scale = options.scale;
            }

            pageScale = Math.max(minPageScale, Math.min(maxPageScale, scale));
            
            updatePageDimensions();
        }

        function isLoading(page) {
            return $(page).hasClass(constants.pageLoadingClassName);
        }

        function viewportMetrics(element) {
            element = (element || pageContainer);
            return metrics(element.scrollTop, element.clientHeight);
        }

        function elementMetrics(element) {
            if ("$" in element) {
                element = element[0];
            }
            return metrics(element.offsetTop, element.offsetHeight);
        }

        function intersects(m1, m2) {
            return (m1.top < m2.bottom && m1.bottom > m2.top);
        }

        function isAbove(base, element) {
            return (element.bottom <= base.top);
        }

        function isBelow(base, element) {
            return (base.bottom <= element.top);
        }

        function metrics(top, height) {
            return {
                top: top,
                height: height,
                bottom: (top + height)
            };
        }

        function clear() {
            disbleInteractivity();

            currentPageIndex = -1;
            pageCount = -1;
            reportReady = false;
            lastScrollTop = 0;
            pendingBookmark = undefined;

            $pageContainer.empty();
            
            showError();
        }

        function setCurrentPage(pageIndex) {
            if (pageIndex !== undefined /* TODO: Research whether the following check is necessary: && pageIndex != currentPageIndex */) {
                var pages = pageContainer.childNodes; //$pageContainer.children();
                if (currentPageIndex >= 0) {
                    $(pages[currentPageIndex]).removeClass("active");
                }

                currentPageIndex = pageIndex;

                var currentPage = pages[currentPageIndex];

                $(currentPage).addClass("active");

                showPageContent(currentPage);

                controller.currentPageNumber(pageNo(pages[currentPageIndex]));
            }
        }

        function findPage(pageNumber) {
            var page;
            $pageContainer.children().each(function (index, page1) {
                if (pageNo(page1) == pageNumber) {
                    page = page1;
                    return false;
                }
            });
            return page;
        }

        function navigateToPage(pageNumber, bookmark) {
            //log("-- navigateToPage(" + pageNumber + ", " + bookmark + ")");
            var page = findPage(pageNumber);
            if (page) {
                if (bookmark) {
                    navigateToBookmark(bookmark);
                } else {
                    scrollIntoView(page);
                }
            }
            else {
                pendingBookmark = bookmark;
                $pageContainer.empty();
                currentPageIndex = -1;

                beginLoadPage(pageNumber);
            }
        }

        function navigateToBookmark(bookmark) {
            log("navigateToBookmark: " + bookmark);
            if (bookmark) {
                $(bookmark)[0].scrollIntoView();
            }
        }

        function scrollIntoView(target) {
            if (target && target.$) {
                target = target[0];
            }

            if (target) {

                var top = 0;
                while (target && target != pageContainer) {
                    log("    scrollIntoView: '" + target.className + ",#" + target.id + "', " + target.offsetTop);
                    top += target.offsetTop;
                    target = target.offsetParent;
                }
                log("    pageContainer: " + pageContainer.offsetTop);

                try {
                    inScroll = true;
                    log("scrollIntoView: top=" + top);
                    //                    $pageContainer.animate({ scrollTop: top}, 1000);
                    $pageContainer.scrollTop(top);
                }
                finally {
                    inScroll = false;
                }
            }
        }

        function showError(error) {
            $errorMessage.html(error);
            (error ? $.fn.show : $.fn.hide).call($errorPane);
            $pageContainer.css("top", (error ? $errorPane[0].offsetHeight : 0) + "px");
            
            if (shouldAutosizePage()) {
                updatePageDimensions();
            }
        }

        function pageNo(page, no) {
            var $page = page.$ ? page : $(page), dataKey = "pageNumber";

            if (no === undefined) {
                return $page.data(dataKey);
            }

            $page.data(dataKey, no);
            return page;
        }

        function beginLoadPage(pageNumber) {
            window.setTimeout(controller.getReportPage.bind(controller, pageNumber), 100);

            return createPagePlaceholder(pageNumber)
                        .html(options.loadingPageHtml.replace("{pagenumber}", pageNumber))
                        .addClass(constants.pageLoadingClassName);
        }

        function createPagePlaceholder(pageNumber) {
            return $("<div />")
                    .addClass("item-placeholder")
                    .data("pageNumber", pageNumber);
        }

        function setPageDimensions(page, scaleMode, scale) {
            var $target = $(page),
                $page = $target.find("div.report-page"),
                $pageContent = $target.find("div.sheet"),
                pageContent = $pageContent[0]
            ;

            if (!pageContent)
                return;

            var pageWidth, pageHeight;

            if ($target.data("pageWidth") === undefined) {
                pageWidth = pageContent.offsetWidth;
                pageHeight = pageContent.offsetHeight;

                $target.data("pageWidth", pageWidth);
                $target.data("pageHeight", pageHeight);
            } else {
                pageWidth = $target.data("pageWidth");
                pageHeight = $target.data("pageHeight");
            }

            var scaleW = $target[0].clientWidth / pageWidth;

            if (scaleMode === undefined || scaleMode == scaleModes.FIT_PAGE_WIDTH) {
                scale = scaleW;
            }
            else if (scaleMode == scaleModes.FIT_PAGE) {
                // make the page fit nicely in the viewport
                var marginTop = 10,
                    marginBottom = 10,
                    scaleH = (pageContainer.clientHeight - marginTop - marginBottom) / pageHeight;

                scale = Math.min(scaleW, scaleH);
            }

            if (!scale) {
                scale = scaleW;
            }

            var scaleValue = "scale(x, x)".replace(/x/gi, "" + scale),
                origin = "0 0";

            $pageContent
                .css("transform", scaleValue)
                .css("-moz-transform", scaleValue)
                .css("-ms-transform", scaleValue)
                .css("-webkit-transform", scaleValue)
                .css("-o-transform", scaleValue)

                .css("-moz-transform-origin", origin)
                .css("-webkit-transform-origin", origin)
                .css("-o-transform-origin", origin)
                .css("-ms-transform-origin", origin)
                .css("transform-origin", origin)
            ;

            var isPageScaled = $target[0].clientWidth < $target[0].scrollWidth;

            $page
                .css("height", scale * pageHeight)
                .css("width", scale * pageWidth);

            if (controller.viewMode == trv.ViewMode.PrintPreview) {
                $page
                    .css("margin-right", isPageScaled ? "20px" : "auto")
                    .css("margin-left", isPageScaled ? "0px" : "auto");
            }
            else if (isPageScaled) {
                $page
                    .css("margin-right", "20px")
                    .css("margin-left", "0px");
            }
        }

        function slidePageIn(page, completeCallback) {
            /*
            $(page)
                .animate({ marginTop: "+=100px" }, 0)
                .animate({ marginTop: "-=100px" }, 500, "linear", completeCallback);
            */

            completeCallback();
        }

        function showPageContent(page) {
            var $pageContent = getPageContent(page);
            if ($pageContent.css("display") == "none") {
                $pageContent.css("display", "block");
            }
        }

        function hidePageContentIfVisible(page) {
            var $pageContent = getPageContent(page);
            var visible = $pageContent.css("display") != "none";
            if (visible) {
                getPageContent(page).css("display", "none");
            }
            return visible;
        }

        function getPageContent(page) {
            return $(page).find("div.sheet");
        }

        function enableInteractivity() {
            $pageContainer.on("click", "[data-reporting-action]", onInteractiveItemClick);
        }

        function disbleInteractivity() {
            $pageContainer.off("click", "[data-reporting-action]", onInteractiveItemClick);
        }

        function onInteractiveItemClick() {
            var $t = $(this);

            var actionId = $t.attr("data-reporting-action");
            var a = getAction(actionId);
            if (a) {
                var args = a.Value || {};

                args.actionId = actionId;

                controller.executeReportAction(a.Type, args);
            }
        }

        function getAction(actionId) {
            var actions = getPageActions();
            if (actions) {

                //return actions[actionId];

                var action;
                $.each(actions, function () {
                    if (this.Id == actionId) {
                        action = this;
                    }
                    return (action === undefined);
                });

                return action;  
            }                
            return null;
        }

        function getPageActions() {
            var pageData = getPageData();
            if (pageData) {
                return pageData.Actions;
            }
            return null;
        }

        function getPageData() {
            var $page = $(findPage(controller.currentPageNumber()));
            var data = $page.data("data");
            if (!data) {
                data = JSON.parse($page.find('script[type="text/reporting"]').html());
                $page.data("data", data);
            }
            return data;
        }

        function setPageContent(pageNumber, pageContent) {
            var $first, $last, $target, atBottom, atTop;

            var children = pageContainer.childNodes;

            if (children.length > 0) {
                $first = $(children[0]);
                $last = $(children[children.length - 1]);
            }

            if ($first && pageNo($first) == pageNumber) {
                $target = $first;
                atTop = true;
            }
            else if ($last && pageNo($last) == pageNumber) {
                $target = $last;
                atBottom = true;
            }

            if (!$target) {
                $target = $last = createPagePlaceholder(pageNumber);
                $pageContainer.append($last);
                atBottom = true;
            }

            var viewport = viewportMetrics(),
                targetMetrics = elementMetrics($target),
                isVisible = intersects(viewport, targetMetrics),
                currentPageTopOffset = 0
            ;

            if (currentPageIndex >= 0) {
                currentPageTopOffset = viewport.top - elementMetrics(children[currentPageIndex]).top;
            }

            var wrapper = $("<div>").append($.parseHTML(pageContent)),
                $pageContent = wrapper.find("div.sheet"),
                $pageStyle = wrapper.find("style"),
                $page = $("<div class='report-page'></div>")
            ;

            $pageContent
                .css("margin", 0);

            $page
                .append($pageStyle)
                .append($pageContent)
            ;

            $target
                .removeClass(constants.pageLoadingClassName)
                .empty()
                .css("position", "relative")
                .append($page)
            ;

            // update the targetMetrics with the new page content
            targetMetrics = elementMetrics($target);

            setPageDimensions($target, pageScaleMode, pageScale);

            if (!isVisible) {
                hidePageContentIfVisible($target);
            }

            if (children.length == 1) {
                setCurrentPage(0);
                slidePageIn($target, function () {
                    navigateToBookmark(pendingBookmark);
                });
            }
            else if (atTop) {
                updateScrollTop(elementMetrics(children[currentPageIndex]).top + currentPageTopOffset);
            }
            else if (isVisible) {
                if (atBottom) {
                    slidePageIn($target, function () { 
                        navigateToBookmark(pendingBookmark);
                    });
                }
            }            
        }
    };

    var pluginName = "telerik_ReportViewer_PagesArea";

    var log = function () {
        if (window.console) {
            window.console.log(arguments);
        }
    };

    $.fn[pluginName] = function (options) {
        return this.each(function () {
            if (!$.data(this, pluginName)) {
                $.data(this, pluginName, new ReportViewerArea(this, options));
            }
        });
    };

    trv.ScaleModes = scaleModes;

}(window.telerikReportViewer = window.telerikReportViewer || {}, jQuery, window, document));