/* 
 * 
 * Copyright (c) 2007 e-nova technologies pvt. ltd. (kevin.muller@enova-tech.net || http://www.enova-tech.net)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *            __             ___      ___    __  __     __     
 *          /'__`\    __   /' _ `\   / __`\ /\ \/\ \  /'__`\   
 *         /\  __/  /\__\  /\ \/\ \ /\ \_\ \\ \ \_/ |/\ \_\.\_ 
 *  (o_    \ \____\ \/__/  \ \_\ \_\\ \____/ \ \___/ \ \__/.\_\    _o)
 *  (/)_    \/____/         \/_/\/_/ \/___/   \/__/   \/__/\/_/   _(\)
 *       
 *           Prevents Headaches !  
 * 
 * JMyCarousel is inspired and based on JCarouselLite, an original concept by Ganeshji Marwaha
 *  
 *
 * $LastChangedDate: 2007-06-22 20:08:34 -0500 (Thu, 22 Nov 2007) $
 * $Rev: 15 $
 *
 * Version: 0.1
 */

(function ($) {                  // Compliant with jquery.noConflict()
    $.fn.jMyCarousel = function (o) {
        o = $.extend({
            btnPrev:null, // previous button customization
            btnNext:null, // next button customization
            mouseWheel:true, // shall the carousel handle the mousewheel event to animate ?
            auto:false, // shall the carousel start automatically

            speed:500, // speed in ms of the animation.
            easing:'linear', // linear animation.

            vertical:false, // set the carousel in a vertical mode
            circular:true, // run in circular mode. Means : images never reach the end.
            visible:'4', // size of the carousel on the screen. Can be in percent '100%', in pixels '100px', or in images '3' (for 3 images)
            start:0, // position in pixels that the carousel shall start at
            scroll:1,

            maxwidth:100, //

            step:50, // value in pixels, or "default"
            eltByElt:false, // if activated, the carousel will move image by image, not more, not less.
            evtStart:'mouseover', // start event that we want for the animation (click, mouseover, mousedown, etc..)
            evtStop:'mouseout', // stop event that we want for the animation (blur, mouseout, mouseup, etc..)
            beforeStart:null, // Not used yet
            afterEnd:null            // Not used yet
        }, o || {});

        return this.each(function () {                           // Returns the element collection. Chainable.
            var running = false, animCss = o.vertical ? "top" : "left", sizeCss = o.vertical ? "height" : "width";
            var div = $(this), ul = $("ul", div), tLi = $("li", ul), tl = tLi.size(), v = o.visible;
            var mousewheelN = 0; // will help for the mousewheel effect (to count how many steps we have to walk ahead)
            var defaultBtn = (o.btnNext === null && o.btnPrev === null) ? true : false;
            var cssU = (v.toString().indexOf("%") != -1 ? '%' : (v.toString().indexOf("px") != -1) ? 'px' : 'el');
            var direction = null; // used to keep in memory in which direction the animation is moving

            // circular mode management
            // we add at the end and at the beginning some fake images to make the circular effect more linear, so it never breaks
            // It is still possible to improve the memory management by adding exactly the number of images requested.
            if (o.circular) {
                var imgSet = tLi.clone();
                ul.prepend(imgSet).append(imgSet.clone());
            }

            var li = $("li", ul);								// list
            div.css("visibility", "visible");
            li.css("overflow", "hidden")// If the list item size is bigger than required
                .css("float", o.vertical ? "none" : "left")// Horizontal list
                .children().css("overflow", "hidden");          // If the item within li overflows its size, hide'em
            if (!o.vertical) {
                li.css("display", "inline");
            }		// IE double margin bug - rooo..
            if (li.children().get(0).tagName.toLowerCase() == 'a' && !o.vertical) {
                li.children().css('float', 'left');
            }
            if (o.vertical && jQuery.browser.msie) {                // Hack IE (again..) / purpose is to cancel the white space below the image when the carousel is in vertical mode
                // The issue comes up when li is not in float:left. so we put it in float:left and adjust the size
                li.css('line-height', '4px').children().css('margin-bottom', '-4px');
            }


            ul.css("margin", "0")// Browsers apply default margin
                .css("padding", "0")// and padding. It is reset here.
                .css("position", "relative")// IE BUG - width as min-width
                .css("list-style-type", "none")// We dont need any icons representing each list item.
                .css("z-index", "1");                           // IE doesnt respect width. So z-index smaller than div

            div.css("overflow", "hidden")// Overflows - works in FF
                .css("position", "relative")// position relative and z-index for IE
                .css("z-index", "2")// more than ul so that div displays on top of ul
                .css("left", "0px");                            // after creating carousel show it on screen

            var liSize = o.vertical ? height(li) : width(li);   // Full li size(incl margin)-Used for animation
            var liSizeV = o.vertical ? elHeight(li) : height(li);	// size of the main layer, in its side
            if (o.visible == '58px') {
                liSize = 85;
                liSizeV = 95;
            }
            if (o.visible == '398px')
                liSizeV = 58;
            else
                liSizeV = 95;
            var curr = o.start;   								// Current position in pixels
            var nbAllElts = li.size();							// Total number of items
            var ulSize = liSize * nbAllElts;                   	// size of full ul(total length, not just for the visible items)

            if (o.visible == '398px') {
                ulSize = o.maxwidth;
                liSize = Math.round(o.maxwidth / 3 / tl);
            }

            var nbElts = tl;									// number of elements (only visible items)
            var eltsSize = nbElts * liSize;						// size of the visible elements only
            var allEltsSize = nbAllElts * liSize;				// Total size of the elements
            //var jmcSize = jmcSize();							// Size of the carousel
            var step = o.step == 'default' ? liSize : o.step;	// step size

            //debug("liSize=" + liSize + "; liSizeV=" + liSizeV + "; curr=" + curr + "; visible : " + liSize * v); // debug
            o.btnPrev = defaultBtn ? $('<input type="button" class="' + (o.vertical ? 'up' : 'prev') + '" />') : $(o.btnPrev);
            o.btnNext = defaultBtn ? $('<input type="button" class="' + (o.vertical ? 'down' : 'next') + '" />') : $(o.btnNext);
            var prev = o.btnPrev;
            var next = o.btnNext;

            /******* Buttons **********/
            if (defaultBtn && o.auto !== true) {                     //Add buttons when necessary (In default mode and not auto)
                prev.css({'opacity':'0.6'});
                next.css({'opacity':'0.6'});
                div.prepend(prev);
                div.prepend(next);
                o.btnPrev = prev;
                o.btnNext = next;
            }

            // Element by element management (eltBYElt = true)
            if (o.eltByElt) {
                step = liSize;									// the step size is necessarily the size of the element
                if (o.start % liSize !== 0) {                        // If a start position was given and was not exactly positionned between 2 images
                    var imgStart = parseInt(o.start / liSize);	// we adjust it
                    curr = o.start = (imgStart * liSize);		// we set the starting position at a fixed point, between 2 images.
                }
            }

            // Adjust the start position in case of circular mode
            if (o.circular) {
                o.start += (liSize * tl);  						// The start position is one carousel length ahead due to the optical effect
                curr += (liSize * tl);							// used for the animation
            }

            // Calculates the size of the main div according to the given size (can be in percent, in value or in pixels)
            var divSize, cssSize, cssUnity;
            if (cssU == '%') {                                    // in percent
                divSize = 0;									// We don't have the value in pixels unless we set the percent value first. So 0, and will catch it later
                cssSize = parseInt(v);
                cssUnity = "%";
            }
            else if (cssU == 'px') {                                    // in pixels
                divSize = parseInt(v);
                cssSize = parseInt(v);
                cssUnity = "px";
            }
            else {                                                    // in elements (number of elements to display)
                divSize = liSize * parseInt(v);
                cssSize = liSize * parseInt(v);
                cssUnity = "px";
            }

            // Adjust the carousel size with the correct values
            //li.css("width", imgSize(li, 'width'))              	// inner li width. this is the box model width
            //.css("height", imgSize(li), 'height');           	// inner li height. this is the box model height
            ul.css(sizeCss, ulSize + "px")// Width of the UL is the full length for all the images
                .css(animCss, -(o.start));                  	 	// Set the starting item
            div.css(sizeCss, cssSize + cssUnity);                	// Width of the DIV. length of visible images
            if (o.vertical && cssUnity == '%') {                        // Bugfix - % in vertical mode are badly handled by the browsers
                var pxsize = ((liSize * nbElts) * (parseInt(v) / 100));
                div.css(sizeCss, pxsize + 'px');					// The height of the carousel is based on the visible elements size
            }

            if (divSize === 0) {                                        // We didn't have the size in pixels in case of % size. Catch up !
                divSize = div.width();								// The size is simply the calculated size in pixels
            }

            // Adjust the height of the carousel (width in vertical mode)
            if (o.vertical) {                                            // vertical mode
                div.css("width", liSizeV + 'px');
                ul.css("width", liSizeV + 'px');
                li.css('margin-bottom', (parseInt(li.css('margin-bottom')) * 2) + 'px');	// bypass the "margin collapsing" effect by multiplying the margin-bottom by 2
                li.eq(li.size() - 1).css('margin-bottom', li.css('margin-top'));			// Last element has to be the right margin since no margin collapse there
            } else {                                                    // horizontal mode
                div.css('height', liSizeV + 'px');
                ul.css('height', liSizeV + 'px');
            }

            // Calculate the number of visible elements inside (in case of size in percent)
            if (cssU == '%') {
                v = divSize / li.width();
                if (v % 1 !== 0) {
                    v += 1;
                }
                v = parseInt(v);
            }

            var divVSize = div.height();													// div height

            ////////////////////////
            // Buttons management //
            ////////////////////////
            if (defaultBtn) {
                next.css({'z-index':200, 'position':'absolute'});
                prev.css({'z-index':200, 'position':'absolute'});
                //Positionate the arrows and adjust the arrow images
                if (o.vertical) {
                    prev.css({'width':prev.width(), 'height':prev.height(), 'top':'0px', 'left':parseInt(liSizeV / 2) - parseInt(prev.width() / 2) + 'px'});
                    next.css({'width':prev.width(), 'height':prev.height(), 'top':(divVSize - prev.height()) + 'px', 'left':parseInt(liSizeV / 2) - parseInt(prev.width() / 2) + 'px'});

                }
                else {
                    prev.css({'left':'0px', 'top':parseInt(liSizeV / 2) - parseInt(prev.height() / 2) + 'px'});
                    next.css({'right':'0px', 'top':parseInt(liSizeV / 2) - parseInt(prev.height() / 2) + 'px'});
                }
            }

            var btnHiSpeed = false;
            var btnHiSpeed2 = false;

            // Bind the events with the "previous" button
            if (o.btnPrev) {
                $(o.btnPrev).bind(o.evtStart, function () {
                    if (defaultBtn) {
                        o.btnPrev.css('opacity', 0.9);
                    }

                    direction = 'backward';

                    if (!o.auto) {
                        running = true;
                        return backward();
                    }
                    else {
                        if (!btnHiSpeed) {
                            o.speed = o.speed / 2;
                            btnHiSpeed = true;
                        }

                        running = true;
                        return backward();
                    }
                });

                $(o.btnPrev).bind(o.evtStop, function () {
                    if (defaultBtn) {
                        o.btnPrev.css('opacity', 0.6);
                    }
                    if (!o.auto) {
                        running = false;
                        direction = null;
                        return stop();
                    }
                });

                $(o.btnPrev).bind("mousedown", function () {
                    if (!btnHiSpeed2) {
                        o.speed = o.speed / 2;
                        btnHiSpeed2 = true;
                    }
                });

                $(o.btnPrev).bind("mouseup", function () {
                    if (btnHiSpeed2) {
                        o.speed = o.speed * 2;
                        btnHiSpeed2 = false;
                    }
                });

                $(o.btnPrev).bind("mouseout", function () {
                    if (btnHiSpeed) {
                        o.speed = o.speed * 2;
                        btnHiSpeed = false;
                    }

                    if (btnHiSpeed2) {
                        o.speed = o.speed * 2;
                        btnHiSpeed2 = false;
                    }
                });
            }


            // Bind the events with the "next" button
            if (o.btnNext) {
                $(o.btnNext).bind(o.evtStart, function () {
                    if (defaultBtn) {
                        o.btnNext.css('opacity', 0.9);
                    }

                    direction = 'forward';

                    if (!o.auto) {
                        running = true;
                        return forward();
                    }
                    else {
                        if (!btnHiSpeed) {
                            o.speed = o.speed / 2;
                            btnHiSpeed = true;
                        }

                        running = true;
                        return forward();
                    }
                });
                $(o.btnNext).bind(o.evtStop, function () {
                    if (defaultBtn) {
                        o.btnNext.css('opacity', 0.6);
                    }
                    if (!o.auto) {
                        running = false;
                        direction = null;
                        return stop();
                    }
                });

                $(o.btnNext).bind("mousedown", function () {
                    if (!btnHiSpeed2) {
                        o.speed = o.speed / 2;
                        btnHiSpeed2 = true;
                    }
                });

                $(o.btnNext).bind("mouseup", function () {
                    if (btnHiSpeed2) {
                        o.speed = o.speed * 2;
                        btnHiSpeed2 = false;
                    }
                });

                $(o.btnNext).bind("mouseout", function () {
                    if (btnHiSpeed) {
                        o.speed = o.speed * 2;
                        btnHiSpeed = false;
                    }

                    if (btnHiSpeed2) {
                        o.speed = o.speed * 2;
                        btnHiSpeed2 = false;
                    }
                });
            }

            // auto scroll management (auto = true). => launch the animation
            if (o.auto === true) {
                running = true;
                forward();
            }

            // Mousewheel management
            if (o.mouseWheel && div.mousewheel) {
                div.mousewheel(function (e, d) {
                    if (!o.circular && (d > 0 ? (curr + divSize < ulSize) : (curr > 0)) || o.circular) { //prevents the mouse events to occur in case of circular mode
                        mousewheelN += 1; 				//one more step to do, store it.
                        if (running === false) {
                            if (d > 0) {
                                forward(step, true);
                            }
                            else {
                                backward(step, true);
                            }
                            running = true;
                        }
                    }
                });
            }

            /**
             * Animate the track by moving it forward according to the step size and the speed
             * @param stepsize, the size of the step (optional)
             * @param once, shall the animation continue endlessly until we set running to false ? (optional)
             */
            function forward(stepsize, once) {
                var s = (stepsize ? stepsize : step);

                if (running === true && direction === "backward") {
                    return;
                }

                //If not circular, no need to animate endlessly
                if (!o.circular) {
                    //will the next step overtake the last  image ?
                    if (curr + s + (o.vertical ? divVSize : divSize) > eltsSize) {
                        s = eltsSize - (curr + (o.vertical ? divVSize : divSize));
                    }
                }

                ul.animate(
                    animCss == "left" ? { left:-(curr + s) } : { top:-(curr + s) }, o.speed, o.easing,
                    function () {
                        curr += s; //Add step size
                        //Calculate whether we cross the limit,
                        //if so, put the carousel one time backward
                        if (o.circular) {
                            if (curr + (o.vertical ? divVSize : divSize) + liSize >= allEltsSize) {
                                ul.css(o.vertical ? 'top' : 'left', -curr + eltsSize);
                                curr -= eltsSize;
                            }
                        }

                        if (!once && running) {
                            forward();
                        }
                        else if (once) {
                            if (--mousewheelN > 0) {
                                this.forward(step, true);
                            }
                            else {
                                running = false;
                                direction = null;
                            }
                        }
                    }
                );
            }

            /**
             * Animate the track by moving it backward according to the step size and the speed
             * @param stepsize, the size of the step (optional)
             * @param once, shall the animation continue endlessly until we set running to false ? (optional)
             */
            function backward(stepsize, once) {
                var s = (stepsize ? stepsize : step);

                if (running === true && direction === "forward") {
                    return;
                }

                //If not circular, no need to animate endlessly
                if (!o.circular) {
                    //will the next step overtake the first image ?
                    if (curr - s < 0) {
                        s = curr - 0;
                    }
                }

                ul.animate(
                    animCss == "left" ? { left:-(curr - s) } : { top:-(curr - s) }, o.speed, o.easing,
                    function () {
                        curr -= s;
                        //Calculate if we cross the limit,
                        //if so, put the carousel one time backward
                        if (o.circular) {
                            if (curr <= liSize) {
                                ul.css(o.vertical ? 'top' : 'left', -(curr + eltsSize));
                                curr += eltsSize;
                            }
                        }

                        if (!once && running) {
                            backward();
                        }
                        else if (once) {
                            if (--mousewheelN > 0) {
                                backward(step, true);
                            }
                            else {
                                running = false;
                                direction = null;
                            }
                        }
                    }
                );
            }

            /**
             * Stops the animation
             * Basically, tells the animation not to continue
             */
            function stop() {
                if (!o.eltByElt) {     //If we don't move elements by elements, then we can stop immediately
                    ul.stop(); 		// stop the animation straight
                    curr = 0 - parseInt(ul.css(animCss));	// We stopped suddenly, so the curr variable is not refreshed. We refresh it with the true value
                }
                running = false; 	// default value and in case we proceed element by element (eltByElt = true)
                direction = null;
            }

            /**
             * Return the size of the carousel, everything included (height or length depending on o.vertical)
             */
            /*function jmcSize(){
             var img = $('ul li img', div);
             var sizeLi = (o.vertical ? img.width() : img.height());
             var elt = img;
             while(elt.parent().get(0).tagName.toLowerCase() != 'div'){
             sizeLi += (o.vertical ? (parseInt(elt.css('marginLeft')) + parseInt(elt.css('marginRight')) + parseInt(elt.css('paddingRight')) + parseInt(elt.css('paddingLeft'))) : (parseInt(elt.css('marginTop')) + parseInt(elt.css('marginBottom')) + parseInt(elt.css('paddingTop')) + parseInt(elt.css('paddingBottom'))));
             elt = elt.parent();
             }
             return sizeLi;
             }*/

            /**
             * Calculate and return the size of the image in the element
             * @param el, the element
             * @param dimension, 'width' or 'height'.
             * @return the requested size in pixels.
             */
            function imgSize(el, dimension) {
                if (dimension == 'width') {
                    return el.find('img').width();
                }
                else {
                    return el.find('img').height();
                }
            }

            /**
             * Size of an element li with its margin calculated from scratch (without any call to width except for the image size)
             * usefull in case of vertical carousel, when the size of each element is 100%.
             * @param el, the element
             * @return the size of the element in pixels
             */
            function elHeight(el) {
                var elImg = el.find('img');
                if (o.vertical) {
                    return parseInt(el.css('margin-left')) + parseInt(el.css('margin-right')) + parseInt(elImg.width()) + parseInt(el.css('border-left-width')) + parseInt(el.css('border-right-width')) + parseInt(el.css('padding-right')) + parseInt(el.css('padding-left'));
                }
                else {
                    return parseInt(el.css('margin-top')) + parseInt(el.css('margin-bottom')) + parseInt(elImg.width()) + parseInt(el.css('border-top-height')) + parseInt(el.css('border-bottom-height')) + parseInt(el.css('padding-top')) + parseInt(el.css('padding-bottom'));
                }
            }

            function debug(html) {
                $('#debug').html($('#debug').html() + html + "<br/>");
            }

        });
    };

    function css(el, prop) {
        return parseInt($.css(el[0], prop)) || 0;
    }

    function width(el) {
        return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
    }

    function height(el) {
        //return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
        return 95;
    }

})(jQuery);
