//open encapsulation wrapper
(function() {

    //== configuration ========================================================

    //whether to animate the restoration of a deep menu state (true)
    //or just snap to position without the animation (false)
    var anirestore = false;

    //animation settings
    var aniresolution = {
        'position': 36, //for the left/right position shift (ipod effect)	
        'height': 6		//for the expanding/contracting vertical space
    };

    //base cookie key [final keys will be base+"section" and base+"ident"]
    var basekey = 'deewr-ipodnavigation-';

    //=========================================================================


    //-- global values --------------------------------------------------------

    //identify IE6 for some hackery
    var IE6 = typeof document.uniqueID != 'undefined' && /msie 6/i.test(navigator.userAgent);

    //get a reference to the main menu container
    var menu = document.getElementById('ipodnavigation');

    //get the current section from the menu's class name
    //if no section is indicated, set the value to "_none"
    //so that we always have a string value we can save to cookie
    var section = menu.className;
    if (!section) { section = '_none'; }

    //the width of the menu for left-position calculations
    var unitwidth = menu.offsetWidth;

    //create a busy flag to ignore menu events when the ipod animation is in progress
    var busy = false;

    //create an activemenu object to track which menu is currently visible
    //which we'll use to prevent action events on hidden menus
    var activemenu = null;



    //-- initialization -------------------------------------------------------

    //get the collection of menu list items
    var items = menu.getElementsByTagName('li');

    //iterate through them for initialization
    for (var i = 0; i < items.length; i++) {
        //if this is the top-level item that corresponds 
        //with the currently-open section (if there is one)
        if (items[i].className.indexOf(section) != -1) {
            //add an ident property to every list item inside this section
            //this isn't the most efficient way of doing this, but it is the simplest
            //and for the number of items we're dealing with the difference is negligible
            var sectionitems = items[i].getElementsByTagName('li');
            for (var j = 0; j < sectionitems.length; j++) {
                sectionitems[j]._ident = j.toString();
            }
        }

        //ignore top level items
        if (items[i].parentNode.className.indexOf('top-level') != -1) { continue; }

        //if this items has a submenu
        var childlists = items[i].getElementsByTagName('ul');
        if (childlists[0]) {
            //get a reference to the link
            var link = items[i].getElementsByTagName('a').item(0);

            //get the text before modification
            var linktext = link.innerHTML;

            //add an arrow indicator 
            link.innerHTML += '<span class="arrow"></span>';

            //add a class name for CSS and internal identification
            link.className = 'next';

            //add a "back" link to the top of the submenu
            //which is simply a deep clone of the "next" link with a modified class name
            var backlink = link.cloneNode(true);
            backlink.className = 'back';
            var backitem = document.createElement('li');
            backitem.appendChild(backlink);
            childlists[0].insertBefore(backitem, childlists[0].getElementsByTagName('li').item(0));
        }
    }


    //-- utility methods ------------------------------------------------------


    //open a new menu
    function openNextMenu(item, currentmenu, putfocus, animate, oncomplete) {
        //if the putfocus flag is undefined, set it to true
        if (typeof putfocus == 'undefined') { putfocus = true; }

        //if the animate flag is undefined, set it to true
        if (typeof animate == 'undefined') { animate = true; }

        //set the busy flag
        busy = true;

        //get a reference to the new menu and reset its height
        var newmenu = item.getElementsByTagName('ul').item(0);
        newmenu.style.height = 'auto';

        //set the menu to active
        activemenu = newmenu;

        //set the new menu's top position to match the current menu
        //which will be the inverse of the parent item's offset top
        newmenu.style.top = (0 - item.offsetTop) + 'px';

        //make the new menu visible
        newmenu.style.display = 'block';

        //get a reference to the parent (second-level) menu
        var parentmenu = newmenu;
        while (parentmenu.className.indexOf('menuData') == -1) {
            parentmenu = parentmenu.parentNode;
        }

        //abstract the tasks to perform when the menu shift is complete
        //which is just to avoid the code repetition
        function shiftComplete() {
            //IE6 hackery, to compensate for the fact that an element's height
            //is limited by the height of its children, rather than its children overflowing
            //so we have to set each child to have absolute positioning, 
            //so that they effectively have no height in the parent context
            //nb. with this childNodes collection we're also relying on the fact
            //that whitespace-only text nodes do not show up, therefore the list-items
            //are all direct firstChild elements of the parent list
            if (IE6) {
                var children = currentmenu.childNodes;
                for (var i = 0; i < children.length; i++) {
                    children[i].style.position = 'absolute';
                }
                newmenu.style.top = 'auto';
            }

            //if the putfocus flag is true
            //set focus on the first link in the new menu
            if (putfocus) {
                newmenu.getElementsByTagName('a').item(0).focus();
            }

            //if we have an oncomplete method, call it now
            if (typeof oncomplete == 'function') {
                oncomplete();
            }

            //clear the busy flag
            busy = false;
        }

        //now, if the animate flag is true we want to animate this menu's opening
        if (animate) {
            //animate the parent menu's height to match the new menu
            new Animator('height', aniresolution.height, parentmenu, parentmenu.offsetHeight, newmenu.offsetHeight);

            //animate the left-position shift
            new Animator('left', aniresolution.position, parentmenu, parentmenu.offsetLeft, parentmenu.offsetLeft - unitwidth,

            //and when that completes
			function() {
			    //call the shift complete method
			    shiftComplete();
			});
        }

        //otherwise we just want a snap to position
        else {
            //set the parent menu's height to match the new menu
            parentmenu.style.height = newmenu.offsetHeight + 'px';

            //set the parent menu's left position 
            parentmenu.style.left = (parentmenu.offsetLeft - unitwidth) + 'px';

            //call the shift complete method
            shiftComplete();
        }
    }


    //-- restore a deep menu state from cookie --------------------------------

    //if we have a stored section and it corresponds with this section
    var storedsection = getcookie('section', basekey);
    if (storedsection && storedsection == section) {
        //get the stored ident
        var storedident = getcookie('ident', basekey);

        //now we need to get the collection of menu-triggering list items
        //from the top-level menu to the menu containing the stored ident
        //which we'll have to do backwards, starting by identifying the ident item
        for (var triggeritems = [], i = 0; i < items.length; i++) {
            if (items[i]._ident == storedident) {
                var identitem = items[i];
            }
        }

        //then iterating backwards through the parent triggers
        //to build an inverse array of the triggering list items
        //from the ident-item's parent, to the trigger inside the top-level menu
        while (true) {
            identitem = identitem.parentNode;
            if (identitem.className && /menuData/.test(identitem.className)) {
                break;
            }
            else {
                identitem = identitem.parentNode;
            }
            triggeritems.push(identitem);
        }

        //if we have any trigger items
        if (triggeritems.length > 0) {
            //reverse the array to get top-down data
            triggeritems.reverse();

            //now we just need to run through that array, and pass each item
            //and its containing menu to the openNextMenu method
            //using the callback from that to call the next one
            //until we've been through them all, at which point
            //the deepest level menu will now be open :)
            //when calling openNextMenu we also pass the anirestore flag
            //to specify whether to use animation, and false for the putfocus flag
            //so that the programmatic action does not set menu focus
            function restoreNextMenu(item, currentmenu) {
                openNextMenu(item, currentmenu, false, anirestore,
				function() {
				    triggeritems.splice(0, 1);
				    if (triggeritems.length > 0) {
				        restoreNextMenu(triggeritems[0], triggeritems[0].parentNode);
				    }
				});
            }
            restoreNextMenu(triggeritems[0], triggeritems[0].parentNode);
        }
    }

    //if we have a stored section but it doesn't correspond with this section
    else if (storedsection && storedsection != section) {
        //delete the stored section and ident cookies
        //so that we're not trying to restore the wrong deep menu
        clearcookie('section', basekey);
        clearcookie('ident', basekey);
    }


    //-- event handlers -------------------------------------------------------

    //bind a generic click handler to the main menu
    //this will handle both mouse and keyboard click events on the menu links
    addEvent(menu, 'click', function(e) {
        //get target node, converting text node references
        var target = e.target ? e.target : event.srcElement;
        if (target.nodeName == '#text') { target = target.parentNode; }

        //if the item is a link
        if (target.href) {
            //if it's a next or back link
            if (target.className && /(next|back)/.test(target.className)) {
                //if the busy flag is already true, block this action
                if (busy) {
                    try { e.preventDefault(); } catch (err) { }
                    return false;
                }

                //if this is a next link
                if (/next/.test(target.className)) {
                    //get a reference to the current menu
                    var currentmenu = target.parentNode.parentNode;

                    //if we have an active menu reference 
                    //and it isn't the current menu
                    //shift the focus to the top link in the active menu
                    //then cancel the default action and we're done
                    if (activemenu != null && currentmenu != activemenu) {
                        activemenu.getElementsByTagName('a').item(0).focus();
                        try { e.preventDefault(); } catch (err) { }
                        return false;
                    }

                    //pass the parent list item and current menu
                    //to the openNextMenu method, which is abstracted
                    //so that we can call it programatically to restore a cookie state
                    openNextMenu(target.parentNode, currentmenu);
                }

                //or if it's a backlink
                else {
                    //get a reference to the current menu
                    var currentmenu = target.parentNode.parentNode;

                    //block this action if the menu is not active
                    if (activemenu != null && currentmenu != activemenu) {
                        activemenu.getElementsByTagName('a').item(0).focus();
                        try { e.preventDefault(); } catch (err) { }
                        return false;
                    }

                    //set the busy flag
                    busy = true;

                    //get a reference to the new menu 
                    var newmenu = currentmenu.parentNode.parentNode;

                    //set the menu to active
                    activemenu = newmenu;

                    //IE6 hackery, to reset the hackery we did when opening this menu
                    if (IE6) {
                        var children = newmenu.childNodes;
                        for (var i = 0; i < children.length; i++) {
                            children[i].style.position = 'relative';
                        }
                        currentmenu.style.top = (0 - target.parentNode.parentNode.parentNode.offsetTop) + 'px';
                    }

                    //get a reference to the parent (second-level) menu
                    //which may in fact be the same reference as newmenu, but that doesn't matter
                    var parentmenu = currentmenu;
                    while (parentmenu.className.indexOf('menuData') == -1) {
                        parentmenu = parentmenu.parentNode;
                    }

                    //store the current parent menu height
                    var currentheight = parentmenu.offsetHeight;

                    //reset the parent menu's height, unless this is IE6 and the parentmenu is not the newmenu
                    //which we have to do in case parentmenu and newmenu are the same reference
                    //otherwise we'd just be setting it to its current (previous-state) height
                    //but if we do this under the specific conditions in IE6 
                    //we'll get a flash of no height, which is its initial auto value
                    //we do however have to do it if the parentmenu is the newmenu
                    //otherwse the parentmenu won't contract to its correct height
                    //however since we have to reset the child heights in IE6 before doing this
                    //it means that if the newmenu is taller than the current menu
                    //the change in height will be snap motion, not animation
                    if (!(IE6 && parentmenu != newmenu)) {
                        parentmenu.style.height = 'auto';
                    }

                    //animate the parent menu's height to match the new menu
                    new Animator('height', aniresolution.height, parentmenu, currentheight, newmenu.offsetHeight);

                    //animate the left-position shift
                    new Animator('left', aniresolution.position, parentmenu, parentmenu.offsetLeft, parentmenu.offsetLeft + unitwidth,

                    //and when that completes
					function() {
					    //hide the current menu
					    currentmenu.style.display = 'none';

					    //set focus on the parent link in the new menu
					    //currentmenu.parentNode.getElementsByTagName('a').item(0).focus();

					    //clear the busy flag
					    busy = false;
					});
                }

                //don't follow the default action
                try { e.preventDefault(); } catch (err) { }
                return false;
            }

            //otherwise it's a regular link
            else {
                //get the parent list item, allowing for intermediate headings
                var parentitem = target;
                while (!/li/i.test(parentitem.nodeName)) {
                    parentitem = parentitem.parentNode;
                }

                //if the item has an ident, save it and the section name to cookies
                if (typeof parentitem._ident != 'undefined') {
                    setcookie('section', basekey, section, (60 * 60 * 1000));
                    setcookie('ident', basekey, parentitem._ident, (60 * 60 * 1000));
                }

                //allow the default action and follow the link
                return true;
            }
        }

        //if we get here just allow the default action and do nothing
        return true;
    });


    //-- menu animation object ------------------------------------------------

    //animation object for position and height shifts
    //we have this as a constructor function rather than a simple method
    //so that every instance is unique, which helps us to reduce collision
    function Animator(property, resolution, menu, current, end, oncomplete) {
        //if this is a height animation and the menu already has one running
        //stop the timer and nullify the reference
        //we don't need to do this with position animations because
        //the busy flag prevents colliding instances
        if (property == 'height' && menu['height-timer']) {
            window.clearInterval(menu['height-timer']);
            menu['height-timer'] = null;
        }

        //work out whether the direction of movement is positive or negative
        var positive = current < end ? true : false;

        //start the animation timer, and bind the instance as a property of the menu
        //so that we can prevent more than once height instance per menu
        menu[property + '-timer'] = window.setInterval(function() {
            //increment or decrement the property value 
            //by the number of pixels specified in the resolution argument
            if (positive) { current += resolution; }
            else { current -= resolution; }

            //***DEV
            //document.title = 'current = ' + current + ', end = ' + end;

            //if we've reached or exceeded the end value
            //round it off then stop the timer
            //and set a finished flag so we know when to call the oncomplete callback
            if ((positive && current >= end) || (!positive && current <= end)) {
                current = end;
                window.clearInterval(menu[property + '-timer']);
                var finished = true;
            }

            //set the property value
            menu.style[property] = current + 'px';

            //if the finished flag is defined, 
            //and we have an oncomplete callback, call it now
            if (typeof finished != 'undefined' && typeof oncomplete == 'function') {
                oncomplete();
            }

            //any faster than this won't actually be any faster
        }, 20);
    }

    //close encapsulation wrapper
})();

function resetIpodAndRedirect(sectionKey, nextUrl) {
    clearcookie('section', 'deewr-ipodnavigation-');
    clearcookie('ident', 'deewr-ipodnavigation-');
    window.location.href = nextUrl;
}

//add-event construct
function addEvent(element, type, handler) {
    if (typeof element.addEventListener != 'undefined') {
        element.addEventListener(type, handler, false);
    }
    else if (typeof element.attachEvent != 'undefined') {
        element.attachEvent('on' + type, handler);
    }
}

//get a cookie
function getcookie(key, basekey) {
    //create an initially null cookie object
    var cookie = null;

    //if we have a cookie with the specified key
    if (document.cookie.indexOf(basekey + key) != -1) {
        //mash the cookie to extract our data
        cookie = document.cookie.split(key + '=')[1].split(';')[0].split('=')[0];
    }

    //return the final data string (or null)
    return cookie;
}

//set a cookie
function setcookie(key, basekey, datastring, expiration) {
    //format the expiry date (for one hour) and create the cookie
    var now = new Date();
    now.setTime(now.getTime() + expiration);
    document.cookie = basekey + key + '='
			+ datastring
			+ '; expires=' + now.toGMTString()
			+ '; path=/';
}

//clear a cookie
function clearcookie(key, basekey) {
    //create a cookie with the expiry date in the past
    var now = new Date();
    now.setTime(now.getTime() - 3600);
    document.cookie = basekey + key + '='
			+ '; expires=' + now.toGMTString()
			+ '; path=/';
}