var iface = {
  title: document.title,
  hash: document.location.hash,
  menu: null,
  menu_fixed: 0, // keep track of whether or not top menu (for stats and help) is in position:fixed
  is_go: 0,
  width: 0,
  height: 0, // we set it to "0" because we want it to change when the first interval runs so it applies the fixed/absolute change immediately, if needed.
  ctrl: 0, // we monitor for when either control or shift is held down, in which case we cancel the ajax action and let the link open in a new window/tab as per default browser behavior
  firefox: navigator.userAgent.match(/firefox/i), // firefox auto-urldecodes stuff in the hash, much to the dismay of developers everywhere, so we have to treat it a bit differently
  msiesux: $.browser.msie && navigator.userAgent.match(/msie [2-8]/i), // MSIE 8 and lower don't get ajax interface because they're horrible browsers that work like crap
  mobile: navigator.userAgent.match(/(iphone|ipod|ipad|android|webos|windows phone)/i), // mobile browsers don't get fixed tabs when scrolling down because they take too much space and behave strangely when zooming in
  time: 0, // stores the time of the last mouse movement so we know if the user is away, we can slow down the intervals to save resources.
  date: '', // we store the current date (refreshed on mouse movement) so we know if the date string in the URL matches 'today' or not (because we only auto-refresh for 'today')
  is_loading: 0, // when a page is being refreshed from a user action, this is set to 1, so that if auto-refresh tries to take over, we can just ignore it
  iframe: ( self != top ), // are we in an iframe?
  no_ajax: 0, // preference to force ajax off
  no_refresh: 0, // preference to disable auto-refresh after user has been away for at least 5 minutes
  version: 126, // when we update this file, increment this number, which will force everyone's browser to refresh to get the newest version. head.php needs to be in sync with this.
  
  
  time_get: function() {
    return Math.round( (new Date()).getTime() / 1000 );
  },
  
  time_set: function() {
    iface.time = iface.time_get();
    iface.date_set();
  },
  
  time_away: function() {
    return iface.time_get() - iface.time;
  },
  
  date_set: function() {
    var today = new Date();
    var d = today.getDate();
    var m = today.getMonth()+1;
    var y = today.getFullYear();
    if( d<10 ) d='0'+d;
    if( m<10 ) m='0'+m;
    iface.date = y+'-'+m+'-'+d;
    delete today, d, m, y;
  },
  
  is_away: function() {
    // interval decay starts after 5 minutes of no mouse movement, so that's what we're defining as 'away'
    // just want to centralize it in case we change the definition in the future.
    // and we're going to round it down a few seconds so when the interval hits at 5 minutes, we can say AT THAT INTERVAL they are away, rather than the next one.
    return iface.time_away() >= 295;
  },
  
  mousemove: function() {
    // before we update to the current time, check if we were previously 'away', so that if we were we can reset the stats interval so it starts updating again immediately
    // we have to call time_set() before we reset the interval because otherwise the next iteration will be on the same delay as it just was.
    // er, nevermind, because now stats.refresh won't fire...
    // ok, we'll just pass a variable to stats.intervals which will tell stats.refresh to refresh or die.
    if( iface.is_away()) {
      iface.time_set();
      stats.intervals('', 1); // 1 = tell stats.refresh to force refresh
    }
    else iface.time_set();
  },
  
  
  
  init: function() {
    
    // 2011-08-04 - FINALLY found the bug where loading a stats page directly instead of via ajax would make stats.header not update
    // it's because time_set() hadn't been called yet so stats.interval_timer() was returning the max time (7200) based on iface.time_away()... argh!!
    iface.time_set();
    
    // make tabs fixed position when scrolling down
    // tried making this an interval since this event doesn't get fired during "smooth scrolling" (e.g. macs/ipads)
    // but it just didn't work as well, also having ANOTHER interval, didn't like that - expensive
    $(window).scroll( function() {
      if( iface.mobile ) return;
      var offset = 10 + $('#header').position().top + $('#header').height();
      if( $(window).scrollTop() > offset ) {
        if( iface.menu && !iface.menu_fixed ) {
          iface.menu_fixed_set(offset);
        }
      }
      else {
        if( iface.menu_fixed ) {
          iface.menu_fixed_set(0);
        }
      }
    });
    
    // override a few things if we're in an iframe
    if( iface.iframe ) {
      $('#nav').hide();
      $('#header').css({'padding-top':'10px'});
      $('#main').css({'box-shadow':'0 0 0'});
      $('body').addClass('iframe');
    }
    
    // iface.menu isn't set until either iface.refresh() is called, or we load directly on a stats page
    // the way the stats stuff is init'd (must wait until page is fully loaded before calling menu_set, so that site_id/date stuff gets in there right)
    //   makes it impossible to just call menu_set() directly from here
    // problem is new design has menu for /help as well, but as is, it doesn't load if /help/ is the first page loaded
    // so for now, ugly hack, we'll check if the iface.dir_get('menu') returns 'help', if so, we'll manually call iface.menu_set()
    var menutest = iface.dir_get('menu');
    if( menutest && menutest.match(/^help/)) iface.menu_set();
    
    
    // need this check several times so... var.
    var page = iface.dir_get('page');
    
    // setup listeners for window size so we can change sidebar behavior, as well as set min-height for sidebar/main
    // also turns off spy when people navigate away from it
    // also hide/show stats-header as we move into / out of reporting interface
    // note - we made this its own function so we can call it immediately instead of having to wait for interval to start
    iface.init_interval();
    //iface.scroll_interval();
    
    // everything after this point, MSIE <9 doesn't get, because it's horrible.
    // or, if they have no_ajax set.
    
    if( iface.msiesux ) iface.no_ajax = 1;
    if( iface.no_ajax ) return;
    
    // setup listener for mouse movements
    $(document).mousemove( iface.mousemove );
    
    iface.is_go = 1;
    if( iface.hash ) iface.refresh(); // no need for this - it doesn't do the necessary "is this is a good hash" checks. hash_monitor will fire in 20 milliseconds anyways.
    iface.hash_monitor();
    
    
    // listen for control/shift/command keydowns so we can disable ajax loading if someone is trying to open a link in a new window/tab. metaKey == cmd on Macs
    $(document).keyup( function( e ) {  
      // used to listen for the specific key up like we do keydown, but that doesn't actually work with ctrlKey syntax (seems to for 'which', but listening for command key on macs with which doesn't sound reliable). 
      // so now we just assume, if they were holding down a special key, and they let up a key, don't open new tab/window
      iface.ctrl=0;
    }).keydown( function( e ) {
      if( e.shiftKey || e.ctrlKey || e.metaKey ) iface.ctrl = 1;
    }).blur( function(){
      iface.ctrl = 0; // when page is in background, it doesn't get keyup event, so when it's blurred we just assume it was let up.
    });
    
    
    // dont want to auto-ajax links that have a class of 'no-ajax'
    // for some reason, doing .not('.no-ajax') doesn't work when it's chained in there, so we're doing it on a per item basis
    
    // attach onclick events to tabs, subtabs, and any link inside #main, as they are injected
    $('#tabs > a').live('click', function() {
      if( $(this).attr('href').match(/bigscreen/)) {
        var new_url = $(this).attr('href');
        this.href = new_url.replace(/site_id=[0-9]+/, "site_id=" + stats.site_id);
        return;
      }
      if( $(this).hasClass('no-ajax') || $(this).attr('target') || iface.ctrl ) return;
      
      if( localhost( this.href )) {
        $('#tabs .subtabs').hide( 'blind', '', 200, function() { 
          var x = this;
          setTimeout( function(){ $(x).remove(); }, 300 ); // for some reason the callback is called immediately, instead of after the effect is done. so we have to add a timeout.
        });
        $('#tabs a').removeClass('current');
        $(this).addClass('current');
        
        iface.menu_shadow(); // remove shadow
        
        var subtabs = $('#subtabs-'+this.rel);
        if( subtabs[0] ) {
          
          $(this).closest('.tabs').append( '<div class="subtabs subtabs-pending">' + $(subtabs).html() + '</div>' ); // subtabs-pending removes hover effects so we don't get flashing
          
          if (this.rel == 'index')
            $('.subtabs-pending').hide().show( 'blind', '', 200, function(){
              var x = this;
              setTimeout( function(){ $(x).removeClass('subtabs-pending'); }, 300 );
            }).find('a[rel^=dashboard]').removeClass('current').end().find('a[rel^=dashboard-'+stats.dashboard_id+']').addClass('current');
            // the prepared dashboard sub-menu has class=current for the default dashboard ID at the time the prepared sub-menu was generated
            // this is never changed though unless the whole page is reloaded, so we have to manually remove the class before injecting into the DOM when the sub-menu is refreshed by clicking the 'dashboard' link
          else
            $('.subtabs-pending').hide().show( 'blind', '', 200, function(){
              var x = this;
              setTimeout( function(){ $(x).removeClass('subtabs-pending'); }, 300 );
            }).find('a:first').addClass('current');
          delete subtabs;
        }
        
        // site_id/date embedded in links may not be up to date if we're doing ajax, so update them!
        // NOTE - don't do this for user links (need to fix)
        iface.hash_set( iface.dehost( this.href ).split('?')[0] + ( iface.menu=='stats' ? '?' + stats.query() : '' ));
        return false;
      }
    });
    
    
    $('#sidebar .subtabs a').live('click', function() {
      if( $(this).hasClass('no-ajax')) return;
      if( iface.ctrl ) return;
      $('.subtabs a').removeClass('current');
      $(this).addClass('current');
      
      // site_id/date embedded in links may not be up to date if we're doing ajax, so update them!
      var did = null;
      if (iface.menu == 'stats' && this.rel)
        did = this.rel.split('-')[1];

      iface.hash_set( iface.dehost( this.href ).split('?')[0]
      + ( iface.menu == 'stats'
          ? '?' + stats.query() + ((did) ? '&dashboard_id=' + did : '')
          : '' ));
      return false;
    });
    
    
    // localhost() runs a few regex's but it's not called until a link is clicked on, so this isn't as inefficient as it might first appear.
    // also, we ignore all links that have target attributes so they can open in new windows
    // also, ignore links to images, otherwise the browser loads them as text - looks great!
    $('#nav a, #main-ajax a, .jGrowl a').live('click', function() {
      if( 
        iface.ctrl ||
        !this.href ||
        $(this).hasClass('no-ajax') || 
        $(this).attr('target') || 
        !localhost( this.href ) ||
        jslink( this.href ) ||
        this.href.match(/\.(gif|jpg|png|csv|xml|js|json)$/i)
      ) return;
      
      var href = iface.dehost( $(this).attr('href'));
      if( href.substr(0,1) != "/" ) {
        href = iface.dir_get() + href;
      }
      iface.hash_set( href );
      return false;
    });
  },
  
  
  
  init_interval: function() {
    
    // for msie
    if( !$(window).scrollTop()) iface.menu_fixed_set(0);
    
    // not sure what this accomplished but leaving it in for reference
    //if( iface.no_ajax ) return;
    
    
    // KILL SPY when we stop viewing spy
    if( window.spy && spy.interval_process && iface.dir_get('page') != 'spy' ) {
      spy.kill_intervals();
    }
    if( window.spybasic && spybasic.int_get && iface.dir_get('page') != 'spy-basic' ) {
      spybasic.kill_intervals();
    }
    
    //alert(iface.menu);
    
    // HIDE/SHOW STATS-HEADER
    // and setup intervals for stats.header / stats.refresh
    if( iface.menu == 'stats' ) {
      if( !stats.interval ) { // only need to do stuff if interval does not exist
        $('#stats-header-container').show();
        $('#stats-menus').show();
        $('#sidebar-container').css({'display':'block'});//show(); // this element starts out hidden, which doesn't work with jquery.hide/show - so we have to do it manually
        stats.intervals(1);
      }
    } 
    else {
      $('#stats-header-container').hide();
      $('#stats-menus').hide();
      if( iface.menu == 'help' || iface.menu == 'helpy' ) {
        $('#sidebar-container').css({'display':'block'});//show();
      }
      else {
        $('#sidebar-container').css({'display':'none'});//hide();
      }
      stats.kill_intervals();
    }
    
    setTimeout( iface.init_interval, 500 );
  },
  
  /*
  scroll_interval: function() {
    var offset = 10 + $('#header').position().top + $('#header').height();
    if( $(window).scrollTop() > offset ) {
      if( iface.menu && !iface.menu_fixed ) {
        iface.menu_fixed_set(offset);
      }
    }
    else {
      if( iface.menu_fixed ) {
        iface.menu_fixed_set(0);
      }
    }
    setTimeout( iface.scroll_interval, 50 );
  },
  */
  
  
  html: function( html ) {
    $('#main-ajax').html( html ).find('.dropdown').click(stopbubble);
  },
  
  
  dir_get: function( type ) {
    // returns the directory we're currently viewing. if type==root, then we only return the first step of the directory
    // if type==menu, then it's the same as root, but without the starting/ending /'s. we use this to keep track of which menu to display.
    // note - if we don't have a hash yet (since we're no longer force refreshing to ajax pages), then use the current page
    // update - we want to be able to use this too to know what "page" we're on, e.g. for spy, so we know if we're no longer viewing spy, we should kill the intervals.
    var hash = iface.hash || '#' + document.location.pathname + document.location.search;
    
    // strip off query, otherwise there are some weird bugs, e.g. when filtering by href=/some/page then trying to click any link, it's all messed up
    hash = hash.split('?')[0];
    
    if( type ) {
      if( type == 'menu' && iface.dir_get('page') == 'wp-iframe' ) return null; // ugly hack
      
      var x = hash.substr(1).split('/'); //alert( type+' '+x[1] );
           if( type == 'root' ) return '/'+x[1]+'/';
      else if( type == 'page' ) return x[ x.length-1 ].split('?')[0];
      else if( type == 'menu' ) return ( x[1].match(/^(stats|help)/)) ? x[1] : null;
      else if( type == 'full' ) return hash; // 2011-05-04 added if we want the full path plus script name, e.g. /stats/visitors - didnt have a way to do that before >:(
    }
    // default - returns entire directory structure we're currently in (everything up to and including the last '/' - this is why filtering by href was broken for a while)
    return hash.substr(1).replace(/\/[^\/]+$/, '/');
  },
  
  menu_set: function( menu ) {
    if( !menu ) menu = iface.dir_get('menu');
    if( menu != iface.menu ) {
      iface.menu = menu;
      iface.menu_load();
    }
  },
  
  menu_load: function() {
    if( !iface.menu ) {
      $('#menu-container').html(''); // no more global menu, we just have ones on /stats and /help, so just empty it out and return
      return;
    }
    if( iface.no_ajax || iface.msiesux ) return; // don't actually LOAD it for no ajax interface, but we need the iface.menu variable to be SET for a few other things to work right (e.g. stats-header)
    
    var args = { menu: iface.menu, iface_hash: iface.hash.substr(1).split('?')[0] };
    if( iface.menu == 'stats' && stats.site_id ) args.site_id = stats.site_id;
    $.get( '/ajax/sidebar-menu', args, function( data ) {
      $('#menu-container').html( data ).find('.dropdown').click(stopbubble);
      links_new_window('#menu-container');
    });
  },
  
  menu_fixed_set: function( x ) {
    // x = how much to offset. if 0, then it removes fixed positioning
    if( x ) {
      iface.menu_fixed = 1;
      $('#sidebar-container').addClass( 'fixed' ).css({'top': 0});//( $('#nav:visible').length ? $('#nav').outerHeight() : 0 ) });
      $('#main-ajax').css({'margin-top': $('#sidebar-container').outerHeight() }); // when sidebar-container goes fixed, it leaves a void that must be filled wth extra margin, otherwise, the stuff under the tabs jumps around
      iface.menu_shadow(1);
    }
    else {
      iface.menu_fixed = 0;
      $('#sidebar-container').removeClass( 'fixed' );
      $('#main-ajax').css({'margin-top':'0'});
      iface.menu_shadow();
    }
  },
  
  menu_shadow: function( x ) {
    if( x ) {
      var side = $('#sidebar-container');
      $('#sidebar-shadow') . attr('width', $(side).width() +'px') . css('top', ( /*$('#nav:visible').outerHeight()*/ + $(side).outerHeight() ) + 'px' ).removeClass('hideme');
      //$('#sidebar-container').after('<img id=sidebar-shadow width="' + $(side).width() +'px" height="8px" style="z-index:1000; position: fixed; top: ' + ( $('#nav:visible').outerHeight() + $(side).outerHeight() -2 ) + 'px" src="/media/image_shadow.png" />' );
    }
    else {
      $('#sidebar-shadow').addClass('hideme');
    }
  },
  
  
  title_set: function( title ) {
    // this sets document.title
    // we're centralizing it so we can easily preprend the current "visitors online now" value to document.title whenever the title changes
    // but we'll store the actual title (without prepended number) in iface.title so that's what we can pass to clicky.log so it doesn't log the prepended number to our stats, which we don't want
    // add a slight delay too, otherwise when we leave stats directory, iface.menu hasn't been updated yet so on first page view outside of /stats/ it still prepends this
    // meh, i hate delays, let's just grab dir_get directly to make sure we have actual live fully up to date menu!
    // update - somehow, the (X) part sometimes sneaks in here, so regex that out before setting it
    if( title ) iface.title = title.replace(/^(\([0-9]+\) ?)+/,'');
    document.title = ( iface.dir_get('menu') == 'stats' && stats.visitors_online ? '('+stats.visitors_online+') ' : '' ) + iface.title;
  },
  
  
  refresh: function( hash, auto ) {
    // if hash is set, that means we want to force it to load that page instead of iface.hash
    if( !hash ) hash = iface.hash;
    
    // if hash doesn't start with a slash - abort mission
    if( hash.substr(1,1) != "/" ) return;
    
    // auto==1 when we are auto-refreshing the page after the user has been away.
    // but if iface.is_loading==1 then that means we're already trying to load something else, so just ignore the request.
    if( auto ) {
      if( iface.is_loading ) return;
    }
    else {
      // used to scroll to top here when we just showed the loading image, but now that we're using a transparent lightbox we'll wait until the page has loaded
      iface.is_loading = 1;
    }
    iface.loading(1);
    
    
    // stupid javascript split(), if you set a limit, instead of just returning everything after the limit in the last key, it just completely cuts it off
    // that is so NOT useful i don't even know where to begin
    // so we have to check stuff.length, if greater than 2, then combine back together keys 1->last
    var stuff = hash.substr(1).split('?');
    if( stuff.length > 2 ) {
      for( var i=2, l=stuff.length; i<l; i++ ) {
        stuff[1] += '?' + stuff[i];
      }
    }
    
    $.get( stuff[0], (stuff[1] || ''), function( data ) {
      iface.html( data );
      iface.menu_set();// we don't do this until after the page has loaded, in case we're viewing stats, we need to wait for stats.site_id to be set
      iface.loading(); // stop loading graphic
      if( !auto ) $('html,body').animate( { scrollTop: 0 }, 0 );
      
      // need to tag all outbounds and downloads because the tracking code won't automatically see them
      // meh, downloads aren't a big deal. we'll just worry about outbounds for now.
      // update - also, convert e.g. "visitors" to "/stats/visitors" so middle/shift/control clicking to open new tab/window works.
      
      $('#main-ajax a').each( function(){
        if( !this.href ) return; // not sure how this is happening but on the dashboard, some hrefs are blank which causes iface.dehost to die and hence javascript dies
        if( localhost( this.href )) {
          // need to dehost before checking jslink, otherwise it messes up autoscroll links
          var href = iface.dehost( $(this).attr('href'));
          if( !jslink( href )) {
            if( href.substr(0,1) != "/" ) {
              $(this).attr('href',iface.dir_get() + href);
            }
          }
          delete href;
        }
        else if( this.href.match(/^https?:\/\//)) {
          if( window.clicky && clicky.add_event && clicky.outbound ) clicky.add_event( this, "mousedown", clicky.outbound );
        }
      });
      
      // log as a normal page view, unless auto==1, in which case it's an auto-refresh so nevermind
      if( !auto ) setTimeout( function(){ clicky.log( hash.substr(1), iface.title, 'pageview' ); }, 200 ); // slight delay to ensure we have an up-to-date page title
      
      autoforms();
      autoscroll();
      autotoggle();
      links_new_window('#main-ajax');
      
      // we're done doing stuff. auto refresh can start happening again.
      iface.is_loading = 0;
    });
    
    iface.stop_refreshing = 0;
  },
  
  
  hash_monitor: function() {
    setInterval( function() {
      // only update if the hash starts with a / - otherwise, it's a scrolly one or something else - we don't want those to make new ajax requests!
      // note - if someone is on a "normal" page, then loads something via ajax, then hits "back" - nothing happens
      // so we compensate for that too
      // hmm, we were just setting iface.hash to a blank string but then that breaks some things, e.g. menu automatically changing
      // so we'll need to set it manually but then also set a variable saying, "don't do this again", so it doesn't loop infinitely
      // and that variable will unset whenever refresh() is called
      if( iface.hash && !document.location.hash && !iface.stop_refreshing ) {
        iface.hash = '#' + document.location.pathname + document.location.search;
        iface.refresh();
        iface.stop_refreshing = 1;
      }
      else if( document.location.hash != iface.hash && document.location.hash.substr(1,1) == '/' ) {
        iface.hash = document.location.hash;
        iface.refresh();
      }
    }, 100 );
  },
  
  hash_set: function( hash ) {
    if( !hash ) return;
    
    // when filtering visitors by URLs, the "href=..." part of the URL is already URL encoded, but when set document.hash to that value then read it back, it becomes decoded
    // encodeURI works here because it doesn't encode anything that's already part of the valid URL, but it does encode '%' signs (so they're double-encoded)
    // and since href=... has %'s in it, the href basically gets double-encoded, but only single-decoded when we read it back
    // this is good because that's how we want to send the URL to the server.
    // the main problem here was when there was '&' in the URL we're filtering by, with single-encoding it was become just '&' again and the server thought it was actually another parameter instead of part of 'href'
    // update - oh, guess this was only a problem with firefox? because it turns out chrome handles it just fine.
    // opera and safari are ok too. don't care about MSIE. Hmm... well that's interesting! ok we'll only do this for firefox then.
    // yeah, this bug in firefix is NINE YEARS OLD... NINE!!! it's marked as fixed but it's not. *sigh*... https://bugzilla.mozilla.org/show_bug.cgi?id=135309
    if( iface.firefox ) hash = encodeURI( hash );
    
    // if hash is the same as we're already on, then just force refresh the page
    if( hash == document.location.hash || '#'+hash == document.location.hash ) iface.refresh();
    
    document.location.hash = hash.substr(0,1) == '#' ? hash : '#' + hash;
  },
  
  // when site_id or date is updated dynamically, we need to update the variable in the hash tag. this finds and replaces the value
  hash_var_set: function( key, value ) {
    // if we try to set a hash var before the hash exists (e.g. after initial page load) then it dies. so if hash doesn't exist, we just grab the current "real" URL and use that.
    var url = iface.hash || location.pathname + location.search;
    iface.hash_set( url_var( key, value, url ));
  },
  
  loading: function( state ) {
    // state 1 = start, 0 = end
    if( state ) {
      $('#main-ajax').prepend('<div id=loading-lightbox></div>').find('#loading-lightbox').css({ 'width': $('#main-ajax').width(), 'height': $('#main-ajax').height() });
      $('#loading').show().css({ 'left': $('#main').position().left - 35 });
    }
    else {
      $('#loading-lightbox').remove();
      $('#loading').hide();
    }
  },
  
  // updates display value for whatever dropdown menu
  set_dropdown: function( id, title ) {
    if( !id || !title ) return;
    $('#'+id).html( '&#9660; ' + title );
    dropdown.close();
  },
  
  dehost: function( url ) {
    return url.replace(/^https?:\/\/[^\/]+/i,'');
  },

  zxc: ''
}


// stats basically just contains the current site_id and date that's being viewed.
// these can change on the fly so we need to monitor the hash tag for changes and update site_id/date in here so we can use it for the sidebar menu when changing reports

var stats = {
  hash: '',
  date: 'today',
  site_id: null,
  dashboard_id: null,
  sites: [], // this will store an array of site_ids / site names so when site ID is changed we can update the menu with the right name
  interval_loading: 0, // when mouse moves, if we were previously in 'away' state, we want to fire off interval again immediately. we don't want to do it a thousand times tho, so we store the fact that we're doing it in here.
  shorten_nickname: 0, // non-paying users have ads at top, to make sure layout doesn't get funky we can only show up to 20 chars here really. we override this to :1 for free users 
  visitors_online: 0,
  
  init: function( site_id ) {
    
    // init is called on every refresh. if it's the same site_id we're already viewing, we can just exit immediately, otherwise we keep setting up new intervals, etc
    if( stats.site_id && stats.site_id == site_id ) return;
    
    stats.site_id = site_id;
    
    // we have to call menu_set when stats.init() is called
    // this is because if /stats/ is initial page view (so it's not loaded via ajax), when jquery.live runs on tabs, menu has not been set to 'stats' yet (it's set to "nothing" initially) so it doesn't add site_id onto links
    // we can't just set it to 'stats' initially though, because we don't want to do that until stats.site_id is set.
    // so basically what this means is, on the initial page view of /stats/, the menu is in place (but not fully functional), but then it's reloaded once stats.init runs
    // this is a bit messy but it works
    iface.menu_set();
    
    if( iface.no_ajax ) return; // need first two things above to happen before exiting so that stats.header() interval still works for no-ajax
    
    stats.set_site_menu( site_id );
    stats.hash_monitor();
    if( !stats.hash ) stats.hash_parse(); // parse site_id and date from non-hash URL
    
    // also call stats.header() for good measure. interval set by iface.init, site_id probably isn't set yet so it won't do anything.
    stats.header();
  },
  
  
  intervals: function( first, force_refresh ) {
    // middle man function to automatically call header() and refresh() on a schedule based on last mouse movement
    // reason we use middle man is so that on $first run, it doesn't actually call the other functions, but just sets timeouts to call itself again which will then call the functions
    // this is because we don't need them to run initially - refresh is unnecessary obviously, and stats.header is automatically updated when a site ID change is detected
    
    // after being 'away', when mouse movement is detected, we want to refresh the data immediately
    // but since onmousemove can fire like a billion events in half a second, we obviously don't want to send all of those our way
    // so interval_loading just means we're already processing the refresh, so don't do any more kthx.
    // force_refresh is also set to 1 after post-away mousemovement, because we reset the time before this is called so that the next interval is in 60 seconds rather than what it would be if nothing had happened.
    
    if( stats.interval_loading ) return;
    stats.interval_loading = 1;
    
    if( stats.interval ) {
      clearTimeout( stats.interval );
      delete stats.interval;
    }
    
    if( iface.menu == 'stats' ) {
      if( !first ) {
        // we do this one first because it will do auto login if necessary (if the session has expired because of no activity for a while)
        stats.refresh( force_refresh );
        stats.header();
      }
      stats.interval = setTimeout( stats.intervals, stats.interval_timer());
    }
    
    stats.interval_loading = 0;
  },
  
  
  kill_intervals: function() {
    if( stats.interval ) {
      clearTimeout( stats.interval );
      delete stats.interval;
    }
  },
  
  interval_timer: function() {
    // stats.header and stats.refresh use this timer to know how long to wait between updates.
    // the value returned is based on how long it's been since mouse movement
    // 60   seconds while active
    // 120  seconds if no movement for >5 minutes
    // 300  seconds if no movement for >10 minutes
    // 600  seconds if no movement for >60 minutes
    // 1200 seconds if no movement for >120 minutes (sessions expire after 24 minutes so we want to to keep max refresh under that limit)
    var away = iface.time_away();
    if( away > 7200  ) return 1200000;
    if( away > 3600  ) return  600000;
    if( away > 600   ) return  300000;
    if( away > 300   ) return  120000; 
    return 60000;
  },
  
  
  
  header: function() {
    if( !stats.site_id || ( window.spy && spy.interval_process )) return;
    $.get('/ajax/stats-header', { site_id: stats.site_id }, function( data ) {
      $('#stats-header-container').html( data );
    });
  },
  
  
  refresh: function( force_refresh ) {
    // refreshes the current report transparently (e.g. no 'loading' image)
    // only do this when the user has been inactive for at least 5 minutes though, and never do it on setup/prefs pages, or spy
    // oh, also only do this if stats.date=='today', otherwise there's no point
    // well, maybe they're viewing a range that INCLUDES today, but it won't be as 'strange' for this not to auto-update as compared to viewing just today.
    // update - this is a complete waste of resources to update whatever report we're on if the user is away
    // so now we'll ONLY do it on force_refresh, which is only sent after mouse movement after 5 minutes of no movement, so as soon as they come back it will update LIKE MAGIC!!!
    if( 
      ( /*iface.is_away() ||*/ force_refresh ) &&
      !iface.no_refresh && // 2011-03-01 added preference to disable auto-refresh of current report
      iface.menu == 'stats' &&
      !iface.dir_get('page').match(/setup|prefs|alerts|spy/i) && 
      ( stats.date == 'today' || stats.date == iface.date ))
    {
      iface.refresh( '', 1 );
    }
  },
  
  
  
  set_site_id: function( site_id, no_refresh ) {
    // this is only called if the site ID is *changed* from the current one. it's not used on initialization.
    if( typeof site_id == 'number' ) {
      if( iface.is_go ) {
        // have to kill spy interval here too, if they change the site! doh!
        if( window.spy ) spy.kill_intervals();
        if( window.spybasic ) spybasic.kill_intervals();
        
        stats.site_id = site_id;
        
        // hash_monitor() notices a new hash, calls hash_parse(), which sets new site_id and date
        // a site_id only changes without user selecting from menu in fairly rare cases (e.g. they view one site, then anotehr, then click back) but we still need to deal with it.
        // but when that does happen, we call set_site_id so it can do a few things, however this is the one thing it doesn't need to do, so we say no_refresh=1 here.
        if( !no_refresh ) iface.hash_var_set( 'site_id', site_id );
        
        stats.set_site_menu( site_id );
        
        // need to manually call stats.header here, because stats.site_id is set before the page refreshes
        stats.header();

        // reload menu, necessary since implementing multiple dashboards
        iface.menu_load();
      }
      else go( url_var( 'site_id', site_id ));
    }
  },
  
  set_date: function( date, and_menu ) {
    if( iface.is_go ) {
      if( typeof date == 'string' || typeof date == 'number' ) {
        stats.date = date;
        iface.hash_var_set( 'date', date );
        if( and_menu ) stats.set_date_menu( date );
      }
    }
    else go( url_var( 'date', date ));
  },
  
  set_site_menu: function( site_id ) {
    if( stats.sites[ site_id ] ) {
      iface.set_dropdown( 'site-select-display', stats.sites[ site_id ].substr( 0, ( stats.shorten_nickname ? 18 : 50 )));
    }
  },
  
  set_date_menu: function( date ) {
    // this is currently only used when setting a custom date range.
    iface.set_dropdown( 'date-select-display', date );
  },
  
  set_visitors_online: function( x, new_visitors, new_actions ) {
    stats.visitors_online = x;
    iface.title_set();
    
    // spy passes in new visitor and actions values to this function, so if these are set, then add these values to existing ones
    // they are in HTML with commas, so we have to remove those, do a parseInt, add these new values, convert back to string, add commas, then insert back into the page - it so easy no wonder it #1 !!!
    if( new_visitors ) {
      var visitors = parseInt( $('#stats-header-visitors').text().replace(',',''));
      $('#stats-header-visitors').text( commafy( visitors + new_visitors ));
      delete visitors, new_visitors;
    }
    
    if( new_actions ) {
      var actions = parseInt( $('#stats-header-actions').text().replace(',',''));
      $('#stats-header-actions').text( commafy( actions + new_actions ));
      delete actions, new_actions;
    }
  },
  
  
  
  hash_parse: function( hash ) {
    // this updates the internal site_id and date values when the hash changes
    // we fallback on the actual non-hash URL if no has is set, so directly loading e.g. /stats/?site_id=X&date=Y still loads those values into javascript
    // we only allow overwriting existing values, and only if they're strings and numbers, to prevent hax0r
    if( !hash ) hash = stats.hash;
    if( !hash ) hash = document.location.search;
    if( !hash ) return; // this happens sometimes when the initial page loaded has a hash in it. it's complicated.
    var args = hash.split('?');
    if( args && args[1] ) args = args[1].split('&');
    var is_date_specified = false;
    for( var i in args ) {
      var arg = args[i].split('=');
      if( stats[ arg[0] ] &&  ( typeof stats[ arg[0] ] == "string" || typeof stats[ arg[0] ] == "number" )) {
        // if this is site_id or date, we need to pass this through the proper functions instead of just setting the value, because a few more things need to happen
        // an example of something that breaks otherwise is stats.header - when we call set_site_id, it automatically updates that, but otherwise it stays the same until next interval refresh.
        // hmm, but calling that then calls hash_var_set which then calls hash_set which then calls refresh which will force refresh the page if the same is the same..
        // site_id is really the onle one that matters, so we'll just do that one, and we'll add a new parameter no_refresh which will make set_site_id() skip the hash_var_set crap.
        if( arg[0] == 'site_id' ) {
          stats.set_site_id( arg[1], 1 );
        }
        else stats[ arg[0] ] = arg[1];

        if( arg[0] == 'date' ) is_date_specified = true;
      }
    }
    if (! is_date_specified) stats['date'] = 'today';
  },
  
  hash_monitor: function() {
    // duh - this should only be running while we're viewing stats reports.
    // so basically if iface.menu != 'stats', then the interval will kill itself, and we'll set stats.site_id=null so stats.init() runs fully again if/when we come back to stats!
    stats.hash_interval = setInterval( function() {
      if( iface.menu != 'stats' ) {
        if( stats.hash_interval ) {
          clearInterval( stats.hash_interval );
          stats.hash_interval = 0;
          stats.site_id = null;
        }
      }
      else if( document.location.hash != stats.hash ) {
        stats.hash = document.location.hash;
        stats.hash_parse();
      }
    }, 100 );
  },
  
  query: function() {
    // this returns the query string to tack onto the end of sidebar tabs (excluding the '?')
    return 'site_id=' + stats.site_id + ( stats.date == 'today' || stats.date == iface.date ? '' : '&date=' + stats.date );
  },
  
  zxc: 0
};



var dash = {
  
  init: function() {
    $('.dash_box').each( function() {
      $(this).find('.dash_box_tabs a').click( function() {
        dash.tab_click( this ); 
        return false;
      });
      $(this).find('.dash_box_tabs a.current').each( function() {
        // need to grab rel value to initiate dash.trends() for this box
        //$(this).addClass("current");
        var x = $(this).attr('rel').split('|');
        dash.trends( x[0], x[1] );
      });
    });
  },
  
  // can specify a date here if we want to make a one time request for something other than the global dash.date
  tab_click: function( tab, date2 ) {
    var x = tab.rel.split('|');
    var box_id = x[0];
    var box_type = x[1];
    $('#'+box_id).find('.dash_box_tabs a.current').removeClass("current");
    $(tab).addClass("current");
    dash.loading( box_id );
    clicky.log( '#dashboard/tab/'+box_type, 'Dashboard tab: '+box_type );
    $.get( "../ajax/dash_box", { site_id: dash.site_id, date: ( date2 ? date2 : dash.date ), type: box_type, box: box_id }, function( data ) {
      $('#'+box_id).find('.dash_box_content').html(data);
      dash.trends( box_id, box_type );
      links_new_window('#'+box_id);
    });
  },
  
  trends: function( box_id, box_type ) {
    $('#'+box_id).find('.dash_box_content a.trend[rel]').click( function() {
      if( !this.rel ) return false;
      dash.loading( box_id );
      clicky.log( '#dashboard/trend/'+box_type, 'Dashboard trend: '+box_type );
      $.get( "../ajax/dash_box", { site_id: dash.site_id, date: dash.date, type: box_type, box: box_id, stat_id: this.rel },  function( data ) {
        $('#'+box_id).find('.dash_box_content').html( data ).find('a.back').click( function(){ dash.refresh( box_id ); return false; });
      });
      return false; 
    });
  },
  
  loading: function( box_id ) {
    $('#'+box_id+' .dash_box_content').html('<center><br><img src="../media/loading-large.gif"><br><br><br></center>');
  },
  
  refresh: function( box_id ) {
    $('#'+box_id+' .dash_box_tabs a.current').click();
    links_new_window('#'+box_id);
    return false;
  },
  
  set_date: function( date, box_id, box_type, id ) {
    // this is used to set the date within an individual module, rather than using the global date.
    // the majority of the code is copied from other methods here because they're doing the same type of requests, the original methods just aren't written well to take extra parameters.
    // eventually, we'll re-write the whole thing.
    if( id ) {
      // stats for an individual item
      dash.loading( box_id );
      $.get( "../ajax/dash_box", { site_id: dash.site_id, date: date, type: box_type, box: box_id, stat_id: id },  function( data ) {
        $('#'+box_id).find('.dash_box_content').html( data ).find('a.back').click( function(){ dash.refresh( box_id ); return false; });
      });
    }
    else if( box_id ) {
      $('#'+box_id+' .dash_box_tabs a.current').each( function() {
        dash.tab_click( this, date );
      });
    }
  }
};




// graphs for popular data - trends and pagination

var graphy = {
  
  init: function() {
    graphy.back = $('#graphy').html();
    //$('#graphy a.nextpage').click( graphy.nextpage );
    $('#graphy a.trend[rel]').click( function() {
      if( !this.rel ) return false;
      var request = { site_id: graphy.site_id, date: graphy.date, type: graphy.type };
      if( graphy.type_parent ) {
        // if we have a parent, then this.rel is id|parent_id instead of just id, so split those up. parent_type is pre-declared in the javascript that the page outputs.
        var temp = this.rel.split('|');
        request.stat_id = temp[0];
        request.id_parent = temp[1];
        request.type_parent = graphy.type_parent;
      }
      else {
        request.stat_id = this.rel;
      }
      graphy.loading();
      $.get( "../ajax/graphy", request, function( data ) {
        $('#graphy').html( data ).find('a.back').click( function() { 
          $('#graphy').html( graphy.back );
          links_new_window('#graphy');
          graphy.init();
          return false; 
        });
      });
      return false; 
    });
  },
  
  /*
  nextpage: function() {
    graphy.loading();
    $.get( "../ajax/graphy", { site_id: graphy.site_id, date: graphy.date, type: graphy.type, page: ++graphy.page, filter: graphy.filter },  function( data ) {
      $('#graphy').html( data );
      graphy.init();
    });
  },
  */
  
  // just like dash, we need a seperate set_date function here, since it wasn't originally written to have this parameter changeable
  // at some point we will re-write it all and all will be well.
  // also we're not using box_id or box_type here but that's how it's always structured in the PHP so we deal with it.
  set_date: function( date, box_id, box_type, id ) {
    if( date && id ) {
      graphy.loading();
      $.get( "../ajax/graphy", { site_id: graphy.site_id, date: date, type: graphy.type, stat_id: id }, function( data ) {
        $('#graphy').html( data ).find('a.back').click( function() { 
          $('#graphy').html( graphy.back );
          graphy.init(); 
          return false; 
        });
      });
    }
  },
  
  loading: function() {
    $('#graphy').html('<center><br><img src="../media/loading-large.gif"><br><br><br></center>');
  }
};

// dropdown menus
var dropdown = {
  
  opened: 0,
  
  open: function( div, ev, stuff, align ) {
    // if "stuff" comes in, that means we want to set the content of this div before we display it. This is used to "reset" a multi-layer menu
    dropdown.close();
    dropdown.opened = div;
    if( stuff ) $('#'+div).html( stuff );
    
    var item  = $( ev.target || ev.srcElement );
    var pos   = $( item ).position();
    
    // change align to auto-detect based on which half of screen menu is on
    // we can still override if it's set, just make it so we don't have to
    if( align ) {
      var left = ( align == 'right' ) ? pos.left - $('#'+div).outerWidth() + $( item ).outerWidth() : pos.left;
    }
    else {
      var w = window.innerWidth || document.documentElement.clientWidth || '';
      if( w && pos.left / w > 0.5 ) {
        var left = pos.left - $('#'+div).outerWidth() + $( item ).outerWidth() ;
      }
      else {
        var left = pos.left;
      }
    }
    
    $('#'+div).show().css("left", left ).css("top", pos.top + $( item ).outerHeight());
    
    clicky.log( '#menu/'+div, 'Menu: '+div );
    stopbubble( ev );
    return false;
  },
  close: function() {
    $('#'+dropdown.opened ).hide();
    $('.dropdown').hide();
  },
  content: function( div, stuff ) {
    $('#'+div).html( stuff );
  },
  content_ajax: function( div, options ) {
    dropdown.loading( div );
    $.get( "../ajax/dropdown_menu", options, function( data ) {
      dropdown.content( div, data );
    });
    // must restate that we dont want clicks inside the menu to close the menu!
    $('.dropdown').click(stopbubble);
  },
  loading: function( div ) {
    dropdown.content( div, "<img class=mb-3 src='../media/loading.gif'> Loading..." );
  }
};




// when DOM is ready, do some useful stuff.

$(function() {
  
  // initialize interface (tabs etc) and do other handy things
  iface.init();
  autoforms();
  autoscroll();
  autotoggle();
  links_new_window();
  
  // close dropdown menu for any click outside of the menu
  //if( location.pathname.match(/\/stats\//)) {
    $('body').click(dropdown.close);
    $('.dropdown').click(stopbubble);
  //}
});

function autoforms() {
  if( document.forms.length ) {
  
    // auto-disable submit button when clicked. only first one though, because if there is another one we need its value.
    $(document.forms).each( function() {
      
      // have to add event listener to delete buttons for ajax forms, because the actual button clicked doesn't get submitted when doing via ajax
      // so we create a hidden element in the form "delete_button=1" when the delete button is clicked, so that can get passed through
      if( iface.is_go ) {
        $(this).find(':submit[name!=submit_button]').click( function() {
          $(this).append('<input type=hidden name='+this.name+' value=1>');
        });
      }
      
      $(this).submit( function() {
        $(this).find(':submit:first[name!=no_disable]').attr( { disabled : 'disabled' } );
        
        // if we've doing ajax interface, then convert form to submit via ajax
        // use the same method that the form specifies, which means we have to use $.ajax
        // update: forms submitted via get (we have a lot of them in the stats interface), this method doesn't udpate the URL bar - that's annoying
        // so if it's "get", send the fastSerialize result to iface.href, otherwise if post, do $.post
        // update: if action is not set, browsers submit normal form stuff to the same page as the form
        // ajax forms aren't doing this though, so we need to check for action, because sometimes i leave it off
        if( iface.is_go ) {
          iface.loading(1);
          var method = $(this).attr('method') || 'get', // we only ever use 'post' if intentional, and 'get' all over the place for mini-forms, so, default to that
              action = $(this).attr('action') || iface.hash.substr(1);
          if( action.substr(0,1) != '/' ) action = iface.dir_get() + action;
          
          if( method == 'get' ) {
            iface.hash_set( action +'?'+ $.param( $(this).fastSerialize()));
          }
          else {
            $.post( action, $.param( $(this).fastSerialize()), function( data ) {
              iface.html( data );
              iface.loading(0);
              $('html,body').animate( { scrollTop: 0 }, 0 );
            });
          }
          
          return false;
        }
      });
    });
    
    // auto-focus first text field, if it's empty and visible
    // but don't do this for filter dropdowns (which are hidden) or msie dies
    //$(':text:first:not([name$=custom_value])').each( function() { // dont remember what this was for...
    $(':text:visible:first').each( function() {
      if( this.value == '' ) this.focus();
    });
  }
}



function autoscroll() {
  $('a[href^=#]:not([href=#])').click( function() {
    var x = $(this.hash);
    if( x.length ) {
      // with new fixed nav bar and sometimes fixed tabs, we need to add extra scrolling
      var extra = $('#nav').outerHeight() + ( $('#sidebar-container').hasClass('fixed') ? $('#sidebar-container').outerHeight() : 0 );
      $('html,body').animate( { scrollTop: x.offset().top - extra }, 500 );
      location.hash = this.hash;
      return false;
     }
  });
}

function autotoggle() {
  // auto-toggle for hidden help boxes
  // auto-focus first form item if there's a form in there
  $('a[rel^="toggle"]').click( function() {
    var id = this.rel.split(':')[1];
    $('#' + id ).toggleClass( "hideme" );
    if( !$('#'+id).hasClass("hideme")) {
      $('#'+id+' :text:first').each( function() {
        if( this.value == '' ) setTimeout( "$('#"+id+" :text:first').focus()", 200);
      });
    }
    clicky.log('#toggle/'+id, id.replace(/(-|_)/g,' '));
    return false;
  });
}


function links_new_window( context ) {
  // context lets us limit the scope so it doesn't have to check every link every time it runs
  if( context ) context += ' ';
  $( ( context || '' ) + "a[href^='http']").each( function() {
    if( !localhost( this.href ) && !$(this).hasClass('no-target')) $(this).attr('target','_blank');
  });
  
  // 2011-07 adding option for requiring links to open in a new window when we're inside an iframe. 
  // just setting a class on them "iframe_new_window", if we see that, and we're in an iframe - boom.
  if( window.iframe || iface.iframe ) {
    $( ( context || '' ) + "a.iframe-new-window").attr('target','_blank');
  }
}


function localhost( url ) {
  // test whether a URL is for the current domain, or external
  //if( url.slice(-1) == '#' ) return false; // this should really return TRUE, but we made it return false just for convenience. but now it's messing things up when i actually want to test for true localhost or not.
  if( url.match(/^(mailto:)/)) return false; // used to test for http here too but that may lead to some problems (e.g. link to secure login) - just let the regex below deal with that decision.
  //return RegExp( "^https?://"+location.host.replace( /^www\./i, "")+"/", "i").test( url );
  return RegExp( "^https?://[^/]*"+location.host.replace( /^www\./i, "")+"/", "i").test( url ); // old - messes up links that point to another sub-domain - we want to keep the sub-domain!
}

function jslink( url ) {
  // test for javascript links, e.g. ones with href="#" onclick=...., or javascript:function()
  // sometimes we pass stuff in here after it's been "dehosted" (e.g. for testing for autoscroll links), so support the link starting with a hash too
  // any link that is just "#", browsers append http://domain.com/ to the beginning automatically, which is really irritating
  // so we have to check for the last character being a "#"
  // note - use slice here, apparently MSIE doesn't support negative indexes for substr(). brilliant as usual microsoft.
  return ( url.slice(-1) == '#' || url.substr(0,1) == "#" || url.match(/^(javascript:)/));
}



function go( url ) {
  dropdown.close();
  if( url ) {
    if( iface.is_go ) {
      iface.hash_set( url );
    }
    else {
      location.href=url;
    }
  }
}

function url_var( key, value, url ) {
  if( !url ) {
    if( iface.is_go ) url = iface.hash.substr(1);
    if( !url ) url = location.pathname + location.search; // backup, in case hash doesn't exist yet
  }
  
  if( !key ) return url;
  //url = url.replace( /#.*$/i, "" );
  
  if( !value ) {
    // if no value is set, we are deleting the variable from the URL
    var x = new RegExp( "(&|\\?)"+key+"=[^&]*" );
    return url.replace( x, "" );
  }
  else {
    // otherwise, we are changing/adding it
    // have to test for both &var and ?var since javascript doesn't support lookbehinds
    var x = new RegExp( "&"+key+"=[^&]*" );
    var y = new RegExp( "\\?"+key+"=[^&]*" );
    var z = new RegExp( "\\#"+key+"=[^&]*" );
    if( url.match( x )) {
      return url.replace( x, "&"+key.replace(/(\\)/g,"")+"="+value );
    }
    else if( url.match( y )) {
      return url.replace( y, "?"+key.replace(/(\\)/g,"")+"="+value );
    }
    else if( url.match( z )) {
      return url.replace( z, "#"+key.replace(/(\\)/g,"")+"="+value );
    }
    else {
      return url + "&" + key.replace(/(\\)/g,"") + "=" + value;
    }
  }
}






// calendar

function cal_show( div, ev ) {
  dropdown.close();
  
  var item  = $( '#date-select-display' );
  var pos   = $( item ).position(); 
  var left = pos.left - $('#'+div).outerWidth() + $( item ).outerWidth();
  $('#'+div).show().
    css("left", left ).
    css("top", pos.top + $( item ).outerHeight());
    
  clicky.log('#menu/calendar','Menu: calendar'); 
}

function calendar( date1, date2, date_min ) {
  //this.date_min = date_min;
  this.date_max = new Date();
  this.date1 = date1;
  this.date2 = date2 || date1;
  
  this.cal1 = new dhtmlxCalendarObject( 'cal1', false );
  //this.cal1.setSensitive( this.date_min, this.date_max );
  this.cal1.setDate( date1 );
  this.cal1.setOnClickHandler( cal_date1 );
  
  this.cal2 = new dhtmlxCalendarObject( 'cal2', false );
  //this.cal2.setSensitive( this.date_min, this.date_max );
  this.cal2.setDate( date2 );
  this.cal2.setOnClickHandler( cal_date2 );
  
  this.get_date = function() {
    return this.date1 + ( this.date2 && this.date2 != this.date1  ?  "," + this.date2  :  "" );
  }
}

// onclick didn't work right when these functions were part of the calendar class
function cal_date1( date ) {
  cal.cal1.setDate( date );
  cal.date1 = cal.cal1.getFormatedDate( "%Y-%m-%d", date );
}
function cal_date2( date ) {
  cal.cal2.setDate( date );
  cal.date2 = cal.cal2.getFormatedDate( "%Y-%m-%d", date );
}





var clicky = { log: function(){ return; } };

