; (function (trv, $, window, document, undefined) {
    'use strict';

    var sr = trv.sr;
    if (!sr) {
        throw 'Missing telerikReporting.sr';
    }
    
    var utils = trv.utils;
    if (!utils) {
        throw 'Missing telerikReporting.utils';
    }
    
    trv.ViewMode = {
        Interactive: 'Interactive',
        PrintPreview: 'PrintPreview'
    };

    var defaultSettings = {
        pagePollIntervalMs: 500,
        documentInfoPollIntervalMs: 2000
    };

    function ReportViewerController(settings) {
        var controller = {},
            $controller = $(controller),
            clientId,
            reportInstanceId,
            reportDocumentId,
            registerClientDfd,
            reportInstanceDfd,
            getDocumentFormatsDfd,
            report,
            parameterValues,
            currentPageNumber,
            pageCount,
            bookmarks,
            viewMode = trv.ViewMode.Interactive,
            documentFormats
        ;

        settings = jQuery.extend({}, defaultSettings, settings);

        var client = settings.serviceClient;
        if (!client) {
            throw 'No serviceClient has been specified for this controller.';
        }
        
        function getFormat() {
            if (viewMode === trv.ViewMode.PrintPreview) {
                return 'HTML5';
            }
            return 'HTML5Interactive';
        }

        function isInitialized() {
            return Boolean(clientId);
        }

        function initializeClientAsync() {
            if (clientId) {
                return clientId;
            }

            if (registerClientDfd) {
                return registerClientDfd;
            }

            registerClientDfd = client
                   .registerClient()
                   .done(function (id) {
                       clientId = id;
                       registerClientDfd = null;
                   })
                   .fail(function (xhr, status, error) {
                       registerClientDfd = null;
                       logError('Error registering the viewer with the service');
                   });

            return registerClientDfd;
        }
        
        function registerInstanceAsync() {
            if (reportInstanceId) {
                return reportInstanceId;
            }

            if (reportInstanceDfd) {
                return reportInstanceDfd;
            }

            reportInstanceDfd = createReportInstanceAsync(report, parameterValues)
                .done(function(id) {
                    setReportInstance(id);
                    reportInstanceDfd = null;
                })
                .fail(function() {
                    reportInstanceDfd = null;
                });

            return reportInstanceDfd;
        }
        
        function clearReportState(ignoreCache) {
            if (ignoreCache) {
                deleteReportInstance();                
            }
            bookmarks = reportDocumentId = reportInstanceId = null;
            currentPageNumber = pageCount = 0;
        }
        
        function setReportInstance(id) {
            reportInstanceId = id;
        }
        
        function deleteReportInstance() {
            if (clientId && reportInstanceId) {
                client
                    .deleteReportInstance(clientId, reportInstanceId)
                    .fail(function (xhr, status, error) {
                        logError('Error deleting report instance: {0}', reportInstanceId);
                    });
            }
        }
        
        function logError() {
            var msg = '', len = arguments.length;
            if (len == 1) {
                msg = arguments[0];
            }
            else if (len > 1) {
                msg = utils.stringFormat(arguments[0], Array.prototype.slice.call(arguments, 1));
            }

            controller.error(msg);
            //console.log(msg);
        }
        
        function createReportInstanceAsync(report, parameterValues) {
            throwIfNotInitialized();
            
            return client.createReportInstance(clientId, report, parameterValues);
        }

        function registerDocumentAsync(format, deviceInfo) {
            throwIfNotInitialized();
            throwIfNoReportInstance();

            return client.createReportDocument(clientId, reportInstanceId, format, deviceInfo);
        }
        
        function getPageRecursive(deferred, pageNo) {
            // NOTE: do not call this method; instead use getReportPage(pageNo)
            
            client
                .getPage(clientId, reportInstanceId, reportDocumentId, pageNo)
                .done(function (info, status, xhr) {
                    if (info && info.pageReady) {
                        deferred.resolve.apply(deferred, arguments);
                    } else {
                        deferred.notify.apply(deferred, arguments);
                        window.setTimeout(function () { getPageRecursive(deferred, pageNo); }, settings.pagePollIntervalMs);
                    }
                })
                .fail(function () {
                    deferred.reject.apply(deferred, arguments);
                });
        }
        
        function getPageAsync(pageNo) {
            // NOTE: do not call this method; instead use getReportPage(pageNo)
            
            var d = $.Deferred();

            getPageRecursive(d, pageNo);

            return d.promise();
        }
        
        function onBeginLoadPage(pageNo) {
            controller.beginLoadPage(pageNo);
        }
        
        function onPageLoadComplete(pageNo, pageInfo) {
            controller.pageReady(pageInfo);
        }
        
        function getReportPage(pageNo) {
            throwIfNotInitialized();
            throwIfNoReportInstance();
            throwIfNoReportDocument();

            onBeginLoadPage(pageNo);

            getPageAsync(pageNo)
                .done(function(info) {
                    onPageLoadComplete(pageNo, info);
                })
                .fail(function() {
                    logError('Error loading page #{0}', pageNo);
                })
                .progress(function() {
                    
                });
        }
        
        function onBeforeLoadReport() {
            controller.beforeLoadReport();
        }
        
        function onBeginLoadReport() {
            controller.beginLoadReport();
        }
        
        function onReportLoadComplete(info) {
            pageCount = info.pageCount;
            bookmarks = info.bookmarkNodes;

            controller.reportLoadComplete(info);
        }
        
        function onReportLoadProgress(info) {
            pageCount = info.pageCount;
            
            controller.reportLoadProgress(info);
        }

        function getReportDocumentReady() {
            throwIfNotInitialized();
            throwIfNoReportInstance();
            throwIfNoReportDocument();
            
            getDocumentReadyAsync(reportDocumentId)
                .done(function (info) {
                    onReportLoadComplete(info);
                })
                .fail(function() {
                    logError('Error retrieving report info');
                })
                .progress(function (info) {
                    onReportLoadProgress(info);
                });
        }
        
        function getDocumentInfoRecursive(deferred, documentId) {
            client.getDocumentInfo(clientId, reportInstanceId, documentId)
                .done(function (info, status, xhr) {
                    if (info && info.documentReady) {
                        deferred.resolve.apply(deferred, arguments);
                    }
                    else {
                        deferred.notify.apply(deferred, arguments);
                        window.setTimeout(function () { getDocumentInfoRecursive(deferred, documentId); }, settings.documentInfoPollIntervalMs);
                    }
                })
                .fail(function () {
                    deferred.reject.apply(deferred, arguments);
                });
        }
        
        function getDocumentReadyAsync(documentId) {
            var dfd = $.Deferred();

            getDocumentInfoRecursive(dfd, documentId);

            return dfd.promise();
        }
        
        function loadReportAsync(ignoreCache) {
            throwIfNoReport();

            clearReportState(ignoreCache);

            onBeforeLoadReport();
            
            var format = getFormat(),
                deviceInfo = { ActionsAsAttributes: true }
            ;

            $.when(initializeClientAsync())
                .then(registerInstanceAsync)
                .then(
                    function () {
                        return registerDocumentAsync(format, deviceInfo);
                    },
                    function (xhr, status, error) {
                        logError('Error creating report instance: Report={0}', report);
                    }
                )
                .then(
                    function (id) {
                        onReportDocumentRegistered(id);
                    },
                    function (xhr, status, error) {
                        logError('Error creating report document: Report={0}; Format={1}', report, format);
                    }
                );
        }
        
        function onReportDocumentRegistered(id) {
            reportDocumentId = id;
            onBeginLoadReport();
            getReportDocumentReady();
        }
        
        function onExportStarted() {
            controller.exportStarted();
        }
        
        function onExportDocumentReady(args) {
            controller.exportReady(args);
        }

        function onPrintStarted() {
            controller.printStarted();
        }

        function onPrintDocumentReady(args) {
            controller.printReady(args);
        }

        function printReport() {
            throwIfNoReport();

            onPrintStarted();

            exportAsync("PDF", { ImmediatePrint: true })
                .then(
                    function (info) {
                        var url = client.formatDocumentUrl(info.clientId, info.instanceId, info.documentId);
                        onPrintDocumentReady({ url: url });
                        return url;

                    },
                    function () {
                        logError();
                    })
                .then(function (url) {
                    window.open(url, "_blank");
                });
        }
        
        function exportReport(format, deviceInfo) {
            throwIfNoReport();

            onExportStarted();

            exportAsync(format, deviceInfo)
                .then(
                    function (info) {
                        var url = client.formatDocumentUrl(info.clientId, info.instanceId, info.documentId);
                        onExportDocumentReady({ url: url });
                        return url;

                    },
                    function () {
                        logError();
                    })
                .then(function (url) {
                    window.open(url, "_self");
                });
        }

        function exportAsync(format, deviceInfo) {
            throwIfNoReport();

            var dfd = $.Deferred();

            var documentID;
            
            $.when(initializeClientAsync())
                .then(registerInstanceAsync)
                .then(
                    function () {
                        return registerDocumentAsync(format, deviceInfo);
                    },
                    function (xhr, status, error) {
                        logError('Error creating report instance: Report={0}', report);
                        dfd.reject();
                    }
                )
                .then(
                    function (docId) {
                        documentID = docId;
                        return getDocumentReadyAsync(docId);
                    },
                    function() {
                        logError('Error creating report document: Report={0}; Format={1}', report, format);
                        dfd.reject();
                    }
                )
                .then(
                    function (info) {
                        dfd.resolve({
                            clientId: clientId,
                            instanceId: reportInstanceId,
                            documentId: documentID
                        });
                    },
                    function (info) {
                        logError('Error obtaining document ready state: Report={0}; Format={1}', report, format);
                        dfd.reject();
                    }
                );

            return dfd.promise();
        }

        function execServerAction(actionId) {
            throwIfNoReport();
            throwIfNoReportInstance();
            throwIfNoReportDocument();

            client.execServerAction(clientId, reportInstanceId, reportDocumentId, actionId);
            controller.refreshReport();
        }
        
        function throwIfNotInitialized() {
            if (!isInitialized()) {
                throw sr.controllerNotInitialized;
            }
        }
        
        function throwIfNoReportInstance() {
            if (!reportInstanceId) {
                throw sr.noReportInstance;
            }
        }
        
        function throwIfNoReportDocument() {
            if (!reportDocumentId) {
                throw 'No report document.';
            }
        }
        
        function throwIfNoReport() {
            if (!report) {
                throw 'No report.';
            }
        }
        
        function getEventHandlerFromArguments(args) {
            var arg0;
            if (args && args.length) {
                arg0 = args[0];
            }
            if (typeof arg0 == 'function') {
                return arg0;
            }
            return null;
        }
        
        function eventFactory(event, args) {
            var h = getEventHandlerFromArguments(args);
            if (h) {
                $controller.bind(event, h);
            } else {
                $controller.trigger(event, args);
            }
            return controller;
        }
        
        function loadParametersAsync(paramValues) {

            var d = new $.Deferred();

            $.when(initializeClientAsync())
                .then(
                    function() {
                        return client.getParameters(clientId, report, paramValues || parameterValues || {});
                    },
                    function() {
                        d.reject.apply(d, arguments);
                    }
                )
                .then(
                    function (args) {
                        d.resolve(args);
                    },
                    function () {
                        d.reject.apply(d, arguments);
                    }
                );

            return d.promise();
        }

        function getDocumentFormatsAsync() {
            if (documentFormats)
                return documentFormats;

            if (getDocumentFormatsDfd)
                return getDocumentFormatsDfd;

            getDocumentFormatsDfd = client.getDocumentFormats()
                .done(function (formats) {
                    documentFormats = formats;
                    getDocumentFormatsDfd = null;
                })
                .fail(function () {
                    getDocumentFormatsDfd = null;
                });

            return getDocumentFormatsDfd;
        }
        
        function getPageForBookmark (nodes, id) {
            if (nodes) {
                for (var i = 0, len = nodes.length; i < len; i++) {
                    var node = nodes[i];
                    if (node.id == id) {
                        return node.page;
                    } else {
                        var page = getPageForBookmark(node.items, id);
                        if (page) {
                            return page;
                        }
                    }
                }
            }
            return null;
        };
        
        var actionHandlers = {
            'toggleVisibility': function (args) {
                execServerAction(args.actionId);
            },

            'navigateToReport': function (args) {
                controller
                    .reportSource({
                        report: args.Report,
                        parameters: args.ParameterValues
                    })
                    .refreshReport();
            },

            'navigateToUrl': function (args) {
                window.open(args.Url, args.Target);
            },

            'navigateToBookmark': function (args) {
                var id = args,
                    page = getPageForBookmark(id);

                controller.navigateToPage(page, id);
            },
        };

        function executeReportAction(actionType, args) {
            var handler = actionHandlers[actionType];
            if (typeof handler === 'function') {
                window.setTimeout(function () { handler(args); }, 0);
            }
        }
        
        // --- A P I ---
        $.extend(
            controller,
            {
                reportSource: function(rs) {
                    if (!rs) {
                        return {
                            report: report,
                            parameters: $.extend({}, parameterValues)
                        };
                    } else {
                        report = rs.report;
                        parameterValues = rs.parameters;

                        this.reportSourceChanged();
                        
                        return this;
                    }
                },

                pageCount: function () {
                    return pageCount;
                },

                currentPageNumber: function (pageNo) {
                    if (pageNo === undefined)
                        return currentPageNumber;

                    var num = utils.tryParseInt(pageNo);
                    if (num != currentPageNumber) {
                        currentPageNumber = num;
                        this.currentPageChanged();                        
                    }

                    return this;
                },
                
                viewMode: function (vm) {
                    if (!vm) {
                        return viewMode;
                    }

                    if (viewMode != vm) {
                        viewMode = vm;

                        controller.viewModeChanged(vm);

                        if (report) {
                            controller.refreshReport();
                        }
                    }

                    return controller;
                },
                
                refreshReport: function (ignoreCache) {
                    return loadReportAsync(ignoreCache);
                },
                
                exportReport : function(format, deviceInfo) {
                    exportReport(format, deviceInfo);
                },

                printReport : function() {
                    printReport();
                },
                
                getReportPage: function(pageNumber) {
                    getReportPage(pageNumber);
                },
                
                executeReportAction: function(actionType, args) {
                    executeReportAction(actionType, args);
                },
                
                loadParameters: function (paramValues) {
                    return loadParametersAsync(paramValues);
                },
                
                getDocumentFormats: function () {
                    return getDocumentFormatsAsync();                    
                },
                
                // --- E V E N T S ---
                error: function() {
                    return eventFactory('rv.ERROR', arguments);
                },

                exportStarted: function() {
                    return eventFactory('rv.EXPORT_STARTED', arguments);
                },
                
                exportReady: function() {
                    return eventFactory('rv.EXPORT_DOCUMENT_READY', arguments);
                },

                printStarted: function() {
                    return eventFactory('rv.PRINT_STARTED', arguments);
                },
                
                printReady: function () {
                    return eventFactory('rv.PRINT_DOCUMENT_READY', arguments);
                },
                
                beforeLoadReport: function() {
                    return eventFactory('rv.BEFORE_LOAD_REPORT', arguments);
                },
                
                beginLoadReport: function() {
                    return eventFactory('rv.BEGIN_LOAD_REPORT', arguments);
                },
                
                reportLoadComplete: function() {
                    return eventFactory('rv.REPORT_LOAD_COMPLETE', arguments);
                },
                
                reportLoadProgress: function() {
                    return eventFactory('rv.REPORT_LOAD_PROGRESS', arguments);
                },
                
                beginLoadPage: function() {
                    return eventFactory('rv.BEGIN_LOAD_PAGE', arguments);
                },
                
                pageReady: function() {
                    return eventFactory('rv.PAGE_READY', arguments);
                },
                               
                viewModeChanged: function () {
                    return eventFactory('rv.VIEW_MODE_CHANGED', arguments);
                },                              
                
                reportSourceChanged: function () {
                    return eventFactory('rv.REPORT_SOURCE_CHANGED', arguments);
                },

                navigateToPage: function () {
                    return eventFactory('rv.NAVIGATE_TO_PAGE', arguments);
                },

                currentPageChanged: function () {
                    //TODO: currentPageChanged, navigateToPage???
                    return eventFactory('rv.CURRENT_PAGE_CHANGED', arguments);
                },

                getDocumentMapVisible: function () {
                    return eventFactory('rv.GET_DOCUMENT_MAP_VISIBLE', arguments);
                },

                setDocumentMapVisible: function () {
                    return eventFactory('rv.SET_DOCUMENT_MAP_VISIBLE', arguments);
                },

                getParameterAreaVisible: function () {
                    return eventFactory('rv.GET_PARAMETER_AREA_VISIBLE', arguments);
                },

                setParameterAreaVisible: function () {
                    return eventFactory('rv.SET_PARAMETER_AREA_VISIBLE', arguments);
                },

                scale: function() {
                    return eventFactory('rv.PAGE_SCALE', arguments);
                },
                
                getScale: function() {
                    return eventFactory('rv.GET_PAGE_SCALE', arguments);
                }
            }
        );

        //initializeClientAsync();

        return controller;
    }

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