// Creates a new scrolling list and returns it.
var scrollingList = function(listItems)
{
    var MAX_COVERS = 4;
    var TIMER_DELAY = 25;
    var FADE_STEPS = 5;
    var SCROLL_SPEED = 20;
    var SCROLL_SPEED_MAX = 80;
    var SCROLL_FALLOFF = 3;
    var SCROLL_OVERFLOW = 50;
    var MOUSE_REPEAT = 40;
    var MOUSE_SAMPLE_DELAY = 200;

    // Private Attributes
    var that = {};
    var listRef;
    var containerRef;
    var arrowLeftRef;
    var arrowRightRef;
    var closeRef;
    var listOffset = 0;
    var currentDesc;
    var listWidth;

    var fadeTimerId;
    var opacity;

    var scrollTimerId;
    var scrollSpeed;

    var mouseDownTimerId;

    var x;
    var mouseDownCoverId;
    var isMouseDown = false;
    var sampleTimerId;
    var sampleX;


    // Public Attributes
    that.divRef;


    // Private Methods

    function stopFade()
    {
        clearTimeout(fadeTimerId);
        fadeTimerId = undefined;
    }


    function timerFadeIn()
    {
        opacity += 1 / FADE_STEPS;

        if (opacity >= 1) {
            opacity = 1;
            stopFade();
        }

        setOpacity(currentDesc, opacity);
    }


    function timerFadeOut()
    {
        opacity -= 1 / FADE_STEPS;

        if (opacity <= 0) {
            currentDesc.removeChild(closeRef);
            currentDesc.style.display = "none";
            mouseDownCoverId = undefined;
            stopFade();
        }
        else
        {
            setOpacity(currentDesc, opacity);
        }
    }


    function startFadeIn()
    {
        stopFade();
        fadeTimerId = setInterval(timerFadeIn, TIMER_DELAY);
    }


    function startFadeOut()
    {
        stopFade();
        fadeTimerId = setInterval(timerFadeOut, TIMER_DELAY);
    }


    // Sample the current x value each MOUSE_SAMPLE_DELAY ms.
    function mouseMoveSample()
    {
        sampleX = x;
    }


    function coverMouseDown(event)
    {
        if (event === undefined)
        {
            event = window.event;
        }

        if (scrollTimerId !== undefined)
        {
            // Stop any movement.
            clearInterval(scrollTimerId);
            scrollTimerId = undefined;
        }

        if (this.descriptionId !== undefined)
        {
            mouseDownCoverId = this.descriptionId;
        }

        // Get mouse movement.
        x = event.clientX + document.body.scrollLeft;

        isMouseDown = true;
    }


    function coverMouseMove(event) {
        if (isMouseDown)
        {
            if (event === undefined)
            {
                event = window.event;
            }

            var x2 = event.clientX + document.body.scrollLeft;
            var dist = x2 - x;

            if (mouseDownCoverId === undefined)
            {
                // Move the covers.
                listOffset += dist;
                listRef.style.left = listOffset + "px";

                x = x2;
            }
            else if (Math.abs(dist) > 3)
            {
                mouseDownCoverId = undefined; // Prevents clicks

                // Start the sample timer that will determine mouse up cover speeds.
                sampleX = x;
                sampleTimerId = setInterval(mouseMoveSample, MOUSE_SAMPLE_DELAY);
            }
        }
    }


    function coverMouseUp(event)
    {
        if (event === undefined)
        {
            event = window.event;
        }

        if (isMouseDown)
        {
            // Check if we click a cover.
            if (mouseDownCoverId !== undefined)
            {
                currentDesc = document.getElementById(mouseDownCoverId);
                setOpacity(currentDesc, 0);
                currentDesc.style.display = "block";

                // Add close button to description.
                currentDesc.appendChild(closeRef);

                // Start the fade in.
                opacity = 0;
                startFadeIn();
            }
            else
            {
                // Otherwise we stop scrolling, but the list might have some momentum.

                // Stop sample timer
                clearTimeout(sampleTimerId);
                sampleTimerId = undefined;

                // Calculate the momentum
                var dist = sampleX - x;
                scrollSpeed = 0;

                if (Math.abs(dist) > 0)
                {
                    scrollSpeed = Math.floor(-dist / 4);
                }

                scrollTimerId = setInterval(scrollTimerTick, TIMER_DELAY);
            }

            isMouseDown = false;
        }

        // Also clear any arrow timers
        arrowMouseUp();
    }


    function closeDescription()
    {
        startFadeOut();
    }


    function calcSpeed(dist)
    {
        var spd = 0;
        var dst = 0;

        while (dst < dist)
        {
            spd += SCROLL_FALLOFF;
            dst += spd;
        }

        return spd;
    }


    function scrollTimerTick()
    {
        // Clamp scrollspeed
        scrollSpeed = Math.min(Math.max(scrollSpeed, -SCROLL_SPEED_MAX), SCROLL_SPEED_MAX);

        // Keep scrolling with falling speed.
        if (scrollSpeed > 0)
        {
            scrollSpeed = Math.max(scrollSpeed - SCROLL_FALLOFF, 0);
        }
        else
        {
            scrollSpeed = Math.min(scrollSpeed + SCROLL_FALLOFF, 0);
        }

        if (scrollSpeed === 0)
        {
            // Stop scrolling if at end of list.
            if (listOffset > 0)
            {
                if (listOffset > SCROLL_FALLOFF)
                {
                    scrollSpeed = -calcSpeed(listOffset);
                }
                else
                {
                    listOffset = 0;
                    listRef.style.left = listOffset + "px";
                }
            }
            else if (listOffset < -listWidth)
            {
                if (listOffset < -listWidth - SCROLL_FALLOFF)
                {
                    scrollSpeed = calcSpeed(-listOffset - listWidth);
                }
                else
                {
                    listOffset = -listWidth;
                    listRef.style.left = listOffset + "px";
                }
            }
            else
            {
                // Stop scrolling.
                clearInterval(scrollTimerId);
                scrollTimerId = undefined;
            }
        }
        else
        {
            listOffset += scrollSpeed;
            listRef.style.left = listOffset + "px";

            if (listOffset > SCROLL_OVERFLOW ||
                listOffset < -listWidth - SCROLL_OVERFLOW)
            {
                scrollSpeed = 0;
            }
        }
    }


    function setNewCover(newId)
    {
        if (newId !== null && newId !== undefined)
        {
            var newCover = document.getElementById(newId);

            currentDesc.removeChild(closeRef);
            currentDesc.style.display = "none";

            newCover.appendChild(closeRef);
            newCover.style.display = "block";
            setOpacity(newCover, 1);

            currentDesc = newCover;
            mouseDownCoverId = newId;
        }
    }


    function arrowLeftDown()
    {
        if (fadeTimerId !== undefined)
        {
            return;
        }

        // If description is shown, cycle descriptions instead of covers.
        if (mouseDownCoverId !== undefined)
        {
            // Find id in list
            var newId = null;
            for (var i = 0; i < listItems.length; i++)
            {
                if (mouseDownCoverId === listItems[i].id && i !== 0)
                {
                    newId = listItems[i - 1].id;
                    break;
                }
            }

            setNewCover(newId);
        }
        else
        {
            if (scrollTimerId === undefined)
            {
                // We are not scrolling, so start.
                scrollSpeed = SCROLL_SPEED;
                scrollTimerId = setInterval(scrollTimerTick, TIMER_DELAY);
            }
            else if (mouseDownTimerId !== undefined)
            {
                // We are scrolling and mouse is down.
                if (listOffset < 0)
                {
                    scrollSpeed = SCROLL_SPEED;
                }
                else
                {
                    // Mouse is down and list is out of page, so don't keep trying to move.
                    return;
                }
            }

            mouseDownTimerId = setTimeout(arrowLeftDown, MOUSE_REPEAT);
        }
    }


    function arrowRightDown()
    {
        if (fadeTimerId !== undefined)
        {
            return;
        }

        // If description is shown, cycle descriptions instead of covers.
        if (mouseDownCoverId !== undefined)
        {
            // Find id in list
            var newId = null;
            for (var i = 0; i < listItems.length; i++)
            {
                if (mouseDownCoverId === listItems[i].id && i !== listItems.length - 1)
                {
                    newId = listItems[i + 1].id;
                    break;
                }
            }

            setNewCover(newId);
        }
        else
        {
            if (scrollTimerId === undefined)
            {
                // We are not scrolling, so start.
                scrollSpeed = -SCROLL_SPEED;
                scrollTimerId = setInterval(scrollTimerTick, TIMER_DELAY);
            }
            else if (mouseDownTimerId !== undefined)
            {
                // We are scrolling and mouse is down.
                if (listOffset > -listWidth)
                {
                    scrollSpeed = -SCROLL_SPEED;
                }
                else
                {
                    // Mouse is down and list is out of page, so don't keep trying to move.
                    return;
                }
            }

            mouseDownTimerId = setTimeout(arrowRightDown, MOUSE_REPEAT);
        }
    }


    function arrowMouseUp()
    {
        clearTimeout(mouseDownTimerId);
        mouseDownTimerId = undefined;
    }


    // Initialize all the covers.
    (function()
    {
        that.divRef = document.createElement("div");
        that.divRef.className = "game_covers";
        listRef = document.createElement("ul");

        // Arrows
        arrowLeftRef = document.createElement("div");
        arrowLeftRef.className = "arrow_left";
        arrowLeftRef.onmousedown = arrowLeftDown;
        that.divRef.appendChild(arrowLeftRef);

        arrowRightRef = document.createElement("div");
        arrowRightRef.className = "arrow_right";
        arrowRightRef.onmousedown = arrowRightDown;
        that.divRef.appendChild(arrowRightRef);

        // Close button
        closeRef = document.createElement("div");
        closeRef.className = "close_tab";
        closeRef.onclick = closeDescription;

        // Div for all covers that hides overflowing
        containerRef = document.createElement("div");
        containerRef.className = "game_cover_container";

        // Put all covers into a scrollable div
        for (var i = 0; i < listItems.length; i++)
        {
            var item = listItems[i];
            var itemRef = document.createElement("li");
            var image = new Image();

            image.className = "game_cover";
            image.src = item.src;
            image.alt = item.alt;
            image.width = "152";
            image.height = "276";
            image.onmousedown = coverMouseDown;

            // IE - To prevent default image dragging.
            image.ondragstart = function() { return false; }

            image.descriptionId = item.id; // Special tag to get the description page for the item.

            itemRef.appendChild(image);
            listRef.appendChild(itemRef);
        }

        // Hook move and up event on body, so that the movement will work on the whole page.
        document.body.onmousemove = coverMouseMove;
        document.body.onmouseup = coverMouseUp;

        containerRef.appendChild(listRef);
        that.divRef.appendChild(containerRef);

        // Calculate the width of the list (as much as it can move).
        listWidth = (listItems.length * (152 + 7)) - 630; // Width of container
    }());


    return that;
};

