// Load function
$(document).ready(function(){
    $('a[@target$=blank]').not(':has(img)').not('[@href$=pdf]').append('<img src="/compo/img/primary/icon-blank.png" alt="" class="icon" />');
    $('a[@href$=pdf]').not(':has(img)').append('<img src="/compo/img/primary/icon-pdf.png" alt="" class="icon" />');
    $('#header ul.global-nav').set_menuClone();
    $('dl.local-nav dd ul li').set_menuActive({ child:'on', cl:'active' });
    $('img,input').set_rollovers({
        active_extension: '_h',
        menu: {
            "/": "gn-home_o.png",
            "/package/": "gn-package_o.png",
            "/solution/": "gn-solution_o.png",
            "/enterprise/": "gn-package_o.png",
            "/personal/": "gn-package_o.png",
            "/case/": "gn-package_o.png",
            "/partner/": "gn-package_o.png",
            "/trial/": "gn-package_o.png",
            "/support/": "gn-support_o.png",
            "/news/": "gn-news_o.png",
            "/corporate/": "gn-info_o.png",
            "/corporate/recruit/": "un-recruit_o.png",
            "/corporate/privacy.html": "",
            "/corporate/security.html": "",
            "/corporate/link.html": "",
            "/corporate/sitemap.html": "",
            "/contact/": "un-contact_o.png",
            "/en/": "gn-home_o.png",
            "/en/solution/": "gn-solution_o.png",
            "/en/software/": "gn-package_o.png",
            "/en/corporate/": "gn-info_o.png"
        }
    });
    
    $('#content ul.col2 > li, #content ul.index > li, #primary ul.img-col2 > li').set_height({ items_per_row:2, group_by_parent: 'ul' });
    $('#content ul.col3 > li').set_height({ items_per_row:3, group_by_parent: 'ul' });
    $('body.sitemap #primary div.section > ul > li').set_height({ items_per_row:3, group_by_parent: 'ul' });
    
    $('#main-visual').set_flash({ version: 7 });
    $('object').each(function(){ this.outerHTML = this.outerHTML; });
    
});


/***
* グローバルナビゲーションをコンテンツエリア最下部に追加する
* 
***/
$.fn.set_menuClone = function( options ){
    if(!this.length){return}
    var _target = $('#content');
    var _item = $('>li>a',this)
    var out_html = '<ul class="global-nav">';
    _item.each(function(){
        var elm_href = $(this).attr('href'), elm_text = $('>img',this).attr('alt');
        out_html += '<li><a href="'+elm_href+'">'+elm_text+'</a></li>';
    });
    out_html += '</ul>';
    _target.append(out_html);
};


/***
* ローカルナビゲーションのカレント表示を自動化する
* 
* Attributes:
* -@options: オプションのハッシュ
*  -color: cssのcolor
*  -bg: cssのbackground
*  -cl: class名
***/
$.fn.set_menuActive = function( options ){
    var settings = {
        child: false,
        color: false,
        bg: false,
        cl: false
    };
    if(options){ jQuery.extend(settings, options); }
    if(!this.length){return}
    //現在のURL
    var _currentUrl = window.location.href.match(/(http(|s)\:\/\/[^\/]*)([^\s]*)/)[3].split('index.html')[0];
    //現在のURLとローカルナビゲーションからのリンクURLが一致した場合に
    //settingsで設定された値をつける
    this.each(function(){
        if( _currentUrl == $('>a',this).attr('href') ){
            if(settings.color){$('>a',this).css( "color", settings.color)};
            if(settings.bg){$('>a',this).css( "background-color", settings.bg)};
            if(settings.cl){$('>a',this).attr( "class", settings.cl)};
            if($('>a>img',this).length){
                $('img',this).attr('src',$('img',this).attr('src').replace('_o.gif','_h.gif'));
            };
        }
    });
    //2階層目以降の子リストがある場合の動作
    if(settings.child=='on'){
        $('>ul',this).hide();//初期設定2階層目以降のメニューは隠す
        var g_this = this;
        this.each(function(){
            var _this = $(this);
            var _third_li = $('>ul>li>ul>li',g_this);// 3階層目限定li要素
            var _childList = $('>ul',_this).not(_third_li.parent());
            var _child = $('>ul>li>a',_this).not(_third_li.find('a'));
            if(!_this.length){return}
            //URIとナビゲーションのhrefが一致し、子リストを保持している場合、その子リストを表示
            if( _currentUrl.match($('>a',this).attr('href')) ){
                _childList.show();
                $('>a',this).addClass('on');
            }
        });
    }
};


/***
* 画像のロールオーバー
* 
***/
$.fn.set_rollovers = function( options ){

   var settings = {
      extension: '_o',
      hover_extension: '_h',
      active_extension: false,
      fade: false,
      menu:{},
      menu_type: 'files'
   };
   if(options){ jQuery.extend(settings, options); };
   
   if(!settings.active_extension){ settings.active_extension = settings.hover_extension; }
   var path = String(document.location);
   var active = false;
   var longest_match = 0;
   $.each( settings.menu , function( menu_path ){
      if( settings.menu_type == 'files' ){
         if( menu_path.match(/|/) ){
            menu_path_arr = menu_path.split('|');
            for( k=0; k<menu_path_arr.length; k++ ){
               if( path.match( menu_path_arr[k] ) && menu_path_arr[k].length>longest_match ){ 
                   active = settings.menu[menu_path_arr[k]]; 
                   longest_match = menu_path_arr[k].length; 
               };
            };
         } else {
            if( path.match( menu_path ) && menu_path.length>longest_match ){ active = settings.menu[menu_path]; longest_match = menu_path.length; };
         };
      } else if( settings.menu_type == 'nodes' ){
         if( $(menu_path).length ){ active = settings.menu[menu_path]; };
      };
   });
   
   return this.filter( '[@src*='+ settings.extension +'.]' ).each(function(){
      var roll = new Image;
      roll.src = this.src.replace( settings.extension+'.', settings.hover_extension+'.');
      if( this.src.match(/[^\/]+$/)[0] == active){
        this.src = this.src.replace( new RegExp(settings.extension+"(\.[a-z]+)$"), settings.active_extension+"$1");
        $(this).hover(
          function(){ this.src = this.src.replace( new RegExp(settings.active_extension+"(\.[a-z]+)$") , settings.hover_extension+"$1"); },
          function(){ this.src = this.src.replace( new RegExp(settings.hover_extension+"(\.[a-z]+)$") , settings.active_extension+"$1"); }
        );
      }else{
         if( !settings.fade ){
            $(this).hover(
              function(){ this.src = this.src.replace( new RegExp(settings.extension+"(\.[a-z]+)$") , settings.hover_extension+"$1"); },
              function(){ this.src = this.src.replace( new RegExp(settings.hover_extension+"(\.[a-z]+)$") , settings.extension+"$1"); }
            );
         } else {
            var img_holder = $(this).wrap('<span style="position:relative; display:block;"></span>').parent();
            img_holder.append('<img src="'+roll.src+'" style="position:absolute; top:0; left:0;" />');
            img_holder.find('> :last-child').hide();
            img_holder.hover(
               function(){ $("> img:last-child", this).fadeIn(settings.fade); },
               function(){ $("> img:last-child", this).fadeOut(settings.fade); }
            );
         };
         if( this.parentNode.nodeName.match(/^a$/i) ){
            $(this).parent().focus(function(){ $(">img", this).trigger("mouseover"); }).blur(function(){ $(">img", this).trigger("mouseout"); });
         }else if( this.parentNode.nodeName.match(/span/i) && this.parentNode.parentNode.nodeName.match(/a/i) ){
            $(this).parents("a").focus(function(){ $("> span > img:last-child", this).fadeIn(settings.fade); }).blur(function(){ $("> span > :last-child", this).fadeOut(settings.fade); });
         };
      };
   });

};


/***
* 要素の高さ揃え
* 
***/
$.fn.set_height = function( options ){

   var settings = {
      items_per_row: false,
      one_height: false,
      delay: 1000,
      group_by_parent: false
   };
   if(options){ jQuery.extend(settings, options); };
   
   if( $("#js_etalon").length ){ var etalon = $('#js_etalon').get(0); }
   else { var etalon = $('body').append('<span style="position:absolute;left:-9999px;top:0;" id="js_etalon">&nbsp;</span>').find('#js_etalon').get(0); };
   
   var _add = function(array, item){
     for(var i=0; i<array.length; i++){ if(array[i]==item){ return; }; };
     array.push(item);
   };
   
   var nodes = this;
   var groups = [];
   var hidden_groups = [];
   var images_to_load = [];
   var _set_height = function(){
     for(var i=0; i<groups.length; i++){
       var max_height = 0;
       var vert_padding = groups[i][0].currentStyle ?
         parseInt(groups[i][0].currentStyle["paddingTop"]) + parseInt(groups[i][0].currentStyle["paddingBottom"]) :
         parseInt(document.defaultView.getComputedStyle(groups[i][0],null).getPropertyValue("padding-top")) + parseInt(document.defaultView.getComputedStyle(groups[i][0],null).getPropertyValue("padding-bottom"));
       for(var j=0; j<groups[i].length; j++){
         groups[i][j].style.height = "auto";
         max_height = Math.max(groups[i][j].offsetHeight-vert_padding, max_height);
       };
       for(var j=0; j<groups[i].length; j++){ groups[i][j].style.height = max_height+"px"; };
     };
   };
   var _init = function(){
     var cur_group = 0; var count = 0; var cur_parent = null; var prev_parent = null;
     groups[cur_group] = [];
     var group_inc = false;
     for(var i=0; i<nodes.length; i++){
       if(settings.group_by_parent){
         cur_parent = $(nodes[i]).parents(settings.group_by_parent)[0];
         if( i>0 && cur_parent != prev_parent && !group_inc ){ groups[++cur_group] = []; count=0; };
         prev_parent = cur_parent;
       };
       if(settings.items_per_row){
         nodes[i].className += " nb"+parseInt(count%settings.items_per_row+1);
         if( !(count%settings.items_per_row)&&count>0 ){ groups[++cur_group] = []; count=0; group_inc = true; };
       };
       groups[cur_group][count++] = nodes[i];
       group_inc = false;
     };
     for(var i=0; i<groups.length; i++){
       if(!groups[i][0].offsetHeight){
         var cur_node = groups[i][0];
         while(cur_node.style.display!="none"){ cur_node = cur_node.parentNode; };
         hidden_groups.push(cur_node);
       };
       for(var j=0; j<groups[i].length; j++){
         var imgs = groups[i][j].getElementsByTagName('img');
         for(var k=0; k<imgs.length; k++){ _add(images_to_load, imgs[k].src); };
       };
     };
   };
   
   if(nodes.length){
      _init();
      var base_size = etalon.offsetHeight;
      var interval = setInterval(function(){
         var current_size = etalon.offsetHeight;
         if( current_size != base_size ){ base_size = current_size; _set_height(); };
         for(var i=0; i<hidden_groups.length; i++){
           if( hidden_groups[i].style.display!="none" ){
             _set_height();
             hidden_groups = [];
             for(var j=0; j<groups.length; j++){
               if(!groups[j][0].offsetHeight){
                 var cur_node = groups[j][0];
                 while(cur_node.style.display!="none"){ cur_node = cur_node.parentNode; };
                 hidden_groups.push(cur_node);
               };
             };
           };
         };
      }, settings.delay);
      _set_height();
      if(images_to_load.length){
        var dummy_images = [];
        var loaded_images = 0;
        for(var i=0; i<images_to_load.length; i++){
          dummy_images[i] = document.createElement('img');
          dummy_images[i].onload = function(){
            loaded_images++;
            if(loaded_images==images_to_load.length){ _set_height(); };
          };
          dummy_images[i].src = images_to_load[i];
        };
      };
   };
   
   if( navigator.userAgent.match("MSIE 5.5") && nodes.length ){ setTimeout(function(){ main(nodes), 0 }); };
   
   return this;

};


/***
* ポップアップウィンドウ
* 
***/
$.set_links = function( settings ){

   $("a[@rel=popup],area[@rel=popup]").click(function(){
      var params=""; var longest_match=0; var s_id="";
      for(i in settings){
         if(i.match(">>")){
            data = i.split(">>");
            if( String(window.location).match(data[0]) && this.href.match(data[1]) ){ s_id = i; break; };
         };
         if( i.match(/^\.(.*)/) ){
           var m_class = new RegExp( "\\b"+RegExp.$1+"\\b" );
           if( this.className.match( m_class ) ){
             for(param in settings[i]){ if(param=="name"){ continue; }; params += param + "=" + settings[i][param] + ","; };
             if( this.href.match(/width=(\d+).*height=(\d+)/) ){
               var _w = RegExp.$1; var _h = RegExp.$2;
               if(params.match("width")&&params.match("height")){
                 params = params.replace(/width=\d+/,"width="+_w);
                 params = params.replace(/height=\d+/,"height="+_h);
               } else { params += "width="+_w+",height="+_h; };
             };
             var w_name = settings[i]['name'] ? settings[i]['name'] : "";
             window.open( this.href, w_name, params.split(/,$/)[0] ).focus();
             this.blur();
             return false;
           };
         }else{
           if( this.href.match(i) && i.length>longest_match ){ s_id = i; longest_match = i.length; };
         };
      };
      for(param in settings[s_id]){ if(param=="name"){ continue; }; params += param + "=" + settings[s_id][param] + ","; };
      var w_name = settings[s_id]['name'] ? settings[s_id]['name'] : "";
      window.open( this.href, w_name, params.split(/,$/)[0] ).focus();
      this.blur();
      return false;
   });
   return this;

};


/***
* Flashのセッティング
* 
***/
$.fn.set_flash = function( options ){

   var settings = {
      version: 6,
      movie: '',
      name: '',
      title: '',
      height: 0,
      width: 0,
      no_version: 'no version',
      no_flash: 'no flash'
   };
   if(options){ jQuery.extend(settings, options); }
   
   return this.each(function(i){
      _flash_version = function(){
         if (navigator.plugins && navigator.plugins.length){
            if( !navigator.plugins["Shockwave Flash"] ){ return false; }
            if(navigator.plugins["Shockwave Flash"].description){
               navigator.plugins["Shockwave Flash"].description.match(/(\d+)\.\d/);
               return parseInt(RegExp.$1);
            };
         }else{
            for(var j=15; j>=0;j--){
               try{ new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+j); return j; }
               catch(e){ };
            }
            return false;
         };
         return false
      };
      
      var attributes = {};
      if( settings.height && settings.width && settings.movie!="" ){
        attributes = settings;
      }else{
        var attrs = this.className.match( /[a-z]+\[[^\]]+\]/g );
        for(i=0; i<attrs.length; i++){
          attrs[i].match(/([a-z]+)\[([^\]]+)\]/);
          attributes[RegExp.$1] = RegExp.$2;
        };
      }
      
      var embed_options = ""; var params_options = "";
      
      if(attributes["wmode"]){
         embed_options += ' wmode="'+attributes["wmode"]+'"';
         params_options += '<param name="wmode" value="'+attributes["wmode"]+'">';
      };
      
      if( !_flash_version() ){ $(this).html(settings.no_flash); return; }
      if( _flash_version() <(attributes["version"] || settings.version) ){ $(this).html(settings.no_version); return; }
      
      $("> *", this).hide();
      $(this).html(
        '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" height="'+attributes["height"]+'" width="'+attributes["width"]+'" name="'+attributes["name"]+'" title="'+attributes["title"]+'">'+
        '<param name="movie" value="'+attributes["movie"]+'">'+
        '<param name="name" value="'+attributes["name"]+'">'+
        '<param name="quality" value="high">'+
        params_options +
        '<embed src="'+attributes["movie"]+'" quality="high" type="application/x-shockwave-flash" height="'+attributes["height"]+'" width="'+attributes["width"]+'"'+embed_options+'>'+
        '</object>'
      );
   });

};


$.cookie = function( name, value, options ){
   var settings = {
      days: 360,
      path: '/'
   };
   if(options){ jQuery.extend(settings, options); };
   
   var action = value ? 'set' : 'get';
   if( action == 'get' ){
      name = name + '=';
      var cookies = document.cookie.split('; ');
      for( var i=0; i<cookies.length; i++ ){
         if ( cookies[i].indexOf(name) == 0 ){ return cookies[i].substring(name.length, cookies[i].length); };
      }
      return null;
   }
   else if( action == 'set' ){
      var date = new Date();
      date.setTime(date.getTime()+(settings.days *24*60*60*1000));
      var expires = '; expires=' + date.toGMTString();
      document.cookie = name + '=' + value + expires + '; path=' + settings.path ;
   }
};


/***
* 画像ビューワー
* 
***/
$.fn.set_viewer = function( options ){
   
   var settings = {
      bg_opacity: 0.5,
      html_height: 480,
      html_width: 640,
      line_break: " -- ",
      speed: 3000,
      on_show: null,
      on_hide: null
   };
   if(options){ jQuery.extend(settings, options); };
   var viewer_visible = false;
   var transition_finished = false;
   var viewer_holder = $('body').append('<div id="viewer-holder"></div>').children('#viewer-holder').css({ position: 'absolute',
                                                                                                             display: 'none',
                                                                                                             zIndex: '101'
   });
   
   $('body').append('<div id="viewer-bg" style="display:none;"></div>');
   var viewer_bg = $('div#viewer-bg');
   viewer_bg.css({ position: 'absolute', width: '100%', top: 0, left: 0, zIndex: '100'}).click(function(){ _close_viewer(); });
   viewer_bg.fadeTo(1,0, function(){ $(this).hide(); });
   var image;
   _repos_holder = function(){
      var is_out = ( ( $(document).scrollTop() + $(window).height() ) >= $(document).height() ) && ( viewer_holder[0].clientHeight >= $(window).height() );
      var viewer_top = viewer_holder[0].clientHeight > $(window).height() ? $(document).scrollTop() : $(document).scrollTop()+$(window).height()/2-viewer_holder[0].clientHeight/2;
      if( is_out ){
         viewer_holder.css({ top: viewer_top + "px" });
         return
      };
      if( !viewer_visible ) return;
      //console.log("repos", { top: viewer_top +'px', left: $(window).width()/2-viewer_holder[0].clientWidth/2 + "px" })
      viewer_holder.css({ top: viewer_top +'px', left: $(window).width()/2-viewer_holder[0].clientWidth/2 + "px" });
   };
   _resize_bg = function(){
      viewer_bg.css({ height: $(document).height()+"px",
                       width: $(document).width()+"px"
      });
   };
   _close_viewer = function(){
      if( !transition_finished ){ return };
      if( image ){ image.onLoad = null; }
      viewer_holder.empty().hide();
      viewer_bg.fadeTo("slow", 0, function(){ $(this).hide() });
      viewer_visible = false;
      if(settings.on_hide){ settings.on_hide.call(); };
   };
    
   _resize_viewer = function( width, height, callback ){
      viewer_holder.animate({ left:$(window).width()/2-width/2-parseInt(viewer_holder.css('padding-left')), top:$(document).scrollTop()+$(window).height()/2-height/2 }, (settings.speed/3), function(){} );
      $('#viewer-loader').animate({ width:width, height:height }, (settings.speed/3), callback );
   };
   
   _switch_image = function( link ){
      transition_finished = false;
      var info_area_html = '<div id="info_area" style="display:none;">';
      var in_group = $(link).parents()[0].tagName == 'LI';
      var group_html = '';
      if(in_group){
         var list = $(link).parents()[1];
         var list_size = $('li',list).size();
         var current_list = $(link).parents()[0];
         $('li',list).each( function(i){ if(this == current_list) list_index = i+1; } );
         var prev_item = $('a',list).get(list_index-2);
         var next_item = $('a',list).get(list_index);
         var next_html = list_index != list_size ? '<a href="#" id="viewer-next">&gt;</a>' : '';
         var previous_html = list_index != 1 ? '<a href="#" id="viewer-prev">&lt;</a>' : '';
         info_area_html += list_size > 1 ? '<p id="viewer-nav">'+ previous_html +' <span id="viewer-state">'+list_index+' / '+list_size+'</span> '+ next_html +'</p>' : '' ;
      };
      var caption = ($('img',link).get(0) ? $('img',link).get(0).alt : '') || link.title || '';
      info_area_html += caption != '' ? '<p id="viewer-caption">'+caption.replace(settings.line_break,"<br />")+'</p>' : '' ;
      info_area_html += '</div>';
      $('#viewer-loader img').remove();
      image = new Image();
      image.onload = function(){
        _resize_viewer( image.width, image.height, function(){
            $('#viewer-loader').append('<img id="viewer-image" src="'+image.src+'" alt="'+caption+'" style="opacity:0;filter:alpha(opacity=0);" />');
            $('#viewer-loader img').fadeTo( (settings.speed/3), 1, function(){
               viewer_holder.append( info_area_html + '<a href="#" id="viewer-close">X</a>');
               if( in_group || caption!='' ){
                 $('#info_area').css({ width:image.width }).slideDown( (settings.speed/3), function(){ 
                   viewer_holder.animate({ 
                     top:$(document).scrollTop()+$(window).height()/2-image.height/2-$("#info_area").outerHeight()/2-( parseInt(viewer_holder.css('padding-top'))+parseInt(viewer_holder.css('padding-bottom')) ) },
                     (settings.speed/3),
                     function(){ transition_finished = true; _repos_holder(); } );
                 });
               } else { transition_finished = true; _repos_holder(); };
               if(in_group){
                  $('#viewer-prev').click( function(){
                     $('#info_area').slideUp( (settings.speed/3), function(){$('#info_area').remove(); $('#viewer-close').remove(); _switch_image(prev_item); } );
                     $('#viewer-loader img').fadeTo( (settings.speed/3.5) ,0, function(){ $('#viewer-loader img').remove(); } );
                     transition_finished = false;
                     return false;
                  });
                  $('#viewer-next').click( function(){
                     $('#info_area').slideUp( (settings.speed/3), function(){ $('#info_area').remove(); $('#viewer-close').remove(); _switch_image(next_item); } );
                     $('#viewer-loader img').fadeTo( (settings.speed/3.5) , 0, function(){ $('#viewer-loader img').remove(); } );
                     transition_finished = false;
                     return false;
                  });
               };
               $("#viewer-close").click(function(){ _close_viewer(); return false; });
            });
         });
      };
      image.src = link.href;
   };
   
   _show_viewer = function(link){
      viewer_holder.empty().show();
      viewer_visible = true;
      _resize_bg();
      if(settings.on_show){ settings.on_show.call(); };
      var caption = ($('img',link).get(0) ? $('img',link).get(0).alt : '') || link.title || '';
      var link_type = link.href.toLowerCase().match(/jpg$|jpeg$|png$|gif$|bmp$/) ? 'image' : 'page';
      viewer_bg.show().fadeTo("slow", settings.bg_opacity);
      if( link_type == 'image' ){
         viewer_holder.addClass('image').removeClass('html');
         viewer_holder.append('<div id="viewer-loader"></div>');
         _repos_holder();
         _switch_image( link );
      } else if( link_type == 'page' ){
         transition_finished = true;
         viewer_holder.addClass('html').removeClass('image');
         $("#viewer-holder").append('<p id="viewer-title">'+caption+'</p><a href="#" id="viewer-close">X</a><iframe src="'+link+'" id="viewer-frame" style="width:'+settings.html_width+'px;height:'+settings.html_height+'px;"></iframe>');
         $("#viewer-close").click(function(){ _close_viewer(); return false; });
         _repos_holder();
      };
   };

   $(window).scroll(function(){ 
      if( !viewer_visible ) return;
      _repos_holder();
   }).resize(function(){
      if( !viewer_visible ) return;
      _repos_holder();
      _resize_bg();
   });

   return this.click(function(){
      _show_viewer( this );
      return false;
   });
   
};


/* Copyright (c) 2007 Paul Bakaus (paul.bakaus@googlemail.com) and Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * $LastChangedDate: 2007-08-17 13:14:11 -0500 (Fri, 17 Aug 2007) $
 * $Rev: 2759 $
 *
 * Version: 1.1.2
 *
 * Requires: jQuery 1.1.3+
 */


// store a copy of the core height and width methods
var height = $.fn.height,
    width  = $.fn.width;

$.fn.extend({
    /**
     * If used on document, returns the document's height (innerHeight).
     * If used on window, returns the viewport's (window) height.
     * See core docs on height() to see what happens when used on an element.
     *
     * @example $("#testdiv").height()
     * @result 200
     *
     * @example $(document).height()
     * @result 800
     *
     * @example $(window).height()
     * @result 400
     *
     * @name height
     * @type Number
     * @cat Plugins/Dimensions
     */
    height: function() {
        if ( !this[0] ) error();
        if ( this[0] == window )
            if ( $.browser.opera || ($.browser.safari && parseInt($.browser.version) > 520) )
                return self.innerHeight - (($(document).height() > self.innerHeight) ? getScrollbarWidth() : 0);
            else if ( $.browser.safari )
                return self.innerHeight;
            else
                return $.boxModel && document.documentElement.clientHeight || document.body.clientHeight;
        
        if ( this[0] == document ) 
            return Math.max( ($.boxModel && document.documentElement.scrollHeight || document.body.scrollHeight), document.body.offsetHeight );
        
        return height.apply(this, arguments);
    },
    
    /**
     * If used on document, returns the document's width (innerWidth).
     * If used on window, returns the viewport's (window) width.
     * See core docs on width() to see what happens when used on an element.
     *
     * @example $("#testdiv").width()
     * @result 200
     *
     * @example $(document).width()
     * @result 800
     *
     * @example $(window).width()
     * @result 400
     *
     * @name width
     * @type Number
     * @cat Plugins/Dimensions
     */
    width: function() {
        if (!this[0]) error();
        if ( this[0] == window )
            if ( $.browser.opera || ($.browser.safari && parseInt($.browser.version) > 520) )
                return self.innerWidth - (($(document).width() > self.innerWidth) ? getScrollbarWidth() : 0);
            else if ( $.browser.safari )
                return self.innerWidth;
            else
                return $.boxModel && document.documentElement.clientWidth || document.body.clientWidth;

        if ( this[0] == document )
            if ($.browser.mozilla) {
                // mozilla reports scrollWidth and offsetWidth as the same
                var scrollLeft = self.pageXOffset;
                self.scrollTo(99999999, self.pageYOffset);
                var scrollWidth = self.pageXOffset;
                self.scrollTo(scrollLeft, self.pageYOffset);
                return document.body.offsetWidth + scrollWidth;
            }
            else 
                return Math.max( (($.boxModel && !$.browser.safari) && document.documentElement.scrollWidth || document.body.scrollWidth), document.body.offsetWidth );

        return width.apply(this, arguments);
    },
    
    /**
     * Gets the inner height (excludes the border and includes the padding) for the first matched element.
     * If used on document, returns the document's height (innerHeight).
     * If used on window, returns the viewport's (window) height.
     *
     * @example $("#testdiv").innerHeight()
     * @result 210
     *
     * @name innerHeight
     * @type Number
     * @cat Plugins/Dimensions
     */
    innerHeight: function() {
        if (!this[0]) error();
        return this[0] == window || this[0] == document ?
            this.height() :
            this.is(':visible') ?
                this[0].offsetHeight - num(this, 'borderTopWidth') - num(this, 'borderBottomWidth') :
                this.height() + num(this, 'paddingTop') + num(this, 'paddingBottom');
    },
    
    /**
     * Gets the inner width (excludes the border and includes the padding) for the first matched element.
     * If used on document, returns the document's width (innerWidth).
     * If used on window, returns the viewport's (window) width.
     *
     * @example $("#testdiv").innerWidth()
     * @result 210
     *
     * @name innerWidth
     * @type Number
     * @cat Plugins/Dimensions
     */
    innerWidth: function() {
        if (!this[0]) error();
        return this[0] == window || this[0] == document ?
            this.width() :
            this.is(':visible') ?
                this[0].offsetWidth - num(this, 'borderLeftWidth') - num(this, 'borderRightWidth') :
                this.width() + num(this, 'paddingLeft') + num(this, 'paddingRight');
    },
    
    /**
     * Gets the outer height (includes the border and padding) for the first matched element.
     * If used on document, returns the document's height (innerHeight).
     * If used on window, returns the viewport's (window) height.
     *
     * The margin can be included in the calculation by passing an options map with margin
     * set to true.
     *
     * @example $("#testdiv").outerHeight()
     * @result 220
     *
     * @example $("#testdiv").outerHeight({ margin: true })
     * @result 240
     *
     * @name outerHeight
     * @type Number
     * @param Map options Optional settings to configure the way the outer height is calculated.
     * @cat Plugins/Dimensions
     */
    outerHeight: function(options) {
        if (!this[0]) error();
        options = $.extend({ margin: false }, options || {});
        return this[0] == window || this[0] == document ?
            this.height() :
            this.is(':visible') ?
                this[0].offsetHeight + (options.margin ? (num(this, 'marginTop') + num(this, 'marginBottom')) : 0) :
                this.height() 
                    + num(this,'borderTopWidth') + num(this, 'borderBottomWidth') 
                    + num(this, 'paddingTop') + num(this, 'paddingBottom')
                    + (options.margin ? (num(this, 'marginTop') + num(this, 'marginBottom')) : 0);
    },
    
    /**
     * Gets the outer width (including the border and padding) for the first matched element.
     * If used on document, returns the document's width (innerWidth).
     * If used on window, returns the viewport's (window) width.
     *
     * The margin can be included in the calculation by passing an options map with margin
     * set to true.
     *
     * @example $("#testdiv").outerWidth()
     * @result 1000
     *
     * @example $("#testdiv").outerWidth({ margin: true })
     * @result 1020
     * 
     * @name outerHeight
     * @type Number
     * @param Map options Optional settings to configure the way the outer width is calculated.
     * @cat Plugins/Dimensions
     */
    outerWidth: function(options) {
        if (!this[0]) error();
        options = $.extend({ margin: false }, options || {});
        return this[0] == window || this[0] == document ?
            this.width() :
            this.is(':visible') ?
                this[0].offsetWidth + (options.margin ? (num(this, 'marginLeft') + num(this, 'marginRight')) : 0) :
                this.width() 
                    + num(this, 'borderLeftWidth') + num(this, 'borderRightWidth') 
                    + num(this, 'paddingLeft') + num(this, 'paddingRight')
                    + (options.margin ? (num(this, 'marginLeft') + num(this, 'marginRight')) : 0);
    },
    
    /**
     * Gets how many pixels the user has scrolled to the right (scrollLeft).
     * Works on containers with overflow: auto and window/document.
     *
     * @example $(window).scrollLeft()
     * @result 100
     *
     * @example $(document).scrollLeft()
     * @result 100
     * 
     * @example $("#testdiv").scrollLeft()
     * @result 100
     *
     * @name scrollLeft
     * @type Number
     * @cat Plugins/Dimensions
     */
    /**
     * Sets the scrollLeft property for each element and continues the chain.
     * Works on containers with overflow: auto and window/document.
     *
     * @example $(window).scrollLeft(100).scrollLeft()
     * @result 100
     * 
     * @example $(document).scrollLeft(100).scrollLeft()
     * @result 100
     *
     * @example $("#testdiv").scrollLeft(100).scrollLeft()
     * @result 100
     *
     * @name scrollLeft
     * @param Number value A positive number representing the desired scrollLeft.
     * @type jQuery
     * @cat Plugins/Dimensions
     */
    scrollLeft: function(val) {
        if (!this[0]) error();
        if ( val != undefined )
            // set the scroll left
            return this.each(function() {
                if (this == window || this == document)
                    window.scrollTo( val, $(window).scrollTop() );
                else
                    this.scrollLeft = val;
            });
        
        // return the scroll left offest in pixels
        if ( this[0] == window || this[0] == document )
            return self.pageXOffset ||
                $.boxModel && document.documentElement.scrollLeft ||
                document.body.scrollLeft;
                
        return this[0].scrollLeft;
    },
    
    /**
     * Gets how many pixels the user has scrolled to the bottom (scrollTop).
     * Works on containers with overflow: auto and window/document.
     *
     * @example $(window).scrollTop()
     * @result 100
     *
     * @example $(document).scrollTop()
     * @result 100
     * 
     * @example $("#testdiv").scrollTop()
     * @result 100
     *
     * @name scrollTop
     * @type Number
     * @cat Plugins/Dimensions
     */
    /**
     * Sets the scrollTop property for each element and continues the chain.
     * Works on containers with overflow: auto and window/document.
     *
     * @example $(window).scrollTop(100).scrollTop()
     * @result 100
     * 
     * @example $(document).scrollTop(100).scrollTop()
     * @result 100
     *
     * @example $("#testdiv").scrollTop(100).scrollTop()
     * @result 100
     *
     * @name scrollTop
     * @param Number value A positive number representing the desired scrollTop.
     * @type jQuery
     * @cat Plugins/Dimensions
     */
    scrollTop: function(val) {
        if (!this[0]) error();
        if ( val != undefined )
            // set the scroll top
            return this.each(function() {
                if (this == window || this == document)
                    window.scrollTo( $(window).scrollLeft(), val );
                else
                    this.scrollTop = val;
            });
        
        // return the scroll top offset in pixels
        if ( this[0] == window || this[0] == document )
            return self.pageYOffset ||
                $.boxModel && document.documentElement.scrollTop ||
                document.body.scrollTop;

        return this[0].scrollTop;
    },
    
    /** 
     * Gets the top and left positioned offset in pixels.
     * The positioned offset is the offset between a positioned
     * parent and the element itself.
     *
     * For accurate calculations make sure to use pixel values for margins, borders and padding.
     *
     * @example $("#testdiv").position()
     * @result { top: 100, left: 100 }
     *
     * @example var position = {};
     * $("#testdiv").position(position)
     * @result position = { top: 100, left: 100 }
     * 
     * @name position
     * @param Object returnObject Optional An object to store the return value in, so as not to break the chain. If passed in the
     *                            chain will not be broken and the result will be assigned to this object.
     * @type Object
     * @cat Plugins/Dimensions
     */
    position: function(returnObject) {
        return this.offset({ margin: false, scroll: false, relativeTo: this.offsetParent() }, returnObject);
    },
    
    /**
     * Gets the location of the element in pixels from the top left corner of the viewport.
     * The offset method takes an optional map of key value pairs to configure the way
     * the offset is calculated. Here are the different options.
     *
     * (Boolean) margin - Should the margin of the element be included in the calculations? True by default.
     * (Boolean) border - Should the border of the element be included in the calculations? False by default. 
     * (Boolean) padding - Should the padding of the element be included in the calculations? False by default. 
     * (Boolean) scroll - Should the scroll offsets of the parent elements be included in the calculations? True by default.
     *                    When true it adds the total scroll offsets of all parents to the total offset and also adds two
     *                    properties to the returned object, scrollTop and scrollLeft.
     * (Boolean) lite - When true it will use the offsetLite method instead of the full-blown, slower offset method. False by default.
     *                  Only use this when margins, borders and padding calculations don't matter.
     * (HTML Element) relativeTo - This should be a parent of the element and should have position (like absolute or relative).
     *                             It will retreive the offset relative to this parent element. By default it is the body element.
     *
     * Also an object can be passed as the second paramater to
     * catch the value of the return and continue the chain.
     *
     * For accurate calculations make sure to use pixel values for margins, borders and padding.
     * 
     * Known issues:
     *  - Issue: A div positioned relative or static without any content before it and its parent will report an offsetTop of 0 in Safari
     *    Workaround: Place content before the relative div ... and set height and width to 0 and overflow to hidden
     *
     * @example $("#testdiv").offset()
     * @result { top: 100, left: 100, scrollTop: 10, scrollLeft: 10 }
     *
     * @example $("#testdiv").offset({ scroll: false })
     * @result { top: 90, left: 90 }
     *
     * @example var offset = {}
     * $("#testdiv").offset({ scroll: false }, offset)
     * @result offset = { top: 90, left: 90 }
     *
     * @name offset
     * @param Map options Optional settings to configure the way the offset is calculated.
     * @param Object returnObject An object to store the return value in, so as not to break the chain. If passed in the
     *                            chain will not be broken and the result will be assigned to this object.
     * @type Object
     * @cat Plugins/Dimensions
     */
    offset: function(options, returnObject) {
        if (!this[0]) error();
        var x = 0, y = 0, sl = 0, st = 0,
            elem = this[0], parent = this[0], op, parPos, elemPos = $.css(elem, 'position'),
            mo = $.browser.mozilla, ie = $.browser.msie, oa = $.browser.opera,
            sf = $.browser.safari, sf3 = $.browser.safari && parseInt($.browser.version) > 520,
            absparent = false, relparent = false, 
            options = $.extend({ margin: true, border: false, padding: false, scroll: true, lite: false, relativeTo: document.body }, options || {});
        
        // Use offsetLite if lite option is true
        if (options.lite) return this.offsetLite(options, returnObject);
        // Get the HTMLElement if relativeTo is a jquery collection
        if (options.relativeTo.jquery) options.relativeTo = options.relativeTo[0];
        
        if (elem.tagName == 'BODY') {
            // Safari 2 is the only one to get offsetLeft and offsetTop properties of the body "correct"
            // Except they all mess up when the body is positioned absolute or relative
            x = elem.offsetLeft;
            y = elem.offsetTop;
            // Mozilla ignores margin and subtracts border from body element
            if (mo) {
                x += num(elem, 'marginLeft') + (num(elem, 'borderLeftWidth')*2);
                y += num(elem, 'marginTop')  + (num(elem, 'borderTopWidth') *2);
            } else
            // Opera ignores margin
            if (oa) {
                x += num(elem, 'marginLeft');
                y += num(elem, 'marginTop');
            } else
            // IE does not add the border in Standards Mode
            if ((ie && jQuery.boxModel)) {
                x += num(elem, 'borderLeftWidth');
                y += num(elem, 'borderTopWidth');
            } else
            // Safari 3 doesn't not include border or margin
            if (sf3) {
                x += num(elem, 'marginLeft') + num(elem, 'borderLeftWidth');
                y += num(elem, 'marginTop')  + num(elem, 'borderTopWidth');
            }
        } else {
            do {
                parPos = $.css(parent, 'position');
            
                x += parent.offsetLeft;
                y += parent.offsetTop;

                // Mozilla and IE do not add the border
                // Mozilla adds the border for table cells
                if ((mo && !parent.tagName.match(/^t[d|h]$/i)) || ie || sf3) {
                    // add borders to offset
                    x += num(parent, 'borderLeftWidth');
                    y += num(parent, 'borderTopWidth');

                    // Mozilla does not include the border on body if an element isn't positioned absolute and is without an absolute parent
                    if (mo && parPos == 'absolute') absparent = true;
                    // IE does not include the border on the body if an element is position static and without an absolute or relative parent
                    if (ie && parPos == 'relative') relparent = true;
                }

                op = parent.offsetParent || document.body;
                if (options.scroll || mo) {
                    do {
                        if (options.scroll) {
                            // get scroll offsets
                            sl += parent.scrollLeft;
                            st += parent.scrollTop;
                        }
                        
                        // Opera sometimes incorrectly reports scroll offset for elements with display set to table-row or inline
                        if (oa && ($.css(parent, 'display') || '').match(/table-row|inline/)) {
                            sl = sl - ((parent.scrollLeft == parent.offsetLeft) ? parent.scrollLeft : 0);
                            st = st - ((parent.scrollTop == parent.offsetTop) ? parent.scrollTop : 0);
                        }
                
                        // Mozilla does not add the border for a parent that has overflow set to anything but visible
                        if (mo && parent != elem && $.css(parent, 'overflow') != 'visible') {
                            x += num(parent, 'borderLeftWidth');
                            y += num(parent, 'borderTopWidth');
                        }
                
                        parent = parent.parentNode;
                    } while (parent != op);
                }
                parent = op;
                
                // exit the loop if we are at the relativeTo option but not if it is the body or html tag
                if (parent == options.relativeTo && !(parent.tagName == 'BODY' || parent.tagName == 'HTML'))  {
                    // Mozilla does not add the border for a parent that has overflow set to anything but visible
                    if (mo && parent != elem && $.css(parent, 'overflow') != 'visible') {
                        x += num(parent, 'borderLeftWidth');
                        y += num(parent, 'borderTopWidth');
                    }
                    // Safari 2 and opera includes border on positioned parents
                    if ( ((sf && !sf3) || oa) && parPos != 'static' ) {
                        x -= num(op, 'borderLeftWidth');
                        y -= num(op, 'borderTopWidth');
                    }
                    break;
                }
                if (parent.tagName == 'BODY' || parent.tagName == 'HTML') {
                    // Safari 2 and IE Standards Mode doesn't add the body margin for elments positioned with static or relative
                    if (((sf && !sf3) || (ie && $.boxModel)) && elemPos != 'absolute' && elemPos != 'fixed') {
                        x += num(parent, 'marginLeft');
                        y += num(parent, 'marginTop');
                    }
                    // Safari 3 does not include the border on body
                    // Mozilla does not include the border on body if an element isn't positioned absolute and is without an absolute parent
                    // IE does not include the border on the body if an element is positioned static and without an absolute or relative parent
                    if ( sf3 || (mo && !absparent && elemPos != 'fixed') || 
                         (ie && elemPos == 'static' && !relparent) ) {
                        x += num(parent, 'borderLeftWidth');
                        y += num(parent, 'borderTopWidth');
                    }
                    break; // Exit the loop
                }
            } while (parent);
        }

        var returnValue = handleOffsetReturn(elem, options, x, y, sl, st);

        if (returnObject) { $.extend(returnObject, returnValue); return this; }
        else              { return returnValue; }
    },
    
    /**
     * Gets the location of the element in pixels from the top left corner of the viewport.
     * This method is much faster than offset but not as accurate when borders and margins are
     * on the element and/or its parents. This method can be invoked
     * by setting the lite option to true in the offset method.
     * The offsetLite method takes an optional map of key value pairs to configure the way
     * the offset is calculated. Here are the different options.
     *
     * (Boolean) margin - Should the margin of the element be included in the calculations? True by default.
     * (Boolean) border - Should the border of the element be included in the calculations? False by default. 
     * (Boolean) padding - Should the padding of the element be included in the calcuations? False by default. 
     * (Boolean) scroll - Sould the scroll offsets of the parent elements be included int he calculations? True by default.
     *                    When true it adds the total scroll offsets of all parents to the total offset and also adds two
     *                    properties to the returned object, scrollTop and scrollLeft.
     * (HTML Element) relativeTo - This should be a parent of the element and should have position (like absolute or relative).
     *                             It will retreive the offset relative to this parent element. By default it is the body element.
     *
     * @name offsetLite
     * @param Map options Optional settings to configure the way the offset is calculated.
     * @param Object returnObject An object to store the return value in, so as not to break the chain. If passed in the
     *                            chain will not be broken and the result will be assigned to this object.
     * @type Object
     * @cat Plugins/Dimensions
     */
    offsetLite: function(options, returnObject) {
        if (!this[0]) error();
        var x = 0, y = 0, sl = 0, st = 0, parent = this[0], offsetParent, 
            options = $.extend({ margin: true, border: false, padding: false, scroll: true, relativeTo: document.body }, options || {});
                
        // Get the HTMLElement if relativeTo is a jquery collection
        if (options.relativeTo.jquery) options.relativeTo = options.relativeTo[0];
        
        do {
            x += parent.offsetLeft;
            y += parent.offsetTop;

            offsetParent = parent.offsetParent || document.body;
            if (options.scroll) {
                // get scroll offsets
                do {
                    sl += parent.scrollLeft;
                    st += parent.scrollTop;
                    parent = parent.parentNode;
                } while(parent != offsetParent);
            }
            parent = offsetParent;
        } while (parent && parent.tagName != 'BODY' && parent.tagName != 'HTML' && parent != options.relativeTo);

        var returnValue = handleOffsetReturn(this[0], options, x, y, sl, st);

        if (returnObject) { $.extend(returnObject, returnValue); return this; }
        else              { return returnValue; }
    },
    
    /**
     * Returns a jQuery collection with the positioned parent of 
     * the first matched element. This is the first parent of 
     * the element that has position (as in relative or absolute).
     *
     * @name offsetParent
     * @type jQuery
     * @cat Plugins/Dimensions
     */
    offsetParent: function() {
        if (!this[0]) error();
        var offsetParent = this[0].offsetParent;
        while ( offsetParent && (offsetParent.tagName != 'BODY' && $.css(offsetParent, 'position') == 'static') )
            offsetParent = offsetParent.offsetParent;
        return $(offsetParent);
    }
});

/**
 * Throws an error message when no elements are in the jQuery collection
 * @private
 */
var error = function() {
    throw "Dimensions: jQuery collection is empty";
};

/**
 * Handles converting a CSS Style into an Integer.
 * @private
 */
var num = function(el, prop) {
    return parseInt($.css(el.jquery?el[0]:el,prop))||0;
};

/**
 * Handles the return value of the offset and offsetLite methods.
 * @private
 */
var handleOffsetReturn = function(elem, options, x, y, sl, st) {
    if ( !options.margin ) {
        x -= num(elem, 'marginLeft');
        y -= num(elem, 'marginTop');
    }

    // Safari and Opera do not add the border for the element
    if ( options.border && (($.browser.safari && parseInt($.browser.version) < 520) || $.browser.opera) ) {
        x += num(elem, 'borderLeftWidth');
        y += num(elem, 'borderTopWidth');
    } else if ( !options.border && !(($.browser.safari && parseInt($.browser.version) < 520) || $.browser.opera) ) {
        x -= num(elem, 'borderLeftWidth');
        y -= num(elem, 'borderTopWidth');
    }

    if ( options.padding ) {
        x += num(elem, 'paddingLeft');
        y += num(elem, 'paddingTop');
    }
    
    // do not include scroll offset on the element ... opera sometimes reports scroll offset as actual offset
    if ( options.scroll && (!$.browser.opera || elem.offsetLeft != elem.scrollLeft && elem.offsetTop != elem.scrollLeft) ) {
        sl -= elem.scrollLeft;
        st -= elem.scrollTop;
    }

    return options.scroll ? { top: y - st, left: x - sl, scrollTop:  st, scrollLeft: sl }
                          : { top: y, left: x };
};

/**
 * Gets the width of the OS scrollbar
 * @private
 */
var scrollbarWidth = 0;
var getScrollbarWidth = function() {
    if (!scrollbarWidth) {
        var testEl = $('<div>')
                .css({
                    width: 100,
                    height: 100,
                    overflow: 'auto',
                    position: 'absolute',
                    top: -1000,
                    left: -1000
                })
                .appendTo('body');
        scrollbarWidth = 100 - testEl
            .append('<div>')
            .find('div')
                .css({
                    width: '100%',
                    height: 200
                })
                .width();
        testEl.remove();
    }
    return scrollbarWidth;
};



