var ShowcaseItem = new Class({
    'Implements': [Chain, Options],
    'Binds': ['show', 'hide', 'intro', 'outro', 'toFront', 'toBack', 'continueShow'],
    'options': {},

    /** ShowcaseItem */
    'initialize': function init (element, showcase, options) {
        this.setOptions(options);
        this.element = $(element);

        this.showcase = showcase;

        this.fade = new Fx.Tween(
            this.element, {
                'property': 'opacity',
                'link': 'chain',
                'onComplete': this.callChain.bind(this)
            });

        this.hide();
        this.toBack();
    },

    /** intro - the animation intro an item */
    intro: function intro () {
        this.fade.set(1);
        this.toFront();

        return this.continueShow();
    },

    /** outro - the animation to outro an item */
    outro: function outro () {
        this.fade
            .start(0)
            .chain(
                function () {
                    this.toBack();
                    this.continueShow();
                }.bind(this));

        return this.showcase;
    },

    /** toBack - sets the z-index of the element to some low number */
    toBack: function toBack () {
        return this.element.setStyle('z-index', 1);
    },

    /** toFront - sets the z-index of the element to some high number */
    toFront: function toFront () {
        return this.element.setStyle('z-index', 1000);
    },

    /** hide - hides the element */
    hide: function hide () {
        this.fade.set(0);
        return this.continueShow();
    },

    /** show - shows the element */
    show: function show () {
        this.fade.set(1);
        return this.continueShow();
    },

    /** callParent - calls the parents chain */
    continueShow: function continueShow () {
        return this.showcase.callChain();
    }
});

function newShowcaseItem (element, options){
    return new ShowcaseItem(element, options);
}

var Showcase = new Class({
    /** Showcase - A display widget */
    'Implements': [Chain, Options, Events],

    'options': {
        'interval': 5000, // the time (ms) between slides
        'playOnStart': false,
        'childConstructor': ShowcaseItem,
        'itemOptions': {}
        // onPlay: $empty,
        // onPause: $empty,
        // onToggle: $empty
    },

    'initialize': function init (items, options) {
        this.setOptions(options);

        if (items) {
            this.adopt(items);
            this.current = this.getItem(0);
            this.current.intro(this);
        }

        this.playing = !!this.options.playOnStart;
        if (this.playing) this.play();
    },

    items: [],

    /** transition - animate from one item to another */
    transition: function transition (fromItem, toItem) {
        fromItem = this.getItem(fromItem);
        toItem = this.getItem(toItem);

        if (toItem !== fromItem) {
            this.current = toItem;

            this.chain(
                toItem.show,
                fromItem.outro,
                toItem.intro,
                fromItem.hide
            );

            return this.callChain();
        } else return this;
    },

    /** show - transition from current to an item */
    show: function show (item) {
        return this.transition(this.current, item);
    },

    /** skipTo - shows an item, pausing the slideshow first */
    skipTo: function skipTo (item) {
        // because this pauses the slideshow, it's safe to call at any time
        this.pause();
        return this.show(item);
    },

    'next': function (){
        this.skipTo('next');
    },

    'previous': function (){
        this.skipTo('previous');
    },

    /** getItem - always returns an item */
    getItem: function getItem (i) {
        return i instanceof ShowcaseItem ? i
            :  i == 'next'               ? this.getNext()
            :  i == 'previous'           ? this.getPrevious()
            :  i in this.items           ? this.items[i]
            :  this.items[0];
    },

    /** getNext - gets the next item after current */
    getNext: function getNext () {
        return this.getItem(this.index(this.items.indexOf(this.current) + 1));
    },

    /** getPrevious - gets the next item after current */
    getPrevious: function getPrevious () {
        return this.getItem(this.index(this.items.indexOf(this.current) - 1));
    },

    index: function (i){
        return i % this.items.length + (i < 0 && this.items.length);
    },

    /** play - periodically show the next slide */
    play: function play () {
        this.clearChain();

        var playNext = function (){
            if (this.playing){
                try {
                    this.show('next')
                        .chain(
                            this.wait(this.options.interval),
                            playNext
                        );
                } catch (err) {}
            }
        }.bind(this);

        this.playing = true;
        this.fireEvent('play');
        this.fireEvent('toggle');

        playNext.delay(this.options.interval);
    },

    // pause - stops the showcase
    pause: function pause () {
        this.clearChain();
        this.playing = false;
        this.fireEvent('pause');
        this.fireEvent('toggle');
    },

    toggle: function (){
        this[this.playing ? 'pause' : 'play']();
    },

    wait: function(period){
        return function(){
            var resume = this.callChain.bind(this);
            resume.delay(period);
        };
    },

    /** adopt - new item */
    adopt: function adopt (items) {
        var options = this.options.itemOptions;
        this.items.extend(
            $$(items).map(
                function make (item) {
                    return item instanceof ShowcaseItem ? item
                        :  new this.options.childConstructor(item, this, options);
                }, this));
    }
});



