var ui = window.ui || {};

ui.SlideViewer = Class.create({
    
    initialize: function(args) {
        
        // required args
        this.mover = args.mover.addClassName(this.CSS.mover);
        this.clipper = args.clipper.addClassName(this.CSS.clipper);
        this.sizeOfSlide = args.sizeOfSlide;
        this.sizeOfClipper = this._getClipperSize();
        this.onComplete = args.onComplete || Prototype.emptyFunction;
        
        // optional
        this.speed = args.speed || 150;
        
        // properties
        this.slides = [];
        this._locked = false;
        this._sizeType = 'width';
        this._posType = 'left';
    },
    
    // insert content
    push: function(content) {
        this._add();
        var slide = this._newSlide(content);
        this.mover.insert(slide);
        this.slides.push(slide);
        return slide;
    },
    
    unshift: function(content) {
        // TODO: do same kind of logic as push
        var slide = this._newSlide(content);
        this.mover.insert({top: slide});
        this._add(true);
        this.slides.unshift(slide);
        return slide;
    },
    
    // remove content
    pop: function() {
        if(!this.slides.size()) {
            return;
        }
        var slide = this.slides.pop().remove();
        this._remove();
        return slide;
    },
    
    shift: function() {
        if(!this.slides.size()) {
            return;
        }
        var slide = this.slides.shift().remove();
        this._remove(true);
        return slide;
    },
    
    clear: function() {
        this._clear();
        this.mover.update(''); // TODO: remove differently
        this.slides.clear();
    },
    
    // navigate slides
    next: function() {
        this._navigate(1);
    },
    
    prev: function() {
        this._navigate(-1);
    },
    
    navigate: function(by, onComplete) {
       if(onComplete && Object.isFunction(onComplete)) {
               this.onComplete = onComplete;
       }
       this._navigate(by);
    },
    
    setSizeOfSlide: function(sizeOfSlide) {
        this.sizeOfSlide = sizeOfSlide;
    },

    getMover: function() {
        return this.mover;
    },
    
    getClipper: function() {
        return this.clipper;
    },
    
    // PRIVATE METHODS
    _clear: function() {
        this.mover.style[this._sizeType] = '';
        this.mover.style[this._posType] = '';
    },
    
    _remove: function(shift) {
        this.mover.style[this._sizeType] = (this._getMoverSize() - this.sizeOfSlide) + 'px';
        this.mover.style[this._posType] = (this.mover.positionedOffset()[this._posType] + (shift ? this.sizeOfSlide : 0)) + 'px';
    },
    
    _add: function(unshift) {
        this.mover.style[this._sizeType] = (this._getMoverSize() + this.sizeOfSlide) + 'px';
        this.mover.style[this._posType] = (this.mover.positionedOffset()[this._posType] - (unshift ? this.sizeOfSlide : 0)) + 'px';
    },
    
    _newSlide: function(content) {
        var elem = new Element('DIV', {'class': this.CSS.slide});
        elem.style[this._sizeType] = this.sizeOfSlide + 'px';
        elem.insert(content);
        return elem;
    },
    
    _getClipperSize: function() {
        return this.clipper.getWidth();
    },
    
    _getMoverSize: function() {
        return this.mover.getWidth();
    },
    
    _navigate: function(by) {
        if(this._locked) {
            return;
        }
        if(!by) {
            return;
        }
        this._locked = true;
        var endValue, maxValue = 0, minValue = -((this.slides.size()-1) * this.sizeOfSlide), onComplete = this.onComplete;
        endValue = this.mover.positionedOffset()[this._posType] - (by * this.sizeOfSlide);
        endValue = (endValue < minValue ? minValue : endValue);
        endValue = (endValue > maxValue ? maxValue : endValue);
        
        this.mover.setStylePeriodically({
                property:     this._posType,
                endValue:     endValue,
                increment:    this.speed,
                units:        'px',
                onComplete:   function() {
                                  this._locked = false;
                                  if(onComplete && Object.isFunction(onComplete)) {
                                      onComplete(by);
                                  }
                              }.bind(this)
        });
    },
    
    // Class Props
    CSS: {
        slide: 'slide',
        mover: 'mover',
        clipper: 'clipper'
    }
        
});
ui.MouseView = Class.create({

    initialize: function(args) {
        this.element = args.element;
        this.subject = args.subject;
        this.speed = args.speed || 0.3;
        
        // private
        this._pe = {};
        
        // register event handlers
        this._events();
    },

    fadeTo: function(endValue) {
        if(this._pe[endValue]) return;
        Object.values(this._pe).invoke('stop');
        this._pe = {};
        this._pe[endValue] = this.element.setStylePeriodically({
                property: 'opacity',
                endValue: endValue,
                increment: this.speed
        });
    },

    mousemove: function(evt) {
        (Event.findElement(evt).descendantOf(this.subject) ? this.fadeTo(1) : this.fadeTo(0));
    },
    
    _events: function() {
        $(document).observe('mousemove', this.mousemove.bind(this));
    }

});


Element.addMethods(['DIV', 'SPAN', 'IMG'],{
        
        setStylePeriodically: function(element, args) {
            
            // required args
            var property = args.property;
            var endValue = args.endValue;
            var increment = args.increment;
            
            // optional args
            var units = args.units || '';
            var onComplete = (args.onComplete && Object.isFunction(args.onComplete) ? args.onComplete : Prototype.emptyFunction);
            var currentValue = args.startValue || parseFloat(element.getStyle(property));
            
            // function that will make the change over time
            var getUpdater = function() {
                var updaters = {
                    add: function() {
                        currentValue = (currentValue + increment <= endValue ? currentValue + increment : endValue);
                        return currentValue >= endValue;
                    },
                    sub: function() {
                        currentValue = (currentValue - increment >= endValue ? currentValue - increment : endValue);
                        return currentValue <= endValue;
                    }
                };
                return (Math.min(currentValue, endValue) == currentValue ? updaters.add : updaters.sub);
            };
            var updater = getUpdater();
            
            // mechanism that will make the change over time
            var _pe = new PeriodicalExecuter(
                function(pe) {
                    var done = updater();
                    element.style[property] = currentValue + units;
                    if(done) {
                        pe.stop();
                        onComplete();
                    }
                },
            0.050);
            return _pe;
        }

});

