function Examples()
{
    var _sections = null;
    var _searchInput = document.querySelector('#examples-search');
    var _updatingHash = false;
    var _timeoutId;
    var _prevSearchStr;

    var _delimiterStr = {
        or  : ['||', '|', ','],
        and : ['&&', '&', '+']
    };

    var _delimiterRegEx = {
        or  : createRegExp(_delimiterStr.or),
        and : createRegExp(_delimiterStr.and)
    };

    function addListeners()
    {
        var clearSearchBtn = document.querySelector('#clear-search-btn');

        // MSIE doesn't support sticky position, causing the nav to move as user scrolls. On IE 11
        // clicking example links jumps to the top of the page making for bad user experience
        var hasStickyNav = window.getComputedStyle(document.querySelector('nav'), null).getPropertyValue('position') === 'sticky';

        if (hasStickyNav
            && !mobileAndTabletcheck())
        {
            _searchInput.focus();
    
            _searchInput.addEventListener('focusout', function(e)
            {
                _searchInput.focus();
            });
        }

        _searchInput.addEventListener('input', function(e)
        {
            clearTimeout(_timeoutId);
            _timeoutId = setTimeout(filterExamples, 250);
        });

        var changeClearSearchBtnVisibility = function()
        {
            if (_searchInput.value)
                clearSearchBtn.classList.remove('hidden');
            else
                clearSearchBtn.classList.add('hidden');
        };

        _searchInput.addEventListener('input', changeClearSearchBtnVisibility);
        changeClearSearchBtnVisibility();

        clearSearchBtn.addEventListener('click', function(e)
        {
            _searchInput.value = "";
            clearSearchBtn.classList.add('hidden');
            filterExamples();
        });

        window.onhashchange = function()
        {
            var hash = unhashify(location.hash.substr(1));
            _searchInput.value = hash;
            updateAndCreate(hash);
            changeClearSearchBtnVisibility();
        }

        window.onkeypress = function(e)
        {
            if (e.key === "Enter")
                filterExamples();
        }
    }

    function filterExamples()
    {
        clearTimeout(_timeoutId);
        updateAndCreate(_searchInput.value);
    }

    function updateAndCreate(searchStr)
    {
        if (searchStr !== _prevSearchStr)
        {
            updateHash(searchStr);
            createExamples(_sections, searchStr);
        }

        _prevSearchStr = searchStr;
    }

    function updateHash(searchStr)
    {
        _updatingHash = true;

        if (searchStr.length > 0)
            location.hash = "#" + hashify(searchStr);

        // https://stackoverflow.com/questions/1397329/how-to-remove-the-hash-from-window-location-url-with-javascript-without-page-r/5298684#5298684
        else
            history.pushState("", document.title,
                window.location.pathname + window.location.search);
    }

    function hashify(str)
    {
        return str.replace(_delimiterRegEx.or, ",")
                  .replace(_delimiterRegEx.and, "+")
                  .replace(/\s/g, "-");
    }

    function unhashify(str)
    {
        return str.replace(/\-/g, " ");
    }

    function createRegExp(arr) {
        return new RegExp(
            arr.map(function(str)
            {
                return str.split('').map(function(char)
                {
                    return "\\" + char;
                })
                .join('');
            })
            .join('|')
        );
    }

    /**
     * Create a 2 dimensional array representing boolean search logic.
     *
     * @param {String} searchStr The search string to convert into an array
     * @return {Array} 2 dimensional array:
     *                  - First level represents the OR logic ['vod', 'live'] = vod OR live
     *                  - Second level represents the AND logic  [['vod', 'live']] = vod AND live
     */
    function createFilterArray(searchStr)
    {
        return searchStr.toLowerCase().split(_delimiterRegEx.or).map(function(el)
        {
            if (_delimiterRegEx.and.test(el))
                return el.split(_delimiterRegEx.and);

            return el;
        });
    }

    function categorySearchTest(categories, filterArr)
    {
        return filterArr.some(function(or)
        {
            if (Array.isArray(or))
            {
                return or.every(function(and)
                {
                    return categories.toLowerCase().indexOf(and) > -1
                });
            }

            return categories.toLowerCase().indexOf(or) > -1
        });
    }

    function createExamples(sections, searchStr)
    {
        sections = sections || [];

        var filter = createFilterArray(searchStr);

        var secs = [];

        function linkify(name, link)
        {
            if (!link)
                return name;

            var val = '<a href="{link}">' + 
                          '{name} <span><i class="fa fa-link"></i></span>' + 
                      '</a>';

            return val.replace(/\{link\}/, link)
                      .replace(/\{name\}/, name);
        }

        sections.forEach(function(sec)
        {
            var subs = [];

            sec.subs.forEach(function(sub)
            {
                var cards = [];

                sub.examples.forEach(function(ex)
                {
                    var categories = [
                        sec.name,
                        sub.name,
                        ex.title,
                        ex.category
                    ].join(',');

                    if (categorySearchTest(categories, filter))
                    {
                        var cardHtml = '<a href="{link}">' +
                                           '<div class="card">' +
                                               '<div class="card-body">' +
                                                   '<h5 class="card-title">{title}</h5>' +
                                                   '<p class="card-text">{description}</p>' +
                                               '</div>' +
                                           '</div>' +
                                       '</a>';

                        cards.push(cardHtml
                                    .replace(/\{link\}/, ex.link)
                                    .replace(/\{title\}/, ex.title)
                                    .replace(/\{description\}/, ex.description));
                    }
                });

                if (cards.length > 0)
                {
                    var subsHtml = '<div class="sub-section">' +
                                       '<h4>{name}</h4>' +
                                       '<div class="card-columns">' +
                                           '{cards}' +
                                       '</div>' +
                                   '</div>';

                    subs.push(subsHtml
                                .replace(/\{name\}/, linkify(sub.name, sub.link))
                                .replace(/\{cards\}/, cards.join('')));
                }
            });

            if (subs.length > 0)
            {
                var secsHtml = '<div class="section">' +
                                   '<h1>{name}</h1>' +
                                   '<hr/>' +
                                   '<div>{description}</div>' +
                                   '<div class="sub-sections">' +
                                       '{subSections}' +
                                   '</div>' +
                               '</div>';

                secs.push(secsHtml
                            .replace(/\{name\}/, linkify(sec.name, sec.link))
                            .replace(/\{description\}/, sec.description)
                            .replace(/\{subSections\}/, subs.join('')));
            }
        });

        if (!secs.length)
            document.querySelector('.sections').innerHTML = '<div class="no-results">' +
                                                                '<span style="margin-right:12px;" class="fa fa-ban"></span>' +
                                                                'No search results were found.' +
                                                            '</div>';
        else 
            document.querySelector('.sections').innerHTML = secs.join('');
    }

    
    function createPopover(selector, config)
    {
        if (!selector)
            return console.warn('[createPopover] selector required');
        
        // @see https://getbootstrap.com/docs/4.0/components/popovers/#options
        config = $.extend({
            html    : true,
            trigger : 'manual',
            timeout : 6000
        }, config);

        var popover = $(selector)
                        .popover(config);

        var popoverTimeoutId;

        var clearPopover = function()
        {
            clearTimeout(popoverTimeoutId);
            window.removeEventListener('mouseup', clearPopover);

            popover.popover('hide')
                .on('hidden.bs.popover', function () {
                    if (config.dispose)
                        popover.popover('dispose');
                });
        };

        clearTimeout(popoverTimeoutId);
        popoverTimeoutId = setTimeout(clearPopover, config.timeout);

        // TODO: Clear on touch for mobile as well
        window.addEventListener('mouseup', clearPopover);

        return popover;

        // TODO: Look into 'boundary' option to see if we can prevent popover from moving with scrolling content: https://getbootstrap.com/docs/4.0/components/popovers/#options
    }

    function initSearchInfoPopover() {
        var searchInfoContent = 
            'Filter the examples by section/sub-section header, title, or category. ' +
            'Also perform boolean searches using the following:' +
            '<br><br>' +
            '<ul>' +
                '<li>{or} to do a logical OR, e.g. <code>vod,preroll</code> for examples with VOD content <i>OR</i> preroll ads</li>' +
                '<li>{and} to do a logical AND, e.g. <code>vod+preroll</code> for examples with VOD content <i>AND</i> preroll ads</li>' +
            '</ul>' +
            'Searches can be shared by copy/pasting the updated browser url.';

        var codify = function(el) {
            return '<code>' + el + '</code>';
        };

        var space = '&nbsp;&nbsp;&nbsp;';

        createPopover('#searchInfoBtn', 
            {
                title   : 'Search Info',
                trigger : mobileAndTabletcheck() ? 'click' : 'hover',
                content : searchInfoContent
                            .replace(/\{or\}/,  _delimiterStr.or.map(codify).join(space))
                            .replace(/\{and\}/, _delimiterStr.and.map(codify).join(space))
            }
        );
    }

    function mobileAndTabletcheck() {
        var check = false;
        (function (a) { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true; })(navigator.userAgent || navigator.vendor || window.opera);
        return check;
    }

    /**
     * Get search from the url hash or saved search from local storage, then create the filtered examples
     * @param {Array} sections List of section data
     */
    this.retrieveAndCreate = function(sections)
    {
        _sections = sections;

        var searchStr = '';
        
        if (location.hash.length > 0)
        {
            searchStr = unhashify(location.hash.substr(1));
            createPopover('#examples-search', 
                {
                    title   : 'Filtered Results',
                    content : 'The example pages are being filtered.' +
                              '<br><br>' + 
                              'Update this search input to get different results.' +
                              '<br><br>' + 
                              'Clear this search input to see all example pages.',
                    dispose : true
                }
            ).popover('show');
        }

        _searchInput.value = searchStr;

        addListeners();
        initSearchInfoPopover();

        createExamples(sections, searchStr);
    };
}

Examples.prototype.constructor = Examples;

Examples.init = function (data)
{
    if (Examples._instance)
        return console.warn('[init] Examples already initialized');
    
    var e = Examples._instance = new Examples();

    e.retrieveAndCreate(data);

    if (window.location.hostname !== 'cvpdev.turner.com')
    {
        document.querySelector('.navbar-nav').innerHTML += 
            '<li class="nav-item"><a class="nav-link" href="../examples/validation">Validation</a></li>';

        var navbar = document.querySelector('.navbar');
        navbar.classList.remove('navbar-expand-sm');
        navbar.classList.add('navbar-expand-md');
    }
};