/// <reference path="~/Abl_Scripts/Source/jquery.js" />

// JSLint
// var Abl, $, jQuery, alert, setTimeout, clearTimeout;


/*
** This file contains three classes:
**
** Abl.UI.MenuManager      Collectively manages instances of Abl.UIU.Clamshell and Abl.UI.Menu
** Abl.UI.Clamshell         Manages a clamshell navigation control instance
** Abl.UI.Menu               Manages a horizontal or vertical drop-down menu instance
**
** This menu was inspired by the jQuery.jdMenu by Jonathan Sharp (http://jdsharp.us),
** particulary his technique for calculating the ul.subGroup sizes and his
** positionBy() method.
**
** 30.9.2009               Added vertical/horizontal menu options to the MenuManager
**                           class and introduced the 'disableAction' option.
*/



/*******************************************************************************************
* Abl.UI.BaseMenu                                                                          *
*******************************************************************************************/
/*
** Provides a base class and common properties and functionality for the Abl.UI.Menu and
** Abl.UI.Clamshell classes.
*/
Abl.UI.BaseMenu = function(menu, options) {

   return (function(menu, options) {
   
      var   foo = {};
            foo.$menu = ((menu instanceof jQuery) ? menu : $(menu)).eq(0);         // Only one menu at a time!
            foo.$rootGroup = foo.$menu.children("ul.rootGroup");
            foo.$parentItems = foo.$menu.find("li.menuItem.hasChildren");         // All menu items with children
            foo.params = $.extend(true, {}, Abl.UI.BaseMenu.defaults, options);


      /*******************************************************************************************
      * Link Mapper                                                                              *
      *******************************************************************************************/
      // Applies 'location sensitive' context attributes to the li.menuItem/a.link elements
      // based on their relevance to the current page location.  The class attributes applied
      // are as follows:
      //
      //   'folderMatch'      => The href folder matches the current page's folder
      //   'exactMatch'      => The href path matches the current page's path
      //   'targetTrail'      =>   A descendent element is an 'exactMatch'
      foo.mapLocation = function() {
         var   pageUri = new Abl.Uri(),      // Get the uri of the current page (window.location)
               linkUri = new Abl.Uri(),      // Use to store the uri of each a.link element
               pagePath = pageUri.getPath(),
               $link, href;

         $("a.link", foo.$menu).each(function() {
            $link = $(this);
            href = $link.attr("href");
            if ((href) && (href.length > 0)) {
               // See if the link href folder matches that of the current page.  Note, it 
               // doesn't make any sense to match on just the root folder - so we don't!
               linkUri.setUri(href);
               if ((linkUri.getFolder()) &&  (linkUri.compare(pageUri, "folder", true))) {
                  if ((pageUri.getFolder().length > 1) && (linkUri.getFolder().length > 1)) {
                     $link.parent("li.menuItem").andSelf().addClass("folderMatch");
                  }
                  
                  // While we are here, check to see if we have an exact match and, if
                  // we do, build a 'targetTrail' for the ancestral hierarchy - note that
                  // we also have to handle the situation when only the path is specified
                  // and the web server loads the default page...
                  // www.abldev.com/contactus => www.abldev.com/contactus/default.aspx
                  if (
                     ((pagePath.substr(pagePath.length - 1, 1) === "/") && (linkUri.getFile().toLowerCase() === "default.aspx" ) && (linkUri.compare(pageUri, "folder", false))) || (linkUri.compare(pageUri, "path"))
                  ) {
                     $link.parent("li.menuItem").andSelf().addClass("exactMatch");
                     $link.parent("li.menuItem").parents("li.menuItem").each(function() {
                        $(this).children("a.link").andSelf().addClass("targetTrail");
                     });
                  }
               }
            }
         });
      };



      /*******************************************************************************************
      * Event Management                                                                         *
      *******************************************************************************************/
      foo.wireEvents = function() {
         throw "Do not inherit from Abl.UI.BaseMenu.wireEvents()!";
      };



      /*******************************************************************************************
      * Object Initialisation                                                                    *
      *******************************************************************************************/
      foo.init = function() {
         if (foo.params.mapLocation) {
            foo.mapLocation();
         }
         foo.wireEvents();
      };



      /*******************************************************************************************
      * Object Disposal                                                                          *
      *******************************************************************************************/
      foo.dispose = function() {
         foo.$parentItems = null;
         foo.$rootGroup = null;
         foo.$menu = null;
      };


      return foo;
   }(menu, options));
};

Abl.UI.BaseMenu.defaults = {
   mapLocation: true
};





/*******************************************************************************************
* Abl.UI.Menu (Horizontal and Vertical)                                                    *
*******************************************************************************************/

Abl.UI.Menu = function(menu, options) {

   return (function(menu, options) {

      var   base = {},
            foo = Abl.UI.BaseMenu(menu, $.extend(true, {}, Abl.UI.Menu.defaults, options));


      /*******************************************************************************************
      * Helper Methods                                                                           *
      *******************************************************************************************/
      
      // clearTimer()
      // 
      // Utility method for clearing the timer event associated
      // with the ul.subGroup's expando data attribute.
      function clearTimer(subGroup) {
         if (subGroup.timerId)
         {
            clearTimeout(subGroup.timerId);
            subGroup.timerId = null;
         }
      }



      // setPosition()
      // 
      // Correctly positions the div.subGroupCanvass and its child ul.subGroup
      // in relation to their parent li.menuItem.  The technique is as follows:
      // 
      // 1) Normalise their css properties - we need a known/constant element
      //    profile to work with.
      // 2) Position the elements in a negative z,y quadrant so we can make
      //    the 'visible' (though unseen by the user).
      // 3) Obtain the dimensions of the bounding/enclosing rectangle - we have
      //    to play some tricks with the inner li elements here to keep IE6 happy.
      // 4) Optionally hide the elements.
      // 5) Move them into their correct/final positions.
      //
      // Parameters:
      // $canvass      div.subGroupCanvass element
      // params      {
      //                  x: 120         -- horizonatl offset from parent li.menuItem
      //                  y: 14            -- vertical offset from parent li.menuItem
      //                  hide: true      -- hide/re-hide elements of positioning...
      //               }                  -- calling method will apply animation effects
      function setPosition($canvass, params) {
         var   $subGroup = $canvass.children("ul.subGroup"),
               ulWidth = 0, ulHeight = 0, ulOuterWidth = 0, ulOuterHeight = 0;
         
         // Normalise the div.subGroupCanvass and the ul.subGroup elements
         $canvass.css({
            "display": "block",
            "position": "absolute",
            "left": "-5000px",      // Position way over to the top/left to avoid triggering scrollbars
            "top": "-5000px",
            "width": "1000px",      // Make sure the canvass is big enough to avoid constraining the subGroup
            "height": "1000px",
            "margin": "0",
            "padding": "0"
            // "background-color": "#000"
         }).show();
         
         $subGroup.css({
            "display": "block",
            "position": "absolute",
            "left": "0",
            "top": "0",
            "width": "auto",
            "height": "auto"
         }).show();

         // The important bit! Now they are both 'visible', we can interrogate their 
         // overall dimensions!  Note, we need the outerWidth/outerHeight to size
         // the background div.subGroupCanvass.
         ulWidth = $subGroup.width();
         ulHeight = $subGroup.height();
         ulOuterWidth = $subGroup.outerWidth(true);
         ulOuterHeight = $subGroup.outerHeight(true);

         // Abl.DEBUG.trace("width: " + ulWidth + "px, outerWidth: " + ulOuterWidth + "px");
         // Abl.DEBUG.trace("height: " + ulHeight + "px, outerHeight: " + ulOuterHeight + "px");

         // Typically, li.menuItems are floated, so their widths shrink to accomodate
         // their descendant content - which is exactly what we want to happen (IE6
         // has a tendency to allow the li.menuItem element to grow to the width of
         // screen!).  The containing ul.subGroup element will therefore be sized to
         // accommodate the widest li.menuItem. The only problem with this is that if
         //  background styling is applied to the li.menuItems, it will fall short of
         // the full width and will be displayed as a 'ragged' right edge.
         // 
         // Since we now know the width of the ul.subGroup, we can do interrogate the
         // overall width of the li.menuItems, do some math, and fix accordingly! This
         // way all li.menuItems should a) be the same width and b) fill the 
         // ul.subGroup - perfect!
         $subGroup.children("li.menuItem").each(function() {
            var   $li = $(this),
                  liWidth = $li.width(),
                  liOuterWidth = $li.outerWidth(true),
                  delta = ulWidth - liOuterWidth;
                  
            // Abl.DEBUG.trace(liWidth + "/" + liOuterWidth + " in " + ulWidth + " (delta: " + delta +")");
            $li.width(liWidth + delta);
         });

         // Now, in reverse order, update the ul.subGroup and the div.subGroupCanvass...
         $subGroup.css({
            "display": (params.hide) ? "none" : "block"
         });

         $canvass.css({
            "display": (params.hide) ? "none" : "block",
            "width": ulOuterWidth + "px",
            "height": ulOuterHeight + "px",
            "left": params.x + "px",         // Position relative to the parent object
            "top": params.y + "px"
         }).data("ablMenu", {                  // Add an expando flag to indicate that this
            hasLayout: true                  // div.subGroupCanvass has been positioned
         });                                 // correctly for layout.
      }



      /*******************************************************************************************
      * Event Handlers                                                                           *
      *******************************************************************************************/
      foo.wireEvents = function()
      {
         // Add a class='hover' attribute to the a.link's parent li.menuItem element
         // as this will make life easier when formatting is applied to the li.menuItem
         foo.$menu.find("a.link").hover(function() {
            $(this).parent().addClass("hover");
         }, function() {
            $(this).parent().removeClass("hover");
         });


         // Provide an option for disabling the dynamic mouseenter/mouseleave events.  This gives
         // a fully styled and tagged menu structure withoput the expand and collpase effects which
         // can be useful when creating simple navigation structures (like OriginAM).
         if (!foo.params.disableAction) {
            // Mouseenter - the mouse is over a li.menuItem that has children, so
            // we need to expand the next menu level
            foo.$parentItems.mouseenter(function() {
               var   $item = $(this),                                    // li.menuItem
                     $link = $item.children("a.link"),                  // a.link
                     $canvass = $item.children("div.subGroupCanvass"),   // div.subGroupCanvass
                     canvassData = $canvass.data("ablMenu"),
                     $subGroup = $canvass.children("ul.subGroup");      // div.subGroupCanvass > ul.subGroup

               clearTimer(this);
               this.timerId = setTimeout(function() {
                  // It's possible that, even with the timer, this event is being run
                  // even though animation from a previous event has already started.
                  // By checking the existing state, we can prevent the menu 'hunting'
                  // backwards and forwards.
                  if ($subGroup.is(":visible")) { return; }
                  
                  // When you come to think about it, you only have to set the position of
                  // each sub-menu once - the first time it is displayed.  After that, it's
                  // just a simple matter of making in visible/invisible in-situ.
                  // Conveniently, we get the setPosition() method to set an expando flag
                  // against the div.subGroupCanvass element and here we just need to check
                  // to see if it has been set or whether we are dealing a 'first instance'
                  // display situation.
                  if ((!canvassData) || (!canvassData.hasLayout)) {
                     // Position the ul.subGroup relative to its parent.  We calculate the
                     // absolute left,top offset from the parent li.menuItem as the
                     // div.subGroupCanvass and it's ul.subGroup are absolutely positioned
                     // relative to their parent li.menuItem.
                     if ((foo.$menu.is(".horizontal")) && ($subGroup.is(".level1"))) {
                        // In a horizontal menu, the first sub-menu's north-west corner
                        // is positioned near to the south-west corner of its parent
                        setPosition($canvass, {
                           x: foo.params.xOffset,
                           y: ($item.outerHeight() - foo.params.yOffset),
                           hide: true
                        });
                     } else {
                        // In all other instances, the sub-menu's north-west corner is
                        // positioned near to the north-east corner of its parent
                        setPosition($canvass, {
                           x: ($item.outerWidth() - foo.params.xOffset),
                           y: foo.params.yOffset,
                           hide: true
                        });
                     }
                  }
                  // Dynamically apply css class attributes to this li.menuItem and
                  // children.  Note the creation of a local z-index 'context stack'
                  // this will ensure the expanded items overlay the rest of the menu
                  // structure - even in IE 6!  Also note that we add the 'expanded'
                  // class attribute directly to the a.link element. This makes life
                  // easier when styling for browsers that do not support the 
                  // 'immediate child' selector (e.g. li.menuItem > a.link).
                  $item.addClass("expanded").css({"z-index": "1"});
                  $link.addClass("expanded");
                  $canvass.css({"z-index": "2"}).show();
                  if (typeof jQuery.fn.bgiframe === "function") { $canvass.bgiframe(); }
                  $subGroup.css({"z-index": "3"}).show(foo.params.expandSpeed);
               }, foo.params.expandDelay);
            });



            // Mouseleave - the mouse has left an expanded li.menuItem so we need to collapse
            // the expanded sub-menu items
            foo.$parentItems.mouseleave(function() {
               var   $item = $(this),                                    // li.menuItem
                     $link = $item.children("a.link"),                  // a.link
                     $canvass = $item.children("div.subGroupCanvass"),   // div.subGroupCanvass
                     $subGroup = $canvass.children("ul.subGroup");      // ul.subGroup

               clearTimer(this);
               this.timerId = setTimeout(function() {
                  // It's possible that, even with the timer, this event is being run
                  // even though animation from a previous event has already started.
                  // By checking the existing state, we can prevent the menu 'hunting'
                  // backwards and forwards.
                  if (!$subGroup.is(":visible")) { return; }
                  
                  // Remember, if collapseSpeed is zero, the callback function
                  // never gets called!
                  $subGroup.hide(foo.params.collapseSpeed + 1, function() {
                     $(this).css({"z-index": "0"});
                     $canvass.hide().css({"z-index": "0"}).prev("iframe").remove();
                     $link.removeClass("expanded");
                     $item.removeClass("expanded").css({"z-index": "0"});
                  });
               }, foo.params.collapseDelay);
            });
         }   // if (!foo.params.disableAction)
      };

      // The following code could be used if it becomes
      // necessary to remove the div.clearfix element from the menu structure
      // base.init = foo.init;
      // foo.init = function() {
      //      base.init();
      //      foo.$menu.height(foo.$rootGroup.outerHeight(true));
      // };

      /*******************************************************************************************
      * Object Disposal                                                                          *
      *******************************************************************************************/
      base.dispose = foo.dispose;
      foo.dispose = function() {
         $("a.link", foo.$menu).unbind();                              // Remove 'hover' event
         $("div.subGroupCanvass", foo.$menu).removeData("ablMenu");   // Remove 'expando' hasLayout data from setPosition()
         foo.$parentItems.unbind().each(function() {                  // Remove 'mouseenter'/'mouseleave' events
            clearTimer(this);                                          // Clear and remove li.menuItem.timerId expando
         });
         base.dispose();
      };


      return foo;
   }(menu, options));
};

Abl.UI.Menu.defaults = {
   expandSpeed: 200,
   collapseSpeed : 300,
   expandDelay: 150,
   collapseDelay : 250,
   xOffset: 5,
   yOffset: 3
};





/*******************************************************************************************
* Abl.UI.Clamshell                                                                         *
*******************************************************************************************/
// Notes:
// Because IE6 cannot handle the $.slideDown()/$.slideUp() methods correctly, we have to
// resort to using a custom height animation.  We calculate the original heights of the
// ul.subGroup elements and store it as expando data gainst the element.  This way we
// can animate the ul.subGroup element height to zero and restore it to its original
// dimension as requred.  See the captureSate() method in the initialisation section.

Abl.UI.Clamshell = function(menu, options) {
   return (function(menu, options) {
      var   base = {},
            foo = Abl.UI.BaseMenu(menu, $.extend(true, {}, Abl.UI.Clamshell.defaults, options));



      /*******************************************************************************************
      * Expand/Collapse Methods                                                                  *
      *******************************************************************************************/
      foo.collapseAll = function() {
         foo.$menu.find("ul.subGroup.expanded")
         .removeClass("expanded")
         .parent("li.menuItem").removeClass("expanded").end()
         .animate({height: 0}, foo.params.collapseSpeed, function() {
            $(this).hide();
         });
      };



      /*******************************************************************************************
      * Event Management                                                                         *
      *******************************************************************************************/
      foo.wireEvents = function() {
         foo.$parentItems.click(function(evt) {
            var   $item = $(this),
                  $target = $(evt.target),
                  $subGroup = $item.children("ul.subGroup");
            
            // Only animate if we're a section header item (li.hasChjildren) 
            if ($target.parents("li.menuItem:first").hasClass("hasChildren")) {
               evt.preventDefault();
               if ($subGroup.is(":visible")) {   // If it's visible then we need to collapse it
                  $item
                  .removeClass("expanded")
                  .find("ul.subGroup")            // Close ALL expanded children
                  .removeClass("expanded")
                  .animate({height: 0}, foo.params.expandSpeed, function() {
                     $(this).hide();
                  });
               } else {
                  if (foo.params.autoClose) { foo.collapseAll(); }
                  $item.addClass("expanded");
                  $subGroup
                  .addClass("expanded")
                  .height($subGroup.data("dims").height)
                  .slideDown(foo.params.expandSpeed);
                  //.animate({height: $subGroup.data("dims").height}, );
               }
            }
         });
         
         // Add a class='hover' attribute to the a.link's parent li.menuItem element
         // as this will make life easier when formatting is applied to the li.menuItem
         foo.$menu.find("a.link").hover(function() {
            $(this).parent().addClass("hover");
         }, function() {
            $(this).parent().removeClass("hover");
         });
         
      };



      /*******************************************************************************************
      * Object Initialisation                                                                    *
      *******************************************************************************************/
      function captureState() {
         var   $subGroup;
         
         foo.$parentItems.find("ul.subGroup").each(function() {
            $subGroup = $(this);
            $subGroup
            .data("dims", {height: $subGroup.height()})   // Store original height
            .height(0);      // Set initial height to zero ready for the first expansion
         });
      }


      base.init = foo.init;
      foo.init = function() {
         captureState();
         base.init();
      };



      /*******************************************************************************************
      * Object Disposal                                                                          *
      *******************************************************************************************/
      base.dispose = foo.dispose;
      foo.dispose = function() {
         $("a.link", foo.$menu).unbind();
         foo.$parentItems.unbind().find("ul.subGroup").removeData();
         base.dispose();
      };


      return foo;
   }(menu, options));
};


Abl.UI.Clamshell.defaults = {
   expandSpeed: 600,
   collapseSpeed : 600,
   autoClose: true
};





/*******************************************************************************************
* Abl.UI.MenuManager                                                                       *
*******************************************************************************************/
Abl.UI.MenuManager = function(menu, options) {
   return (function(menu, options) {
      var   foo = {},
            menuStack = [];
            
      // Normalise arguments...
      if ((arguments.length === 1) && (!(arguments[0] instanceof jQuery))) {
         options = arguments[0];
         menu = null;
      }
      
      foo.params = $.extend(true, {}, Abl.UI.MenuManager.defaults, options);
      
      

      /*******************************************************************************************
      * Add Menu Method                                                                          *
      ******************************************************************************************/
      foo.add = function(menu, options) {
         var   $menu = (menu instanceof jQuery) ? menu : $(menu);
         $menu.filter(foo.params.menuIdentifier).each(function() {
            var $menu = $(this), ablMenu = null;
            if ($menu.is(foo.params.clamshellClass)) {
               ablMenu = Abl.UI.Clamshell($menu, $.extend(true, {}, foo.params.clamshell, (options) ? options.clamshell || options : null));
            } else if ($menu.is(foo.params.horizontalMenuClass)) {
               ablMenu = Abl.UI.Menu($menu, $.extend(true, {}, foo.params.horizontalMenu, (options) ? options.horizontalMenu || options : null));
            } else if ($menu.is(foo.params.verticalMenuClass)) {
               ablMenu = Abl.UI.Menu($menu, $.extend(true, {}, foo.params.verticalMenu, (options) ? options.verticalMenu || options : null));
            } else {
               throw "Unidentified menu type '" + $menu.attr("class") + "'!";
            }
            menuStack.push(ablMenu);
            ablMenu.init();
         });
      };



      /*******************************************************************************************
      * Object Disposal                                                                          *
      *******************************************************************************************/
      foo.dispose = function() {
         var menu, i;
         for (i = 0; i < menuStack.length; i++) {
            menuStack[i].dispose();
         }
      };



      /*******************************************************************************************
      * Object Initialisation                                                                    *
      *******************************************************************************************/
      if (menu) {
         foo.add(menu);
         // Abl.DEBUG.trace("Abl.UI.MenuManager.init() - " + menuStack.length + " menu(s) added");
      }
      
      return foo;
   }(menu, options));
};

Abl.UI.MenuManager.defaults = {
   menuIdentifier: "div.ablMenu",
   clamshellClass: "div.clamshell",
   verticalMenuClass: "div.vertical",
   horizontalMenuClass: "div.horizontal",
   clamshell: {},
   horizontalMenu: { disableAction: false },
   verticalMenu: { disableAction: false }
};
