/*
*
* 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,

                        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", "block"); }         // 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
                        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)
                        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

                                ////////////////////////
                                // Start / stop management //
                                ////////////////////////
                                $.fn.jMyCarouselStart = function(o) {
                                        return start();
                                }

                                $.fn.jMyCarouselStop = function(o) {
                                        return stop();
                                }

                                ////////////////////////
                                // 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'});
                                        }
                                }

                                // Bind the events with the "previous" button
                                if(o.btnPrev){
                                        $(o.btnPrev).bind(o.evtStart, function() {
                                                if(defaultBtn){ o.btnPrev.css('opacity',0.9); }
                                                running = true;
                                                direction = 'backward';
                                                return backward();
                                        });

                                        $(o.btnPrev).bind(o.evtStop, function() {
                                                if(defaultBtn){ o.btnPrev.css('opacity',0.6); }
                                                running = false;
                                                direction = null;
                                                return stop();
                                        });
                                }


                                // Bind the events with the "next" button
                                if(o.btnNext){
                                        $(o.btnNext).bind(o.evtStart, function() {
                                                if(defaultBtn){ o.btnNext.css('opacity',0.9); }
                                                running = true;
                                                direction = 'forward';
                                                return forward();
                                        });
                                        $(o.btnNext).bind(o.evtStop,function() {
                                                if(defaultBtn){ o.btnNext.css('opacity',0.6); }
                                                running = false;
                                                direction = null;
                                                return stop();
                                        });
                                }

                                // auto scroll management (auto = true). => launch the animation
                                if(o.auto === true){
                                        start();
                                }

                                // 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;
                                                                }
                                                        }
                                                });
                                        }

                                        function start(){
                                                running = true;
                                                forward();
                                        }

                                        /**
                                        * 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');
                        }

                })(jQuery);

