/*
    SiteComponents version:
    6.8.0.1, tag SC_6_8_0_1, created Fri Aug 20 11:18:33 +0200 2010

    Disclaimer
    
    While we make every effort to ensure that this code is fit for its intended
    purpose, we make no guarantees as to its functionality. CoreTrek AS will
    accept no responsibility for the loss of data or any other damage or
    financial loss caused by use of this code.


    Copyright
    
    This programming code is copyright of CoreTrek AS. Permission to run this
    code is given to approved users of CoreTrek's publishing system CorePublish.
    
    This source code may not be copied, modified or otherwise repurposed for use
    by a third party without the written permission of CoreTrek AS.
    
    Contact webmaster@coretrek.com for information.
    
*/

/*jslint laxbreak: true, sub: true, white: false, browser: true,
onevar: false, nomen: false, noindent: true, eqeqeq: false, plusplus: false,
forin: true */
/*global siteComponentsConfig: false, getSiteComponentsConfig: false,
Ajax: false, $$: false, $: false, Element: false, window: false, Class: false,
Effect: false, lightbox: false, PeriodicalExecuter: false, Event: false,
CtCookie: false, cpKeywords: false, CtTooltip: false, Prototype: false,
cpWriteMediaObject: false, getThemeName: false, Hash: false */

/*

    ============================================================================
    IMPORTANT! This javascript is dependent on Prototype. If Scriptaculous is
               available the tooltip will use some nice effects.
    ============================================================================

    JavaScript enhancement for the star rating
    
*/

var StarRating = Class.create({
	
    /**
     * Initialize the star rating from the plain HTML form
     */
    initialize: function(element) {
        this.container = element;
        
        // This must reflect the number of stars in the image. However the 
        // star rating backend requires that this always be 5  
        this.numberOfStars = 5;
        
        this.starBackgroundUrl = '/themes/' + getThemeName() + '/images/rating/stars.png';
        
        this.starImage = new Image();
        Event.observe(this.starImage, 'load', this.imageLoadListener.bindAsEventListener(this));
        
        this.starImage.src = this.starBackgroundUrl;
    },
    
    /**
     * Set this.box to the position of the star rating layer. The position is
     * used so that we know when a mouse out event is actually triggered when
     * the pointer leaves the star "container", and not only moved over a
     * different star layer.
     */
    initBoxPosition: function() {
        var starsOffset = this.container.cumulativeOffset();
        var starDim = this.container.getDimensions();
        
        this.box = {
            left: starsOffset['left'],
            top: starsOffset['top'],
            right: (starsOffset['left'] + starDim['width']),
            bottom: (starsOffset['top'] + this.getBackgroundHeight())
        };
    },
    
    reinitializeElements: function() {
        this.initStyle();
        this.initBoxPosition();
    },
    
    getBackgroundWidth: function() {
        return this.starImage.width;
    },
    
    getBackgroundHeight: function() {
        // The image should always contain three star rows evenly spaced
        return this.starImage.height / 3;
    },
    
    getRatingScale: function() {
        return this.getBackgroundWidth() / 100;
    },
    
    getInitialRatingWidth: function() {
        return this.initialStars * this.getStarWidth();
    },
    
    getStarWidth: function() {
        return this.getBackgroundWidth() / this.numberOfStars;
    },
    
    getStarFromPosition: function(position) {
        var width = position['left'] - this.box.left;
        return this.getStarFromWidth(width);
    },
    
    getStarFromWidth: function(width) {
        return Math.ceil(width / this.getStarWidth());
    },
    
    getStarFromRating: function(rating) {
        // This code is duplicated _from_ CtEntityRatingStar::getPercentRating.
        // If the rating type score rsolving is changed, this method must
        // be updated to reflect the changes
        if(rating >= 80) {
            return 5;
        } else if(rating >= 60) {
            return 4;
        } else if(rating >= 40) {
            return 3;
        } else if(rating >= 20) {
            return 2;
        } else {
            return 1;
        }
    },
    
    getWidthFromStar: function(star) {
        return star * this.getStarWidth();
    },
    
    /**
     * Initialize the star layers. This positions the three star layers above
     * one another and fix background images.
     */
    initStyle: function() {
        this.container.setStyle({
            width: this.getBackgroundWidth() + 'px',
            position: 'relative',
            padding: 0,
            margin: 0,
            background: 'none' // just in case the container has a spinner bg
        });
                
        this.gray.setStyle({
            position: 'absolute',
            top: '0px',
            left: '0px',
            width: this.getBackgroundWidth() + 'px',
            height: this.getBackgroundHeight() + 'px',
            background: 'url('+this.starBackgroundUrl+') no-repeat'
        });
        
        this.blue.setStyle({
            position: 'absolute',
            top: '0px',
            left: '0px',
            height: this.getBackgroundHeight() + 'px'
        });
        
        this.yellow.setStyle({
            position: 'absolute',
            top: '0px',
            left: '0px',
            height: this.getBackgroundHeight() + 'px'
        });
    },
    
    setInitialStars: function() {
        this.blue.setStyle({
            background: 'url('+this.starBackgroundUrl+') no-repeat 0 -' + this.getBackgroundHeight() + 'px',
            width: this.getInitialRatingWidth() + 'px'
        });
        
        this.yellow.setStyle({
            background:'url('+this.starBackgroundUrl+') no-repeat 0 -' + this.getBackgroundHeight() * 2 + 'px',
            width:'0px'
        });
        
        this.label.setStyle({
            paddingTop: this.getBackgroundHeight() + "px"
        });
    },
    
    /**
     * Add needed listeners to the star.
     */
    addListeners: function() {
        Event.observe(this.container, 'mousemove', this.mousemoveListener.bindAsEventListener(this));
        Event.observe(this.container, 'click', this.clickListener.bindAsEventListener(this));
        Event.observe(this.container, 'mouseout', this.mouseoutListener.bindAsEventListener(this));    
    },
    
    /**
     * Removes event listeners added by addListeners()
     */
    removeListeners: function() {
        this.container.stopObserving('mousemove');
        this.container.stopObserving('mouseout');
        this.container.stopObserving('click');
    },
    
    /**
     * Function sets a new rating for the entity. The transition from previous
     * to new rating is animated. After setting new rating, the listeners is
     * removed so that it is not possible to vote again.
     */
    setRating: function(req) {
        var summary = req.responseText.evalJSON();
        var width = this.getWidthFromStar(this.getStarFromRating(summary['avg']));
        
        try {       
            new Effect.Fade(this.yellow, { duration: 0.1, queue: { position: 'end', scope: 'starrating' } });
            new Effect.Appear(this.blue, { duration: 0.1, queue: { position: 'end', scope: 'starrating' } });
            new Effect.Morph(this.blue, {
                style: 'width: ' + width + 'px',
                duration: 1,
                queue: { position: 'end', scope: 'starrating' }
            });
            this.updateLabel(summary['count']);
        } catch (err) {
            this.yellow.hide();
            this.blue.show();
            this.blue.setStyle({
                width: width + "px"
            });
        }
        
        this.removeListeners();
    },
    
    updateLabel: function(count) {
        var labelText = this.labelText.replace(/\{0\}/, count);
        this.label.update(labelText);
    },
    
    /**
     * The handler will return with error when a user is trying to vote but
     * is not allowed. This can happen e.g. when the rating is limited to one
     * per IP address or one per user ID (or any other custom limitations).
     * We handle this simply by removing event listeners from the stars and
     * reverting to the original blue star indicator. 
     */ 
    handleRatingError: function(req) {
        // remove listeners and cancel all running effects 
        this.removeListeners();
        this.cancelEffects();
        
        try {
            new Effect.Fade(this.yellow, {
                    duration: 0.2,
                    queue: { position: 'end', scope: 'starrating' }
                }
            );
            new Effect.Appear(this.blue, {
                    duration: 0.2,
                    queue: { position: 'end', scope: 'starrating' }
                }
            );
        } catch (Exeption) {
            this.yellow.hide();
            this.blue.show();
        }
    },
    
    /**
     * Start an animation for the hoved layer current position to the mouse
     * hover position.
     */
    animate: function() {
        this.cancelEffects();
        this.yellow.show();
        this.yellow.setOpacity(1);
    
        if(this.blue.visible()) {
            this.blue.hide();
        }
        
        try {
            this.mousemoveEffect = new Effect.Morph(this.yellow, {
                style: 'width: ' + (this.getStarWidth() * this.hoverStar) + 'px',
                duration: 0.1,
                queue: { position: 'end', scope: 'starrating' }
            });
        } catch (err) {
            this.yellow.setStyle({
                width: (this.getStarWidth() * this.hoverStar) + "px"
            });
        }
    },
    
    pointerInsideBox: function(event) {
        return !(event.pointerX() <= this.box['left'] ||
               event.pointerY() <= this.box['top'] ||
               event.pointerX() >= this.box['right'] ||
               event.pointerY() >= this.box['bottom']);
    },
    
    // ========================================================================
    // Event listeners
    
    /**
     * Triggeren when moving the mouse over the star rating box
     */ 
    mousemoveListener: function(event) {
        if(this.pointerInsideBox(event)) {
	        var leftInBox = event.pointerX() - event.element().positionedOffset()['left'];
	        var star = this.getStarFromPosition({left:event.pointerX()});
	        
	        if(this.hoverStar != star) {
	            this.hoverStar = star;
	            if(typeof this.timeout != 'undefined') {
	                clearTimeout(this.timeout);
	            }
	            
	            // We set the animate call with a timeout, this is to prevent
	            // starting the animation before the user has settled the mouse
	            // over a star to rate
	            //this.timeout = setTimeout(this.animate.bind(this), 25);
	            this.animate();
	        }
        }
    },
    
    /**
     * Triggered when moving the mouse out of the star rating box
     */
    mouseoutListener: function(event) {
        if(!this.pointerInsideBox(event)) {
            try {
                // Cancel any running effects
                this.cancelEffects();
                
                new Effect.Fade(this.yellow, { duration: 0.3, queue: { position: 'end', scope: 'starrating' } });
                new Effect.Appear(this.blue, { duration: 0.3, queue: { position: 'end', scope: 'starrating' } });
            } catch (err) {
                this.yellow.setStyle({
                    width: "0px"
                });
                this.blue.show();
            }
            
            this.hoverStar = null;
        }
    },
    
    cancelEffects: function() {
        clearTimeout(this.timeout);
        try {
            // Get and cancel all running starrating effects
	        var queue = Effect.Queues.get('starrating');
	        queue.each(function(effect) { effect.cancel(); });
        } catch (err) {
            // Scriptsculous is not available, just ignore
        } 
    },
    
    /**
     * Triggered when user clicks in the star rating box.
     */
    clickListener: function(event) {
        var ratingValue;
        
        switch(this.getStarFromPosition({left: event.pointerX()})) {
            case 1:
                ratingValue = 0;
                break;
            case 2:
                ratingValue = 25;
                break;
            case 3:
	            ratingValue = 50;
	            break;
            case 4:
	            ratingValue = 75;
	            break;
            case 5:
	            ratingValue = 100;
	            break;
            default:
                // Invalid rating ignore
                return;
        }
        
        this.params.set('rating', ratingValue);
        
        new Ajax.Request(this.form.getAttribute('action'), {
            parameters: this.params,
            onSuccess: this.setRating.bindAsEventListener(this),
            onFailure: this.handleRatingError.bindAsEventListener(this)
        });
    },
    
    imageLoadListener: function(event) {
        // Store the original form
        this.form = this.container.select('form').first();
        this.params = new Hash(this.form.serialize(true));
        
        // Get the amount of stars pre selected
        this.initialStars = this.container.select('input[name="initial_stars"]')[0].value;
        this.initialRatingCount = this.container.select('input[name="rating_count"]')[0].value;
        this.labelText = this.container.select('input[name="rating_count_label"]')[0].value;
        
        // First we insert all three star container <div>'s and set the initial styles
        this.gray = $(this.container.update("<div></div><div></div><div></div><div class=\"rating-label\"></div>").firstChild);
        this.yellow = this.gray.next();
        this.blue = this.yellow.next();
        this.label = this.blue.next();
        
        this.updateLabel(this.initialRatingCount);
        
        this.initStyle();
        this.setInitialStars();
        this.addListeners();
        
        this.initBoxPosition();
    }

});

