/*
*
*   JJ-GPS wNewsLoader
*   Author: Sebastian Stefaniuk (Webitects)
*
*/

(function ($) {
    $.wNewsLoader = function (element, opts) {
        var plugin = this;

        plugin.settings = $.extend(true, {
            area: '',
            url: {
                article: '/news/article/{0}/{1}',
                loadNews: '/news/load-section-news'
            }
        }, opts);

        var $element = $(element),
            element = element,

            $updated,
            $newsWrapper,

            _area = plugin.settings.area,
            _anchor,
            _loadingTimer,
            _news = [],
            _newsLoaded = false,

            HCLASS = {
                newsWrapper: 'news-wrapper',
                updated: 'updated'
            };

        var fn = {
            init: function () {
                // anchor can be saved in element's data-for attribute
                // OR fetch anchor from its closest ".section" parent's ID
                if ($element.data('for') !== undefined)
                    _anchor = $element.data('for');
                else
                    _anchor = $element.closest('.section').attr('id');

                fn.loadNews();
            },

            initNews: function () {
                var html = [];
                html.push('<p><a class="' + HCLASS.updated + '" href="#">Updated</a></p>');
                html.push('<div class="' + HCLASS.newsWrapper + '" style="display: none;"></div>');

                $element.append($(html.join('')));
                $updated = $element.find('.' + HCLASS.updated);
                $updated.data('open', false);
                $newsWrapper = $element.find('.' + HCLASS.newsWrapper);

                $updated.on('click', function (e) {
                    var isOpen = $(this).data('open');
                    if (isOpen) {
                        $newsWrapper.slideUp('fast', function () {
                            $updated.data('open', false);
                        });
                    }
                    else {
                        fn.showNews();
                    }

                    e.preventDefault();
                    return false;
                });
            },

            loadNews: function () {
                $.ajax({
                    data: { area: _area, anchor: _anchor },
                    type: 'Post',
                    url: plugin.settings.url.loadNews,
                    success: function (obj) {
                        var model = obj.Model !== undefined && obj.Model !== null ? obj.Model : [];
                        _news = model;
                        _newsLoaded = true;

                        if (_news.length > 0) {
                            fn.initNews();
                            fn.populateNews(_news);
                        }
                    },
                    error: function () { }
                });
            },

            populateNews: function(news) {
                $newsWrapper.empty();
                var html = ['<dl class="news">'];
                for(var i = 0; i < news.length; i++) {
                    var n = news[i];
                    var date = webitects.utility.jsonDateToDate(n.date).format('mmm, d yyyy');
                    var link = plugin.settings.url.article.format(n.id, n.slug);
                    html.push('<dt><a href="{0}">{1}</a></dt>'.format(link, n.title));
                    html.push('<dd>{0}</dd>'.format(date));
                }
                html.push('</dl>');
                $newsWrapper.append($(html.join('')));
            },

            showNews: function () {
                $newsWrapper.slideDown('fast', function () {
                    $updated.data('open', true);
                });
            }
        }

        /***********************
        * Initialize plugin
        ***********************/
        fn.init();
    };

    $.fn.wNewsLoader = function (opts) {
        return this.each(function () {
            if ($(this).data('wNewsLoader') == undefined) {
                var plugin = new $.wNewsLoader(this, opts);
                $(this).data('wNewsLoader', plugin);
            }
        });
    };

})(jQuery);;
(function ($) {
    'use strict';

    window.webitects = window.webitects || {};
    var w = window.webitects;

    window.jjgps = window.jjgps || {};

    jjgps.publicationLoader = (function () {

        var cls = function (options) {
            var opts = $.extend(true, {
                ajaxUrl: '/data-sources/get-all',
                container: $('<div />'),
                template: '<%= title %><dl class="publications">'
                    + '<% _.forEach(pubs, function(p) { %>'
                    + '<dt><a href="<%= p.link %>"><%= p.title %></a></dt><dd><%= p.author %></dd>'
                    + '<% }); %>'
                    + '</dl>',
                title: '<h3>Data source</h3>'
            }, options);

            var template = _.template(opts.template),
                $container = $(opts.container);

            var fn = {
                init: function () {

                },

                loadPublications: function (parameters) {
                    parameters = $.extend(true, {
                        StateId: undefined,
                        SectionId: undefined
                    }, parameters);

                    var promise = w.ajaxHelper.resolveAjax($.ajax({
                        url: opts.ajaxUrl,
                        data: parameters,
                        type: 'Post'
                    }));

                    promise.then(function (data) {
                        // success
                        fn.populateContainer(data);
                    }, function (err) {
                        // error
                    })
                    .then(function () {
                        // done
                    });
                },

                populateContainer: function (publications) {
                    $container.empty();

                    if (publications === undefined || publications === null || !publications.length)
                        return;

                    var html = template({
                        pubs: publications,
                        title: opts.title
                    });

                    $container.html(html);
                }
            };

            this.fnLoadPublications = function (parameters) {
                fn.loadPublications(parameters);
            };

            fn.init();
        }

        return cls;

    })();

})(jQuery);;
// jquery.ba-hashchange.min.js: http://benalman.com/projects/jquery-hashchange-plugin/
(function (e, t, n) { "$:nomunge"; function c(e) { e = e || location.href; return "#" + e.replace(/^[^#]*#?(.*)$/, "$1") } var r = navigator.userAgent.toLowerCase(); var i = /msie/.test(r); var s = "hashchange", o = document, u, a = e.event.special, f = o.documentMode, l = "on" + s in t && (f === n || f > 7); e.fn[s] = function (e) { return e ? this.bind(s, e) : this.trigger(s) }; e.fn[s].delay = 50; a[s] = e.extend(a[s], { setup: function () { if (l) { return false } e(u.start) }, teardown: function () { if (l) { return false } e(u.stop) } }); u = function () { function d() { var n = c(), r = p(a); if (n !== a) { h(a = n, r); e(t).trigger(s) } else if (r !== a) { location.href = location.href.replace(/#.*/, "") + r } u = setTimeout(d, e.fn[s].delay) } var r = {}, u, a = c(), f = function (e) { return e }, h = f, p = f; r.start = function () { u || d() }; r.stop = function () { u && clearTimeout(u); u = n }; i && !l && function () { var t, n; r.start = function () { if (!t) { n = e.fn[s].src; n = n && n + c(); t = e('<iframe tabindex="-1" title="empty"/>').hide().one("load", function () { n || h(c()); d() }).attr("src", n || "javascript:0").insertAfter("body")[0].contentWindow; o.onpropertychange = function () { try { if (event.propertyName === "title") { t.document.title = o.title } } catch (e) { } } } }; r.stop = f; p = function () { return c(t.location.href) }; h = function (n, r) { var i = t.document, u = e.fn[s].domain; if (n !== r) { i.title = o.title; i.open(); u && i.write('<script>document.domain="' + u + '"</script>'); i.close(); t.location.hash = n } } }(); return r }() })(jQuery, this)

// Hash must be in this format: #section?param1=a&param2=b, where part from ? is optional
var SectionHash = (function ($, w) {

    var $document = $(document),
        $breadcrumb,
        $window = $(window);

    var vars = {
        sections: [],
        hasPrint: false,
        interacted: false,
        maxWait: 2000,
        waiting: 0,
        currentArea: ''
    };

    var fn = {
        /////////////////////////
        // Initializers
        /////////////////////////
        init: function (options) {
            $window.bind('touchstart touchend click mousewheel keypress', fn.onUserInteraction);
            $window.hashchange(fn.onHashChange);
            $document.hashchange(fn.onHashChange);

            vars = $.extend(true, vars, options);
            $breadcrumb = $('#breadcrumb');

            var uri = new URI(window.location.href);
            vars.hasPrint = uri.query_data !== undefined && uri.query_data.indexOf('print') === 0;

            vars.sections.foreach(function (s) {
                if (vars.hasPrint)
                    $('#' + s.section.anchor).addClass('noprint');
            });
            
            var hash = location.hash;
            var hasHash = /^#/.test(hash);
            if (hasHash)
                fn.onHashChange(null);
        },

        initSection: function (section, sectionVar, updateMethod) {
            vars.sections.push({
                section: section,
                variable: sectionVar,
                method: updateMethod
            });
        },

        initSectionRedirect: function(sectionAnchor, redirectToSection, stateSlug) {
        	var redirectToUrl = '/';

        	if (typeof redirectToSection.area === 'function')
        		redirectToUrl = redirectToSection.area().url
        	else
        		redirectToUrl = redirectToSection.area.url;

        	if (stateSlug !== undefined)
        		redirectToUrl += '/' + stateSlug;

        	redirectToUrl += '#' + redirectToSection.anchor;

        	vars.sections.push({
        		section: {
        			anchor: sectionAnchor
        		},
        		redirectTo: redirectToUrl
        	});
        },

        /////////////////////////
        // Event handlers
        /////////////////////////
        onHashChange: function (e) {
            var hash = location.hash;
            var hasHash = /^#/.test(hash);
            if (hasHash)
                fn.parseHash(hash);
        },

        onUserInteraction: function (e) {
            if (!vars.interacted) {
                vars.interacted = true;
            }
            
        },

        /////////////////////////
        // Functional
        /////////////////////////
        callSection: function (section, paramString) {
        	var s = vars.sections.find(function (s) { return s.section.anchor === section });
            
            if (s !== undefined) {
                var params = fn.parseParams(paramString);
                var $section = $('#' + s.section.anchor);

                if (s.redirectTo !== undefined) {
                	window.location.href = s.redirectTo
						+ (paramString === undefined
							? ''
							: '?' + paramString);
                }

                if ($section.length) {
                    // only scroll if not in print mode.
                    if (!vars.hasPrint) {
                        setInterval(function () {
                            if (!vars.interacted && vars.waiting < vars.maxWait) {
                                $('html, body').scrollTop($section.offset().top - 100);
                                vars.waiting += 15;
                            }
                        }, 15);
                    }

                    // change breadcrumb to [link(Area)] >> [State], but only if coming from parent page
                    if (s.section.parent !== undefined) {
                        var ref = $breadcrumb.data('ref'),
                            parentPath = $breadcrumb.data('parent'),
                            parentTitle = $breadcrumb.data('parent-title'),

                            parentSection = typeof s.section.parent === 'function' && s.section.parent() !== undefined
                                ? s.section.parent().anchor
                                : s.section.parent,

                            state = $breadcrumb.data('state'),
                            stateSlug = $breadcrumb.data('stateslug');

                        if (ref === '/' + parentPath)
                            $breadcrumb.html('<a href="/{0}#{1}">{2}</a> &raquo; <a href="/{3}">{4}</a>'.format(parentPath, parentSection, parentTitle, stateSlug, state));
                    }

                    if (typeof s.method === 'function')
                        s.method(params);

                    if (vars.hasPrint)
                        $section.removeClass('noprint');

                    var uri = new URI(window.location.href);
                    $('#print-back').attr('href', uri.path + '#' + uri.hash);
                }

                // track jump to section load
                if (paramString === undefined || paramString.length === 0)
                    track(tracker.category.generic.jumpToSection, tracker.action.load, vars.currentArea + ' > ' + s.section.name);
            }
        },

        parseArray: function(arrayStr) {
            if (typeof arrayStr === 'string' && arrayStr.indexOf(',') > -1) {
                var array = arrayStr.split(',');

                array.foreach(function (a, i) {
                    var num = parseFloat(a);

                    if (!isNaN(num))
                        array[i] = num;
                });

                return array;
            }

            return arrayStr;
        },

        parseHash: function (hash) {
            if (hash !== undefined) {
                var selector;
                var paramStr;

                if (hash.indexOf('?') > -1) {
                    selector = hash.substring(1, hash.indexOf('?'));
                    paramStr = hash.substring(hash.indexOf('?') + 1);
                }
                else
                    selector = hash.substring(1);

                fn.callSection(selector, paramStr);                
            }
        },

        parseParams: function (paramString) {
            var params = {};

            if (typeof paramString === 'string') {
                var parts = paramString.split('&');
                parts.foreach(function (p) {
                    var group = p.split('=');                    
                    if (group.length == 2) {
                        group[1] = fn.parseArray(group[1]);
                        group[1] = group[1] === 'undefined' ? undefined : group[1];

                        var num = parseFloat(group[1]);

                        if (!isNaN(num) && num.toString() === group[1])
                            params[group[0]] = num;
                        else
                            params[group[0]] = group[1];
                    }
                });
            }

            return params;
        }
    };

    return {
        init: function(options) {
            fn.init(options);
        },

        isPrint: function() {
            return vars.hasPrint;
        },

        initSection: function (section, sectionVar, updateMethod) {
            fn.initSection(section, sectionVar, updateMethod);
        },

        initSectionRedirect: function (sectionAnchor, redirectToSection, stateSlug) {
        	fn.initSectionRedirect(sectionAnchor, redirectToSection, stateSlug);
        }
    }

})(jQuery, webitects);;
var HiddenTable = (function ($, w) {

    var _classPrefix = 'hidden-table';

    var classes = {
        download: 'download',
        filterText: 'filter-text',
        open: 'open',
        plugin: _classPrefix,
        tableToggleClass: 'table-toggler',
        tableWrapperClass: 'table-wrapper'
    };

    var cls = function (options) {
        var _this = this,
            _fixedHeader;

        var defaults = {
            dataTables: {
                'bAutoWidth': false,
                'bFilter': false,
                'bInfo': false,
                'bPaginate': false,
                'bLengthChange': false,
                'aaData': {},
                'fnDrawCallback': function (oSettings) { }
            },
            events: {
                onExport: function (_this) { },
                hide: function (ht) { },
                show: function (ht) { },
                sort: function (ht, column, direction) { }
            },
            exportOpts: {
                citationLink: '',
                filename: 'export.xlsx',
                title: 'Export',
                url: setup.url.exportOpts.data
            },
            fixedHeader: {
                enabled: false,
                topOffset: 0
            },
            table: {
                data: [],
                headings: [],
                selector: '',
                width: '100%'
            },
            template: new w.templater({
                html: function () {
                    var html = '';
                    html += '<div class="{{wrapperClass}}">';
                    html += '    <p style="text-align: center;"><a href="#" class="{{tableToggleClass}}">{{closedToggleText}}</a></p>';
                    html += '    <div class="{{tableWrapperClass}}">';
                    html += '        <p>';
                    html += '            <a class="r {{downloadClass}}" href="#"><i class="fa fa-download"></i> Download</a>';
                    html += '        </p>';
                    html += '        <h3 class="{{filterTextClass}}"></h3>';
                    html += '        <div class="clear"></div>';
                    html += '    </div>';
                    html += '</div>';
                    return html;
                }
            }),
            text: {
                closedToggler: '<i class="fa fa-table"></i> Show table and download this data',
                openToggler: 'Hide table'
            }
        };

        options = $.extend(defaults, options);

        var $download,
            $element,
            $exportData,
            $form,
            $table = $(options.table.selector),
            $toggler,
            $wrapper,
            _processingExport;

        /**************************
        * Initializers
        **************************/
        var _init = function () {
            options.template.fields.closedToggleText = options.text.closedToggler;
            options.template.fields.downloadClass = classes.download;
            options.template.fields.filterTextClass = classes.filterText;
            options.template.fields.wrapperClass = classes.plugin;
            options.template.fields.tableToggleClass = classes.tableToggleClass;
            options.template.fields.tableWrapperClass = classes.tableWrapperClass;

            var wrapperHtml = options.template.html();

            $table.before($(wrapperHtml));
            $element = $table.prev('.' + classes.plugin);
            $element.find('.' + classes.tableWrapperClass).append($table);

            $download = $element.find('.' + classes.download);
            $toggler = $element.find('.' + classes.tableToggleClass);
            $wrapper = $element.find('.' + classes.tableWrapperClass);
            $wrapper.hide();

            _processingExport = false;

            _buildTable(options.table);
            _initDataTable();
            _initDownload();
            _initExportForm();
            _initToggler();
        }

        var _initDataTable = function () {
            $table.css('width', options.table.width);
            $table.dataTable(options.dataTables);

            $table.find('thead th').bind('click', function (e) {
                var $cell = $(this);  
                var guid = $cell.data('wfh-id');

                // get aria-sort from table
                var i = $(this).filter(function () { return $(this).text() === $cell.text(); }).index();
                if (i > -1 && _fixedHeader !== undefined) {                    
                    if (guid !== undefined) {
                        var $headerCell = $table.find('td,th[data-wfh-id="' + guid + '"]');
                        if ($headerCell.length > 0)
                            $cell.attr('aria-sort', $headerCell.attr('aria-sort'));
                    }
                }

                var thClass = $(this).attr('class');
                var sort = $(this).attr('aria-sort');
                var col = w.utility.stripHtmlTags($(this).html());

                // check for sorting_asc/sorting_desc if aria-sort not there
                if (sort === undefined && thClass !== undefined && thClass.indexOf('sorting_') > -1) {
                    if (thClass.indexOf('sorting_asc') > -1) sort = 'ascending';
                    else if (thClass.indexOf('sorting_desc') > -1) sort = 'descending';
                }

                if (typeof options.events.sort === 'function')
                    options.events.sort(_this, col, sort);

                if (options.fixedHeader.enabled) {
                    _fixedHeader.matchTableHeader();
                }
            });

            if (options.fixedHeader.enabled) {
                _fixedHeader = new wFixedHeader({
                    table: $table,
                    topOffset: options.fixedHeader.topOffset,
                    wrapper: $table.closest('.' + classes.plugin)
                });
            }
        }

        var _initDownload = function () {
            $download.bind(setup.events.virtualTap, function (e) {
                if (!_processingExport) {
                    options.events.onExport(_this);
                    _processingExport = true;
                    var data = _processDataForExport();
                    $exportData.val(data);
                    $form.submit();
                    _processingExport = false;
                }

                e.preventDefault();
                return false;
            });
        }

        var _initExportForm = function () {
            $download.wrap('<form action="' + options.exportOpts.url + '" method="post" />');
            $form = $download.closest('form');
            $form.append($('<input type="hidden" name="data" value="" />'));
            $form.append($('<input type="hidden" name="title" value="' + options.exportOpts.title + '" />'));
            $form.append($('<input type="hidden" name="filename" value="' + options.exportOpts.filename + '" />'));
            $form.append($('<input type="hidden" name="citationLink" value="' + options.exportOpts.citationLink + '" />'));
            $exportData = $form.find("input[name='data']");
        }

        var _initToggler = function () {
            $toggler.bind(setup.events.virtualTap, function (e) {
                if (_isOpen()) {
                    $wrapper.slideUp(175, function () {
                        $wrapper.removeClass(classes.open);
                        $toggler.html(options.text.closedToggler);
                        if (typeof options.events.hide === 'function')
                            options.events.hide(_this);
                    });
                }
                else {
                    $wrapper.slideDown(175);
                    $wrapper.addClass(classes.open);
                    $toggler.html(options.text.openToggler);
                    if (typeof options.events.show === 'function')
                        options.events.show(_this);
                }

                e.preventDefault();
                return false;
            });
        }

        /**************************
        * Functional
        **************************/
        var _buildTable = function (tableOptions) {
            if (tableOptions.headings !== undefined && tableOptions.headings.length > 0) {
                _buildHeadings(tableOptions.headings);
            }

            if (tableOptions.data !== undefined && tableOptions.data.length > 0) {
                _buildData(tableOptions.data);
            }
        }

        var _buildHeadings = function (_headings) {
            var headings = new w.list(_headings);
            var thead = '<tr>';

            headings.foreach(function (h, i) {
                var thTemp = new w.templater({ html: '<th{{attr}}>{{content}}<span class="fa fa-caret-up"></span><span class="fa fa-caret-down"></span></th>' });

                if (typeof h === 'object') {
                    thTemp.fields.attr = _getAttribs(h.attr);
                    thTemp.fields.content = h.name;
                    
                }
                else {
                    thTemp.fields.content = h;
                }

                thead += thTemp.html();
            });

            thead += '</tr>';
            $table.find('thead').html($(thead));
        }

        var _buildData = function (data) {
            var tbody = [];

            data.foreach(function (d) {
                var row = d;
                var rowTemp = new w.templater({ html: '<tr {{htmlClass}}>' });

                if (d.data !== undefined)
                    row = d.data;

                if (d.rowClass !== undefined)
                    rowTemp.fields.htmlClass = 'class="' + d.rowClass + '"';
                
                tbody.push(rowTemp.html());
                row.foreach(function (r) {
                    var colTemp = new w.templater({ html: '<{{td}} {{htmlClass}} {{attr}}>{{label}}{{sort}}</{{td}}>' });

                    if (r.sort !== undefined)
                        colTemp.fields.sort = '<input type="hidden" data-sort="' + r.sort + '" value="' + r.value + '">';

                    if (r.htmlClass !== undefined)
                        colTemp.fields.htmlClass = 'class="' + r.htmlClass + '"';

                    colTemp.fields.attr = _getAttribs(r.attr);
                    colTemp.fields.label = r.label;
                    colTemp.fields.td = r.isHeader !== undefined && r.isHeader ? 'th' : 'td';
                    tbody.push(colTemp.html());
                });
                tbody.push('</tr>');
            });

            $table.find('tbody').html($(tbody.join('')));
        }

        var _getAttribs = function (attributes) {
            var attribs = '';
            if (attributes !== undefined) {
                var delim = ' ';
                attributes.foreach(function (a) {
                    attribs += delim + a[0] + '="' + $('<div/>').text(a[1]).html() + '"';
                });
            }
            return attribs;
        }

        var _isDataTable = function () {
            var settings = $.fn.dataTableSettings;
            for (var i = 0, iLen = settings.length; i < iLen; i++)
                if (settings[i].nTable == $table[0])
                    return true;
            return false;
        }

        var _isOpen = function () {
            var c = $element.find('.' + classes.tableWrapperClass).attr('class');
            return c !== undefined && c.indexOf(classes.open) > -1;
        }

        var _processDataForExport = function () {
                var delim = '';
                var rowDelim = '';
                var json = [];
                json.push('[');

                // build header
                $table.find('thead tr').each(function (i) {
                    delim = '';
                    json.push(rowDelim + '[');
                    $(this).find('th').each(function () {
                        var $this = $(this);
                        if ($this.css('display') != 'none') {
                            json.push(delim + _cellToObject($this));
                            delim = ',';
                        }
                    });
                    json.push(']');
                    rowDelim = ',';
                });

                // build body
                $table.find('tbody tr').each(function (i) {
                    delim = '';
                    json.push(rowDelim + '[');
                    $(this).find('td,th').each(function () {
                        var $this = $(this);
                        if ($this.css('display') != 'none') {
                            json.push(delim + _cellToObject($this));
                            delim = ',';
                        }
                    });
                    json.push(']');
                });

                json.push(']');
                return json.join('');
            }

        var _cellToObject = function ($cell) {
            var overrideText = $cell.data('export-override-text');
            var text = ($cell.html() + '').toString();
            var colspan = $cell.attr('colspan') !== undefined ? $cell.attr('colspan') : 1;
            var rowspan = $cell.attr('rowspan') !== undefined ? $cell.attr('rowspan') : 1;

            if (overrideText !== undefined)
                text = overrideText.toString();

            // escape quotes
            text = text.replace(/\"/g, '\\\"').trim();

            return JSON.stringify({
                colspan: colspan,
                rowspan: rowspan,
                th: $cell.is('th'),
                value: utility.stripHtmlTags(text)
            });
        }

        /**************************
        * Public methods
        **************************/
        this.cleanTable = function () {
            if (_isDataTable())
                $table.fnDestroy();
            $table.find('thead').empty();
            $table.find('tbody').empty();
        }

        this.resetData = function (data) {
            _buildData(data);
        }

        this.resetDataTable = function (optionsArray) {
            if (optionsArray !== undefined && optionsArray.length > 0) {
                for (var i = 0; i < optionsArray.length; i++)
                    options.dataTables[optionsArray[i][0]] = optionsArray[i][1];
            }

            _initDataTable();
        }

        this.resetHeadings = function (headings) {
            _buildHeadings(headings);
        }

        this.setCitationLink = function (link) {
            if (options.exportOpts !== undefined)
                options.exportOpts.citationLink = link;
            $form.find('input[name="citationLink"]').val(link);
        }

        this.setExportTitle = function (title) {
            if (options.exportOpts !== undefined)
                options.exportOpts.title = title;
            $form.find('input[name="title"]').val(title);
        }

        this.setExportFilename = function (filename) {
            if (options.exportOpts !== undefined)
                options.exportOpts.filename = filename;
            $form.find('input[name="filename"]').val(filename);
        }

        this.setFilterText = function (text) {
            $wrapper.find('.' + classes.filterText).html(text);
        }

        _init();
    }

    return cls;

})(jQuery, webitects);;
var Map = (function ($, w) {

    var cls = function (options) {
        var defaults = {
            selector: '',
            legendSelector: '',
            legendTemplate: new w.templater({ html: '<ul>{{items}}</ul>' }),
            legendItemTemplate: new w.templater({
                html: function () {
                    var html = '';
                    html += '<li class="{{htmlClass}}" {{styles}}>';
                    html += '    <span class="count" style="background-color:{{color}}; border-bottom-color:{{color}}">{{count}}</span>';
                    html += '    <span class="label">{{label}}</span>';
                    html += '</li>';
                    return html;
                }
            }),
            tooltip: {
                enabled: false,
                formatter: function (mapObject) { return undefined; }
            },
            events: {
                stateClick: function (state) { }
            }
        };

        var _this = this;

        options = $.extend(true, defaults, options);

        var $legend = $(options.legendSelector),
            _lastWidth = 0,
            _legendTemplate = options.legendTemplate,
            _legendItemTemplate = options.legendItemTemplate,
            _map = d3.select(options.selector),
            _$map = $(options.selector),
            _mapLegend = [],
            _mapObjects = [],
            _states = [],
            _tooltip;

        var fn = {
            init: function () {
                if (options.tooltip.enabled) {
                    _tooltip = new Map.popup({
                        map: _this,
                        formatter: options.tooltip.formatter,
                        parent: $(options.selector).closest('.map-wrapper')
                    });
                }

                $(options.selector + ' path.map-state').each(function () {
                    _states.push({
                        id: $(this).data('id'),
                        code: $(this).data('code'),
                        name: $(this).data('name'),
                        slug: $(this).data('slug')
                    });
                });

                $(options.selector + ' path').on('click', function (e) {
                    var $path = $(this);
                    var id = $path.data('id'),
                        code = $path.data('code'),
                        name = $path.data('name'),
                        slug = $path.data('slug');

                    var state = {
                        id: id,
                        code: code,
                        name: name,
                        slug: slug
                    };

                    if (typeof options.events.stateClick === 'function')
                        options.events.stateClick(state);
                });

                $(options.selector + ' path').each(function () {
                    if ($(this).attr('class') === undefined)
                        $(this).attr('class', '');
                });

                $(options.selector + ' .state-code').on('click', function (e) {
                    var stateCode = $(this).data('code');
                    var $state = $(options.selector + ' path[data-code="' + stateCode + '"]');
                    if ($state.length)
                        $state.trigger('click');
                });

                $(options.selector + ' .state-code')
                    .on('mouseenter', function (e) {
                        var stateCode = $(this).data('code');
                        var $state = $(options.selector + ' path[data-code="' + stateCode + '"]');
                        if ($state.length) {
                            fn.addClass($state, 'hover');
                        }
                    })
                    .on('mouseleave', function (e) {
                        var stateCode = $(this).data('code');
                        var $state = $(options.selector + ' path[data-code="' + stateCode + '"]');
                        if ($state.length) {
                            fn.removeClass($state, 'hover');
                        }
                    });
            },

            addClass: function ($el, className) {
                var c = $el.attr('class');
                if (c === undefined)
                    c = '';
                c = $.grep(c.split(' '), function (x) { return x; });
                if (c.indexOf(className) === -1)
                    c.push(className);
                $el.attr('class', c.join(' '));
            },

            removeClass: function ($el, className) {
                var c = $el.attr('class');
                if (c === undefined)
                    c = '';
                c = $.grep(c.split(' '), function (x) { return x; });
                var idx = c.indexOf(className);
                if (idx > -1)
                    c.splice(idx, 1);
                $el.attr('class', c.join(' '));
            }
        }

        this.buildLegend = function () {
            $legend.empty();

            var legendWidth = $legend.width();
            var steps = _mapLegend.length;
            var itemWidth = legendWidth > 0 ? (parseInt((legendWidth / steps) - 2) / legendWidth) * 100 : _lastWidth;
            var legendItems = [];
            _lastWidth = itemWidth;

            _mapLegend.foreach(function (mlo) {
                _legendItemTemplate.fields.color = mlo.colorRgba;
                _legendItemTemplate.fields.count = mlo.count;
                _legendItemTemplate.fields.label = mlo.label();
                _legendItemTemplate.fields.styles = 'style="width:' + itemWidth + '%;"';
                _legendItemTemplate.fields.htmlClass = mlo.legendClass !== undefined ? mlo.legendClass : '';

                if (mlo.legendClass === '') {
                    if (mlo.value === null) _legendItemTemplate.fields.htmlClass = 'null';
                    else _legendItemTemplate.fields.htmlClass = '';
                }

                legendItems.push(_legendItemTemplate.html());
            });

            _legendTemplate.fields.items = legendItems.join('');
            var legendHtml = _legendTemplate.html();
            $legend.append($(legendHtml));
        }

        this.mapLegend = function (mapLegend) {
            if (arguments.length === 0)
                return _mapLegend;
            else {
                if (mapLegend instanceof webitects.list)
                    mapLegend = mapLegend.toArray();

                _mapLegend = mapLegend;
            }
        }

        this.mapObjects = function (mapObjects) {
            if (arguments.length === 0)
                return _mapObjects;
            else {
                if (mapObjects instanceof webitects.list)
                    mapObjects = mapObjects.toArray();

                _mapObjects = mapObjects;
            }
        }

        this.renderStates = function () {
            var nullLegend = _mapLegend.find(function (ml) { return ml.isNull; });

            _states.foreach(function (state, i) {
                var path = _map.select('path[data-id="' + state.id + '"]');
                var x = _mapObjects.find(function (mo) { return mo.stateId === state.id; });

                if (x !== undefined) {
                    var legend = _mapLegend.find(function (m) { return m.value === x.value; });// || (x.value === 0 && m.value === null) });      
                    
                    if (path !== undefined) {
                        if (legend !== undefined) {
                            path.style('fill', legend.colorRgba);
                        }
                        else {
                            if (nullLegend !== undefined)
                                path.style('fill', nullLegend.colorRgba);
                            else
                                path.style('fill', 'rgb(221, 221, 221)');
                        }

                        if (_tooltip !== undefined) {
                            _tooltip.setup($(options.selector).find('path[data-id="' + x.stateId + '"]'), options.tooltip.formatter(x));
                        }
                    }
                }
                else
                    path.style('fill', 'rgb(221, 221, 221)');
            });
        }

        this.map = _map;
        this.$map = _$map;

        fn.init();
    }

    /**************************
    * Inner classes
    **************************/
    var _mapLegendObject = (function () {
        var ml = function (value, color, count, template, isNull, legendClass) {
            var _value = value;
            var _color = color;
            var _count = count;
            var _isNull = isNull;
            var _legendClass = legendClass;
            var _template = new w.templater({ html: template !== undefined ? template.toString() : '{{value}}' });

            this.value = _value;
            this.color = _color;
            this.colorRgba = 'rgba(' + _color.r + ',' + _color.g + ',' + _color.b + ',' + _color.a + ')';
            this.count = _count;
            this.isNull = _isNull;
            this.legendClass = _legendClass;

            this.label = function () {
                _template.fields.value = _value;
                return _template.html();
            }
        }

        return ml;
    });

    var _mapObject = (function () {
        var mo = function (stateId, value) {
            var _stateId = stateId,
                _value = value;

            this.stateId = _stateId;
            this.value = _value;
        }

        return mo;
    });

    var _popup = (function () {
        var p = function (opts) {
            var defaults = {
                formatter: function (mapObject) { return ''; },
                map: undefined,
                parent: undefined
            };

            opts = $.extend(true, defaults, opts);

            var $parent,
                $this;

            var vars = {
                mouse: {
                    currentX: 0,
                    currentY: 0
                },
                parent: {
                    width: 0,
                    height: 0
                },
                latency: 75,
                height: 0,
                width: 0,
                shown: false,
                showTimeout: undefined,
                hideTimeout: undefined
            }

            var fn = {
                init: function () {
                    $parent = $(opts.parent);
                    $this = $('<div class="map-popup"></div>');
                    $parent.prepend($this);
                    $this.hide();

                    vars.parent.width = $parent.width();
                    vars.parent.height = $parent.height();
                },

                showPopup: function (x, y, $s) {
                    if (x !== undefined && y !== undefined)
                        fn.determinePosition(x, y);

                    var stateId = $s.data('id');
                    var mo = opts.map.mapObjects().find(function (o) { return o.stateId === stateId; });
                    
                    if (typeof opts.formatter === 'function' && mo !== undefined) {
                        var html = opts.formatter(mo);
                        if (html !== undefined)
                            $this.html(html);
                    }

                    vars.height = $this.height();
                    vars.height += parseInt($this.css('padding-top'), 10);
                    vars.height += parseInt($this.css('padding-bottom'), 10);

                    vars.width = $this.width();
                    vars.width += parseInt($this.css('padding-right'), 10);
                    vars.width += parseInt($this.css('padding-left'), 10);
                                        
                    $this.show();
                    vars.shown = true;
                },

                hidePopup: function () {
                    $this.hide();
                    vars.shown = false;
                },

                determinePosition: function (x, y) {
                    var px = x + 4;
                    var py = y + 40;

                    if (vars.parent.width - x < vars.width) {
                        px = x - vars.width - 4;
                    }

                    if (vars.parent.height - y < vars.height) {
                        py = y - vars.height - 40;
                    }

                    $this.css('margin-left', px + 'px');
                    $this.css('margin-top', py + 'px');
                },

                rebindMouseStop: function ($s) {
                    $s.off('mousestop');
                    $s.on('mousestop', function (e) { fn.rebindMouseStop($(this)); });

                    vars.showTimeout = setTimeout(function () {
                        fn.showPopup(vars.mouse.currentX, vars.mouse.currentY, $s);
                    }, vars.latency);
                    vars.showTimeout = setTimeout(function () {  }, 0);
                }
            }

            this.setup = function ($item, html) {

                $item
                .on('mouseenter', function (e) {
                    var c = $item.attr('class');
                    c = c !== undefined ? (c.indexOf('hover') == -1 ? c + ' hover' : c) : 'hover';
                    $item.attr('class', c);
                    fn.rebindMouseStop($item);
                })
                .on('mouseleave', function (e) {
                    var c = $item.attr('class');
                    c = c !== undefined ? (c.indexOf('hover') > -1 ? c.replace('hover', '') : c) : '';
                    $item.attr('class', c);
                    vars.hideTimeout = setTimeout(function () {
                        fn.hidePopup();
                    }, vars.latency);
                })
                .on('mousemove', function (e) {
                    clearTimeout(vars.showTimeout);
                    //if (e.offsetX !== undefined) {
                    //    console.log('e.offset');
                    //    vars.mouse.currentX = e.offsetX;
                    //    vars.mouse.currentY = e.offsetY;
                    //}
                    //else {
                    //    vars.mouse.currentX = e.pageX - $parent.offset().left;
                    //    vars.mouse.currentY = e.pageY - $parent.offset().top;
                    //}

                    vars.mouse.currentX = e.pageX - $parent.offset().left;
                    vars.mouse.currentY = e.pageY - $parent.offset().top;
                })
                .on('mousestop', function (e) {
                    var $item = $(this);
                    fn.rebindMouseStop($item);
                });

                var $code = defaults.map.$map.find('.state-code[data-code="' + $item.data('code') + '"]');
                if ($code.length) {
                    $code
                        .on('mouseenter', function (e) { $item.trigger('mouseenter'); })
                        .on('mouseleave', function (e) { $item.trigger('mouseleave'); })
                        .on('mousemove', function (e) { $item.trigger('mousemove'); })
                        .on('mousestop', function (e) { $item.trigger('mousestop'); });
                }
            }

            fn.init();
        }

        return p;
    });

    cls.mapLegend = new _mapLegendObject();
    cls.mapObject = new _mapObject();
    cls.popup = new _popup();

    return cls;

})(jQuery, webitects);;
var PlaySlider = (function ($) {

    var cls = function (options) {
        var $element = $(options.element),
            $legend,
            $slider,
            $play,

            _onChange = typeof options.onChange === 'function' ? options.onChange : function (value) { },
            _onPlay = typeof options.onPlay === 'function' ? options.onPlay : function (ps) { },
            _onPause = typeof options.onPause === 'function' ? options.onPause : function (ps) { },

            _currIndex,
            _currValue,
            _data = options.data,
            _width = options.width !== undefined ? options.width : 550,
            _playing = false,
            _showAllLabels = options.showAllLabels !== undefined ? options.showAllLabels : false,
            _showPlayButton = options.showPlayButton !== undefined ? options.showPlayButton : true,
            _time = options.interval !== undefined ? options.interval : 500,
            _timeout,
            _this = this;

        var _init = function () {
            $element.append($('<a class="action" href="#"><i class="fa fa-play"></i></a>'));
            $element.append($('<div class="slider"></div>'));
            $element.append($('<div class="play-slider-legend"></div>'));

            $play = $element.find('.action');
            $slider = $element.find('.slider');
            $legend = $element.find('.play-slider-legend');

            $legend.css('width', _width);
            $slider.css('width', _width);

            _currIndex = options.index === undefined ? 0 : options.index;

            _initSlider();
            _initPlay();

            _data.foreach(function (d, i) {
                var tickWrapper = '';
                var tickWidth = _width / (_data.count() - 1);
                var halfTickWidth = tickWidth / 2;
                var left = i * tickWidth - halfTickWidth - 1;

                if ((i == 0 || i == _data.count() - 1) || _showAllLabels)
                    tickWrapper = '<div class="tick" style="left:' + left + 'px"><span class="tick-line" style="margin:0 ' + halfTickWidth + 'px;"></span><span class="tick-value">' + d.label + '</span></div>';
                else
                    tickWrapper = '<div class="tick" style="left:' + left + 'px"><span class="tick-line" style="margin:0 ' + halfTickWidth + 'px;"></span><span class="tick-value"></span></div>';

                $legend.append($(tickWrapper));
            });

            $legend.append($('<div class="clear"></div>'));
            $element.append($('<div class="clear"></div>'));

        }

        var _initSlider = function () {
            if (_data.count() > 0) {
                var values = [];
                _currValue = _data.itemAt(_currIndex).value;
                _data.foreach(function (d, i) { values.push(i); });

                $slider.slider({
                    min: 0,
                    max: _data.count() - 1,
                    step: 1,
                    slide: function (e, ui) {
                        _currIndex = ui.value;
                        _currValue = _data.itemAt(ui.value).value;
                        _onChange(_currValue);
                    },
                    change: function (e, ui) {
                        if (_currValue !== _data.itemAt(ui.value).value) {
                            _currIndex = ui.value;
                            _currValue = _data.itemAt(ui.value).value;
                            _onChange(_currValue);
                        }
                    },
                    value: _currIndex
                });
            }
        }

        var _initPlay = function () {
            if (_showPlayButton) {
                if (_playing)
                    $play.html('<i class="fa fa-pause"></i>');
                else
                    $play.html('<i class="fa fa-play"></i>');

                $play.unbind(setup.events.virtualTap);
                $play.bind(setup.events.virtualTap, function (e) {
                    if (!_playing) {
                        _play();
                        $(this).html('<i class="fa fa-pause"></i>');
                        if (typeof _onPlay === 'function')
                            _onPlay(_this);
                    }
                    else {
                        _stop();
                        $(this).html('<i class="fa fa-play"></i>');
                        if (typeof _onPause === 'function')
                            _onPause(_this);
                    }

                    e.preventDefault();
                    return false;
                });
            }
            else
                $play.hide();
        }

        var _play = function () {
            _playing = true;
            _currIndex++;
            if (_currIndex >= _data.count()) _currIndex = 0;
            else if (_currIndex < 0) _currIndex = _data.count() - 1;

            $slider.slider('value', _currIndex);
            _timeout = setTimeout(_play, _time);
        }

        var _stop = function () {
            if (_playing) {
                _playing = false;
                if (_timeout !== undefined)
                    clearTimeout(_timeout);
            }
        }

        this.reset = function (data, index) {
            if (typeof index !== undefined)
                options.index = index;

            $slider.slider('destroy');
            $element.empty();
            _data = data;
            _init();
        }

        this.getValue = function () {
            return _data.toArray()[_currIndex].value;
        }

        this.playing = function () {
            return _playing;
        }

        this.setValue = function (val) {
            var index = _data.findIndex(function (so) { return so.value === val; });
            if (index !== -1) {
                _currIndex = index;
                $slider.slider('value', _currIndex);
            }
        }

        this.stop = function () {
            _stop();
        }

        _init();
    }

    var _sliderObject = (function () {
        var so = function (label, value) {
            var _label = label,
                _value = value;

            this.label = _label;
            this.value = _value;
        }

        return so;
    });

    cls.sliderObject = new _sliderObject();

    return cls;

})(jQuery);;
