/**
 * Class rSlidePane
 *
 * Takes a set of elements, crops to one (or more) and animates shifting of 
 * those elements into and out of visiblity. The elements are wrapped by a 
 * 'shift' element which is wrapped in a 'crop' element.
 *
 * @copyright Cuban Council
 * @author Chris Forrette
 */

var rSlidePane = new Class({
        
    options: {
        cropping: "static", // Cropper element animation, static/auto/auto-width/auto-height
        initial: 0, // Index item to start at
        offset: 0, // Item display offset, e.g. if you want 2 items to show at a time, etc.
        mode: "horizontal", // Slide direction: horizontal or vertical
        positionalOffset: 0, // Positioning offset for shifter element in pixels
        fxOptions: {}, // FX options to pass to shift and crop (if applicable) FX objects
        size:  "auto", // Crop size, either 'auto' (sets to size of first element) or an x/y object, e.g.: {x: 100, y: 100}
        onShiftStart: Class.empty,
        onShiftComplete: Class.empty
    },
    
    /**
     * Initialize
     *
     * @param elements  Array   Array of elements to crop, e.g. via $$()
     * @param options   Object  (optional) Object of options to override defaults
     */
    
    initialize: function(elements, options) {
        
        if (elements.length > 0) {
            
            this.currentItem = 0;
            
            this.setOptions(options);
            
            if (!this.items) {
                this.setItems(elements); // Handle elements
            }
                        
            // Crop element styles
            
            var aCropStyles = {
                "display": "block",
                "position": "relative",
                "overflow": "hidden"
            };
            
            // Set crop element width
            
            if (this.options.size.x) {
                aCropStyles.width = this.options.size.x;
            } else {
                aCropStyles.width = this.items[0].size.x;
                
                if (this.options.offset > 0 && this.options.mode != "vertical") {
                    aCropStyles.width = aCropStyles.width * (this.options.offset + 1);
                }
            }
            
            // Set crop element height
            
            if (this.options.size.y) {
                aCropStyles.height = this.options.size.y;
            } else {
                aCropStyles.height = this.items[0].size.y;
                
                if (this.options.offset > 0 && this.options.mode == "vertical") {
                    aCropStyles.height = aCropStyles.height * (this.options.offset + 1);
                }
            }
            
            // Create crop element
            
            this.oCrop = new Element("div", {
                "class": "slidepane-wrapper",
                "styles": aCropStyles
            });
                        
            // Shifter element styles
            
            var styles = {
                "display": "block",
                "left": 0,
                "position": "absolute",
                "overflow": "hidden",
                "top": 0
            };
            
            // Shifter element size and position
            
            if (this.options.mode == "vertical") {
                styles.height = this.totalSize;
                styles.top = this.options.positionalOffset;
                styles.width = aCropStyles.width;
            } else {
                styles.height = aCropStyles.height;
                styles.left = this.options.positionalOffset;
                styles.width = this.totalSize;
            }
            
            // Create shifter element
            
            var oShift = new Element("div", {
                "class": "slidepane-shifter",
                "styles": styles
            });
            
            // Inject shift and cropper elements

            this.oCrop.injectBefore(elements[0]);
            this.oShift = oShift.injectBefore(elements[0]);
            this.oShift.adopt(elements);
            this.oCrop.adopt(this.oShift);

            // Shift FX
            
            var fxOptions = this.options.fxOptions;
            fxOptions.property = (this.options.mode == "vertical") ? "top" : "left";

            fxOptions.onStart = function () {
                this.fireEvent('onShiftStart', this.currentItem);
            }.bind(this);

            fxOptions.onComplete = function () {
                this.fireEvent('onShiftComplete', this.currentItem);
            }.bind(this);
            
            this.oShiftFx = new Fx.Style(this.oShift, fxOptions.property, fxOptions);
            
            // Crop FX
            
            var fxOptions = this.options.fxOptions;
            
            if (this.options.cropping == "auto-width" || this.options.cropping == "auto-height") {
                var property = (this.options.cropping == "auto-width") ? "width" : "height";
                fxOptions.property = property;
                this.oCropFx = new Fx.Style(this.oCrop, fxOptions.property, fxOptions);
            }
                        
            // Initial
            
            if (this.options.initial > 0) {
                this.set(this.options.initial);
            } else {
                this.setCrop(0);
            }
            
        }
        
    },
    
    /**
     * Takes the group of elements to be cropped and extracts information from them
     * into this.items
     *
     * @param elements  Array   Array of elements to be cropped via $$()
     */
    
    setItems: function(elements) {
        var totalSize = 0;
        
        this.elements = elements;
        this.items = [];
                
        this.elements.each(function(item, index) {
            
            var size = item.getSize().size;
            
            var pos = (totalSize > 0) ? "-" + totalSize : 0;
            
            var item = {
                item: item,
                size: size,
                position: pos
            };
            
            if (this.options.mode == "vertical") {
                totalSize += size.y;
            } else {
                totalSize += size.x;
            }
            
            this.items.push(item);
            
        }, this);
        
        this.totalSize = totalSize;
        
        return this.items;
        
    },
    
    /**
     * Calculates shift position considering item position and 'positionalOffset' option
     *
     * @param index Integer Index of item in question from this.items
     */
    
    getShiftPosition: function(index) {
        return (this.items[index].position.toInt() + this.options.positionalOffset.toInt());
    },
    
    /**
     * Returns index to shift to considering 'offset' option
     *
     * @param index Integer Index of item in question from this.items
     */
    
    getShiftIndex: function(index) {
        var idx = index;
        
        if (idx >= (this.items.length - this.options.offset)) {
            idx = this.items.length - this.options.offset - 1;
        }
                
        if (idx < 0) {
            idx = 0;
        }
        
        return idx;
    },
    
    /**
     * Animate shift to specified index
     *
     * @param index Integer Index of item from this.items to shift to
     */
    
    shift: function(index) {
        if (this.currentItem != index) {
            var idx = this.getShiftIndex(index);
            this.setCrop(idx);
            this.oShiftFx.start(this.getShiftPosition(idx));
            this.currentItem = idx;
        }
    },
    
    /**
     * Set item from specified index as visible with no animation
     *
     * @param index Integer Index of item from this.items to set visible
     */
    
    set: function(index) {
        var idx = this.getShiftIndex(index);
        this.setCrop(idx);
        this.oShiftFx.set(this.getShiftPosition(idx));
        this.currentItem = idx;
    },
    
    /**
     * Whether there is a next item or not
     * @return Boolean
     */
    
    hasNext: function() {
        return this.currentItem < this.items.length;
    },
    
    /**
     * Shift to next item, if there is one
     */
    
    next: function() {
        if (this.hasNext()) {
            this.shift(this.currentItem + 1);
        }
    },
    
    /**
     * Whether there is a previous item or not
     * @return Boolean
     */
    
    hasPrev: function() {
        return this.currentItem > 0;
    },
    
    /**
     * Shift to previous item, if there is one
     */
     
    prev: function() {
        if (this.hasPrev()) {
            this.shift(this.currentItem - 1);
        }
    },
    
    /**
     * Bind a set of specified elements to shift to 'next' when clicked
     *
     * @param elements  Array   Array of elements, e.g. via $$(), to be bound to 'next' method when clicked
     */
    
    setNextButtons: function(elements) {
        
        if ($type(elements) != 'array') {
            elements = [elements];
        }
        
        if (elements.length > 0) {
            elements.each(function(item, index) {
                item.addEvent('click', function(evt) {
                    var evt = new Event(evt);
                    evt.stop();
                    this.next();
                }.bind(this));
            }, this);
        }
    },
    
    /**
     * Bind a set of specified elements to shift to 'prev' when clicked
     *
     * @param elements  Array   Array of elements, e.g. via $$(), to be bound to 'prev' method when clicked
     */
    
    setPrevButtons: function(elements) {
        
        if ($type(elements) != 'array') {
            elements = [elements];
        }
        
        if (elements.length > 0) {
            elements.each(function(item, index) {
                item.addEvent('click', function(evt) {
                    var evt = new Event(evt);
                    evt.stop();
                    this.prev();
                }.bind(this));
            }, this);
        }
    },
    
    /**
     * Bind a set of specified elements to shift to a specific index based on their position in the array
     *
     * @param elements  Array   Array of elements, e.g. via $$(), to be bound to the 'shift' method, passing 
     *                          their position in their array as the index to shift to
     */
    
    setIndexButtons: function(elements) {
        if (elements.length > 0) {
            var i = 0;
            var obj = this;
            while (i < this.items.length) {
                var idx = elements[i].retrieve("slidepane_index");
                idx = idx ? idx : i;
                elements[i].addEvent('click', function(evt, idx) {
                    var evt = new Event(evt);
                    evt.stop();
                    this.shift(idx);
                }.bindWithEvent(this, idx));
                i++;
            }
        }
    },
    
    /**
     * If this.options.cropping is not static, this handles the width/height animation when 'shift' is called
     *
     * @param index Integer Index of item in this.items to adjust for
     */
    
    setCrop: function(index) {

        var oCropSize = this.oCrop.getSize().size;

        if (this.options.cropping == "auto-width") {
            var tgt = this.items[index].size.x;
            if (oCropSize.x != tgt) {
                this.oCropFx.start(tgt);
            }
        } else if (this.options.cropping == "auto-height") {
            var tgt = this.items[index].size.y;
            if (oCropSize.y != tgt) {
                this.oCropFx.start(tgt);
            }
        } else if (this.options.cropping == "auto") {
            var tgt_x = this.items[index].size.x;
            var tgt_y = this.items[index].size.y;
            if (oCropSize.x != tgt_x || oCropSize.y != tgt_y) {
                this.oCropFx.start({
                    "height": tgt_y,
                    "width": tgt_x
                });
            }
        }
    }
    
});

rSlidePane.implement(new Events, new Options);
