;(function($) {
// todo, add a comment about what this plugin does
$.fn.tv2topstoryteaser = function(options) {
    var opts = $.extend(true, {}, $.fn.tv2topstoryteaser.defaults, options),
        scroller,
        mode = {},
        currentItem = { num: 0 },
        entries,
        contents = {},
        countdown,
        relatedAnimating = false,

    cycle = function(){
        var t = null, i = 0, width, timeout, step = 1,

        progress = function(){
            if(i < 100){
                i += step;
                countdown.interval.css('backgroundPosition',(100-i)+'% 0');
            }
            else {
                i = 0;
                countdown.interval.css('backgroundPosition','100% 0');
                display.change();
            }
        },

        stop = function(){
            if(typeof t == 'number') window.clearTimeout(t);
            countdown.wrapper.fadeOut('fast',function(){
                i = 0;
                countdown.interval.css('backgroundPosition','100% 0');
            });
        },

        start = function(){
            if(opts.cycle.activated){
                if(typeof t == 'number'){ window.clearTimeout(t); }

                if(width == undefined){
                    // Should only run this once
                    width = parseInt( countdown.interval.css('width'), 10 );
                    step = 100 / width;
                    timeout = opts.cycle.timeout / width;
                }

                countdown.wrapper.fadeIn('slow',function(){
                    t = window.setInterval(progress, timeout);
                });
            }
        };

        return {
            stop : stop,
            start : start,
            reset : function(){ stop(); start(); }
        };
    }(),

    /* this postpone object creates a timeout before a function is executed.
     * The timeout, and its dedicated function, is canceled if a new postpone
     * is registered--A postpone can be killed if needed.
     *
     * This postpone is used when change-to-item-when-hovering-contents-menu
     * is activated, causing it to ignore the mouse, when it is dragged over
     * multiple elements in a short period of time.
     */
    postpone = function(){
        var t = null,

        cancel = function(){
            if(typeof t == 'number') window.clearTimeout(t);
        };

        return {
            register : function(callback){
                cancel();
                t = window.setTimeout(callback, 400);
            },
            cancel : cancel
        };
    }(),

    /* */
    display = function(){
        var show = function(n){
            var oldPos = currentItem.num,
                b = opts.cycle.rotate;

            /* the 'b'-variable is a bool, if true it should rotate the list,
             * instead of returning the opposite direction, when hitting an
             * end point. */
            if(n < 0) n = b ? entries.length - 1 : 0;
            else if(n >= entries.length) n = b ? 0 : entries.length-1;
            currentItem.num = n;

            /* keep it from executing any code, if it is not needed */
            if(oldPos == currentItem.num){
                return;
            }
            else {
                // will change to new item

                /* remove the current class from the list elements and add it
                 * to the new current */
                 contents.list
                    .find('li.current').removeClass('current').end()
                    .find('li:eq('+n+')').addClass('current');

                /* Stop any activities at the old (eg. video) */
                var $oldItem = $(entries[oldPos]);
                    oldMedia = $oldItem.find('.media');

                    if(oldMedia.hasClass('video')){ video.stop(oldMedia); }

                /* Start activities at the new (eg. video) */
                var $newItem = $(entries[n]),
                    newMedia = $newItem.find('.media');

                    if(newMedia.hasClass('video')){
                        if(opts.video.autoplay)
                            video.play(newMedia);
                        else
                            video.addButton(newMedia);
                    }

                if(countdown){
                    contents.list
                        .find('li.current').append(countdown.wrapper);
                }
            }

            if(n == 0){
                // console.log('changing to the first of the list');
                if(opts.cycle.behaviour === 'bounce'){
                    display.change = display.next;
                    cycle.reset();
                }
            }
            else if(n == entries.length - 1){
                // console.log('changing to the last of the list');
                if(opts.cycle.behaviour === 'bounce'){
                    display.change = display.previous;
                    cycle.reset();
                }

                // set autoplay to false after the list has been cycled once
                if(opts.video.disableAutoPlayVideoAfterFirstCycle){
                    opts.video.autoplay = false;
                }
            }
            else {
                // console.log('changing to the n-th of the list');
            }

            var newPosition = currentItem.num * currentItem.width * -1;

            if(opts.enableAnimation){
                /* slide into */
                scroller.animate({ 'marginLeft': newPosition }, 'slow');
            }
            else {
                /* no animation */
                scroller.css('marginLeft', newPosition);
            }
        },

        next = function(){ display.show( currentItem.num + 1 ); },
        previous = function(){ display.show( currentItem.num - 1 ); };

        return {
            show: show,
            next : next, previous : previous,
            change : next
        };
    }(),

    /* Videoplayer */
    video = (function(){
        var videoplayer = 'http://'+($.tv2common)+'/flash/videoplayer.swf',
            playerCache = {},
            cachedVideoId,

        create = function(a){
            /* we'll need a new video player, asap
             * the current one is not able to loop a video, and we do not
             * need statistics for the news autoplay-preview, that I'm
             * originally implementing this for. */
            var xml = 'http://'+($.tv2common)+'/'
                + 'flashplayer/playlist.xml.php/alias-videoplayer_front_small'
                + '/autoplay-1/clipid-'+cachedVideoId
                + '/controls-0.xml';

            $(a).flash({
                name: 'videoclip',
                src: 'http://'+($.tv2common)+'/flash/videoplayer.swf',
                width: 394, height: 222,
                allowfullscreen: (opts.video.allowFullscreen?'true':'false'),
                allowscriptaccess: 'always',
                wmode: 'transparent',
                quality: 'high', bgcolor: '#000000',

                flashvars: {
                    xmlfile: xml, stats: 'false',
                    controls: '0'
                }
            });
        },

        findVideoId = function(str){
            /* look for a class name of the patteren 'video-id-123456' */
            var a = parseInt(/(?:video-id-)([0-9]+)/.exec(str)[1],10);
            return typeof a == 'number' ? a : null;
        };

        return {
            addButton : function(a){
                /* create a play button, or use the one already created */
                var play = a.find('div.play');
                if(play[0])
                    play.show();
                else{
                    /* add the button, add a ie7 class so that we can target
                     * it from css without using hacks */
                    var c = $.browser.msie ? 'play ie7' : 'play';
                    a.append(['<div class="', c, '"></div>'].join(''));
                }
            },
            play : function(a){
                /* disable cycle, if the option is set */
                if(opts.video.stopCycleOnVideoPlay){ opts.cycle.activated = false; }

                cachedVideoId = findVideoId(a[0].className);
                if(playerCache[cachedVideoId]){
                    /* If we are using the cache-object strategy, we could
                     * put the video back into the dom by applying this code:
                     *
                    playerCache[cachedVideoId].appendTo(a);
                     */

                    /* Display the video, again */
                    a.children().show().end().find('.play').hide();
                }
                else {
                    /* Create a new element
                     *
                     * Notice: The 'caching' I'm doing here are a bit silly.
                     * The element stays in the Dom, so we could just look
                     * for its existance when, and if, we are displaying it
                     * again */
                    playerCache[cachedVideoId] = true;
                    create(a);
                }
            },
            stop : function(a){
                /* enable cycle, if the option is set */
                if(opts.video.stopCycleOnVideoPlay){ opts.cycle.activated = true; }

                cachedVideoId = findVideoId(a[0].className);
                if(playerCache[cachedVideoId]){ a.children().hide(); }

                /* The following is a different strategy, it removes the
                 * element from the dom, and places it in a cache object.
                 * When, and if, it is needed agian, it can be pulled from
                 * the cache.
                 *
                 * Notice: if we want to go with this instead, we'll need to
                 * check for the existance of the video before we cache it,
                 * because it is possible that the video-object could recieve
                 * a stop request, whitout ever running a start.
                 *
                 *
                playerCache[cachedVideoId] = a.children().remove();
                 */
            }
        };
    }()),

    /* handle clicks wihtin the display area */
    displayClickHandler = function(event){
        // event.preventDefault();
        var $target = $(event.target),
            entry = $target.closest('.hentry'),
            bookmark = entry.find('a[rel=bookmark]')[0];

        /* add interactive elements here */
        if($target.is('a')){

            return;
        }

        else if(
                $target.is('button')
                && $target.parent('span').hasClass('pagination')
            ){
            event.preventDefault();
            if(relatedAnimating) return;

            /* the following makes it possible to paginate through the
             * related news items */
            var related = $(entries[currentItem.num]).find('.related ul'),
                relatedOffset = related.offset().left,

                /* calculate the positions of the list elements */
                positions = related.find('li').map(function(){
                    return relatedOffset - $(this).offset().left;
                }),

                /* current position */
                pos = parseInt(related.css('marginLeft'),10),
                cur = 0;

            /* We are looking for the current element, which should be the
             * element, cloest to zero, when we are subtracting the current
             * position (the sliders margin left value), from the list items
             * current position.
             *
             * This is a bit cumbersome, because an error in IE8 and FF3.5
             * where the current position wasn't exactly the same as the
             * calculated stops in the positions list; therefore we have to
             * calculate the "closest to zero", and not just compare the two
             * values. */
            var smallest;
            positions.each(function(key,value){
                /* we are looking for the element, closest to zero */
                var a = Math.abs(value - pos);
                /* compare the element with the previous smallest, and set it
                 * if it is smaller. */
                if(key == 0 || a < smallest){ smallest = a; cur = key; }
            });

            /* forward pagination */
            if($target.parent('.pagination').hasClass('next'))
                pos = cur+1 < positions.length ? cur+1 : 0;

            /* backwards pagination */
            else if($target.parent('.pagination').hasClass('previous'))
                pos = cur-1 >= 0 ? cur-1 : positions.length-1;

            relatedAnimating = true;
            related.animate(
                { 'marginLeft': positions[pos] },
                'normal',
                null,
                function(){ relatedAnimating = false; }
            );
            related.find('li').removeClass('current').end()
                   .find('li:eq('+pos+')').addClass('current');
        }

        else if($target.is('div.media') || $target.parent().is('div.media')){
            var $media = $target.closest('div.media');
            if($media.hasClass('video')) video.play($media);
        }
    },

    /* handle clicks within the table of contents area */
    contentsClickHandler = function(event){
        event.preventDefault();
        var $target = $(event.target),
            li = $target.closest('li');

        if(li[0] !== undefined){
            var n = parseInt(li[0].className.match(/item-([0-9]+)/)[1], 10);
            if(typeof n === 'number'){
                if(event.type == 'click'){
                    display.show(n);
                }
                else {
                    postpone.register( function(){ display.show(n); } );
                }
            }
        }
    };

    return this.each(function() {
        var $this = $(this);

        scroller = $this.find('.hfeed');
        entries = $this.find('.hentry', scroller);
        currentItem.width = $(entries[0]).width();

        /* Create the table of contents */
        contents.wrapper = $('<div class="feed-content"></div>');
        contents.list = $('<ol></ol>');
        contents.wrapper.append(contents.list);

        /* create a contents list from the given entries, and add it to the
         * top of the topstory teaser
         *
         * The tallestEntryHeight is finding the tallest entry in the list.
         * After the list is run through, it assign the tallest height to all
         * the elements, so that the related stories, positioned absolute to
         * the bottom, will stay at the same place.
         **/
        var tallestEntryHeight = { 'inner': 0, 'outer': 0 },
            widthOfRelatedNewsBar = (
                // First get the width af a news item pane
                entries.outerWidth() - 17
                // then subtract the width of the 'related news'-title label
                - entries.find('div.related h4').outerWidth()
                // finally subtract the width of the pagination buttons
                - (18 * 2)
            );

        entries.each(function(key){
            $item = $(this);

            /* remove tab focus from the elements. This is not that good, but
             * it fix a problem, where the focusing of an element in the
             * content area, would cause the slider to get out of place.
             *
             * This is obviously not good for people, who prefer navigating
             * with the keyboard, but the table of contents is still available
             * and links to the articles when pressing enter.
             */
            $('a,button,input',$item).attr('tabindex','-1');

            /* create a link for the table of content (toc) list */
            var link = $('<li></li>').append(
                $item.find('.entry-title a').clone()
            );

            /* remove orphans in the title */
            link.find('a:first').removeOrphan();

            /* assign classes to the toc list item, if we need further classes
             * put your class adding logic here. */
            if(key == 0){ link.addClass('current'); }

            link.addClass('item-'+key);

            if($item.hasClass('breaking')){
                mode.breaking = true;
                link.addClass('breaking')
                    .find('a:eq(0)')
                    .prepend('<strong>Breaking News:</strong>');
            }

            /* add video class, if the item has a video */
            if($item.hasClass('video')){ link.addClass('video'); }

            /* Append the link to the TOC */
            contents.list.append(link);

            /* calculate the width of the related story bar, if it is wider
             * than the related newsbar, it will add pagination arrows */
            var totalWidthOfRelatedEntries = 0;
            $item.find('.related ul li').map(function(){
                totalWidthOfRelatedEntries += $(this).innerWidth();
            });

            if(totalWidthOfRelatedEntries > widthOfRelatedNewsBar){
                /* adding pagination to the current items related field */
                $item.find('.related ul')
                .find('li:eq(0)').addClass('current').end()
                .before([
                    '<span class="pagination previous">',
                        '<button>Forrige</button>',
                    '</span>'
                ].join(''))
                .after([
                    '<span class="pagination next">',
                        '<button>Nęste</button>',
                    '</span>'
                ].join(''));
            }

            /* calculate the height of the current element, later we will
             * use this to set the height of the entire box */
            var height = $item.height();
            if(tallestEntryHeight.inner < height)
                tallestEntryHeight = {
                    'inner': height, 'outer': $item.outerHeight()
                };
        });

        /* add the table of contents to the top story teaser box */
        // $this.prepend(contents.wrapper);
        $this.find('h2:eq(0)').after(contents.wrapper);

        /* Force the box to be the same height as the tallest element in the
         * list--except when the table of contents is taller, then the box
         * will get that height.
         *
         * We calculate the 'tallestEntryHeight' in the each-loop above.
         **/
        var tocHeight = 0;
        $('li',contents.wrapper).each(function(){
            tocHeight += $(this).innerHeight();
        });

        entries.css(
            'height',
            tallestEntryHeight.inner > tocHeight
                ? tallestEntryHeight.inner
                : tocHeight - 16
        );

        /******************************************************************
         * Event handlers
         ******************************************************************/
        scroller.click(displayClickHandler);

        /* Determine if it should activate item cycling */
        var shouldActivateCycle = (
            /* If the list is set to cycle on an interval */
            opts.cycle.activated
            /* If the list is set to hold, if we are in breaking-mode, and
             * an element with the class 'breaking' is found in the list */
            && !(opts.cycle.holdOnBreaking && mode.breaking)
        );

        if(shouldActivateCycle){
            countdown = {
                wrapper : $('<div class="count-down"></div>'),
                interval : $('<div class="interval"></div>')
            };
            countdown.wrapper.append(countdown.interval);
            contents.list.find('.current').append(countdown.wrapper);

            cycle.start();
            /* pause cycling when mouse hovers the element*/
            $this.hover(
                cycle.stop,
                function(){ postpone.register(cycle.start); }
            );

            scroller.parent().mouseover(function(){
                postpone.cancel(); cycle.stop();
            });
        }

        if(opts.list.changeOnHover)
            contents.list.mouseover(contentsClickHandler);

        if(opts.list.changeOnClick)
            contents.wrapper.click(contentsClickHandler);

        if(opts.video.autoplay && $(entries[0]).find('.media').hasClass('video'))
            video.play($(entries[0]).find('.media'));

    });

};

// default options
$.fn.tv2topstoryteaser.defaults = {
    cycle : {
        /* true if the list should change to next in list after a timeout */
        activated: true,
        /* true if the list should continue after reaching an end point */
        rotate: true,
        /* timeout before changing to next in list */
        timeout: 15000,
        /* behaviours: bounce, cycle */
        behaviour: 'cycle',
        /* true if the list should be fixed on the breaking news item, when
         * we are in breaking news-mode */
        holdOnBreaking : false
    },
    list : {
        /* display the element beneeth the mouse after a timeout */
        changeOnHover : true,
        /* Change to the focused element on click
         * if false, it will use the default link behaviour */
        changeOnClick : false
    },
    video : {
        disableAutoPlayVideoAfterFirstCycle : true,
        autoplay : false,
        stopCycleOnVideoPlay : true,
        allowFullscreen : true
    },
    /* if false, the panels will not slide on change */
    enableAnimation : true
};

})(jQuery);
