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],

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

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

        if (items) {
            this.adopt(items);
            this.current = this.getItem(1);
            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);
    },

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

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

    /** play - periodically show the next slide */
    play: function play () {
        var playNext = function () {
            if (this.playing){
                this.show('next')
                    .chain(
                        this.wait(this.options.interval),
                        playNext
                    );
            }
        }.bind(this);
        this.playing = true;

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

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

    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));
    }
});


/**
 * An example of a ShowcaseItem subclass
 */
var SlidingDoorItem = new Class({
    Extends: ShowcaseItem,

    options: {
        'slideRange': [40, 100],
        'slideUnit': '%'
    },

    initialize: function  (element, showcase, options) {
        this.setOptions(options);
        this.parent(element, showcase, options);

        this.description = this.element.getElement('.description');

        this.slide = new Fx.Tween(
            this.description, {
                'property': 'width',
                'unit': this.options.slideUnit,
                'link': 'chain',
                'onComplete': this.callChain.bind(this)
            });

        this.slide.set(this.options.slideRange[1]);
    },

    intro: function intro () {
        this.fade.set(1);
        this.toFront();
        this.slide.start(this.options.slideRange[0]);
        return this.chain( this.continueShow );
    },

    outro: function outro () {
        this.chain(
            function () { return this.slide.start(this.options.slideRange[1]); },
            function () { return this.fade.start(0); },
            function () {
                this.toBack();
                return this.callChain();
            },

            this.continueShow
        ).callChain();
        return this.showcase;
    }
});


var LoadingItem = new Class({
    /** LoadingItem - handles image loading */
    'Extends': ShowcaseItem,
    'Implements': [Options, Events],
    'options': {},

    'initialize': function init (link, showcase, options) {
        var element = new Element(
            'div', {
                'class': 'showcaseItem',
                'text': 'loading...'
            });
        this['super'](element, showcase, options);

        this.loading = false;
        this.src = link.get('href');
        this.description = link.get('text');
        this.element.replaces(link);
    },

    /** load - loads the image and fires the onload event */
    load: function load () {
        this.loading = true;
        this.image = new Asset.image(
            this.src, {
                'onload': function () {
                    this.element.grab(this.image);
                    this.fireEvent('load');
                }.bind(this),
                'alt': this.description
            });

        return this;
    },

    /** waitForLoad - loads the image and continues the chain */
    waitForLoad: function waitForLoad (callback) {
        if (!this.loading) {
            this.addEvent('load', callback || this.continueShow);
            this.load();
        }
        return this;
    },

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