(function($) {
    jQuery.fn.reverse = Array.prototype.reverse;
	
    var config	= {},
    // auxiliar functions
    aux		= {
        setup				: function( $wrapper, $items, opts ) {
				
            // set the wrappers position to relative
            $wrapper.css('position', 'relative');
				
            // save the items position
            aux.saveInitialPosition( $items );
				
            // set the items to absolute and assign top & left
            $items.each(function(i) {
                var $item 	= $(this);
					
                $item.css({
                    position	: 'absolute',
                    left	: $item.data('left'),
                    top		: $item.data('top')
                });
            });
				
            // check how many items we have per row
            var rowCount 	= Math.floor( $wrapper.width() / $items.width() ),
            // number of items to show is rowCount * n rows
            shown		= rowCount * opts.rows,
            // total number of rows
            totalRows	= Math.ceil( $items.length / rowCount );
				
            // save this values for later
            config.totalRows	= totalRows;
            config.rowCount 	= rowCount;
            config.shownItems	= shown;
				
            // show n rowns
            $wrapper.children(':gt(' + (shown - 1) + ')').hide();
				
            // assign row classes to the items
            $items.each(function(i) {
                var $item 	= $(this),
                row		= Math.ceil( (i + 1) / rowCount );
					
                $item.addClass('tj_row_' + row);		
            });
				
            nav.setup( $wrapper, $items, opts );
				
        },
        saveInitialPosition	: function( $items ) {
            $items.each(function(i) {
                var $item 	= $(this);
					
                $item.data({
                    left		: $item.position().left + 'px',
                    top			: $item.position().top + 'px'
                });									
            });
        }
    },
    // navigation types
    nav		= {
        setup			: function( $wrapper, $items, opts ) {
            nav[opts.type.mode].setup( $wrapper, $items, opts );
        },
        def				: {
            setup		: function( $wrapper, $items, opts ) {
                $items.each(function(i) {
                    var $item 	= $(this),
                    row		= Math.ceil( (i + 1) / config.rowCount ),
                    t,
                    f = row % opts.rows;
					
                    if( f === 1 ) {
                        t = '0px';		
                    } else if( f === 0 ) {
                        t = (opts.rows - 1) * $items.height()  + 'px'; 
                    } else {
                        t = (f - 1) * $items.height() + 'px';
                    }
						
                    $item.css({
                        top	: t
                    });
                });	
            },
            pagination	: function( $wrapper, dir, opts ) {
                if( ( dir === 1 && config.currentRow + opts.rows > config.totalRows ) || 
                    ( dir === -1 && config.currentRow - opts.rows <= 0 )
                    ) {
                    $wrapper.data( 'anim', false );
                    return false;
                }
					
                var currentRows	= '', nextRows = '';
					
                for( var i = 0; i < opts.rows; ++i ) {
                    currentRows += '.tj_row_' + (config.currentRow + i) + ',';
						
                    (dir === 1)
                    ? nextRows	+= '.tj_row_' + (config.currentRow + opts.rows + i) + ','
                    : nextRows	+= '.tj_row_' + (config.currentRow - 1 - i) + ',';
                }
					
                $wrapper.children(currentRows).hide();
                $wrapper.children(nextRows).show();
					
                (dir === 1) ? config.currentRow += opts.rows : config.currentRow -= opts.rows;
					
                $wrapper.data( 'anim', false );
            }
        },
        fade			: {
            setup		: function( $wrapper, $items, opts ) {
                // same like def mode
                nav['def'].setup( $wrapper, $items, opts );
            },
            pagination	: function( $wrapper, dir, opts ) {
                if( ( dir === 1 && config.currentRow + opts.rows > config.totalRows ) ||
                    ( dir === -1 && config.currentRow - opts.rows <= 0 )
                    ) {
                    $wrapper.data( 'anim', false );
                    return false;
                }
					
                var currentRows	= '', nextRows = '';
					
                for( var i = 0; i < opts.rows; ++i ) {
                    currentRows += '.tj_row_' + (config.currentRow + i) + ',';
						
                    (dir === 1)
                    ? nextRows	+= '.tj_row_' + (config.currentRow + opts.rows + i) + ','
                    : nextRows	+= '.tj_row_' + (config.currentRow - 1 - i) + ',';
                }
					
                $wrapper.children(currentRows).fadeOut( opts.type.speed, opts.type.easing );
					
                var $nextRowElements= $wrapper.children(nextRows),

                totalNextRows	= $nextRowElements.length,
                cnt				= 0;
						
                $nextRowElements.fadeIn( opts.type.speed, opts.type.easing, function() {
                    ++cnt;
                    if( cnt === totalNextRows ) {
                        $wrapper.data( 'anim', false );
                    }	
                });
					
                (dir === 1) ? config.currentRow += opts.rows : config.currentRow -= opts.rows;
            }
        },
        seqfade			: {
            setup		: function( $wrapper, $items, opts ) {
                // same like def mode
                nav['def'].setup( $wrapper, $items, opts );
            },
            pagination	: function( $wrapper, dir, opts ) {
                if( ( dir === 1 && config.currentRow + opts.rows > config.totalRows ) || 
                    ( dir === -1 && config.currentRow - opts.rows <= 0 )
                    ) {
                    $wrapper.data( 'anim', false );
                    return false;
                }
					
                var currentRows	= '', nextRows = '';
                for( var i = 0; i < opts.rows; ++i ) {
                    currentRows += '.tj_row_' + (config.currentRow + i) + ',';
						
                    (dir === 1)
                    ? nextRows	+= '.tj_row_' + (config.currentRow + opts.rows + i) + ','
                    : nextRows	+= '.tj_row_' + (config.currentRow - 1 - i) + ',';
                }
					
                var seq_t	= opts.type.factor;
					
                var $currentRowElements;
                ( dir === 1 )
                ? $currentRowElements = $wrapper.children(currentRows)
                : $currentRowElements = $wrapper.children(currentRows).reverse();
						
                $currentRowElements.each(function(i) {
                    var $el = $(this);
                    setTimeout(function() {
                        $el.fadeOut( opts.type.speed, opts.type.easing )
                    }, seq_t + i * seq_t);
                });
					
                var $nextRowElements;
                ( dir === 1 )
                ? $nextRowElements = $wrapper.children(nextRows)
                : $nextRowElements = $wrapper.children(nextRows).reverse();
					
                var total_elems	= $nextRowElements.length,
                cnt			= 0;
					
                $nextRowElements.each(function(i) {
                    var $el = $(this);
                    setTimeout(function() {
                        $el.fadeIn( opts.type.speed, opts.type.easing, function() {
                            ++cnt;
                            if( cnt === total_elems ) { 
                                $wrapper.data( 'anim', false );
                            }	
                        })
                    }, (seq_t * 2) + i * seq_t);
                });
					
                (dir === 1) ? config.currentRow += opts.rows : config.currentRow -= opts.rows;
            }
        },
        updown			: {
            setup		: function( $wrapper, $items, opts ) {
                $wrapper.children(':gt(' + (config.shownItems - 1) + ')').css('opacity', 0);
					
                $items.each(function(i) {
                    var $item 	= $(this),
                    row		= Math.ceil( (i + 1) / config.rowCount ),
                    t		= $item.position().top,
                    f = row % opts.rows;
						
                    if( row > opts.rows ) {
                        t = (opts.rows * $items.height());		
                    }
						
                    $item.css({
                        top	: t + 'px'
                        });
                });
            },
            pagination	: function( $wrapper, dir, opts ) {
                if( ( dir === 1 && config.currentRow + opts.rows > config.totalRows ) || 
                    ( dir === -1 && config.currentRow - 1 <= 0 )
                    ) {
                    $wrapper.data( 'anim', false );
                    return false;
                }
					
                var movingRows	= '';
					
                for( var i = 0; i <= opts.rows; ++i ) {
                    ( dir === 1 )
                    ? movingRows += '.tj_row_' + (config.currentRow + i) + ','
                    : movingRows += '.tj_row_' + (config.currentRow + (i - 1)) + ',';
                }
					
                var $elements;
					
                ( dir === 1 )
                ? $elements = $wrapper.children(movingRows)
                : $elements = $wrapper.children(movingRows).reverse();
					
                var total_elems	= $elements.length,
                cnt			= 0;
					
                $elements.each(function(i) {
                    var $el 		= $(this),
                    row			= $el.attr('class'),
                    animParam	= {},
							
                    currentRow	= config.currentRow;
						
                    // if first row fade out
                    // if last row fade in
                    // for all the rows move them up / down
                    if( dir === 1 ) {
                        if(  row === 'tj_row_' + (currentRow) ) {
                            animParam.opacity	= 0;
                        }
                        else if( row === 'tj_row_' + (currentRow + opts.rows) ) {
                            animParam.opacity	= 1;
                        }
                    }
                    else {
                        if(  row === 'tj_row_' + (currentRow - 1) ) {
                            animParam.opacity	= 1;
                        }
                        else if( row === 'tj_row_' + (currentRow + opts.rows - 1) ) {
                            animParam.opacity	= 0;
                        }
                    }
						
                    $el.show();
						
                    (dir === 1)
                    ? animParam.top = $el.position().top - $el.height() + 'px'
                    : animParam.top = $el.position().top + $el.height() + 'px'
						
                    $el.stop().animate(animParam, opts.type.speed, opts.type.easing, function() {
                        if( parseInt( animParam.top ) < 0 || parseInt( animParam.top ) > $el.height() * (opts.rows - 1) )
                            $el.hide();
							
                        ++cnt;
                        if( cnt === total_elems ) {
                            $wrapper.data( 'anim', false );
                        }	
                    });
                });
					
                (dir === 1) ? config.currentRow += 1 : config.currentRow -= 1;
            }
        },
        sequpdown		: {
            setup 		: function( $wrapper, $items, opts ) {
                // same like updown mode
                nav['updown'].setup( $wrapper, $items, opts );
            },
            pagination	: function( $wrapper, dir, opts ) {
                if( ( dir === 1 && config.currentRow + opts.rows > config.totalRows ) || 
                    ( dir === -1 && config.currentRow - 1 <= 0 )	
                    ) {
                    $wrapper.data( 'anim', false );
                    return false;
                }
					
                var movingRows	= '';
					
                for( var i = 0; i <= opts.rows; ++i ) {
                    ( dir === 1 )
                    ? movingRows += '.tj_row_' + (config.currentRow + i) + ','
                    : movingRows += '.tj_row_' + (config.currentRow + (i - 1)) + ',';
                }
					
                var seq_t	= opts.type.factor,
                $elements;
					
                var dircond	= 1;
                if( opts.type.reverse ) dircond = -1;
                ( dir === dircond )
                ? $elements = $wrapper.children(movingRows)
                : $elements = $wrapper.children(movingRows).reverse();
					
                var total_elems	= $elements.length,
                cnt			= 0;
					
                $elements.each(function(i) {
                    var $el 		= $(this),
                    row			= $el.attr('class'),
                    animParam	= {},
							
                    currentRow	= config.currentRow;
							
                    setTimeout(function() {
                        // if first row fade out
                        // if last row fade in
                        // for all the rows move them up / down
                        if( dir === 1 ) {
                            if(  row === 'tj_row_' + (currentRow) ) {
                                animParam.opacity	= 0;
                            }
                            else if( row === 'tj_row_' + (currentRow + opts.rows) ) {
                                animParam.opacity	= 1;
                            }
                        }
                        else {
                            if(  row === 'tj_row_' + (currentRow - 1) ) {
                                animParam.opacity	= 1;
                            }
                            else if( row === 'tj_row_' + (currentRow + opts.rows - 1) ) {
                                animParam.opacity	= 0;
                            }
                        }
							
                        $el.show();
							
                        (dir === 1)
                        ? animParam.top = $el.position().top - $el.height() + 'px'
                        : animParam.top = $el.position().top + $el.height() + 'px'
							
                        $el.stop().animate(animParam, opts.type.speed, opts.type.easing, function() {
                            if( parseInt( animParam.top ) < 0 || parseInt( animParam.top ) > $el.height() * (opts.rows - 1) )
                                $el.hide();
									
                            ++cnt;
                            if( cnt === total_elems ) { 
                                $wrapper.data( 'anim', false );
                            }	
                        });	
                    }, seq_t + i * seq_t);
                });
					
                (dir === 1) ? config.currentRow += 1 : config.currentRow -= 1;
            }
        },
        showhide		: {
            setup 		: function( $wrapper, $items, opts ) {
                $items.each(function(i) {
                    var $item 	= $(this),
                    row		= Math.ceil( (i + 1) / config.rowCount ),
                    t,
                    f = row % opts.rows;
						
                    if( f === 1 ) {
                        t = '0px';		
                    } else if( f === 0 ) {
                        t = (opts.rows - 1) * $items.height()  + 'px'; 
                    } else {
                        t = (f - 1) * $items.height() + 'px';
                    }
						
                    $item.css({
                        top	: t
                    });
                });		
            },
            pagination	: function( $wrapper, dir, opts ) {
                if( ( dir === 1 && config.currentRow + opts.rows > config.totalRows ) || 
                    ( dir === -1 && config.currentRow - opts.rows <= 0 )
                    ) {
                    $wrapper.data( 'anim', false );
                    return false;
                }
					
                var currentRows	= '', nextRows = '';
					
                for( var i = 0; i < opts.rows; ++i ) {
                    currentRows += '.tj_row_' + (config.currentRow + i) + ',';
						
                    (dir === 1)
                    ? nextRows	+= '.tj_row_' + (config.currentRow + opts.rows + i) + ','
                    : nextRows	+= '.tj_row_' + (config.currentRow - 1 - i) + ',';
                }
					
                $wrapper.children(currentRows).hide( opts.type.speed, opts.type.easing );
					
                var $nextRowElements= $wrapper.children(nextRows),
                totalNextRows	= $nextRowElements.length,
                cnt				= 0;
						
                $nextRowElements.show( opts.type.speed, opts.type.easing, function() {
                    ++cnt;
                    if( cnt === totalNextRows ) {
                        $wrapper.data( 'anim', false );
                    }	
                });
					
                (dir === 1) ? config.currentRow += opts.rows : config.currentRow -= opts.rows;
            }
        },
        disperse		: {
            setup 		: function( $wrapper, $items, opts ) {
                $items.each(function(i) {
                    var $item 	= $(this),
                    row		= Math.ceil( (i + 1) / config.rowCount ),
                    t,
                    f = row % opts.rows;
					
                    if( f === 1 ) {
                        t = '0px';		
                    } else if( f === 0 ) {
                        t = (opts.rows - 1) * $items.height()  + 'px'; 
                    } else {
                        t = (f - 1) * $items.height() + 'px';
                    }
						
                    $item.css({
                        top	: t
                    }).data('top', t);
                });
            },
            pagination	: function( $wrapper, dir, opts ) {
                if( ( dir === 1 && config.currentRow + opts.rows > config.totalRows ) || 
                    ( dir === -1 && config.currentRow - opts.rows <= 0 )
                    ) {
                    $wrapper.data( 'anim', false );
                    return false;
                }
					
                var currentRows	= '', nextRows = '';
                for( var i = 0; i < opts.rows; ++i ) {
                    currentRows += '.tj_row_' + (config.currentRow + i) + ',';
						
                    (dir === 1)
                    ? nextRows	+= '.tj_row_' + (config.currentRow + opts.rows + i) + ','
                    : nextRows	+= '.tj_row_' + (config.currentRow - 1 - i) + ',';
                }
					
                $wrapper.children(currentRows).each(function(i) {
                    var $el = $(this);
                    $el.stop().animate({
                        left	: $el.position().left + Math.floor( Math.random() * 101 ) - 50 + 'px',
                        top		: $el.position().top + Math.floor( Math.random() * 101 ) - 50 + 'px',
                        opacity	: 0
                    }, opts.type.speed, opts.type.easing, function() {
                        $el.css({
                            left	: $el.data('left'),
                            top		: $el.data('top')
                        }).hide();
                    });
                });
					
                var $nextRowElements	= $wrapper.children(nextRows);
                total_elems			= $nextRowElements.length,
                cnt					= 0;
					
                $nextRowElements.each(function(i) {
                    var $el = $(this);
						
                    $el.css({
                        left	: parseInt($el.data('left')) + Math.floor( Math.random() * 301 ) - 150 + 'px',	
                        top		: parseInt($el.data('top')) + Math.floor( Math.random() * 301 ) - 150 + 'px',
                        opacity	: 0
                    })
                    .show()
                    .animate({
                        left	: $el.data('left'),
                        top		: $el.data('top'),
                        opacity	: 1
                    }, opts.type.speed, opts.type.easing, function() {
                        ++cnt;
                        if( cnt === total_elems ) { 
                            $wrapper.data( 'anim', false );
                        }
                    });
                });
					
                (dir === 1) ? config.currentRow += opts.rows : config.currentRow -= opts.rows;
            }
        },
        rows			: {
            setup 		: function( $wrapper, $items, opts ) {
                // same like def mode
                nav['def'].setup( $wrapper, $items, opts );
            },
            pagination	: function( $wrapper, dir, opts ) {
                if( ( dir === 1 && config.currentRow + opts.rows > config.totalRows ) || 
                    ( dir === -1 && config.currentRow - opts.rows <= 0 )
                    ) {
                    $wrapper.data( 'anim', false );
                    return false;
                }
					
                var currentRows	= '', nextRows = '';
                for( var i = 0; i < opts.rows; ++i ) {
                    currentRows += '.tj_row_' + (config.currentRow + i) + ',';
						
                    (dir === 1)
                    ? nextRows	+= '.tj_row_' + (config.currentRow + opts.rows + i) + ','
                    : nextRows	+= '.tj_row_' + (config.currentRow - 1 - i) + ',';
                }
					
                $wrapper.children(currentRows).each(function(i) {
                    var $el 	= $(this),
                    rownmb	= $el.attr('class').match(/tj_row_(\d+)/)[1],
                    diff;
							
                    if( rownmb%2 === 0 ) {
                        diff = opts.type.factor;
                    }
                    else {
                        diff = -opts.type.factor;
                    }
						
                    $el.stop().animate({
                        left	: $el.position().left + diff + 'px',
                        opacity	: 0
                    }, opts.type.speed, opts.type.easing, function() {
                        $el.css({
                            left	: $el.data('left')
                        }).hide();
                    });
                });
					
                var $nextRowElements	= $wrapper.children(nextRows);
                total_elems			= $nextRowElements.length,
                cnt					= 0;
					
                $nextRowElements.each(function(i) {
                    var $el = $(this),
                    rownmb	= $el.attr('class').match(/tj_row_(\d+)/)[1],
                    diff;
						
                    if( rownmb%2 === 0 ) {
                        diff = opts.type.factor;
                    }
                    else {
                        diff = -opts.type.factor;
                    }
						
                    $el.css({
                        left	: parseInt($el.data('left')) + diff + 'px',
                        opacity	: 0
                    })
                    .show()
                    .animate({
                        left	: $el.data('left'),
                        opacity	: 1
                    }, opts.type.speed, opts.type.easing, function() {
                        ++cnt;
                        if( cnt === total_elems ) { 
                            $wrapper.data( 'anim', false );
                        }
                    });
                });
					
                (dir === 1) ? config.currentRow += opts.rows : config.currentRow -= opts.rows;
            }
        }
    },
    methods = {
        init 	: function( options ) {
				
            if( this.length ) {
					
                var settings = {
                    rows	: 2,
                    navL	: '#tj_prev',
                    navR	: '#tj_next',
                    type	: {
                        mode		: 'def', 		// use def | fade | seqfade | updown | sequpdown | showhide | disperse | rows
                        speed		: 500,			// for fade, seqfade, updown, sequpdown, showhide, disperse, rows
                        easing		: 'jswing',		// for fade, seqfade, updown, sequpdown, showhide, disperse, rows	
                        factor		: 50,			// for seqfade, sequpdown, rows
                        reverse		: false			// for sequpdown
                    }
                };
					
                return this.each(function() {
						
                    // if options exist, lets merge them with our default settings
                    if ( options ) {
                        $.extend( settings, options );
                    }
						
                    var $el 			= $(this).css( 'visibility', 'hidden' ),
                    // the ul
                    $wrapper		= $el.find('ul.tj_gallery'),
                    // the items
                    $thumbs			= $wrapper.children('li'),
                    total			= $thumbs.length,
                    // the navigation elements
                    $p_nav			= $(settings.navL),
                    $n_nav			= $(settings.navR);
						
                    // save current row for later (first visible row)
                    config.currentRow	= 1;
						
                    // flag to control animation progress
                    $wrapper.data( 'anim', false );
						
                    // preload thumbs
                    var loaded = 0;
                    $thumbs.find('img').each( function(i) {
                        var $img 	= $(this);
                        $('<img/>').load( function() {
                            ++loaded;
                            if( loaded === total ) {
									
                                // setup
                                aux.setup( $wrapper, $thumbs, settings );

                                $el.css( 'visibility', 'visible' );
									
                                // navigation events
                                if( $p_nav.length ) {
                                    $p_nav.bind('click.gridnav', function( e ) {
                                        if( $wrapper.data( 'anim' ) ) return false;
                                        $wrapper.data( 'anim', true );
                                        nav[settings.type.mode].pagination( $wrapper, -1, settings );
                                        return false;
                                    });
                                }
                                if( $n_nav.length ) {
                                    $n_nav.bind('click.gridnav', function( e ) {
                                        if( $wrapper.data( 'anim' ) ) return false;
                                        $wrapper.data( 'anim', true );
                                        nav[settings.type.mode].pagination( $wrapper, 1, settings );
                                        return false;
                                    });
                                }
                                /*
									adds events to the mouse
									*/
                                $el.bind('mousewheel.gridnav', function(e, delta) {
                                    if(delta > 0) {
                                        if( $wrapper.data( 'anim' ) ) return false;
                                        $wrapper.data( 'anim', true );
                                        nav[settings.type.mode].pagination( $wrapper, -1, settings );
                                    }	
                                    else {
                                        if( $wrapper.data( 'anim' ) ) return false;
                                        $wrapper.data( 'anim', true );
                                        nav[settings.type.mode].pagination( $wrapper, 1, settings );
                                    }	
                                    return false;
                                });
									
                            }
                        }).attr( 'src', $img.attr('src') );
                    });
						
                });
            }
        }
    };
	
    $.fn.gridnav = function(method) {
        if ( methods[method] ) {
            return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof method === 'object' || ! method ) {
            return methods.init.apply( this, arguments );
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.gridnav' );
        }
    };
})(jQuery);
