/*
 * Repetoire Grid View v1.0 - Jordy Boezaard - http://www.bitterzoetmedia.nl/
 *
 * Displays the repetoire in a grid
 *
 * TERMS OF USE
 * 
 * Open source under the BSD License. 
 * 
 * Copyright © 2011 Jordy Boezaard
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of 
 * conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list 
 * of conditions and the following disclaimer in the documentation and/or other materials 
 * provided with the distribution.
 * 
 * Neither the name of the author nor the names of contributors may be used to endorse 
 * or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 *  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE. 
 *
*/
function ArtistData ()
{
    IsArtistDataObject = true; // Needed for type checking
    Name = null;
    Tracks = null;
    NameDOM = null;
    ImageContainerDOM = null;
    ImageDOM = null;
    TrackListingDOM = null;
    ImageContainerListPosition = null;
    ImageContainerGridPosition = null;
    ImageSize = null;
}

var RepertoireGridViewMode = {
    ListView:   "LIST_VIEW",
    GridView:   "GRID_VIEW"
}

var RepertoireGridView =
{
    // Configuration
    debug:                              false,
    debugLogCalledFunctions:            false,
    gridTracklistingOverlay:            'tracklistingOverlay',
    
    gridImageSize:                      95,
    gridImageZoomedSize:                350,
    gridHoverGrowMultiplier:            1.2,
    distanceDurationMultiplier:         1,
    
    tracklistingOverlayOpacity:         .9,
    
    gridHoverGrowDuration:              150,
    gridHoverShrinkDuration:            50,
    minMoveToListDuration:              2000,
    minMoveToGridDuration:              750,
    listFadeInDuration:                 1000,
    listFadeOutDuration:                500,
    showTracklistingOverlayDuration:    100,
    hideTracklistingOverlayDuration:    100,
    imageZoomInDuration:                500,
    imageZoomOutDuration:               500,
    
    durationSleepWatchTracklist:        100,
    
    startZIndex:						100,
    
    moveToGridEasing:                   'easeOutBounce',
    moveToListEasing:                   'easeOutElastic',
    gridHoverGrowEasing:                'easeOutBack',
    gridHoverShrinkEasing:              'easeOutQuad',
    imageZoomInFirstStepEasing:         'easeInQuad',
    imageZoomInSecondStepEasing:        'easeOutBack',
    imageZoomOutEasing:                 'easeOutElastic',
    
    // Properties
    containerDivID:						null,
    containerDivListHeight:				null,
    containerDivGridHeight:				null,
    definitionListID:                   null,
    imageTopOffset:                     0,
    artistDataArray:                    new Array(),
    currentViewMode:                    RepertoireGridViewMode.ListView,
    gridImagePositions:                 null,
    gridImageLastZoomedObject:          null,
    numberOfRemainingAnimations:        0,
    currentlyZoomedImage:               null,
    
    // Constructor
    init:                       function (containerDivID, definitionListID, imageTopOffset, startInGrid)
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('init');
                                    
                                    // Definition list id
                                    var containerDivID = 'div#' + containerDivID;
                                    var definitionListID = 'dl#' + definitionListID;
                                    
                                    // Save reference to the definition list
                                    RepertoireGridView.containerDivID = containerDivID;
                                    RepertoireGridView.definitionListID = definitionListID;
                                                                        
                                    // Check if list exists
                                    var definitionListArray = $(definitionListID);

                                    if (definitionListArray.length != 1)
                                    {
                                        if (RepertoireGridView.debug) console.log('definitionListArray != 1');
                                        return;
                                    }
                                    
                                    // Save image top offset
                                    RepertoireGridView.imageTopOffset = imageTopOffset;
                                    
                                    // Create the data objects
                                    try
                                    {
                                        var listChildren = $(definitionListID).children();
                                        
                                        $.each(listChildren, function()
                                        {
                                            if (this.tagName.toLowerCase() == 'dt')
                                            {
                                                var artistData = new ArtistData();
                                                artistData.Name = this.innerHTML;
                                                artistData.Tracks = new Array();
                                                artistData.NameDOM = this;
                                                
                                                RepertoireGridView.artistDataArray.push(artistData);
                                            }
                                            else if(this.tagName.toLowerCase() == 'dd')
                                            {
                                                var artistData = RepertoireGridView.artistDataArray[RepertoireGridView.artistDataArray.length - 1];
                                                
                                                if ($(this).hasClass('artistImage') == true)
                                                {
                                                    artistData.ImageContainerDOM = this;
                                                    artistData.ImageDOM = $(this).children('img');
                                                    artistData.ImageSize = {
                                                        width: $(this).children('img').width(),
                                                        height: $(this).children('img').height()
                                                    }
                                                }
                                                else if ($(this).hasClass('trackListing') == true)
                                                {
                                                    artistData.TrackListingDOM = $(this);
                                                    
                                                    var trackNameListItems = $(this).children('ul').children('li');
                                                    
                                                    $.each(trackNameListItems, function()
                                                    {
                                                        artistData.Tracks.push(this.innerHTML);
                                                    });
                                                }
                                            }
                                        });
                                    }
                                    catch (error)
                                    {
                                        if (RepertoireGridView.debug) console.log(error);
                                        return;
                                    }
                                    
                                    if (RepertoireGridView.artistDataArray.length == 0)
                                    {
                                        if (RepertoireGridView.debug) console.log('RepertoireGridView.artistDataArray.length == 0');
                                        return;
                                    }
                                    
                                    // Set images to absolute positions
                                    RepertoireGridView.imagesToAbsolutePositions();
                                    
                                    // Set height
                                    RepertoireGridView.containerDivListHeight = $(definitionListID).height();
                                    
                                    // Create overlay dom
                                    $("<div/>", {
                                        id:     RepertoireGridView.gridTracklistingOverlay,
                                        width:  RepertoireGridView.gridImageZoomedSize,
                                        height: RepertoireGridView.gridImageZoomedSize,
                                        click:  RepertoireGridView.overlayOnMouseClick,
                                    }).appendTo($(RepertoireGridView.definitionListID).parent());
                                    
                                    // Set toggle button action
									$('#repertoireGridViewToggle').click(function()
									{
										RepertoireGridView.toggleView();
									});
									
									// 
									if (startInGrid == true)
									{
										RepertoireGridView.toggleView(RepertoireGridViewMode.GridView);
									}
                                },
                                
    overlayOnMouseClick:        function ()
                                {
                                    RepertoireGridView.zoomOutImage(RepertoireGridView.gridImageLastZoomedObject);
                                    
                                    RepertoireGridView.gridImageLastZoomedObject = null;
                                },
                                
    imagesToAbsolutePositions:  function ()
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('imagesToAbsolutePositions');
                                                                        
                                    $.each(RepertoireGridView.artistDataArray, function()
                                    {
                                        // Get the current position
                                        var currentContainerPos = $(this.ImageContainerDOM).position();
                                        
                                        this.ImageContainerListPosition = currentContainerPos;
                                        
                                        // Set image position to absolute and position the image to current position
                                        $(this.ImageContainerDOM).css('position', 'inherit');
                                        $(this.ImageDOM).css('top', currentContainerPos.top - RepertoireGridView.imageTopOffset);
                                        $(this.ImageDOM).css('left', currentContainerPos.left);
                                    });
                                },
                                
    toggleView:                 function (viewMode)
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('toggleView');
                                    
                                    // Determine target view mode
                                    var targetViewMode = (RepertoireGridView.currentViewMode == RepertoireGridViewMode.ListView) ? RepertoireGridViewMode.GridView : RepertoireGridViewMode.ListView;
                                    
                                    if (viewMode == RepertoireGridViewMode.ListView
                                        || viewMode == RepertoireGridViewMode.GridView)
                                    {
                                        targetViewMode = viewMode;
                                    }
                                    
                                    // Call view animator
                                    switch (targetViewMode)
                                    {
                                        case RepertoireGridViewMode.ListView:
                                            RepertoireGridView.showListView();
                                            break;
                                            
                                        case RepertoireGridViewMode.GridView:
                                            RepertoireGridView.showGridView();
                                            break;
                                            
                                        default:
                                            if (RepertoireGridView.debug) console.log('Invalid targetViewMode');
                                            return;   
                                    }
                                },

    showListView:               function ()
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('showListView');
                                    
                                    // Set current viewMode
                                    RepertoireGridView.currentViewMode = RepertoireGridViewMode.ListView;
                                    
                                    // Set mouse events
                                    RepertoireGridView.setListMouseEvents();
                                    
                                    // Set number of animations
                                    RepertoireGridView.numberOfRemainingAnimations = RepertoireGridView.artistDataArray.length;
                                    
                                    // Reset zoomed image
                                    RepertoireGridView.gridImageLastZoomedObject = null;
                                    
                                    // Animate objects
                                    $.each(RepertoireGridView.artistDataArray, function()
                                    {
                                        // Stop the current animation
                                        $(this.ImageDOM).clearQueue();
                                        $(this.ImageDOM).stop();

                                        // Animate!
                                        $(this.ImageDOM).animate(
                                            {
                                                top:        this.ImageContainerListPosition.top - RepertoireGridView.imageTopOffset,
                                                left:       this.ImageContainerListPosition.left,
                                                width:      this.ImageSize.width,
                                                height:     this.ImageSize.height
                                            },
                                            RepertoireGridView.minMoveToListDuration,
                                            RepertoireGridView.moveToListEasing,
                                            RepertoireGridView.animatedToListViewHandler
                                        );
                                    });
                                    
                                    // Animate container div size
                                    $(RepertoireGridView.containerDivID).animate(
                                    	{
                                    		height: RepertoireGridView.containerDivListHeight
                                    	},
                                    	RepertoireGridView.minMoveToGridDuration
                                    );
                                },
    
    animatedToListViewHandler:  function ()
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('animatedToListViewHandler');
                                    
                                    RepertoireGridView.numberOfRemainingAnimations--;
                                    
                                    if (RepertoireGridView.numberOfRemainingAnimations == 0)
                                    {
                                        // Fade in list
                                        $.each(RepertoireGridView.artistDataArray, function()
                                        {
                                            $(this.TrackListingDOM).fadeIn(RepertoireGridView.listFadeInDuration);
                                            $(this.NameDOM).fadeIn(RepertoireGridView.listFadeInDuration);
                                        });
                                    }
                                },
    
    showGridView:               function ()
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('showGridView');
                                    
                                    // Set current viewMode
                                    RepertoireGridView.currentViewMode = RepertoireGridViewMode.GridView;
                                    
                                    // Start tracklist watch loop
                                    RepertoireGridView.tracklistWatchLoop();
                                    
                                    // Calculate new image positions
                                    if (RepertoireGridView.gridImagePositions == null)
                                    {
                                        RepertoireGridView.calculateGridPositions();
                                    }
                                    
                                    // Copy array with grid positions
                                    var gridImagePositions = RepertoireGridView.gridImagePositions.slice();

                                    // Set number of animations
                                    RepertoireGridView.numberOfRemainingAnimations = RepertoireGridView.artistDataArray.length;
                                    
                                    // Animate objects
                                    $.each(RepertoireGridView.artistDataArray, function()
                                    {
                                        // Stop the current animation
                                        $(this.ImageDOM).clearQueue();
                                        $(this.ImageDOM).stop();
                                        
                                        // Fade everything except image
                                        $(this.TrackListingDOM).fadeOut(RepertoireGridView.listFadeOutDuration);
                                        $(this.NameDOM).fadeOut(RepertoireGridView.listFadeOutDuration);
                                        
                                        // Animate images to grid positions
                                        var newPosition = gridImagePositions.splice(Math.floor(Math.random() * gridImagePositions.length), 1)[0];
                                        
                                        // Svae grid position
                                        this.ImageContainerGridPosition = newPosition;
                                        
                                        // Calculate distance
                                        var deltaTop = Math.abs(this.ImageContainerListPosition.top - newPosition.top);
                                        var deltaLeft = Math.abs(newPosition.left - this.ImageContainerListPosition.left);
                                        
                                        var distanceDuration = deltaTop * RepertoireGridView.distanceDurationMultiplier + deltaLeft * RepertoireGridView.distanceDurationMultiplier;
                                        
                                        // Animate
                                        $(this.ImageDOM).css('z-index', RepertoireGridView.startZIndex)
                                                        .animate(
                                                            {
                                                                top:        newPosition.top,
                                                                left:       newPosition.left,
                                                                width:      RepertoireGridView.gridImageSize,
                                                                height:     RepertoireGridView.gridImageSize
                                                            },
                                                            RepertoireGridView.minMoveToGridDuration + distanceDuration,
                                                            RepertoireGridView.moveToGridEasing,
                                                            RepertoireGridView.animatedToGridViewHandler
                                                        );
                                    });
                                    
                                    // Animate container div size
                                    $(RepertoireGridView.containerDivID).animate(
                                    	{
                                    		height: RepertoireGridView.containerDivGridHeight
                                    	},
                                    	RepertoireGridView.minMoveToGridDuration
                                    );
                                    
                                },
    
    animatedToGridViewHandler:  function ()
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('animatedToGridViewHandler');
                                    
                                    RepertoireGridView.numberOfRemainingAnimations--;
                                    
                                    if (RepertoireGridView.numberOfRemainingAnimations == 0)
                                    {
                                        RepertoireGridView.setGridMouseEvents();
                                    }
                                },
    
    calculateGridPositions:     function ()
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('calculateGridPositions');
                                    
                                    // Get the max available width
                                    var maxGridWidth = $(RepertoireGridView.definitionListID).parent().width();
                                    
                                    // Calculate max images per row
                                    var maxImagesPerRow = Math.floor(maxGridWidth / RepertoireGridView.gridImageSize);
                                    
                                    // Calculate needed grid rows
                                    var numberOfGridRows = Math.ceil(RepertoireGridView.artistDataArray.length / maxImagesPerRow);
                                    
                                    // Create array with available positions
                                    var gridImagePositions = new Array();
                                    
                                    for (var row = 0; row < numberOfGridRows; row++)
                                    {
                                        for (var i = 0; i < maxImagesPerRow; i++)
                                        {
                                            if ((row * maxImagesPerRow) + i >= RepertoireGridView.artistDataArray.length)
                                            {
                                                break;
                                            }
                                            
                                            gridImagePositions.push({
                                                top:    row * RepertoireGridView.gridImageSize,
                                                left:   i * RepertoireGridView.gridImageSize
                                            });
                                        }
                                    }
                                    
                                    RepertoireGridView.gridImagePositions = gridImagePositions;
                                    
                                    // Calculate container height
                                    var lastGridItem = gridImagePositions[gridImagePositions.length - 1];
                                    
                                    RepertoireGridView.containerDivGridHeight = lastGridItem.top + RepertoireGridView.gridImageSize;
                                },
                                
    setListMouseEvents:         function ()
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('setListMouseEvents');
                                    
                                    $.each(RepertoireGridView.artistDataArray, function()
                                    {
                                        $(this.ImageDOM).unbind();
                                    });
                                },
                                
    setGridMouseEvents:         function (gridImage)
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('setGridMouseEvents');
                                    
                                    var artistDataArray = RepertoireGridView.artistDataArray;
                                    
                                    if (gridImage != undefined
                                        || gridImage != null)
                                    {
                                        // Get artist data object binded to grid image
                                        artistDataArray = new Array();
                                        
                                        $.each(RepertoireGridView.artistDataArray, function()
                                        {
                                            if (this.ImageDOM[0] == gridImage)
                                            {
                                                artistDataArray.push(this);
                                            }
                                        });
                                    }
                                    
                                    // Reset the mouse events
                                    $.each(artistDataArray, function()
                                    {
                                        $(this.ImageDOM).unbind();
                                        $(this.ImageDOM).mouseenter(RepertoireGridView.gridHoverOnMouseEnter);
                                        $(this.ImageDOM).mouseleave(this, RepertoireGridView.gridHoverOnMouseLeave);
                                        $(this.ImageDOM).click(this, RepertoireGridView.gridHoverOnMouseClick);
                                    });
                                },
                                
    gridHoverOnMouseEnter:      function ()
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('gridHoverOnMouseEnter');
                                    
                                    var expandSize = Math.round((RepertoireGridView.gridImageSize * RepertoireGridView.gridHoverGrowMultiplier - RepertoireGridView.gridImageSize) / 2);
                                    
                                    $(this).css('z-index', RepertoireGridView.startZIndex + 1);
                                    $(this).animate(
                                        {
                                            top:       parseInt($(this).css('top')) - expandSize,
                                            left:       parseInt($(this).css('left')) - expandSize,
                                            width:      RepertoireGridView.gridImageSize * RepertoireGridView.gridHoverGrowMultiplier,
                                            height:     RepertoireGridView.gridImageSize * RepertoireGridView.gridHoverGrowMultiplier
                                        },
                                        RepertoireGridView.gridHoverGrowDuration,
                                        RepertoireGridView.gridHoverGrowEasing
                                    );
                                },
    
    gridHoverOnMouseLeave:      function(eventData)
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('gridHoverOnMouseLeave');
                                    
                                    $(this).css('z-index', RepertoireGridView.startZIndex);
                                    $(this).animate(
                                        {
                                            top:        eventData.data.ImageContainerGridPosition.top,
                                            left:       eventData.data.ImageContainerGridPosition.left,
                                            width:      RepertoireGridView.gridImageSize,
                                            height:     RepertoireGridView.gridImageSize
                                        },
                                        RepertoireGridView.gridHoverShrinkDuration,
                                        RepertoireGridView.gridHoverShrinkEasing
                                    );
                                },
    
    gridHoverOnMouseClick:      function(eventData)
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('gridHoverOnMouseClick');
                                    
                                    if (RepertoireGridView.gridImageLastZoomedObject != null)
                                    {
                                        RepertoireGridView.zoomOutImage(RepertoireGridView.gridImageLastZoomedObject);
                                        
                                        RepertoireGridView.gridImageLastZoomedObject  = null;
                                    }
                                    
                                    RepertoireGridView.zoomInImage(eventData.data);
                                },
                                
    zoomInImage:                function (artistDataObject)
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('zoomInImage');
                                    
                                    // Set zoomed object
                                    RepertoireGridView.gridImageLastZoomedObject = artistDataObject;
                                    
                                    // Clear current event handlers
                                    $(artistDataObject.ImageDOM).unbind();
                                    
                                    // Calculate grid width
                                    var lastInFirstRowIndex = -1;
                                    
                                    for (var i = 0; i < RepertoireGridView.gridImagePositions.length; i++)
                                    {
                                        if (RepertoireGridView.gridImagePositions[i].top > 0)
                                        {
                                            lastInFirstRowIndex = i - 1;
                                            break;
                                        }
                                    }
                                    
                                    if (lastInFirstRowIndex == -1)
                                    {
                                        lastInFirstRowIndex = RepertoireGridView.gridImagePositions.length - 1;
                                    }
                                    
                                    var gridWidth = RepertoireGridView.gridImagePositions[lastInFirstRowIndex].left + RepertoireGridView.gridImageSize;
                                    var gridHeight = RepertoireGridView.gridImagePositions[RepertoireGridView.gridImagePositions.length - 1].top + RepertoireGridView.gridImageSize;
                                    
                                    var targetLeft = Math.round((gridWidth - RepertoireGridView.gridImageZoomedSize) / 2);
                                    var targetTop = Math.round((gridHeight - RepertoireGridView.gridImageZoomedSize) / 2);
                                    
                                    if (targetLeft < 0
                                        || isNaN(targetLeft) == true)
                                    {
                                        targetLeft = 0;
                                    }
                                    
                                    if (targetTop < 0
                                        || isNaN(targetTop) == true)
                                    {
                                        targetTop = 0;
                                    }

                                    // Tracklisting overlay position
                                    var overlaySelector = 'div#' + RepertoireGridView.gridTracklistingOverlay;
                                    $(overlaySelector).css('top', targetTop);
                                    $(overlaySelector).css('left', targetLeft);

                                    // Calculate durations
                                    var firstStepDuration = Math.round(RepertoireGridView.imageZoomInDuration / 3);
                                    var secondStepDuration = RepertoireGridView.imageZoomInDuration - firstStepDuration;
                                    
                                    // Bring to front
                                    $(artistDataObject.ImageDOM)    .css('z-index', RepertoireGridView.startZIndex + 100)
                                                                    .animate(
                                                                        {
                                                                            top:        Math.round(targetTop / 2) + (RepertoireGridView.gridImageZoomedSize / 2),
                                                                            left:       Math.round(targetLeft / 2) + (RepertoireGridView.gridImageZoomedSize / 2),
                                                                            width:      1
                                                                        },
                                                                        firstStepDuration,
                                                                        RepertoireGridView.imageZoomInFirstStepEasing
                                                                    )
                                                                    .animate(
                                                                        {
                                                                            top:        targetTop,
                                                                            left:       targetLeft,
                                                                            width:      RepertoireGridView.gridImageZoomedSize,
                                                                            height:     RepertoireGridView.gridImageZoomedSize
                                                                        },
                                                                        secondStepDuration,
                                                                        RepertoireGridView.imageZoomInSecondStepEasing,
                                                                        RepertoireGridView.zoomedInHandler
                                                                    );
                                },
                                
    zoomedInHandler:            function (e)
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('zoomedInHandler');
                                    
                                    // Update zoomed image
                                    RepertoireGridView.currentlyZoomedImage = this;
                                },
                                
    createTrackList:            function (artistDataObject)
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('createTrackList');
                                    
                                    var output = '<h1>' + artistDataObject.Name + '</h1><ul>';
                                    
                                    $.each(artistDataObject.Tracks, function () {
                                        output += '<li>' + this + '</li>';
                                    });
                                    
                                    output += '</ul>';
                                    
                                    return output;                                    
                                },
                                
    zoomOutImage:               function (artistDataObject)
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('zoomOutImage');
                                    
                                    // Check parameter type is ArtistData
                                    if (artistDataObject != undefined
                                        && artistDataObject != null
                                        && artistDataObject.IsArtistDataObject != true)
                                    {
                                        artistDataObject = RepertoireGridView.gridImageLastZoomedObject;
                                    }
                                    else
                                    {
                                        artistDataObject = this;
                                    }
                                    
                                    // Stop all running animations
                                    $(artistDataObject.ImageDOM).clearQueue();
                                    $(artistDataObject.ImageDOM).stop();
                                    
                                    // Move image to start position
                                    $(artistDataObject.ImageDOM)    .css('z-index', RepertoireGridView.startZIndex + 99)
                                                                    .animate(
                                                                        {
                                                                            top:        artistDataObject.ImageContainerGridPosition.top,
                                                                            left:       artistDataObject.ImageContainerGridPosition.left,
                                                                            width:      RepertoireGridView.gridImageSize,
                                                                            height:     RepertoireGridView.gridImageSize
                                                                        },
                                                                        RepertoireGridView.imageZoomOutDuration,
                                                                        RepertoireGridView.imageZoomOutEasing,
                                                                        RepertoireGridView.zoomedOutHandler
                                                                    );
                                                                    
                                },
                                
    zoomedOutHandler:           function ()
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('zoomedOutHandler');
                                    
                                    // Set z-index
                                    $(this).css('z-index', RepertoireGridView.startZIndex)
                                    
                                    // Reset mouse events
                                    RepertoireGridView.setGridMouseEvents(this);
                                },
                                
    showTracklistingOverlay:    function ()
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('showTracklistingOverlay');
                                    
                                    // Tracklisting overlay
                                    var overlaySelector = 'div#' + RepertoireGridView.gridTracklistingOverlay;
                                
                                    if ($(overlaySelector).css('display') != 'block')
                                    {
                                        $(overlaySelector).stop();
                                        $(overlaySelector).clearQueue();
                                        
                                    
                                        // Set tracklisting content
                                        $(overlaySelector).html(RepertoireGridView.createTrackList(RepertoireGridView.gridImageLastZoomedObject));
                                    
                                        
                                        $(overlaySelector).css('display', 'block');
                                        $(overlaySelector).fadeTo(RepertoireGridView.showTracklistingOverlayDuration, RepertoireGridView.tracklistingOverlayOpacity);
                                    }
                                },
                                
    hideTracklistingOverlay:    function ()
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('hideTracklistingOverlay');
                                    
                                    // Tracklisting overlay
                                    var overlaySelector = 'div#' + RepertoireGridView.gridTracklistingOverlay;
                                    
                                    if ($(overlaySelector).css('display') != 'none')
                                    {
                                        $(overlaySelector).stop();
                                        $(overlaySelector).clearQueue();
                                        
                                        $(overlaySelector).fadeTo(RepertoireGridView.hideTracklistingOverlayDuration, 0).css('display', 'none');
                                    }
                                },
                                
    tracklistWatchLoop:         function ()
                                {
                                    if (RepertoireGridView.debug && RepertoireGridView.debugLogCalledFunctions) console.log('tracklistWatchLoop');
                                    
                                    // Tracklisting overlay
                                    var overlaySelector = 'div#' + RepertoireGridView.gridTracklistingOverlay;
                                    
                                    // Show if last clicked image is currently zoomed in image
                                    if (RepertoireGridView.gridImageLastZoomedObject != null
                                        && RepertoireGridView.gridImageLastZoomedObject.ImageDOM[0] == RepertoireGridView.currentlyZoomedImage)
                                    {
                                        RepertoireGridView.showTracklistingOverlay();
                                    }
                                    else
                                    {
                                        RepertoireGridView.hideTracklistingOverlay();
                                    }
                                    
                                    // Loop needed?
                                    if (RepertoireGridView.currentViewMode == RepertoireGridViewMode.GridView)
                                    {
                                        // Loop!
                                        setTimeout(RepertoireGridView.tracklistWatchLoop, RepertoireGridView.durationSleepWatchTracklist);
                                    }
                                }
};
