// Copyright © 2006-08 Spiceworks Inc. All Rights Reserved. http://www.spiceworks.com

var Browser = { firefox:false, safari:false, ie6:false, ie7:false, 
  ff3:navigator.userAgent.toLowerCase().indexOf('firefox/3') > -1, // ff3 is annoying enough to warrant browser sniffing
  hideIncompatible:function(){
    var container = $('incompatible');
    if ( container && Cookie.checkSupport() ) container.hide();
  }
};

var ApplicationLoader = {
  initialize: function(){
    AjaxCallbacks.register( [ ButtonManager, PivotManager, SpiceSelectManager, SortableTableManager, CollapsableSectionManager, ClickableTableManager, TipManager ], 'onComplete' );
  }
};
Event.register(ApplicationLoader);

var Application = {
  runMode:'production',
  cobranded:false,
  customInstall:false,
  uuid:null,
  authenticity_token:null,
  initialize: function(){
    this.indicator = $('application_activity_indicator');
    if (console.needsSetup) console.initialize();
  },
  narrowLayout: function(){
    $(document.body).addClassName('narrow');
    Application.narrow = true;
  },
  beginPolling: function( sourceController, sourceAction, options ){
    this._configureOptions( options );
    // keep track of where the user is within the application, so the receiving ajax can determine what extra actions it needs to perform, if any
    this.sourceController = sourceController;
    this.sourceAction = sourceAction;
    this._boundEndPolling = this.endPolling.bind(this);

    this._startPolling();
  },
  endPolling: function(){ this._stopPolling(); },
  renewPolling: function( options ){
    this._configureOptions( options );
    this._stopPolling();
    this._startPolling({delayedStart:false});
  },
  updatePageUri: function(hard_link){
    var my_href = location.href.toString(), matches = null;
    if (matches = my_href.match(/#(.+)/)){
      my_href = my_href.replace(matches[0], hard_link);
      location.href = my_href;
    } else {
      location.href += hard_link;
    }
  },
  updateScanStatus: function(scanRunning){
    if (this.options.scanRunning && !scanRunning) this._scanStopped();
    else if (!this.options.scanRunning && scanRunning) this._scanStarted();
  },
  registerLogoRerenderFix: function(){
    if ( $$('h1').first().down('img') && $('content_wrapper') ){
      Event.observe( $$('h1').first().down('img'), 'load', function(){
        $('content_wrapper').forceRerendering();
      });
    };
  },
  inDevelopment: function() { return this.runMode == 'development'; },
  inProduction: function() { return this.runMode == 'production'; },
  
  _scanStarted: function(){
    this.options.scanRunning = true;
    // renew the poller, since when the scan is running the polling options are different
    this._startPolling();
  },
  _scanStopped: function(){
    this.options.scanRunning = false;
    // renew the poller, since when the scan is NOT running the polling options are different
    this._startPolling();
  },
  _startPolling: function(startOptions){
    startOptions = Object.extend({delayedStart:true, decay:1.5}, startOptions || {});
    
    if (this.sourceController == 'network' || this.options.scanRunning){
      startOptions.decay = false;
      this.options.pollingFrequency = 5;
    } else {
      startOptions.decay = true;
      this.options.pollingFrequency = 10;
    }

    this._stopPolling(); // make sure the poller is not already running
    this.poller = new Ajax.PeriodicalUpdater('', '/finder/application_polling',  { evalScripts:true, decay: startOptions.decay, maxDecay:this.options.maxDecay, frequency: this.options.pollingFrequency, maxFrequency: 60, delayedStart: (startOptions.delayedStart ? this.options.pollingFrequency : false), parameters: { source_controller: this.sourceController, source_action: this.sourceAction } } );
  },
  _stopPolling: function(){
    if ( this.poller && this.poller.stop ) this.poller.stop();
    this.poller = null;
  },
  _configureOptions: function( options ){
    this.options = Object.extend({
      pollingFrequency:10,
      maxDecay:3,
      scanRunning:false
    }, options || {} );
  }
};
Event.register(Application);
// Set up events architecture
Object.extend(Application, ObserverMixin);

// for debugging firebug style, in browser's the don't have firebug
if (!console){
  var console = {
    needsSetup:true,
    initialize: function(){
      if (Application.runMode != 'development') return;
      $(document.body).insert('<textarea id="fake-firebug-console" readonly="readonly" style="display:none;width:100%;height:15em;position:fixed;bottom:0;left:0"></textarea>');
      console.logger = $('fake-firebug-console');
    },
    log: function(string){
      // only output the message if we're in dev mode
      if (Application.runMode != 'development' || !console.logger) return;
      console.logger.insert(string + (Prototype.Browser.IE ? "<br />" : "\n"));
      if (!console.logger.visible()) console.logger.show();
    }
  };
}

var Cookie = {
  get: function( name ){
    var nameEQ = escape(name) + "=", ca = document.cookie.split(';');
    for (var i = 0, c; i < ca.length; i++) {
      c = ca[i];
      while (c.charAt(0) == ' ') c = c.substring(1, c.length);
      if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
  },
  set: function( name, value, options ){
    options = (options || {});
    if ( options.expiresInOneYear ){
      var today = new Date();
      today.setFullYear(today.getFullYear()+1, today.getMonth, today.getDay());
      options.expires = today;
    }
    var curCookie = escape(name) + "=" + escape(value) + 
      ((options.expires) ? "; expires=" + options.expires.toGMTString() : "") + 
      ((options.path)    ? "; path="    + options.path : "") + 
      ((options.domain)  ? "; domain="  + options.domain : "") + 
      ((options.secure)  ? "; secure" : "");
    document.cookie = curCookie;
  },
  hasCookie: function( name ){
    return document.cookie.indexOf( escape(name) ) > -1;
  },
  checkSupport: function(){
    return this.hasCookie('compatibility_test');
  }
};

var finder = {
  localhostPoller:null,
  canceling:false, // used to keep track of transient state changes in the finder
  localhostScanPoller: function( turnOff ){
    if ( turnOff && this.localhostPoller ) this.localhostPoller.stop();
    else if ( !turnOff ) this.localhostPoller = new Ajax.PeriodicalUpdater('', '/finder/status_of_shallow_scan', { asynchronous:true, evalScripts:true, frequency:5 });
  }
};

var cluster_view = {
  counter_mouseover:function(icon){ if (Browser.ie6) $(icon).addClassName('hover'); },
  counter_mouseout:function(icon){ if (Browser.ie6) $(icon).removeClassName('hover'); },
  counter_click:function(icon, category){
    var href = $(icon).up('a').href;
    var quickfind = icon.getAttribute('quickfind');
    href += [ 'software', 'installables', 'installable' ].include( category ) ? '?q=@' + quickfind + '@' : '/@' + quickfind + '@';
    location.href = href;
  }
};

var Messaging = {
  PREFIX: 'application_messaging_',
  initialize: function(){
    if ( !this.initialized ){
      this.initialized = true;
      this.expiredMessages = $A();
      this.container = $( 'application_messaging' );
      this.visible = this.container.visible();
      if ( !this.container ) return;

      this.list = new Element( 'ol' );
      this.container.appendChild(this.list);
      this.list = this.container.down('ol');
    }
  },
  push: function( messageID, messageBody, options ){
    if ( !this.initialized ) this.initialize();
    
    var globalID = this.PREFIX + messageID; // create our element ID, should be unique
    if ( $(globalID) ) return; // if the element is already on the page, don't do anything

    options = Object.extend({
      dismissable:false, // give the message a clickable element to remove it
      informative:false, // tooltip and icon for more information on message
      ajaxOnDismiss:true, // fire an AJAX call when the dismiss button is clicked
      selfRemoving:false, // make the message remove itself after timeSeconds have lapsed
      timeoutSeconds:5 // when selfRemoving is true, this is time duration the message is displayed
    }, options || {});
    
    var message = new Element( 'li', { id: globalID, message_id: messageID }).update( messageBody );
    
    if ( options.informative ) this._makeInformative(message, messageID, options.tipOptions);
    if ( options.dismissable ) this._makeDismissable(message, messageID, options.ajaxOnDismiss);

    this._hideAll();
    this.list.appendChild( message );
    this.container.appear({duration:0.50});
    this.visible = true;

    if ( options.selfRemoving ) this.pop.bind(this, messageID).delay(options.timeoutSeconds);
    
    return message;
  },
  pop: function( messageID ){
    var message;
    if ( messageID ) message = this._removeByID( messageID );
    else message = this._removeLast();
    
    this._hideAll();
    var last = this.messages().last();
    if (last) last.show();
    else this._noMessages();
    
    return message;
  },
  
  dismissMessage: function(event, messageID, ajaxOnDismiss){
    event.stop();
    this.pop(messageID);
    if (ajaxOnDismiss) new Ajax.Request('/view/hide_trait/', { parameters: { id: messageID } } );
  },

  messages: function(){ return this.list.select( 'li' ); },
  
  _noMessages: function(){
    this.container.hide();
    this.visible = false;
  },
  _makeDismissable: function( message, messageID, ajaxOnDismiss ){
    message.addClassName( 'dismissable' );
    var dismisser = new Element('a', { 'class': 'dismisser', title: 'Dismiss this message' } ).update('<img src="/images/icons/square_close_white.png" alt="dismiss" width="10" height="10" />');
    dismisser.observe('click', this.dismissMessage.bindAsEventListener(this, messageID, ajaxOnDismiss));
    message.appendChild(document.createTextNode(' '));
    message.appendChild(dismisser);
  },
  _makeInformative: function( message, messageID, tipOptions ){
    var info = new Element('a', { 'class': 'info' } ).update('<img src="/images/icons/indicators/info.png" alt="" width="10" height="10" />');
    message.appendChild(document.createTextNode(' '));
    message.appendChild(info);
    
    TipManager.addTip(info, tipOptions);
  },
  _removeByID: function( messageID ){
    return this._remove( this.list.down( '#' + this.PREFIX + messageID ) );
  },
  _removeLast: function(){
    return this._remove( this.messages().last() );
  },
  _remove: function( message ){
    if ( message ) message.parentNode.removeChild( message );
    return message;
  },

  _hideAll: function(){ this.messages().invoke( 'hide' ); }
};


/*
 * Helper methods for dealing with Dismissable boxes.
 */
 var DismissableInfoBox = {

   dismiss:function(id){
     if($(id)){
       new Effect.BlindUp(id);
       text = $(id).down('h4').text();
       Messaging.push( id + '_info_box_removed',"Removed '"+ text.truncate(30) + 
       "' (<a href='#' onclick='DismissableInfoBox.undo(\"" + id + "\");return false;'>undo</a>)"  );
     }
     new Ajax.Request( '/view/hide_trait/' + id );
   },

   undo:function(id){
     if($(id)){
       new Effect.BlindDown(id);
       Messaging.pop( id + '_info_box_removed' );
       new Ajax.Request( '/view/show_trait/' + id );
     }
   }
 };

var Community = {
  go: function( uri , new_window){
    var cform = $('community');
    if ( uri ) cform.community_redirect.value = uri;
    else cform.community_redirect.value = '';
 
    if (new_window == true) cform.target = '_blank';
    else cform.target = '';

    // for tracking, one url for the store, and the other for everything else
    var hit = ( ( uri && uri == '/store' ) ? '/store/hit' : '/community/hit' ) + '?redirect=' + uri;
    new Ajax.Request( hit, { method: 'get', onComplete: function(){ $('community').submit(); } } );

    return false;
  },
  uri: function( new_uri ){ 
    var community_form = $('community'); 
    if ( new_uri && community_form ){ 
      // set the uri 
      community_form.setAttribute('action', new_uri); 
    } 
    return ( community_form ? community_form.action : '' );
  }
};

var Navigation = Class.create({
  initialize: function( list ){
    this.list = $(list);
    if (this.list.getAttribute('related_list')) this.relatedList = $(this.list.getAttribute('related_list'));
    this.heading = this.list.down('dt');
    this.key = this.heading.innerHTML.gsub(' ', '-'); // we use this as a unique ID to correspond to this list
    this.items = this.list.select('dd');
    
    this.listeners = {
      headingClick: this.headingClick.bindAsEventListener(this)
    };
    
    if ( this.list.hasClassName('toggleable') ){
      this.heading.observe( 'click', this.listeners.headingClick );
      this._prepareState();
    }
  },
  headingClick: function(event){ this.toggle(); },
  toggle: function(){
    var isClosed = this.list.hasClassName('closed');
    this._setListMode( isClosed ); // we are flipping the state here
    this._saveState( isClosed ? 'open' : 'closed' );
  },
  visible: function(){ return !this.list.hasClassName('closed'); },
  _setListMode: function(isClosed){
    if (isClosed){
      this.list.removeClassName('closed');
      this.heading.setAttribute( 'title', 'Close this section' );
      if (this.relatedList) this.relatedList.removeClassName('closed');
    } else {
      this.list.addClassName('closed');
      this.heading.setAttribute( 'title', 'Open this section' );
      if (this.relatedList) this.relatedList.addClassName('closed');
    }
  },
  _prepareState: function(){
    var state = Cookie.get(this.key);
    if (this.list.down('dd.current')){
      // re-open closed section if it is the current one
      state = 'open';
      this._saveState('open');
    } 
    if (state) this._setListMode( state == 'open' );
  },
  _saveState: function(state){
    Cookie.set(this.key, state, { expiresInOneYear: true } );
  }
});

var NavigationManager = {
  initialize: function(sponsorshipURL){
    this.sponsorshipURL = sponsorshipURL;
    this.sponsorshipBox = $('sponsored_block');

    Event.observe(document, 'dom:loaded', this._initializeSponsorshipBox.bindAsEventListener(this));

    this.lists = $H();
    $$( '#navigation dl' ).each( function( list, index ){
      this.lists.set( list.id || 'navigation_menu_' + index, new Navigation( list ) );
    }.bind( this ));

    this._boundCustomLinkMouseOver = this.customLinkMouseOver.bindAsEventListener(this);
    this._boundCustomLinkMouseOut = this.customLinkMouseOut.bindAsEventListener(this);
    if (Browser.ie6){
      $$('#navigation dl.my_stuff dd.custom').each(function(item){
        this._attachCustomLinkObservers(item); // we have to spoof the hover pseudo-class for IE6
      }.bind(this));
    }
  },
  shouldAllowAddNewClick: function(){ return !this.showingNewForm; },
  toggleDelete: function(){
    if (this.editMode) this.toggleEdit();
    if (!this.deleteMode){
      this.lists.get('navigation_my_stuff').list.addClassName('deleteable');
      Messaging.push('delete_link', "Click on the X icon next to the link to delete");
      this.deleteMode = true;
    } else {
      this.lists.get('navigation_my_stuff').list.removeClassName('deleteable');
      Messaging.pop('delete_link');
      this.deleteMode = false;
    }
  },
  toggleEdit: function(){
    if (this.deleteMode) this.toggleDelete();
    if (!this.editMode){
      this.lists.get('navigation_my_stuff').list.addClassName('editable').select('dd.editable').invoke('removeClassName', 'editing');
      Messaging.push('edit_link', "Click on the edit icon next to the link to edit");
      this.editMode = true;
    } else {
      this.lists.get('navigation_my_stuff').list.select('form').invoke('remove');
      this.lists.get('navigation_my_stuff').list.removeClassName('editable');
      Messaging.pop('edit_link');
      this.editMode = false;
    }
  },
  editingItem: function(item){
    var dd = $('custom_nav_' + item + '_wrapper');
    dd.addClassName('editing');
    dd.down('a.portal_link').blindUp({ duration:0.5 });
  },
  cancelEdit: function(formToRemove){
    if (typeof formToRemove == 'string') formToRemove = $(formToRemove);
    formToRemove.blindUp({duration:0.5});
    this.toggleEdit.bind(this).delay(0.5);
    this.lists.get('navigation_my_stuff').list.select('dd.editing a.portal_link').invoke('blindDown', { duration:0.5 });
    this.lists.get('navigation_my_stuff').list.select('dd.editing').invoke('removeClassName', 'editing');
  },
  newNavigationItemCreated: function(item){
    var dd;
    item = $(item);
    if (item) dd = item.up('dd');
    if (dd && Browser.ie6) this._attachCustomLinkObservers(dd); // we have to spoof the hover pseudo-class for IE6
  },
  newNavigationItemDestroyed: function(item){
    if (this.deleteMode) this.toggleDelete();
    var dd;
    item = $(item);
    if (item) dd = item.up('dd');
    if (dd && Browser.ie6) this._detachCustomLinkObservers(dd); // remove our mouseover/mouseout listeners for IE
  },
  customLinkMouseOver: function(event){
    var item = event.element();
    if (item.tagName.toString().toLowerCase() != 'dd') item = item.up('dd');
    item.addClassName('hover');
  },
  customLinkMouseOut: function(event){
    var item = event.element();
    if (item.tagName.toString().toLowerCase() != 'dd') item = item.up('dd');
    item.removeClassName('hover');
  },
  newNavigationFormShown:function(){
    if (this.editMode) this.toggleEdit();
    if (this.deleteMode) this.toggleDelete();
    this.showingNewForm = true;
    var list = this.lists.get('navigation_my_stuff');
    if (list && !list.visible()) list.toggle();
  },
  newNavigationFormHidden:function(){ this.showingNewForm = false; },
  
  sponsorshipRender: function(content){
    if (content != ''){
      this.sponsorshipBox.update(content);

      this.sponsorshipBox.select('a').each(function(anchor){
        if (!anchor.target) anchor.setAttribute('target', '_blank');
      });

      this.sponsorshipBox.up('#navigation_closeout').addClassName('sponsored');
      this.sponsorshipBox.show();
    }
  },
  _attachCustomLinkObservers: function(link){
    link.observe('mouseover', this._boundCustomLinkMouseOver);
    link.observe('mouseout', this._boundCustomLinkMouseOut);
  },
  _detachCustomLinkObservers: function(link){
    link.stopObserving('mouseover', this._boundCustomLinkMouseOver);
    link.stopObserving('mouseout', this._boundCustomLinkMouseOut);
  },
  _initializeSponsorshipBox: function(){
    var url = this.sponsorshipURL;
    url += ( url.include('?') ? '&' : '?');
    url += 'cobranded=' + Application.cobranded + '&custom_install=' + Application.customInstall + '&uuid=' + escape(Application.uuid);
    DynamicScriptInclude.load( url );
  }
};

var Store = { go: function() { Community.go( '/store' ); return false; } };

var TipManager = {
  initialize: function() {
    this._addNew();
  },
  _addNew: function() {
    $$('a[tip]').each(function(element) {
        var options = element.readAttribute('tip').evalJSON();
        this.addTip(element,options);
    }.bind(this));
  },
  ajaxOnComplete: function(){
    this._addNew();
  },
  addTip: function(element, options) {  
    element = $(element);

    if (element.prototip) {
      if (options.forceRedraw) {
        element.prototip.remove();
      }
      else {
        return; //tip has already been added, don't redraw it
      }
    } 

    var content = options.content;
    delete options.content;
    if (element.up('div.pivotable')) {
      // add prototips to pivot menus
      
      // add a span around the a element to show the talk bubble icon
      if (!element.up('span.hint')) {
        element.wrap('span', { 'class': 'hint' });
      }
      new Tip($(element), content, Object.extend({style:'pivot_menutip'}, options || {}));
    }
    else {
      new Tip($(element), content, Object.extend({style:'default'}, options || {}));
    }
  },
  hideAll: function() { 
    Tips.visible.each(Element.hide);
  },
  hideChildrenOf: function(selector) {
    Tips.tips.select(function(tip) { return tip.element.up(selector); }).invoke('hide');    
  },
  removeTip: function(element) {
    element = $(element);
    if (element.up('div.pivotable') && (element.up('span.hint'))) {
        // remove the a span around the a element to remove the talk bubble icon
        var content = element.innerHTML;
        element.up('li').update(element);
        element.update(content); //ie7 chops the text out, so we have to put it back in
    }
     if (element.prototip) element.prototip.remove();
  }
};
Event.register(TipManager);

var Toolbar = {
  initialize: function(options){
    this.options = Object.extend(this._defaultOptions, options || {});

    if (this.options.inventoryMode){
      this._attachObserverCollection(this._inventoryObservers);
      $w('icon_switcher_browse bulk_operations_outer').each(function(item){
        this.elements.set(item, $(item));
      }.bind(this));
    }
    
    if (this.options.browseMode) this.options.sourcedFrom = 'browse';
    this._attachObserverCollection(this._baseObservers);
  },

  update: function(options){
    // ignore the sourcedFrom parameter if we're already in browse mode, since we don't want to lose that state
    if (options && this.options.browseMode) delete options.sourcedFrom;
    this.options = Object.extend(this.options, options || {});
    
    this.options.activeMode = null;
    // if (this.options.browseMode) 
    this.toggleButtonVisibility();

    if ($('icon_switcher_browse') && !this.options.browseMode){
      var new_url = '/inventory?';
      if(this.options.node) new_url += 'model=' + this.options.nodeType + '&id=' + this.options.node;
      if(this.options.baseCategory ) new_url += '&category=' + encodeURI(this.options.baseCategory);
      $('icon_switcher_browse').href = new_url;
    }
  },
  createTicket: function(event){
    event.stop();
    var postBody = 'from=' + this.options.sourcedFrom;
    var active_tab = icon_view.get_active_tab();
    if (this.options.sourcedFrom && this.options.sourcedFrom.indexOf('ticket') < 0 && this.options.node) postBody += '&ticket[ticketable_id]='+this.options.node;
    if (this.options.sourcedFrom && this.options.sourcedFrom.indexOf('ticket') < 0 && this.options.node && this.options.nodeType) postBody += '&ticket[ticketable_type]='+this.options.nodeType;
    if (this.options.baseCategory) postBody += '&category='+ this.options.baseCategory;
    if (active_tab) postBody += '&active_tab=' + active_tab;
    new Ajax.Request('/tickets/new', { asynchronous:true, evalScripts:true, parameters:postBody });
  },
  createAsset: function(event){
    event.stop();
    if (this.options.canAddNewAsset){
      var request_path = '/asset/new?';
      if (this.options.sourcedFrom) request_path += 'from='+this.options.sourcedFrom;
      if (this.options.baseCategory) request_path += '&category_from=' + encodeURI(this.options.baseCategory);
      new Ajax.Request(request_path, { asynchronous:true, evalScripts:true });
    }
  },
  reclassifyActionMain: function(event){ this.reclassifyAction('reclassify', event); },
  reclassifyActionDelete: function(event){ this.reclassifyAction('delete', event); },
  reclassifyActionEdit: function(event) { this.reclassifyAction('edit', event); },
  groupMove: function(event){
    event.stop();
    if (this.options.canMove){
      this.options.mode = 'move';
      this.renderActions(this.options.node);
    }
    else if (this.moveExcuse() == "createGroup") {
      //trying to move from a manual group to another (which doesn't exist)
      var postBody;
      postBody += '&mode=move&category='+encodeURI(this.options.baseCategory)+'&from='+this.options.sourcedFrom;
      new Ajax.Request('/asset/prompt_before_action', { asynchronous:true, evalScripts:true, parameters:postBody });
    }
  },
  groupCopy: function(event){
    event.stop();
    if (this.options.canCopy){
      this.options.mode = 'copy';
      this.renderActions(this.options.node);
    }
    else if ((this.copyExcuse() == "createAnotherGroup") || (this.copyExcuse() == "createGroup")){
      //we're in a manual group, trying to copy to another manual group (which doesn't exist)
      // OR 
      //we're in a smart group, trying to copy to a manual group (which doesn't exist)
      
      var postBody;
      postBody += '&mode=copy&category='+encodeURI(this.options.baseCategory)+'&from='+this.options.sourcedFrom+"&staticCount="+this.options.staticCount;
      new Ajax.Request('/asset/prompt_before_action', { asynchronous:true, evalScripts:true, parameters:postBody });
    }
  },
  reclassifyAction: function(mode, event){
    event.stop();
    this.options.mode = mode;
    if (this.options.canPerformActions) this.renderActions(this.options.node);
  },
  renderActions: function(selected_id){
    var postBody;
    if (selected_id) postBody = 'selected_id='+selected_id;
    else postBody = 'no_devices=true';

    postBody += '&mode='+this.options.mode+'&category='+encodeURI(this.options.baseCategory)+'&from='+this.options.sourcedFrom;
    new Ajax.Request('/asset/actions', { asynchronous:true, evalScripts:true, parameters:postBody });
  },
  renderComparison: function(left_id, right_id, category){
    var postBody;

    if (left_id) postBody = 'first_device='+left_id+'&category='+category; // this is always the case if we're in "icon mode"
    else postBody = 'no_devices=true&category='+category; // this can occur if we're in "browse mode" and a device hasn't been selected yet

    if (right_id) postBody += '&second_device='+right_id;
    if (this.mode == 'compare') postBody += '&already_comparing=true';

    new Ajax.Request('/asset/compare', { evalScripts:true, parameters:postBody});
    this.mode = 'compare';
  },
  remoteControl: function(event){
    if (event) event.stop();
      var path = '/asset/remote_control?id='+this.options.node+'&category='+encodeURI(this.options.baseCategory)+'&sourced_from='+this.options.sourcedFrom;
      if (this.seenRemoteControlInfo) window.location.href = path;
      else {
        this.seenRemoteControlInfo = true;
        new Ajax.Request(path, { evalScripts: true });
      }
  },
  toggleButtonVisibility: function(){
    var asset = this.elements.get('new_asset');
    if (asset) asset[this.options.canAddNewAsset ? 'removeClassName' : 'addClassName']('disabled');

    this.flipActions();
  },
  moveExcuse: function() {
    if (!this.options.inSmartGroup && this.options.baseCategory != 'installables' && this.options.deviceCount > 0 && this.options.staticCount == 1) {
      return "createGroup";
    }    
    else {
      return false;
    }
  },
  copyExcuse: function() {
    if (this.options.deviceCount > 0 && this.options.baseCategory != 'installables' && this.options.staticCount <= 1) {
      return "createAnotherGroup";
    }
    else if (this.options.inSmartGroup && this.options.baseCategory != 'installables' && this.options.deviceCount > 0 && this.options.staticCount == 0) {
      return "createGroup";
    }
    else {
      return false;
    }
  },
  copyTip: function() {
    //No copy tip.  Leaving this in because this could change.
    
    if (this.options.canCopy) return;
    return false;    
  },
  moveTip: function() {
    if (this.options.canMove) return;
    
    if (this.options.inSmartGroup) {
      return { title: "Can't Move From A Smart Group",
        content: "Move is disabled because <b>" + this.options.nodeCategory.capitalize() + "</b> is a smart group. You can only <i>copy</i> devices from a smart group. "  };
    }
    else {
      return false;
    }
  },
  addTip: function(element, options) {
    TipManager.addTip(element, Object.extend({ forceRedraw: true, style: 'pivot_menutip' }, options || {}));     
  },  
  flipActions: function(){
    $w('reclassify_action reclassify_action_delete').each(function(item){
      var inner_item = this.elements.get(item);
      if(inner_item) inner_item[this.options.canPerformActions ? 'removeClassName' : 'addClassName']('disabled');
    }.bind(this));
    
    var move = this.elements.get('group_move');
    if (move) { 
      var willMove = (this.options.canMove || this.moveExcuse());
      move[willMove ? 'removeClassName' : 'addClassName']('disabled');
      
      if (!this.options.canMove && this.moveTip()) {
        this.addTip(move, this.moveTip());
      }
      else {
         TipManager.removeTip(move);
      }
    }
    
    var copy = this.elements.get('group_copy');
    if (copy) { 
      var willCopy = (this.options.canCopy || this.copyExcuse());
      copy[willCopy? 'removeClassName' : 'addClassName']('disabled');
      if (!this.options.canCopy && this.copyTip()) {
        this.addTip(copy, this.copyTip());
      }
      else {
        TipManager.removeTip(copy);
      }
    }
  },
  
  generatePopup: function(id, title, content){ new Popup(id, title, '<p>' + content + '</p>', {closeable:true}); },
  _attachObserverCollection: function(collection){
    if (!this.elements) this.elements = $H(); // this collection will hold a reference to the dom element, so we don't have to continually look it up
    $H(collection).each(function(pair){
      // store the element reference, keep in mind that the element could be nil
      this.elements.set(pair.key, $(pair.key));
      // if we have the element, then we need to attach the observer
      if (this.elements.get(pair.key)) this.elements.get(pair.key).observe('click', this[pair.value].bindAsEventListener(this));
    }.bind(this));
  },
  
  _defaultOptions:{
    node:null,
    nodeCategory:null,
    nodeType:null,
    nodeBaseType:null,
    canAddNewAsset:false,
    sourcedFrom:'unknown',
    canPerformActions:false,
    deviceCount: null,
    staticCount: null,
    smartCount: null,
    activeGroupType: null,
    activeMode:null, // null is steady-state, can also be reclassify, compare, etc. which are transient states
    inventoryMode:true,
    iconMode:false,
    browseMode:false,
    helpDeskMode:false,
    ticketID:null
  },
  
  // these observers use the element ID as the key and the name of the method to invoke as the value
  _inventoryObservers:{
    'bulk_edit_action':           'reclassifyActionEdit',
    'reclassify_action':          'reclassifyActionMain',
    'group_move':                 'groupMove',
    'group_copy':                 'groupCopy',
    'reclassify_action_delete':   'reclassifyActionDelete'
  },
  _baseObservers:{
    'asset_form_button':          'createAsset',
    'helper_ticket_form_button':  'createTicket'
  }
};

var CheckboxToggler = {
  toggle: function( selector, makeChecked ){
    // sets the state of all checkboxes that match "selector" to the boolean value of "makeChecked"
    var checkboxes = $$( selector );
    if ( checkboxes ){
      $$( selector ).each(function( checkbox ){
        checkbox.checked = makeChecked;
      });
    }
  }
};

var ReclassifyIndividual = {
  toggle: function(){
    var answer = $('reclassify_answer');
    var answer_custom = $('reclassify_answer_custom');
    if (answer.visible()){
      answer.hide();
      answer_custom.hide();
      $$('#reclassify_device input.removers').invoke('show');
    } else {
      answer.show();
      answer_custom.hide();
      $$('#reclassify_device input.removers').invoke('hide');
    }
  },
  addCustom: function(device_id){
    // get the value from 'custom_reclassify'
    // send an ajax call to 'reclassify'
    Form.Element.disable('save_custom_type');
    $('reclass_message').update('Reclassifying this device...');
    var postBody = 'id=' + device_id + '&type=' + escape($('custom_type').value);
    postBody += '&original_category=' + encodeURI(Toolbar.options.baseCategory) + '&from=' + Toolbar.options.sourcedFrom;
    new Ajax.Request('/asset/reclassify', {asynchronous:true, evalScripts:true, parameters:postBody});
  },
  deleteDevice: function(base_url){
    var postBody = 'original_category='+encodeURI(Toolbar.options.baseCategory)+'&from='+Toolbar.options.sourcedFrom;
    new Ajax.Request(base_url, {asynchronous:true, evalScripts: true, parameters:postBody });
  }
};

var AjaxSlideShow = Class.create();
AjaxSlideShow.prototype = {
  activeSlide:null,
  activeChart:null,
  initialize: function( slides, showID, options ){
    this.options = Object.extend({
      slideDuration:10,
      ajaxParameters: {
        slideshow:true
      }
    }, options || {});

    this.slides = $A( slides );
    this.showID = showID;
    this.activeSlide = 0;
    this.running = false;
  },
  refresh: function(){
    this.playbackControl = $( this.showID + '_playback' );
    this._setPlaybackControlState();
  },
  destroy: function(){
  },
  togglePlayback: function(){
    if ( this.running ) this.pause();
    else this.play();
  },
  play: function(){
    this.running = true;
    this.onHold = false;

    if ( !this.playbackControl )  this.playbackControl = $( this.showID + '_playback' );
    this._setPlaybackControlState();

    this.slideInterval = window.setInterval( function(){
      // if we have a trigger element and that element is missing from the page, then stop the slideshow
      if ( !$(this.showID) ) this.pause();
      else this.next();
    }.bind( this ), this.options.slideDuration * 1000 );
  },
  pause: function(){
    this.running = false;
    this.onHold = true;
    this._clearInterval();
    this._setPlaybackControlState();
  },
  next: function( pause ){
    if ( typeof pause == undefined ) pause = false;
    if ( pause ) this.pause();
    this._loadSlide( ++this.activeSlide );
  },
  previous: function( pause ){
    if ( typeof pause == undefined ) pause = false;
    if ( pause ) this.pause();
    this._loadSlide( --this.activeSlide );
  },
  
  _loadSlide: function( slide ){
    if ( slide >= this.slides.size() ){
      // the slideshow has completed one full loop, let's pause and put it back at the first slide
      slide = 0;
      this.pause();
    } else if ( slide < 0 ) slide = this.slides.size() - 1;
    
    this.activeSlide = slide;
    
    new Ajax.Request( this._sanitizeSlideUrl( this.slides[ this.activeSlide] ), { evalScripts: true, parameters: this.options.ajaxParameters } );
  },
  _sanitizeSlideUrl: function( url ){
    return url.replace( /&amp;/, '&' );
  },
  _setPlaybackControlState: function(){
    this.playbackControl.removeClassName( 'playing' ).removeClassName( 'paused' ).addClassName( this.running ? 'playing' : 'paused' ).update( this.running ? '<span>Pause</span>' : '<span>Play</span>' );
    this.playbackControl.setAttribute( 'title', ( this.running ? 'Slideshow currently playing, click to pause' : 'Slideshow currently paused, click to play' ) );
  },
  _clearInterval: function(){
    if ( this.slideInterval ) window.clearInterval( this.slideInterval );
  }
};

var AjaxSlideShowManager = {
  shows:$H(),
  initialize: function(){},
  create: function( showID, slides, options ){
    if ( ! this.shows.get(showID) ) this.shows.set(showID, new AjaxSlideShow( slides, showID, options ) );
  },
  destroyChartIfPresent: function( showID ){
    if ( this.shows.get(showID) && this.shows.get(showID).activeChart && this.shows.get(showID).activeChart.destroy ) this.shows.get(showID).activeChart.destroy();
  },
  renew: function( showID, slides, options ){    if ( this.shows.get(showID) ) this.shows.get(showID).destroy();
    this.shows.set(showID, new AjaxSlideShow( slides, options ));
  },

  togglePlayback: function( showID ){
    this._callShow( 'togglePlayback', showID );
  },
  beginPlayback: function( showID, autoPlay ){
    var show = this.shows.get(showID);
    show.refresh();
    if ( !autoPlay ) show.onHold = true;
    if ( show && !show.running && !show.onHold) show.play();
  },
  pause: function( showID ){ this._callShow( 'pause', showID, true ); },
  next: function( showID ){ this._callShow( 'next', showID, true ); },
  previous: function( showID ){ this._callShow( 'previous', showID, true ); },

  _callShow: function( method, showID, arg ){
    if ( this.shows.get(showID) ) this.shows.get(showID)[method]( arg );
  }
};

var icon_view = {
  reclassify_mode:false,
  compare_mode:false,
  mode:null,
  categoryName:null,
  fetch_compare_uri:'',

  init:function( categoryName, type ){
    icon_view.mode = type ? type : 'hardware';
    icon_view.categoryName = categoryName;
    var device_detail = $('device_detail');
    Event.observe(device_detail, 'click', icon_view.device_detail_clicked);
    if (Browser.ie6){
      Event.observe(device_detail, 'mouseover', icon_view.device_detail_mouseover);
      Event.observe(device_detail, 'mouseout', icon_view.device_detail_mouseout);
    }
    $$('span.count_wrap span.count').each(function(count){
      Event.observe(count, 'click', icon_view.show_by_count);
    });
  },
  show_by_count:function(e){
    e.stop();
    var count = e.findElement( 'span');

    // there is a span that is a child of the span we're looking for, so if we got the child one, let's get the parent
    if (!count.hasClassName('count')) count = count.parentNode;

    var quickfind;
    if (quickfind = count.getAttribute('quickfind')){
      live_search.filter('viewer', 'quickfind', '@' + quickfind + '@', icon_view.categoryName);
    }
  },
  register_icon:function(link, showInfoBox){
    link = $(link);
    link.observe('click', icon_view.icon_clicked);
    if (showInfoBox) link.observe( 'mouseover', icon_view.mouseOverLink );
  },
  detatch_icon:function(link){
    link = $(link);
    link.stopObserving('click', icon_view.icon_clicked);
    link.stopObserving('mouseover', icon_view.mouseOverLink);
    AssetPopupManager.release(link.up('li').getAttribute('item_id'));
  },
  mouseOverLink: function(event){
    var element = event.findElement('li');
    AssetPopupManager.loadForAsset(element.getAttribute('item_id'));
  },
  icon_clicked:function(e){
    var clicked_link = e.findElement( 'a');
    var clicked_li = clicked_link.up( 'li.icon' );

    e.stop();
    
    if (!clicked_li.hasClassName('unclickable')) icon_view.draw_summary(clicked_li.getAttribute('click_url'), clicked_li);
  },
  draw_summary:function(url, clicked_li){
    // do the normal icon click thing...
    icon_view.summary_loading(clicked_li);
    new Ajax.Request(url, {asynchronous:true, evalScripts:true, onFailure:function(request){icon_view.summary_failed();icon_view.summary_loaded();}});
  },
  show_device_summary:function(){
    var device_summary = $('device_summary');
    if (device_summary && !device_summary.visible()) new Effect.BlindDown('device_summary', {duration:0.5});
  },
  my_index:function(li){
    // returns the value from the 'my_index' attribute of a list item, parsed as an integer so we can do conditional stuff
    var li_index = 0;
    if (li && li.getAttribute('my_index')) li_index = parseInt(li.getAttribute('my_index'), 10);
    return li_index;
  },
  device_detail_clicked:function(e){
    live_search.filter('viewer', 'quickfind', '', icon_view.categoryName);
  },

  summary_loading:function(selected){
  
    // get the item, since sometimes this is called with an object and other times with an object ID
    selected = $(selected);

    if (icon_view.mode != 'software'){
      $$('#viewer ul li').invoke('removeClassName', 'selected');
      selected.addClassName('selected');
    }

  },
  summary_loaded:function(){
  },
  summary_failed:function(){
    var summary = $('summary_wrap');
    summary.update('<div id="device_summary" class="no_items"><h3 class="heading"></h3><div id="tab_box" class="no_tabs"><div id="active_overview"><h4 class="error">Unable to load summary, please try again.</h4></div></div></div>');
  },
  check_for_linking:function(base_uri){
    base_uri = base_uri.replace("&amp;", "&");
    var matches = null, fetch_item = false, item_id = 0, item_model = '', tab;
    if (matches = location.href.match(/#model-(\w+)\&id-(\d+)(\&tab-(\w+))?/)){
      fetch_item = true;
      item_id = matches[2];
      item_model = matches[1];
      if (matches[4]) tab = matches[4];
    } else {
      // show the first icon summary
      var viewer = $$('#viewer ul li.icon');
      if (viewer && viewer.length > 0){
        var first_item = viewer.detect(function(item){
          if (item.visible()) return true;
        });
        if (first_item){
          fetch_item = true;
          item_id = first_item.getAttribute('item_id');
          item_model = first_item.getAttribute('item_model');
        }
      } else {
        // try to get the first item in a software table
        var viewer = $$('#viewer table tbody tr');
        
        if (viewer && viewer.length > 0) { 
          var first_item = viewer.detect(function(item){ 
            if (item.visible()) return true; 
          });
          if (first_item) { 
            software_table.row_click(first_item, true); 
          } 
        }
      }
    }

    if (fetch_item){
      base_uri = base_uri.replace('-model-', item_model);
      base_uri = base_uri.replace('-id-', item_id);
      if (tab == undefined && location.href.match(/tab=events/)) {
        tab = "events";
      }
      if (tab) base_uri += (base_uri.indexOf('?') > -1 ? '&' : '?') + 'tab=' + tab;
      if (tab == "events") {
        if (matches = location.href.match(/pivot_value=(\d{4}-\d{1,2}-\d{1,2})/)) {
          base_uri += "&date=" + matches[1];
        }
      }
      base_uri += (base_uri.indexOf('?') > -1 ? '&' : '?') + 'initial_load=' + true;
      if (item_model == 'Software' || item_model == 'Service' || item_model == 'Hotfix' || item_model == 'Ticket'){
        new Ajax.Request(base_uri, {asynchronous:true, evalScripts:true, onComplete:function(request){software_table.summary_loaded();}, onFailure:function(request){software_table.summary_failed();}, onLoading:function(request){software_table.summary_loading();}});
        var selected = $('node_' + item_id);
        if (selected) {
          if (Prototype.Browser.IE) {
            selected.addClassName('clicked').scrollTo(selected.up('div'), { offsetY:20 });
          } else {
            selected.addClassName('clicked').scrollTo(selected.parentNode, { offsetY:-3 });
          }
        }
      } else {
        new Ajax.Request(base_uri, {asynchronous:true, evalScripts:true, onComplete:function(request){icon_view.summary_loaded();}, onFailure:function(request){icon_view.summary_failed();}, onLoading:function(request){icon_view.summary_loading('device_' + item_id);}});
        var selected = $('device_' + item_id);
        if (selected){
          var offset = 0;
          selected.addClassName('selected');
          //.scrollTo(selected.up('div#device_list'), { offsetY:(offset) });
        }
      }
    }
  },
  device_detail_mouseover:function(e){
    var element = e.findElement( 'div');
    element.addClassName('hover');
  },
  device_detail_mouseout:function(e){
    var element = e.findElement( 'div');
    element.removeClassName('hover');
  },
  get_active_tab:function() {
    var summary_tabs = $('summary_tabs');
    var active_tab;
    if (summary_tabs) active_tab = summary_tabs.down('.active').id; 
    return active_tab;
  },
  flip_comment_view:function(show_attachment){
    if (show_attachment){
      $('add_comment').hide();
      $('add_attachment').show();
      if ($('comment_is_public')) $('attachment_comment_is_public').checked = $('comment_is_public').checked;
      $('attachment_comment_body').value = $('comment_body').value;
    } else {
      $('add_comment').show();
      $('add_attachment').hide();
      if ($('comment_is_public')) $('comment_is_public').checked = $('attachment_comment_is_public').checked;
      $('comment_body').value = $('attachment_comment_body').value;
      $('comment_attachment').value = '';
    }
  },
  toggle_compare_section:function(cell){
    var row = $(cell.parentNode);
    var section = $('comparison_' + row.getAttribute('for_section'));

    if (row.hasClassName('shown')){
      // hide the section
      row.removeClassName('shown');
      row.addClassName('hidden');
      section.hide();
    } else {
      // show the section
      row.removeClassName('hidden');
      row.addClassName('shown');
      section.show();
    }
  },
  flip_device_select:function(select_id, other_id) {
    var select = SpiceSelectManager.get(select_id); 
    var other = $(other_id);
    if (other.visible()) {
     other.hide();
     other.down('input').clear();
     select.activator.show();
    }
    else {
     select.menu.hide(); 
     select.activator.hide(); 
     other.show();
     other.down('input').clear().focus();
     other.down('input').highlight();
    }
  },
  alertCleared: function(){ this._incrementCounter(-1, 'alert'); },
  errorCleared: function(){ this._incrementCounter(-1, 'error'); },
  ticketRemoved: function(){ this._incrementCounter(-1, 'ticket'); },
  ticketAdded: function(){ this._incrementCounter(1, 'ticket'); },
  _incrementCounter: function(amountToIncrement, counterName){
    if (!this.categoryName) return; // this code should only run if we're looking at a category in icon view mode
    if (!amountToIncrement) amountToIncrement = 1;
    // probably shouldn't blindly increment/decrement the count of tickets on a category, in the event that the ticket created is not related to the current category
    var existingCount = $$('#aggregate_status span.count_wrap span.count_' + counterName);
    if (existingCount && existingCount.size() > 0){
      existingCount = existingCount.first().down('em');
      var newCount = parseInt( existingCount.innerHTML, 10 ) + amountToIncrement;
      var countWrapper = existingCount.up( 'span.count' );
      existingCount.update(newCount < 0 ? 0 : newCount);
      if (countWrapper.visible() && newCount < 1) countWrapper.hide();
      else if (!countWrapper.visible() && newCount > 0 ) countWrapper.show();
    }
  }
};

var ImageButton = Class.create({
  initialize: function( button, buttonKey ){
    this.button = button;
    this.buttonKey = buttonKey;
    this.button.setAttribute( 'key', this.buttonKey );
    
    this.activeState = 'normal';
    if ( this.button.disabled ) this.activeState = 'disabled';
    
    this.buttonStates = {
      normal: new Image,
      hover: new Image,
      disabled: new Image
    };
    
    this.buttonStates.normal.src = this.button.src.replace( '_hover', '' ).replace( '_disabled', '' );
    this.buttonStates.hover.src = this.buttonStates.normal.src.replace( '.gif', '_hover.gif' );
    this.buttonStates.disabled.src = this.buttonStates.normal.src.replace( '.gif', '_disabled.gif' );
    
    this.events = {
      mouseOver: this.mouseOver.bindAsEventListener( this ),
      mouseOut: this.mouseOut.bindAsEventListener( this )
    };
    this._addObservers();
  },
  setActiveState: function( state ){
    // set to default if an invalid state is passed in...
    if ( ![ 'normal', 'hover', 'disabled' ].include( state ) ) state = 'normal';

    this.activeState = state;
    this.button.disabled = this.disabled();
    this.button.src = this.buttonStates[ this.activeState ].src;
  },

  mouseOver: function(){ this.setActiveState( 'hover' ); },
  mouseOut: function(){ this.setActiveState( 'normal' ); },
  disable: function(){ this.setActiveState( 'disabled' ); },

  normal: function(){ return this.activeState == 'normal'; },
  hover: function(){ return this.activeState == 'hover'; },
  disabled: function(){ return this.activeState == 'disabled'; },
  
  isOrphaned: function(){ return this.button.isOrphaned(); },
  
  destroy: function(){
    this._removeObservers();
    this.button = null;
    this.buttonKey = null;
    this.activeState = null;
    this.buttonStates.normal = null;
    this.buttonStates.hover = null;
    this.buttonStates.disabled = null;
    this.events.mouseOver = null;
    this.events.mouseOut = null;
  },
  _addObservers: function(){
    this.button.observe( 'mouseover', this.events.mouseOver );
    this.button.observe( 'mouseout', this.events.mouseOut );
  },
  _removeObservers: function(){
    this.button.stopObserving( 'mouseover', this.events.mouseOver );
    this.button.stopObserving( 'mouseout', this.events.mouseOut );
  }
});

var ButtonManager = {
  buttons:$H(),
  initialize: function(){
    $$('input[type=image]').each( function( button ){
      this._attachButton( button );
    }.bind( this ));
  },

  ajaxOnComplete: function(){
    this._removeOrphaned();
    this._attachFreshButtons();
  },
  
  alterStateOfButton:  function( buttonKey, state ){
    var button = this.buttons.get(buttonKey);
    if ( button ) button.setActiveState( state );
  },
  
  _removeOrphaned: function(){
    this.buttons.each( function( pair ){
      if ( pair.value.isOrphaned() ){
        pair.value.destroy();
        this.buttons.unset( pair.key );
      }
    }.bind( this ));
  },
  _attachFreshButtons: function(){
    $$('input[type=image][!key]').each( function( button ){
      if ( !button.getAttribute('key') ) this._attachButton( button );
    }.bind( this ));
  },

  _attachButton: function( button ){
    // attach a button to the collection, keyed by either the button ID or a random number
    var buttonKey = button.id ? button.id : ( Math.random() * 100 ).toString();
    this.buttons.set(buttonKey, new ImageButton( button, buttonKey ));
  }
};

Event.register(ButtonManager);

var tabbed_box = {
  active_id:null,
  set_active:function(active_id){
    tabbed_box.active_id = active_id;
    $$('#summary_tabs li').each(function(element){
      Element.removeClassName(element, 'active');
    });
    Element.addClassName(active_id, 'active');
    
    if( typeof(NewBrowse) == 'undefined' ){
      var base_href = location.href.toString();
      location.href = base_href.replace(/(\&)?tab-(\w+)?/, '') + (base_href.indexOf('#') > -1 ? '&' : '#') + 'tab-' + active_id.replace(/_tab/, '');
    }
  }
};

var UserForm = {
  selectAccountForm:null,
  manageAccountForm:null,
  accountSelector:null,
  saveButton:null,
  testButton:null,
  newAccountLink:null,
  initialize: function(){

    this.selectAccountForm = $('fix_login_form_select');
    this.newAccountLink = $('create_account_button_helper');
    this.accountSelector = $('selected_account');
    this.saveButton = $('btn_save');
    this.testButton = $('btn_test');

    this.saveButton.observe( 'click', this.saveSettings.bindAsEventListener(this) );
    this.testButton.observe( 'click', this.testSettings.bindAsEventListener(this) );

    this.accountSelector.observe( 'change', this.accountSelected.bindAsEventListener(this) );
    this.newAccountLink.observe( 'click', this.createAccountClicked.bindAsEventListener(this) );
  },
  testSettings: function(event){
    Element.update('test_results', '');

    $('test_results').update ('Testing, please wait...');
    $('user_test').value = 'test';
    
    this.manageAccountForm.onsubmit();
  },
  saveSettings: function(event){
    $('test_results').update( 'Saving account' );
    $('user_test').value = '';
    this.manageAccountForm.onsubmit();
  },
  createAccountClicked: function(event){
    event.stop();
    this.accountSelector.selectedIndex = this.accountSelector.options.length - 1;
    this.accountSelected();
  },
  accountSelected: function(event){
    this.selectAccountForm.onsubmit();
  },
  accountSettingsChanged: function(event){
    Form.Element.disable( this.saveButton );
    Form.Element.enable( this.testButton );
  },
  attachUserForm: function(){
    this.manageAccountForm = $('fix_login_form');
  },
  callbackToRunScan: function( device, account, userType ){
    new Ajax.Request( '/view/rescan_similar_devices/?device_id=' + device + '&account=' + account + '&user_type=' + userType, { evalScripts: true });
  }
};

var live_search_table = {
  search:function(table, query){
    if (query == ''){
      this.clear(table);
    } else {
      // this bit of code has been optimized to NOT use anything from prototype (well almost anything) as to speed it up as much as possible
      var searchable_collection = document.getElementById(table).getElementsByTagName('span');
      var search, my_row, selected_node;
      var reg = new RegExp(query, "i");
      for (var i=0;i<searchable_collection.length;i++){
        if (searchable_collection[i].className.indexOf('searchable') > -1){
          my_row = searchable_collection[i].parentNode.parentNode;
          if (!selected_node && my_row.className.indexOf('clicked') > -1) selected_node = my_row;
          search = searchable_collection[i].innerHTML;
          reg.test(search) ? my_row.style.display = '' : my_row.style.display = 'none';
        }
      }
      if (selected_node && selected_node.style.display == 'none') {
        $(selected_node).removeClassName('clicked');
        live_search.clear_active_state();
      }
    }
    this.restripe(table);
  },
  clear:function(collection){
    var rows = $(collection).getElementsByTagName('tr');
    for(var i=0;i<rows.length;i++){
      $(rows[i]).show();
    }
  },
  filter:function(to_filter, input, query){
    if ($(to_filter)){
      $(input).value = query;
      if (query == '')
        this.clear();
      else
        this.search(to_filter, query);
    }
  },
  restripe:function(table){
    var counter = 0;
    $(table).select('tbody tr').each(function(row){
      if (row.visible()) row.removeClassName('stripe0').removeClassName('stripe1').addClassName('stripe' + ( counter++ % 2 ? '1' : '0' ));
    });
  },
  keyDown: function(){
    if (window.event && window.event.keyCode == Event.KEY_RETURN) return false;
  }
};

var live_search = {
  key_interval:null,
  search:function(list, query, label){

    if (query == ''){
      live_search.clear(list, label);
    } else {
      var all_items = document.getElementById(list).getElementsByTagName('li');
      var reg = new RegExp(query, "i"), selected_node;
      var current_item, search, searchable;

      for (var i=0;i<all_items.length;i++) {
        if (all_items[i].className.indexOf('icon') > -1 && (current_item = all_items[i])){
          if (current_item.className.indexOf('selected') > -1) selected_node = current_item;
          current_item = $(all_items[i]);
          searchable = current_item.down('.search_text');
          if (searchable){
            search = Helpers.innerText(searchable);

            //To keep IE from crashing when there are no items that satisfy
            //the quicksearch, we put an extra li in that never gets deleted.
            if (current_item.id != 'node_hideme') reg.test(search) ? current_item.style.display='' : current_item.style.display='none';
          } else current_item.style.display='none';
        }
      }
      if (selected_node && selected_node.style.display == 'none') live_search.clear_active_state();
      
      $('clear_filtered_view').show();
    }
  },
  clear_active_state:function(){
    var summary_wrap = $('summary_wrap');
    $$( 'div.detail_box' ).invoke( 'hide' );
    if (summary_wrap.visible()){
      // clear the selected icon, aka item with class == selected
      $('viewer').select('.selected').each(function(element){element.removeClassName('selected');});
      // remove the bottom panel, aka summary_wrap
      summary_wrap.hide();
      WebClip.hide();
    }
  },
  keypress:function(list, query, label){
    if (icon_view.mode == 'software') live_search_table.search(list, $F(query));
    else live_search.search(list, $F(query), label);
  },
  checkForEnter: function( event, label ){
    if ( event.keyCode == 13 ) live_search.keypress('viewer', 'quickfind', label);
  },
  clear:function(list, label){
    $A($(list).getElementsByTagName('li')).each(function(item){
      item.show();
    });

    var input = $('quickfind');
    input.addClassName('init');
    input.value = label;
    $('clear_filtered_view').hide();
  },
  filter:function(list, input, query, category){
    if ($(list)){
      $(input).value = query;

      if (query == '') icon_view.mode == 'software' ? live_search_table.clear(list) : live_search.clear(list, 'Search ' + category);
      else icon_view.mode == 'software' ? live_search_table.search(list, query) : live_search.search(list, query);
    }
  }
};

var search_page = {
  register:function(){
    Event.observe(window, 'load', search_page.init);
  },
  init:function(){
    var google_button = $('google_search_button');
    if (google_button)
      search_page.prep_form(google_button);
  },
  prep_form:function(google){
    Event.observe(google, 'click', search_page.search_google);
  },
  search_google:function(){
    $('google_q').value = $('search_term_main').value;
    $('google_search').submit();
  },
  loading:function(){
    Form.Element.disable('google_search_button');
    Form.Element.disable('internal_search_button');
  },
  complete:function(){
    Form.Element.enable('google_search_button');
    Form.Element.enable('internal_search_button');
  }
};

var Notes = {
  stamped:false,
  editing:false,
  stampNote: function(){
    var note = $('note_body');
    if (note.value == ''){
      note.value = Helpers.today() + " - ";
    } else {
      var today = Helpers.today();
      if (note.value.indexOf(today) < 0) note.value += "\n\n" + Helpers.today() + " - ";
    }

    var node_notes = $('node_notes');
    node_notes.removeClassName('editing');

    this.editing = false;
    this.stamped = true;
  },
  edit: function(){
    if (!this.editing){
      var node_notes = $('node_notes');
      node_notes.addClassName('editing');

      this.editing = true;
      var note = $('note_body');
      $('my_notes').hide();
      $('node_notes_form').show();
    
      // scroll down to the bottom of the textarea
      note.scrollTop = note.scrollHeight;
      // give it focus
      note.focus();
    
      // place cursor at end of textbox (adapted from http://www.codecomments.com/archive298-2006-2-820220.html)
      if (note.setSelectionRange) {
        note.setSelectionRange(note.value.length, note.value.length);
      }
      else if (note.createTextRange) {
        var range = note.createTextRange();
        range.collapse(true);
        range.moveEnd('character', note.value.length);
        range.moveStart('character', note.value.length);
        range.select();
      }
      
       if( this.fireEvent ) this.fireEvent('edit');
      
    }
  },
  cancel: function(){ this.done(); },
  save: function(url){
    var note_body = $('note_body');
    note_body.disabled = true;
    note_body.addClassName('saving');

    new Ajax.Request(url, { asynchronous:true, evalScripts:true, parameters: { 'note[body]': note_body.value } });

    if( this.fireEvent ) this.fireEvent('done');

  },
  done: function(){
    $('my_notes').show();
    $('node_notes_form').hide();

    var node_notes = $('node_notes');
    node_notes.removeClassName('editing');
    Notes.editing = false;

    var note_body = $('note_body');
    note_body.disabled = false;
    note_body.removeClassName('saving');
    
    if( this.fireEvent ) this.fireEvent('done');
    
  }
};
Object.extend( Notes, ObserverMixin );

var GeneralSummary = {
  edit: function(){
    $('my_general_summary').hide();
    $('my_general_summary_form').show();
    
    if( this.fireEvent ) this.fireEvent('edit');
    
    // if( typeof(NewBrowse) != 'undefined' ){ NewBrowse.disableKeys(); }
  }, 
  save: function(url){
    new Ajax.Request(url, {
      parameters:Form.serialize('general_summary_form')
    });
    GeneralSummary.done();
  },
  cancel: function(){
    GeneralSummary.done();
  },
  done: function(){
    $('my_general_summary').show();
    $('my_general_summary_form').hide();
    if( this.fireEvent ) this.fireEvent('done');
    
    // if( typeof(NewBrowse) != 'undefined' ){ NewBrowse.enableKeys(); }
  }
};
Object.extend( GeneralSummary, ObserverMixin );


/* helper methods go here */
var Helpers = {
  innerText:function(element){
    /* 
    This function is a duplicate of a method added to the Element object in prototype.
    */
    element = $(element);
    return element ? (element.innerText ? element.innerText : element.textContent) : '';
  },
  today:function(){
    var d = new Date();
    return d.print(Application.vDateFormat); //(d.getMonth()+1) + "/" + d.getDate() + "/" + d.getFullYear();
  }
};

var TicketManager = {
  checkForNeedToRedraw: function( from ){
    if ( from == 'browse' ){
      // called from the browse page, let's refresh that panel
      this._tryToRedrawInBrowseView();
    } else if ( from == 'fetch_summary_for_item' ) {
      // icon view of devices
      this._tryToRedrawInIconView();
    } else if ( from == 'fetch_installable_summary_for_item' ){
      this._tryToRedrawInListView();
    }
  },
  showAllTickets: function() {
    this.hiddenTickets().each( function(item){
      item.show().removeAttribute('hidden');
    });
    $("ticket_count").up("tr").hide();
  },
  allTickets: function(){
    return $$("table#status_table tr.ticket");
  },
  hiddenTickets: function() {
    return $$("table#status_table tr.ticket").select(function(t){ 
      return (!t.visible() && t.getAttribute("hidden")=="true");
    });
  },
  _tryToRedrawInBrowseView: function(){
    // get any selected item in the browse view
    var selected = $$( '.threecolumnrowhighlight' );
    if ( selected && selected.length > 0 ){
      // get the last selected item
      selected = selected.last();
      Browse.showNextColumn( selected );
    }
  },
  _tryToRedrawInIconView: function(){
    var clicked_item = $$( '#viewer li.selected' );
    if ( clicked_item && clicked_item.length > 0 && ( clicked_item = clicked_item[0] ) ){
      icon_view.draw_summary( clicked_item.getAttribute('click_url'), clicked_item );
    }
  },
  _tryToRedrawInListView: function(){
    new Ajax.Request( '/software/show', { evalScripts:true, parameters: { id: Toolbar.options.node, category: Toolbar.options.nodeCategory } } );
  }
};

var AlertManager = {
  checkForNeedToRedraw: function( from ){
    if ( from == 'browse' ){
      // called from the browse page, let's refresh that panel
      this._tryToRedrawInBrowseView();
    } else if ( from == 'fetch_summary_for_item' ) {
      // icon view of devices
      this._tryToRedrawInIconView();
    } else if ( from == 'fetch_installable_summary_for_item' ){
      this._tryToRedrawInListView();
    }
  },
  dismissAlert: function(alertID, alertCount){
    alertCount = parseInt(alertCount || '0', 10);
    icon_view.alertCleared();
    $(alertID).hide();

    if ($('tickets_and_alerts_wrapper')) {   
      if ($('alert_count').visible) $('alert_count').update("2 of " + alertCount);
      if (alertCount == 0) {
        $('status_table').select('tr.alert, tr.alerts-header').invoke('hide');
        if (TicketManager.allTickets().size() == 0) $('tickets_and_alerts').hide();
      }
      var firstHidden = this._hiddenAlerts().first();
      if (firstHidden) firstHidden.show().removeAttribute('hidden');

      if (this._hiddenAlerts().size() == 0) $('status_table').down('tr.alert.action').hide(); //hide count and "show all" button
    }    
  },
  showAllAlerts: function() {
    this._hiddenAlerts().each( function(item) { item.show().removeAttribute('hidden'); });
    $("alert_count").up("tr").hide();
  },
  _hiddenAlerts: function() {
    return $$("table#status_table tr.alert").select( function(t) { 
      return (!t.visible() && t.getAttribute("hidden")=="true");
    });
  },
  _tryToRedrawInBrowseView: function(){
    // get any selected item in the browse view
    var selected = $$( '.threecolumnrowhighlight' );
    if ( selected && selected.length > 0 ){
      // get the last selected item
      selected = selected.last();
      Browse.showNextColumn( selected );
    }
  },
  _tryToRedrawInIconView: function(){
    var clicked_item = $$( '#viewer li.selected' );
    if ( clicked_item && clicked_item.length > 0 && ( clicked_item = clicked_item[0] ) ){
      icon_view.draw_summary( clicked_item.getAttribute('click_url'), clicked_item );
    }
  },
  _tryToRedrawInListView: function(){
    new Ajax.Request( '/software/show', { evalScripts:true, parameters: { id: Toolbar.node, category: Toolbar.nodeCategory } } );
  }
};

var SimpleProgress = Class.create({
    /* options 
      hidePercent: doesn't show percent at all
      ignoreNegatives: prevents progress bar from shrinking
      showDecimals: shows decimal percent readings
      fixedPercentPosition: keeps percent reading on the right side of the progress bar, rather than floating along with the progress
    */
  
    initialize: function(id, options) {
        this.container = $(id);
        this.progressBar = this.container.down("div.bar");
        this.percentage = this.container.down("span.percentage");
        this.percent=this.percentage.down('span.value');   
        this.lastPercent = 0;
        this.options=options || {};
        this.percentage.hide();
        this._getDimensions();
    },
    _getDimensions: function() {
        this.containerDimensions = this.container.getDimensions();
        this.containerOffset = this.container.cumulativeOffset();
        this.containerRight = (this.containerOffset['left'] + this.containerDimensions['width']);
        this.percentageDimensions = this.percent.getDimensions();
        this.percentageOffset = this.percent.cumulativeOffset();
    },
    update: function(percentComplete) {
        if ((percentComplete < this.lastPercent) && (this.options.ignoreNegatives)) {
          /* don't show the progress bar decreasing */
          return;
        }
        else if (percentComplete < 0) {
          percentComplete = 0;
        }
        
        if (this.containerDimensions['width'] == 0) { 
          /* the container width is never 0, so if we think it is, we're wrong.. */
          this._getDimensions(); 
        }

        percentComplete = (this.options.showDecimals ? Number(percentComplete) : Number(percentComplete).round());
        percentComplete = (percentComplete > 100) ? 100 : percentComplete;
        this.lastPercent = percentComplete;
        
        this.percent.update(percentComplete + " %"); 
        
        var projectedRight = Number(
          this.containerOffset['left'] + 
          (this.containerDimensions['width'] * Number(percentComplete/100)) + 
          this.percent.getWidth() + 
          (Number(this.percentage.getStyle('padding-left').replace('px','')) + Number(this.percentage.getStyle('padding-right').replace('px','')))
        ).ceil();
          
        var barRight = ((this.containerDimensions['width']) * (percentComplete/100)); 
        
        
        if (this.options.fixedPercentPosition) {
          /* if fixed position, just place percent at the end of the progress bar */
           this.percentage.absolutize(); 
           this.percentage.setStyle({ width: '100%'}); //we're right aligning, so make the field as big as possible to avoid anomalies when adding digits
           this.percentage.setStyle({ textAlign: 'right', left: (this.containerDimensions['width'] - this.percent.getWidth()) + 'px'});
           
           /*only show it after it has been initiallly positioned */
           if (!this.options.hidePercent) { this.percentage.show(); } 
         }
         else if ((projectedRight > this.containerRight) && (percentComplete > 50)) {
           /* if percentage is going to fall outside of the progress bar,  place at the end of the bar */
            this.percentage.absolutize();
            if (!this.options.hidePercent) { this.percentage.show(); }
            
            var leftPos=this.containerDimensions['width'] - this.percent.getWidth() - 
              Number(this.percentage.getStyle('padding-left').replace('px', '')) -
              Number(this.percentage.getStyle('padding-right').replace('px', '')) + 'px';
             
            new Effect.Morph(this.percentage, {style:{left:(leftPos)}});
         }
         else {
           /* this works better than letting it float naturally (for handling cases when the progress shrinks, for instance) */
            if (!this.options.hidePercent) { this.percentage.show(); } 
            this.percentage.absolutize(); 
            if (this.percentage.getStyle('left').startsWith('-')) { 
              /* sometimes the percentage will get absolutized out of view, and get stuck over there. In that case, 
                 reset the style so it floats naturally to where it's supposed to be, and then absolutize it again. */
              this.percentage.setStyle('left:' + barRight + 'px');
            }
            else {
              new Effect.Morph(this.percentage, {style:{left: (barRight) + 'px' }});
            }
         }
        
        new Effect.Morph(this.progressBar, {style:{width: percentComplete + '%'}, 
          afterFinishInternal:function(){
            if (percentComplete < 100) {
              if ((this.progressBar.hasClassName('still')) || (!this.progressBar.hasClassName('moving'))) { 
                this.progressBar.removeClassName('still');
                this.progressBar.addClassName('moving');
               }
            }
            if (percentComplete >= 100) {
              this._onFinish(); 
            }          
          }.bind(this)
        });
    },

    _onFinish: function() {
        this.progressBar.removeClassName('moving');
        this.progressBar.addClassName('still');
    }
});

var SimpleProgressManager = {
  update: function (key, progress) {
    if ((!this.bars) || (!this.bars.get(key))) return;
    var bar = this.bars.get(key);
    bar.update(progress);
    return bar;
  }, 
  createNew: function(key, options){
    if (!this.bars) this.bars = $H();
    this.bars.set(key, new SimpleProgress(key, options));
  }
};

var QuickForm = Class.create({
  initialize: function(element, options){
    this.options = Object.extend({ draggable: true }, options || {});
    this.element = $(element);
    this.form = this.element.down('form');
    
    if (this.form && this.element.id != 'ticket_form') window.setTimeout(this.form.focusFirstElement.bind(this.form), 750);
    var draggableOptions = { handle: 'title' };
    // if (Browser.ie6) {
      this._fixIEOverlapping();
      Object.extend(draggableOptions, { 
        change: this._fixIEOverlapping.bind(this)
      });
    // }
    new Draggable(this.element, draggableOptions);
    
    QuickForm.forms.set(this.element.id, this);
    
    if( QuickForm.fireEvent ) QuickForm.fireEvent('open');

    // we want to hide any active pivot menus when displaying a popup
    PivotManager.clearActive();
    SpiceSelectManager.clearActive();
  },
  
  // In IE we have to shimmy an iframe underneath the help window so that form
  // controls and/or Flash ads won't cover it up
  _fixIEOverlapping: function() {
    var id = this.element.id + '_iefix', iefix = $(id);
    if (!iefix) {
      iefix = new Element('iframe', {id:id, style:'position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);', src:'javascript:void(function(){})', frameborder:0, scrolling:'no'});
      this.element.parentNode.appendChild(iefix);
      setTimeout(this._fixIEOverlapping.bind(this), 50);
      return;
    }
    iefix.clonePosition(this.element, {setTop:(!this.element.style.height)});
    iefix.style.zIndex = 499;
    this.element.style.zIndex = 500;
  },
  destroy: function(){
    this.element.fade({duration:0.5});
    
    if( QuickForm.fireEvent ) QuickForm.fireEvent('close');
    
    setTimeout(function(){
      this.element.remove();
      var iefix = $(this.element.id + '_iefix');
      if (iefix) iefix.remove();
      this.element = this.form = this.options = null;
    }.bind(this), 500);
  }
});

Object.extend( QuickForm, ObserverMixin );

QuickForm.forms = $H();
QuickForm.close = function(id){
  var form = this.forms.get(id);
  if (form) form.destroy();
  this.forms.unset(id);
};


/* Easily build a popup and display it on the screen.
   var p = Popup.new( 'id', 'Title of the popup', 'Content of the popup' );
   Optional last param of options: {'class':'my_html_class', 'closeable':true}
*/
var Popup = Class.create({
  /* add a popup to the page */
  initialize:function( id, title, popupContent, options ){
    this.options = Object.extend({ closeable: false, draggable: true, 'class':'', 'insert':'content', 'immediate':false }, options || {});

    if( !$(id) ){
      if (this.options.closeable) popupContent += '<p class="btn"><input type="image" src="/images/forms/buttons/small/close.gif" class="image_button" id="' + id + '_close_button" /></p>';
      new Insertion.Top(this.options['insert'], '<div id="' + id + '" class="quick_form ' + this.options['class'] + '" style="' + (this.options['immediate'] ? '' : 'display:none') + '"><div class="inner"><h3 class="title"><a href="#" id="' + id + '_close" class="close" title="Close this window"><img alt="Orange_round_close" src="/images/icons/orange_round_close.png" title="Close this window" /></a><span id="' + id + '_title">' + title + '</span></h3><div id="' + id + '_content" class="content"></div></div></div>');
      Element.update( id + "_content", popupContent);
      if( ! this.options['immediate'] ){
        new Effect.Appear(id,{duration:0.5});
      }

      this.element = $(id);
      $(id + '_close').observe( 'click', this.close.bindAsEventListener(this));
      if(this.options.closeable) $(id + '_close_button').observe( 'click', this.close.bindAsEventListener(this));


      var draggableOptions = { handle: 'title' };
      if (Browser.ie6) {
        this._fixIEOverlapping();
        Object.extend(draggableOptions, { 
          change: this._fixIEOverlapping.bind(this)
        });
      }

      new Draggable(this.element, draggableOptions);
      Popup.popups.set(this.element.id, this);

      // if fireEvent exists, then call it
      if( Popup.fireEvent ) Popup.fireEvent('open');

      PivotManager.clearActive();
      SpiceSelectManager.clearActive();
    }
  },

  // In IE we have to shimmy an iframe underneath the help window so that form
  // controls and/or Flash ads won't cover it up
  _fixIEOverlapping: function() {
    var id = this.element.id + '_iefix', iefix = $(id);
    if (!iefix) {
      iefix = new Element('iframe', { id:id,src:'javascript:void(function(){})',frameborder:0,scrolling:0,style:'position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);'});
      this.element.parentNode.appendChild(iefix);
      setTimeout(this._fixIEOverlapping.bind(this), 50);
      return;
    }
    iefix.clonePosition(this.element, {setTop:(!this.element.style.height)});
    iefix.style.zIndex = 499;
    this.element.style.zIndex = 500;
  },

  /* close and remove the popup from the document */
  close:function(event, close_options){
    if (event) event.stop();
    close_options = close_options || {};

    if(close_options['immediate']){
      this.element.remove();
      var iefix = $(this.element.id + '_iefix');
      if (iefix) iefix.remove();
    }else{
      new Effect.Fade(this.element,{duration:0.5});

      setTimeout(function() {
        this.element.remove();
        var iefix = $(this.element.id + '_iefix');
        if (iefix) iefix.remove();
      }.bind(this), 500);
    }

    Popup.popups.unset(this.element.id);

    /* if onclose is defined, then call it as a function here. */
    if( this.options['onclose'] ){
      if( this.options['onclose'] instanceof Function ){
        this.options['onclose']();
      }else{
        eval( this.options['onclose'] );
      }
    }

    // if fireEvent exists && this was the last popup, then fire
    if( Popup.fireEvent && Popup.popups.keys().length == 0 ) Popup.fireEvent('close');

    return false;
  }
});

// Make Popup observable.
Object.extend( Popup, ObserverMixin );

Popup.popups = $H();
Popup.close = function( id, close_options ){
  close_options = close_options || {};
  popup = this.popups.get(id);
  if(popup){
    popup.close( null, close_options );
  }
};

var AssetPopup = Class.create();
AssetPopup.prototype = {
  initialize: function( asset ){
    this.asset = asset;
    this.item = $( 'device_' + this.asset );
    this.anchor = this.item.down('a');
    this.hidden = true;
    
    this.listeners = {
      mouseOverItem: this.mouseOverItem.bindAsEventListener( this ),
      mouseOutItem: this.mouseOutItem.bindAsEventListener( this ),
      mouseOverPopup: this.mouseOverPopup.bindAsEventListener( this ),
      mouseOutPopup: this.mouseOutPopup.bindAsEventListener( this )
    };

    this.anchor.observe( 'mouseover', this.listeners.mouseOverItem );
    this.anchor.observe( 'mouseout', this.listeners.mouseOutItem );
    
    this._loadPopup();
  },
  destroy: function(){
    this.anchor.stopObserving( 'mouseover', this.listeners.mouseOverItem );
    this.anchor.stopObserving( 'mouseout', this.listeners.mouseOutItem );

    this.popup.stopObserving( 'mouseover', this.listeners.mouseOverPopup );
    this.popup.stopObserving( 'mouseout', this.listeners.mouseOutPopup );
    
    if (this.popup) this.popup.remove();
    this.popup = null;
  },
  registerPopup: function(){
    // called later, once the popup has been added to the DOM
    this.popup = $( 'device_' + this.asset + '_popup' );

    this.popup.observe( 'mouseover', this.listeners.mouseOverPopup );
    this.popup.observe( 'mouseout', this.listeners.mouseOutPopup );
  },
  mouseOverItem: function( event ){
    // show the popup if it's not already shown, otherwise turn off the hiding flag

    this._stopHiding();

    if ( this.popup && !this.popup.visible() ) this._showPopup();
  },
  mouseOutItem: function( event ){
    // begin the task of hiding the popup, a mouseover on the icon/popup will cancel this task
    this._startHiding();
  },
  mouseOverPopup: function( event ){
    // the popup is already shown if this is happening, make sure we don't hide the popup
    this._stopHiding();
  },
  mouseOutPopup: function( event ){
    // begin the task of hiding the popup, a mouseover on the icon/popup will cancel this task
    this._startHiding();
  },
  popupLoaded: function(){
    // callback after ajax successfully completed, set the flags and show the popup as long as the user hasn't already moved on to something else
    
    this.loaded = true;
    this.registerPopup();
    if (!this.hiding || !this.hidden) this._showPopup();
  },
  _showPopup: function(){
    // draw the popup and set the flags for the current state
    
    this._hideAllPopups(); // only one popup is visible at a time

    var device_position = Position.cumulativeOffset( Browser.ie6 ? this.item : this.anchor );
    var popup_left = device_position[0] - 55;
    var popup_top  = device_position[1] + 70 - $('device_list').scrollTop;
    this.popup.setStyle({ top: popup_top + 'px', left: popup_left + 'px' });
    this.popup.show();
    this.hidden = false;
  },
  _startHiding: function(){
    // probably hide the popup, but we need to wait a little bit to give the user a chance to move their cursor from the icon to the popup
    
    this.hiding = window.setTimeout(function(){
      this._hidePopup();
    }.bind(this), 200);
  },
  _stopHiding: function(){
    if ( this.hiding ) window.clearTimeout(this.hiding);
    this.hiding = null;
  },
  _hidePopup: function(){
    // hide the popup
    if ( this.popup && this.popup.visible() ) this.popup.hide();
    this.hidden = true;
  },
  _hideAllPopups: function(){
    $$( 'div.device_popup' ).invoke('hide');
  },
  _loadPopup: function(){
    // fetch the popup through Ajax
    new Ajax.Request('/view/fetch_device_popup/' + this.asset);
  }
};

var AssetPopupManager = {
  loadForAsset: function(asset){
    // convert to integer
    asset = parseInt( asset, 10 );
    
    if (!this.loadedPopups) this.loadedPopups = $H();

    // create the new popup, but only if we haven't done so already
    // if this popup is already created, then it has it's own registered listeners to show/hide the popup
    if (!this.loadedPopups.get(asset)) this.loadedPopups.set(asset, new AssetPopup(asset));
  },
  loaded: function(asset){
    this.loadedPopups.get(asset).popupLoaded();
  },
  release: function(asset){
    asset = parseInt(asset, 10);
    if ((this.loadedPopups) && (this.loadedPopups.get(asset))){
      this.loadedPopups.get(asset).destroy();
      this.loadedPopups.unset(asset);
    }
  }
};

var CalendarPopup = { 
  setup:function(textFieldID, triggerID) {
    // silently fail if the nodes to attach the calendar to cannot be found
    if( $(textFieldID) && (!triggerID || $(triggerID)) ){
      var calendar = Calendar.setup({ 
        inputField : textFieldID, // ID of the input field 
        ifFormat : Application.dateFormat, // the date format 
        button : triggerID, // ID of the button
        align : 'Bl',
        single_click : true,
        step : 1, // show every year in menu
        cache : true, // reuse the div is calendar is reopened
        showOthers : true,
        weekNumbers : false
      });
    }
  }
};

// Ticket template replacement helper.  Enables clicking on template variable replacements and having them
// added to the form element.
// Taken roughly from: http://alexking.org/blog/2003/06/02/inserting-at-the-cursor-using-javascript/
var template_form_helper = {
  field_name_suffix:null,
  
  // Insert a chunk of text at the cursor of the field.  
  insertAtCursor: function (myValue) {
    if (template_form_helper.field_name_suffix) {
      field_name = $F('selected_template') + this.field_name_suffix;
      field = $(field_name);
    
      //IE support
      if (document.selection) {
        field.focus();
        sel = document.selection.createRange();
        sel.text = myValue;
      }
      //MOZILLA/NETSCAPE support
      else if (field.selectionStart || field.selectionStart == '0') {
        var startPos = field.selectionStart;
        var endPos = field.selectionEnd;
        field.value = field.value.substring(0, startPos)
          + myValue
          + field.value.substring(endPos, field.value.length);
      } else {
        field.value += myValue;
      }
    }
  }
};

var Pivot = Class.create();
Pivot.prototype = {
  initialize: function( activator, menu ) {
    this.activator = activator;
    this.menu = menu;
    
    this.events = {
      mouseOver: this.mouseOver.bindAsEventListener(this),
      mouseOut:  this.mouseOut.bindAsEventListener(this),
      hasFocus:  this.hasFocus.bindAsEventListener(this),
      lostFocus: this.lostFocus.bindAsEventListener(this),
      menuMouseOver: this.menuMouseOver.bindAsEventListener(this),
      itemMouseOver: this.itemMouseOver.bindAsEventListener(this),
      itemMouseOut: this.itemMouseOut.bindAsEventListener(this)
    };
    this._addObservers();
  },
  mouseOver: function(e) {
    this.clearActivatorTimeout();
    this.clearMenuTimeout();
    var timeout  = this.activator.getAttribute('pivot_timeout') || 100;
    this.activatorTimeout = window.setTimeout(this.show.bind(this), timeout);
    this.activator.observe('mouseout',  this.events.mouseOut);
  },
  mouseOut: function(e) {
    this.clearActivatorTimeout();
    this.activator.stopObserving('mouseout',  this.events.mouseOut);
    this.menuTimeout = window.setTimeout(this.hide.bind(this), 1000);
  },
  menuMouseOver: function(e) {
    this.clearMenuTimeout();
  },
  itemMouseOver: function(e) {
    this.hideTips();
    this.clearMenuTimeout();
    var element = e.element();
    if (element.prototip) {
      element.prototip.show();
    }
  },
  itemMouseOut: function(e) {
    
  },
  show: function() {
    this.clearActivatorTimeout();
    this.clearMenuTimeout();
    TipManager.hideAll();  
    
    var dimensions = this.activator.getDimensions();
    var position   = this.activator.cumulativeOffset();
    
    var menuLeft   = position[0] + parseInt(this.activator.getStyle('padding-left'), 10);
    var menuTop    = position[1] + dimensions.height;
    if (!Prototype.Browser.IE) menuTop += 12;
    
    // if another pivot menu is active, hide it and show this one instead
    if (PivotManager.active && PivotManager.active !== this) PivotManager.active.hide();
    PivotManager.active = this;
    
    if (this.menu && !this.menu.hasClassName('moved_pivot')) {
      document.body.insertBefore(this.menu, $('container'));
      this.menu.addClassName('moved_pivot');
    }
    
    this.menu.setStyle({
      zIndex:   500,
      position: 'absolute',
      top:      menuTop  + 'px',
      left:     menuLeft + 'px'
    });
    
    // This is throwing strange errors in IE7 around the clonePosition calls, and appears to not be needed for Flash
    // this._renderZIndexFix();

    this.menu.show();
    
    this.menu.observe('mouseover', this.events.hasFocus);
    this.menu.observe('mouseout',  this.events.lostFocus);
  },
  
  hide: function() {
    TipManager.hideAll(); // FIXME: when we add more prototips throughout the app, this might cause an issue,
                          // but for now it's better than the alternative of having a stuck tip.  Finding the tip
                          // that is a decendant of this.menu and asking it to hide doesn't seem to work in IE7
                          
    if (!this.menu) return; // for cases when the pivot menu is no longer on the page due to an ajax process altering the page content
    this.menu.hide();
    this._destroyZIndexFix();

    this.clearActivatorTimeout();
    this.clearMenuTimeout();

    this.menu.stopObserving('mouseover', this.events.hasFocus);
    this.menu.stopObserving('mouseout',  this.events.lostFocus);    
  },
  clearActivatorTimeout: function() {
    if (this.activatorTimeout) window.clearTimeout(this.activatorTimeout);
    this.activatorTimeout = null;
  },

  setMenuTimeout: function() {
    this.menuTimeout = window.setTimeout( this.hide.bind( this ), 1000 );
  },

  clearMenuTimeout: function() {
    if (this.menuTimeout) window.clearTimeout(this.menuTimeout);
    this.menuTimeout = null;
  },
  
  hasFocus: function(e) {
    document.fire('pivot:hasFocus', this.activator.id);
    var element = e.element();
    if ( element.hasClassName( 'pivotable' ) || element.up( 'div.pivotable' ) ) this.clearMenuTimeout();
  },
  lostFocus: function(e) {
    if (this.ignoreMouseOut) return;
    var element = e.element();
    if ( !element.hasClassName( 'pivotable' ) || !element.up( 'div.pivotable' ) ) this.setMenuTimeout();
  },
  destroy: function(){
    // if the currently active menu is about to be destroyed, we need to clear/hide it first
    if (PivotManager.active && PivotManager.active == this) PivotManager.clearActive();

    this._removeObservers();
    this.activator = null;
    if ( this.menu.parentNode ) this.menu.parentNode.removeChild( this.menu );
    this.menu = null;
  },
  isOrphaned: function(){
    return this.activator.isOrphaned();
  },
  hideTips: function() {
    this.menu.select('a').each ( function(element) {
        if (element.prototip) {          
            element.prototip.hide();
        }
    });
  },
  _addObservers: function() {
    this.activator.observe('mouseover', this.events.mouseOver);
    this.menu.observe('mouseover', this.events.menuMouseOver);
    this.menu.select('a').each ( function(element) {
      element.observe('mouseover', this.events.itemMouseOver);
      element.observe('mouseout', this.events.itemMouseOut);
    }.bind(this));
  },
  _removeObservers: function(){
    this.activator.stopObserving('mouseover', this.events.mouseOver);
    this.menu.stopObserving('mouseover', this.events.menuMouseOver);
    this.menu.select('a').each ( function(element) {
      element.stopObserving('mouseover', this.events.itemMouseOver);
      element.stopObserving('mouseout', this.events.itemMouseOut);
    }.bind(this));
  },
  _renderZIndexFix: function(){
    var id = this.menu.id + '_zindex_fix';
    var iframe = $(id);
    if (!iframe) {
      var newIframe = new Element("iframe", {
        id: id,
        style: "position:absolute; -moz-opacity:0; opacity:0; filter:alpha(opacity=0);",
        src: "javascript:void(function(){})();",
        frameborder: 0,
        scrolling: "no"
      });
      newIframe.clonePosition(this.menu, { setTop: (!this.menu.style.height) });
      this.menu.insert({after: newIframe});
      this._renderZIndexFix.bind(this).defer();
      return;
    }
    iframe.clonePosition(this.menu, { setTop: (!this.menu.style.height) });
    iframe.style.zIndex = 499;
    this.menu.style.zIndex = 500;
    this.zIndexFix = iframe;
  },
  _destroyZIndexFix: function(){
    if ( this.zIndexFix && this.zIndexFix.parentNode ){
      this.zIndexFix.parentNode.removeChild( this.zIndexFix );
      this.zIndexFix = null;
    }
  }
};



var SpiceSelect = Class.create();
SpiceSelect.prototype = Object.extend(new SpiceSelect(), {
  initialize: function( activator, menu, data ) {
    this.activator = activator;
    this.menu = menu;
    this.data = data;
    this.key = this.activator.readAttribute('key'); 
    this.field_id = this.activator.readAttribute('field_id');
    // specifies what hidden form elements will be named
    this.activator.removeAttribute('field_id');
    this.activator.removeAttribute('key');
    
    this.multiSelect = menu.hasClassName('multi');
    this.givenTitle = this.activator.down().innerHTML;
    this._initializeHiddenFields();
    this._updateTitle();
    this.events = {
      activatorMouseDown: this.activatorMouseDown.bindAsEventListener(this), 
      activatorMouseOver: this.activatorMouseOver.bindAsEventListener(this),
      activatorMouseOut:  this.activatorMouseOut.bindAsEventListener(this),
      menuHasFocus:  this.menuHasFocus.bindAsEventListener(this),
      menuLostFocus: this.menuLostFocus.bindAsEventListener(this),
      menuMouseOver: this.menuMouseOver.bindAsEventListener(this),
      itemClick: this.itemClick.bindAsEventListener(this),
      outsideClick: this.outsideClick.bindAsEventListener(this)
    };
    
    this._addObservers();
  },
  uncheckItem: function(element) {
    element.removeClassName('checked');
    element.addClassName('unchecked');
    element.removeAttribute("selected");
    this.data.select("input[key = 'h_" + element.identify() + "']").invoke('remove'); 
  },
  checkItem: function(element) {
    if (this.multiSelect) {
      element.removeClassName('unchecked');
      element.addClassName('checked');
    }
        
    element.writeAttribute("selected", "true");
    var h = this._hiddenField(this.key, element.readAttribute('value'), "h_" + element.identify(), this.field_id);
    this.data.select("input[key = 'h_" + element.identify() + "']").invoke('remove');
    this.data.appendChild(h);
  },
  toggleItem: function(element) {
     (element.hasClassName('checked') ?  this.uncheckItem(element) : this.checkItem(element));
  },
  uncheckAll: function() {
      this.data.update();
      this._checkedItems().each ( function(item) {
        item.removeAttribute('selected');
      });
  },
  itemClick: function(e) {
    var element = e.element();
    
    if (this.multiSelect) {
      this.toggleItem(element);    
      this._updateTitle();
    }
    else {
      this.uncheckAll();
      this.checkItem(element);
      this._updateTitle();
      this.hide();
    }  
  },
  outsideClick: function(e) {
    var element = e.element();
    if (!(element.hasClassName( 'spice_selectable' ) || element.up( 'div.spice_selectable' ))) {
      this.hide();
    }
  },
  activatorMouseDown: function(e) {
    (this.menu.visible() ? this.hide() : this.show());    
  },
  activatorMouseOver: function(e) {
    this.clearActivatorTimeout();
    this.clearMenuTimeout();
  },
  activatorMouseOut: function(e) {
    this.clearActivatorTimeout();
    this.activator.stopObserving('mouseout',  this.events.activatorMouseOut);
    this.menuTimeout = window.setTimeout(this.hide.bind(this), 1000);
  },
  menuMouseOver: function(e) {
    this.clearMenuTimeout();
  },
  menuHasFocus: function(e) {
    var element = e.element();
    if ( element.hasClassName( 'spice_selectable' ) || element.up( 'div.spice_selectable' ) ) this.clearMenuTimeout();
  },
  menuLostFocus: function(e) {
    var element = e.element();
    if ( !element.hasClassName( 'spice_selectable' ) || !element.up( 'div.spice_selectable' ) ) this.menuTimeout = window.setTimeout( this.hide.bind( this ), 1000 );
  },
  show: function() {
    this.clearActivatorTimeout();
    this.clearMenuTimeout();
    
    if (!this.beenShown) {
      // Set the width once. Width is reported as 0 if done in initialize, 
      // and firing it on every show call increases the width each time
      this._setWidth();
      this.beenShown = true;
    }
    
    this._setPosition();
    
    // if another select menu is active, hide it and show this one instead
    if (SpiceSelectManager.active && SpiceSelectManager.active !== this) SpiceSelectManager.active.hide();
    SpiceSelectManager.active = this;
    
    if (this.menu && !this.menu.hasClassName('moved_pivot')) {
      document.body.insertBefore(this.menu, $('container'));
      this.menu.addClassName('moved_pivot');
    }
 
    this.menu.show();
    this.menu.observe('mouseover', this.events.menuHasFocus);
    this.menu.observe('mouseout',  this.events.menuLostFocus);  

    // wait a little bit before watching for an outside click, or else the menu will just flash open and closed
    window.setTimeout( function(){
      document.observe('mousedown', this.events.outsideClick);
    }.bind( this ), 100);    
  },
  
  _setPosition: function() {
    var dimensions = this.activator.getDimensions();
    var position   = this.activator.cumulativeOffset();
    var offset = this.activator.cumulativeScrollOffset();
    // var menuLeft = position.left - this.activator.cumulativeScrollOffset[0] + parseInt(this.activator.getStyle('padding-left'), 10) + document.viewport.getScrollOffsets().left;
    var menuLeft = offset.left + position.left;
    menuTop = position.top + dimensions.height;

    /* Browser specific pixel-perfect adjustements */
    if (Browser.ie6) {
      menuTop -= 4;
      if (this.multiSelect) menuTop += 7;
    }
    else if (Browser.ie7) {
      menuTop -= 6;
      if (this.multiSelect) menuTop += 7;
    }
    else if (Prototype.Browser.Gecko) {
      menuTop += 6;
      menuLeft += 1;
      if (this.multiSelect) menuTop += 5;
    }    
    
    var viewHeight = document.viewport.getHeight();
    var menuHeight = this.menu.getHeight();
    var topHidden = document.viewport.getScrollOffsets().top;
    var belowTheFold = (menuTop + menuHeight) - (viewHeight + topHidden);
    
    if (this.multiSelect) {
     // try keep the menu visible on the page
     if (menuHeight > viewHeight) menuTop = topHidden + 5; 
     else if (belowTheFold > 0) menuTop = menuTop - belowTheFold;   
    }
    else {
     // position it so the selected item is right under the cursor
     menuTop= menuTop - (this._checkedCount() > 0 ? this._itemTopOffset(this._checkedItems().first()) : 0);
    } 
    
    this.menu.setStyle({
      zIndex:   1000,
      position: 'absolute',
      top:      menuTop  + 'px',
      left:     menuLeft + 'px'
    });        
  },
  
  _setWidth: function() {
    var activatorWidth = this.activator.getWidth();
    var menuWidth = this.menu.getWidth();    

    // at a minimum drop down menu should be the width of the select box
    if ((activatorWidth > menuWidth) || (Browser.ie6)) {
       menuWidth=this.activator.getStyle('width');  // set to the width of the menu, for now. 
       this.menu.setStyle({width: menuWidth});
    }
    else {
        this.menu.setStyle({width: this.menu.getWidth() + 'px'});
    }
  },
  
  hide: function() {
    if (!this.menu) return; // for cases when the pivot menu is no longer on the page due to an ajax process altering the page content
    this.menu.hide();

    // this is hacky and causes rendering issues, let's not do it
    //this._destroyZIndexFix();

    this.clearActivatorTimeout();
    this.clearMenuTimeout();

    this.menu.stopObserving('mouseover', this.events.menuHasFocus);
    this.menu.stopObserving('mouseout',  this.events.menuLostFocus);  
    document.stopObserving('mousedown', this.events.outsideClick);
  },
  
  clearActivatorTimeout: function() {
    if (this.activatorTimeout) window.clearTimeout(this.activatorTimeout);
    this.activatorTimeout = null;
  },

  clearMenuTimeout: function() {
    if (this.menuTimeout) window.clearTimeout(this.menuTimeout);
    this.menuTimeout = null;
  },
  destroy: function(){
    // if the currently active menu is about to be destroyed, we need to clear/hide it first
    if (SpiceSelectManager.active && SpiceSelectManager.active == this) SpiceSelectManager.clearActive();

    this._removeObservers();
    this.activator = null;
    if ( this.menu.parentNode ) this.menu.parentNode.removeChild( this.menu );
    this.menu = null;
  },
  isOrphaned: function(){ 
    return this.activator.isOrphaned();
  },
  _updateTitle: function(){   
    if (this.multiSelect) {
      switch (this._checkedCount()) {
        case 0:
          this._setSelectTitle(this.givenTitle);
          break;
        case 1:
           this._setSelectTitle(this._checkedCount() > 0 ? (this._checkedItems().first().innerHTML) : this.givenTitle);
           break;
        default:
           this._setSelectTitle(this._checkedCount() + " selected");
        }
    }
    else {
      if (this._checkedCount() > 0) {
        this._setSelectTitle(this._checkedItems().first().innerHTML);
      }
      else {
        this._setSelectTitle(this.givenTitle);
      }
    }
  },
  _hiddenField: function(name, value, key, id) {
    var h = document.createElement('input');
    h.type="hidden";
    h.value = value;
    h.name = name;
    h.id=id;
    h.setAttribute('key', key);
    return h;
  },
  _selectTitle: function() {
    this.activator.down().innerHTML;
  },
  _setSelectTitle: function(newtitle) {
    this.activator.down().update(newtitle);
  },
  _itemTopOffset: function(element) {
    var id = element.identify();
    var offset = 0;
    var height = 0;
    var topPadding = 0;
    var bottomPadding = 0;
    
    this.menu.select("a.item").each ( function( item ) {
      // calculate using set style since the elements aren't hardcoded with height, and not visible
      topPadding = Number(item.getStyle('padding-bottom').replace('px', ''));
      bottomPadding = Number(item.getStyle('padding-top').replace('px', ''));
      height = Number(item.getStyle('height').replace('px', ''));
      
      if (id == item.readAttribute('id')) {
        offset = offset + height + topPadding;
        throw $break;
      }
      else {
        offset = offset + height + topPadding + bottomPadding;
      }
    });
    
    this.menu.select("li.separator").each ( function( separator ) {
      topPadding = Number(separator.getStyle('padding-bottom').replace('px', ''));
      bottomPadding = Number(separator.getStyle('padding-top').replace('px', ''));

      offset = offset + topPadding + bottomPadding;
    });
    
    return offset;    
  },
  _initializeHiddenFields: function() {
    this.data.update(); // clear existing hidden elements
    this._checkedItems().each( function(item) {
      this.checkItem(item);
    }.bind(this));
  },
  _items: function() {
    return this.menu.select("a:not([class~=unselectable])");
  },
  _checkedItems: function() {
    return this.menu.select("a[selected='true']");
  },
  _itemCount: function() {
    return this._items().size();
  },
  _checkedCount: function() {
    return this._checkedItems().size();
  },
  _addObservers: function() {
    this.activator.observe('mousedown', this.events.activatorMouseDown);
    this.activator.observe('mouseover', this.events.activatorMouseOver);
    this.menu.observe('mousedown', this.events.menuMouseOver);
    
    this._items().each( function( item ){
      item.observe('mousedown', this.events.itemClick);
    }.bind( this ));
  },
  _removeObservers: function(){
    this.activator.stopObserving('mouseover', this.events.activatorMouseOver);
    this.menu.stopObserving('mouseover', this.events.menuMouseOver);
    this._items().each( function( item ){
      item.stopObserving('mousedown', this.events.itemClick);
    }.bind( this ));
  },
  _renderZIndexFix: function(){
    var id = this.menu.id + '_zindex_fix';
    var iframe = $(id);
    if (!iframe) {
      iframe = new Element('iframe', {id:id, src:'javascript:void(function(){})', frameborder:0,scrolling:'no',style:'style="position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);'});
      this.menu.parentNode.appendChild(iframe);
      window.setTimeout(this._renderZIndexFix.bind(this), 50);
      return;
    }
    iframe.clonePosition(this.menu, {setTop:(!this.menu.style.height)});
    iframe.style.zIndex = 499;
    this.menu.style.zIndex = 500;
    this.zIndexFix = iframe;
  },
  _destroyZIndexFix: function(){
    if ( this.zIndexFix && this.zIndexFix.parentNode ){
      this.zIndexFix.parentNode.removeChild( this.zIndexFix );
      this.zIndexFix = null;
    }
  }
});


var PivotManager = {
  active: null,
  initialize: function(){
    if (this.initialized) return;
    if (!this.pivots) this.pivots = $H();
    this._addNew();
    this.initialized = true;
  },
  ajaxOnComplete: function(){
    this._removeOrphaned();
    this._addNew();
  },
  clearActive: function(){
    if ( this.active ) this.active.hide();
    this.active = null;
  },
  _removeOrphaned: function(){
    this.pivots.each( function( pair ){
      if ( pair.value.isOrphaned() ){
        pair.value.destroy();
        this.pivots.unset( pair.key );
      }
    }.bind( this ));
  },
  _addNew: function(){
    // pivot elements are usualy anchors, but can be spans as well, so with the selector we don't want to specify the tag name
    $$( '.pivot' ).each( function( pivot ){
      if ( !this.pivots.get( pivot.id ) ){
        var menu = $("menu_" + pivot.id);
        if ( menu ) this.pivots.set( pivot.id, new Pivot( pivot, menu ) );
      }
    }.bind( this ));     
  }
};
Event.register(PivotManager);

var SpiceSelectManager = {
  active: null,
  initialize: function(){
    if ( !this.spiceSelects ) this.spiceSelects = $H();
    this._addNew();
  },
  ajaxOnComplete: function(){
    this._removeOrphaned();
    this._addNew();
  },
  clearActive: function(){
    if ( this.active ) this.active.hide();
    this.active = null;
  },
  _removeOrphaned: function(){
    this.spiceSelects.each( function( pair ){
      if ( pair.value.isOrphaned() ){
        pair.value.destroy();
        this.spiceSelects.unset( pair.key );
      }
    }.bind( this ));
  },
  _addNew: function(){
    // pivot elements are usualy anchors, but can be spans as well, so with the selector we don't want to specify the tag name
    $$( '.spice_select' ).each( function( activator ){
       if ( !this.spiceSelects.get( activator.id ) ){
         var menu = $("menu_" + activator.id);
         var data = $("data_" + activator.id);
         if ( menu ) this.spiceSelects.set( activator.id, new SpiceSelect( activator, menu, data ) );
       }
     }.bind( this ));  
  },
  get: function(id) {
    var select = this.spiceSelects.get(id);
    return select;
  }
};
Event.register(SpiceSelectManager);

// Disable text selection on an element and all its children.
// Don't do this unless it's temporary (i.e., during a drag operation) or
// something the user doesn't expect to be able to select.
var TextSelection = {
  elements: [],
  initialize: function() {
    this.events = {
      handler: this.handler.bindAsEventListener(this)
    };
    
    Event.observe(window, Prototype.Browser.IE ? 'selectstart' : 'mousedown', this.events.handler);
  },
  
  disable: function(element) {
    element = $(element);
    if (!this.elements.include(element))
      this.elements.push(element);
  },
  
  enable: function(element) {
    element = $(element);
    this.elements = this.elements.without(element);
  },
  
  handler: function(e) {
    var element = e.element();
    do {
      if (this.elements.include(element)) {
        e.stop(); return;
      }
    } while (element = element.parentNode);
  }
};

Event.register(TextSelection);


var BulkEdit = {
  initialize: function() {
    this.element = $('bulk_edit');
    this.form    = $('bulk_edit_form');
    
    this.events = {
      keyupManager: this.keyupManager.bind(this),
      onSubmit:     this.onSubmit.bind(this)
    };
    
    this.saveOriginalValues();
      
    this.element.stopObserving('keyup', this.events.keyupManager);
    this.element.observe('keyup', this.events.keyupManager);
    
    this.form.observe('submit', this.events.onSubmit);
  },
  
  saveOriginalValues: function() {
    this.originalValues = {};
    var inputs = this.form.getInputs('text');
    
    inputs.each( function(input) {
      this.originalValues[input.name] = input.value;
    }.bind(this));
    
  },
  
  keyupManager: function(e) {
    var element = e.element();
    if (!element.type || element.type !== 'text') return;
    
    var originalValue = this.originalValues[element.name],
     newValue = element.getValue();
     
    element.previous('input[type=checkbox]').checked = (originalValue !== newValue);      
  },
  
  onSubmit: function(e) {
    e.stop();
    return Form.request(this.form);
  }
};

var ShowAssetDetails = {
  initialize: function(){
    $$('div.box h3 a.toggle_content').invoke('observe', 'click', this.toggleBoxClicked.bindAsEventListener(this));
    $$('div.box p.items_shown a').invoke('observe', 'click', this.toggleItemsShownClicked.bindAsEventListener(this));
  },
  toggleBoxClicked: function(event){
    event.stop();
    this.toggleBox(event.element());
  },
  toggleBox: function(link){
    link = $( link );
    var box = link.up('.box');
    var box_content = box.down('.box_content');
    if (box.toggleClassName('collapsed').hasClassName('collapsed')){
      box_content.blindUp( { duration: 0.5 } );
      link.update('Show');
      link.setAttribute('title', 'Expand this section');
    } else {
      box_content.blindDown( { duration: 0.5 } );
      link.update('Hide');
      link.setAttribute('title', 'Hide this section');
    }
  },
  toggleItemsShownClicked: function(event){
    event.stop();
    this.toggleItemsShown(event.element());
  },
  toggleAllBoxes: function(link){
    link = $( link );
    var togglers = $$('div.box h3 a.toggle_content').each(function(link){ 
      this.toggleBox(link); 
    }.bind(this));

    var showing = togglers.first().up('.box').hasClassName('collapsed');

    var text = link;
    if (link.hasClassName('toolbar_button'))  { text =link.down('span.icon'); }
    text.update( showing  ? 'Expand all categories' : 'Hide all categories' );

    link.removeClassName( 'expand' ).removeClassName( 'contract' ).addClassName( showing ? 'expand' : 'contract' );
  },
  toggleAllItemsShown: function(link){
    link = $( link );
    var togglers = $$('div.box p.items_shown a').each(function(link){
      this.toggleItemsShown(link);
    }.bind(this));

    if ( togglers && togglers.size() > 0 ){
      var showing = togglers.first().up('.box').down('table').hasClassName('showing_all'); 

      var text = link;
      if (link.hasClassName('toolbar_button')) { text =link.down('span.icon'); }
      text.update( showing ? 'Collapse all lists' : 'Expand all lists' );

      link.removeClassName( 'expand' ).removeClassName( 'contract' ).addClassName( showing ? 'contract' : 'expand' );
    }
  },
  toggleItemsShown: function(link){
    link = $( link );
    var box_content = link.up('.box_content');
    var meta_text = box_content.down('p.items_shown a span');
    var table = box_content.down('table');
    
    // get only the rows that are initially hidden (index higher than 5)
    var trs = table.select('tbody tr').reject( function(tr, index) { return index < 5; } );
    if (table.toggleClassName('showing_all').hasClassName('showing_all')){
      trs.invoke('show');
      meta_text.update('all');
    } else {
      trs.invoke('hide');
      meta_text.update('5 of');
    }
  }
};

var Rater = Class.create();
Rater.prototype = {
  initialize: function(element, options){
    this.options = Object.extend({ rated: false }, options || {});

    this.rater = $(element);
    this.stars = this.rater.select('a.star');
    
    this.stars.invoke('observe', 'mouseover', this.starMouseOver.bindAsEventListener(this));
    this.stars.invoke('observe', 'mouseout', this.starMouseOut.bindAsEventListener(this));
  },
  starMouseOver: function(event){
    var hovered_star = event.element();
    this.rater.addClassName('hover_at_' + hovered_star.getAttribute('rating'));
  },
  starMouseOut: function(event){
    var hovered_star = event.element();
    this.rater.removeClassName('hover_at_' + hovered_star.getAttribute('rating'));
  }
};

var Flyover = {
  prepare: function( darkbox ){
    this._setHeights( darkbox );
    if (!Browser.ff3) this._darkboxFix( darkbox );
  },
  show: function( flyover_content ){
    if (!$(flyover_content)){
      this._fetchFlyover(flyover_content);
      return;
    }

    var lightbox = $( flyover_content ).up( '.lightbox' );
    var darkbox = $( lightbox.getAttribute('id').replace( 'lightbox_', 'darkbox_' ) );
    lightbox.blindDown( { duration: 1 } );
    darkbox.blindDown( { duration: 1 } );
    window.setTimeout( function(){
      this.prepare( darkbox );
    }.bind( this ), 1100);

  },
  hide: function( flyover_content ){
    var lightbox = $( flyover_content ).up( '.lightbox' );
    var darkbox = $( lightbox.getAttribute('id').replace( 'lightbox_', 'darkbox_' ) );
    lightbox.blindUp( { duration: 1 } );
    darkbox.blindUp( { duration: 1 } );
    window.setTimeout( function(){
      $(darkbox.id + '_iefix').remove();
    }.bind( this ), 1100);
  },
  destroy: function( element_inside_lightbox, options){
    options = Object.extend( { instantDisplay:false }, options || {} );
    var lightbox = $( element_inside_lightbox ).up( '.lightbox' );
    var darkbox = $( lightbox.getAttribute('id').replace( 'lightbox_', 'darkbox_' ) );
    
    if ( !options.instantDisplay ){
      lightbox.blindUp( { duration: 1 } );
      darkbox.blindUp( { duration: 1 } );
    }
    window.setTimeout( function(){ 
      var darkboxFix = $(darkbox.id + '_iefix');
      if (darkboxFix) darkboxFix.remove();
      lightbox.remove();
      darkbox.remove();
    }.bind( this ), ( options.instantDisplay ? 0 : 1100 ) );
  },
  _setHeights: function( darkbox ){
    /*
    The height of lightboxes and darkboxes is set in the CSS to be 100% width and height, but in all "good" browsers
    this is rendered as 100% of the width and height of the visible window, and does not include scrollable space
    We need to apply this height fix to make the darkbox and lightbox extend to the full content width
    */
    darkbox = $(darkbox);
    var lightbox = $(darkbox.getAttribute('id').replace('darkbox_', 'lightbox_'));
    
    var height = darkbox.parentNode.offsetHeight;
    if (height && document.documentElement.clientHeight > height) height = document.documentElement.clientHeight;
    darkbox.style.height = height + 'px';
    lightbox.style.height = height + 'px';
  },
  _darkboxFix: function( darkbox ){
    darkbox = $(darkbox);
    var darkboxFix = new Element('iframe', {id:darkbox.id + '_iefix', frameborder:0, scrolling:'no', src:'javascript:void(function(){})', style:'position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);'});
    darkbox.parentNode.appendChild(darkboxFix);
    setTimeout(function(){
      darkboxFix.setStyle({top:0,left:0,width:darkbox.offsetWidth + 'px',height:darkbox.offsetHeight + 'px'});
      darkbox.style.zIndex = 500;
      darkboxFix.style.zIndex = 499;
    }, 50);
  },
  _fetchFlyover: function(flyoverID){
    new Ajax.Request('/utility/flyover/' + flyoverID, {evalScripts:true});
  }
};

var SoftwareList = {
  initialize: function( mode ){
    this.mode = mode;
    this.icon = $('device_detail');
    this.counts = this.icon.select( 'span#aggregate_status span.count_wrap span.count');

    this.searchBox = $('software_filter');
    if ( this.searchBox ) this.searchForm = this.searchBox.up( 'form' );
    
    this.observers = {
      largeIconClicked: this.largeIconClicked.bindAsEventListener( this ),
      searchCountClicked: this.searchCountClicked.bindAsEventListener( this )
    };
    
    this.icon.observe( 'click', this.observers.largeIconClicked );
    this.counts.invoke( 'observe', 'click', this.observers.searchCountClicked );
  },
  largeIconClicked: function( event ){
    var clicked = event.element();
    event.stop();
    
    // make sure the item clicked was not one of the special count spans or it's children
    if ( !clicked.hasClassName( 'count' ) && !clicked.up( 'span.count' ) ){
      // clear the search box and refresh the item list by submitting the search form
      this._triggerSearch();
    }
  },
  searchCountClicked: function( event ){
    var clicked = event.element();
    event.stop();

    // we need the count element, not a child of the count element
    if ( !clicked.hasClassName( 'count' ) ) clicked = clicked.up( 'span.count' );
    
    // enter the special search term into the search box and submit the form
    this._triggerSearch( '@' + clicked.getAttribute( 'quickfind' ) + '@' );
  },
  
  _triggerSearch: function( term ){
    if ( !term ) term = '';
    var searchIsEmpty = this.searchBox.hasClassName( 'init' );
    if ( searchIsEmpty && term != '' ){
      Form.Element.clearDefaultText( this.searchBox, this.searchBox.value );
      this.searchBox.value = term;
      this.searchForm.onsubmit();
    } else if ( !searchIsEmpty && term == '' ){
      this.searchBox.value = this.searchBox.getAttribute( 'default_text' );
      this.searchBox.addClassName('init');
      this.searchForm.onsubmit();
    }
  }
};

var AjaxHelpers = {

  // An exclusion list that the global Ajax listeners use to determine if the application overlay should be shown or not
  urlExclusionList: $A( [ '/tickets/periodic_ticket_table_update', '/help/toggle_visibility', '/view/auto_complete_for_ticket_attached_to', '/reports/auto_complete_for_report_filter_value', '/dashboard/get_widget_content', '/dashboard/remember_state', '/settings/monitors/disable_all_email_notifications', '/helpdesk/', '/dashboard/click_link', '/ads/blocking_detected', '/view/fetch_device_popup', '/view/fetch_environment_summary?chart=', '/view/fetch_environment_summary?summary_type=hardware&chart=', '/tickets/set_requires_purchase', '/search/rebuild_percent_complete', '/finder/application_polling', '/settings/categories/resort', '/dashboard/remember_state', '/dashboard/increment_app_stat', '/view/hide_trait' ] ),
  waitingForAjax: false,
  activeRequests: 0,
  nonExcludedActiveRequests: 0,
  ajaxTimeout: 20,
  overlay: null,
  spinner: null,

  registerResponders: function(){
    Ajax.Responders.register( { onCreate: this.ajaxRequestStarting.bind( this ), onComplete: this.ajaxRequestCompleted.bind( this ) } );
  },
  initialize: function(){
    this.overlay = $( 'app_overlay' );
    this.indicator = $('application_activity_indicator');
    if ( this.overlay ) $( 'close_app_overlay' ).observe( 'click', this.closeOverlayClicked.bindAsEventListener( this ) );
  },
  closeOverlayClicked: function( event ){
    this._unlockApplication();
  },
  ajaxRequestStarting: function( request ){
    this.activeRequests++;
    
    AjaxCallbacks.invoke( request, 'onCreate' );
    
    if ( this.overlay && this._requestUrlNotExcluded( request.url ) && !request.options['skipApplicationLocking'] ){
      this._lockApplication();
    }
  },
  ajaxRequestCompleted: function( request ){
    if ( --this.activeRequests < 0 ) this.activeRequests = 0;
    AjaxCallbacks.invoke( request, 'onComplete' );
    
    if ( this.overlay && this._requestUrlNotExcluded( request.url ) ) this._unlockApplication();
  },
  _lockApplication: function(){
    this.nonExcludedActiveRequests++;

    this.indicator.show();
    this.overlay.show();
  
    if ( this.waitingForAjax ) window.clearTimeout( this.waitingForAjax );
    this.waitingForAjax = window.setTimeout( function(){ this._unlockApplication(); }.bind( this ), this.ajaxTimeout * 1000 );
  },
  _unlockApplication: function(){
    if ( --this.nonExcludedActiveRequests < 0 ) this.nonExcludedActiveRequests = 0;
    if ( this.nonExcludedActiveRequests == 0 ){
      this.indicator.hide();
      this.overlay.hide();
    }
  },
  _requestUrlNotExcluded: function( url ){ return this.urlExclusionList.detect( function( element ){ return url.indexOf( element ) > -1; } ) ? false : true; }
};
AjaxHelpers.registerResponders();
Event.register(AjaxHelpers);

var AjaxCallbacks = {

  onCompleteCallbacks: $A([]),
  onCreateCallbacks: $A([]),
  // references the methods that are to be invoked by this object
  supportedCallbacks: { onComplete: '_onComplete', onCreate: '_onCreate' },
  
  // references the methods that are to be supported and invoked on the passed in object
  expectedCallbacks: { onComplete: 'ajaxOnComplete', onCreate: 'ajaxOnComplete' },
  
  register: function( object, state ){
    if ( object.size ){
      // we have an array of objects to register
      $A( object ).each( function( callback ) {
        this._attach( callback, state );
      }.bind( this ));
    } else {
      this._attach( object, state );
    }
  },
  invoke: function( ajaxRequest, state ){
    // lookup the method to invoke
    var method = this.supportedCallbacks[ state ];
    // invoke the method, if a valid callback was supplied
    
    try{ if ( method ) this[ method ]( ajaxRequest ); }
    catch( err ){ if ( Application.inDevelopment() ) console.log( "Error invoking " + state + " for AjaxCallbacks" ); }
    
  },

  _attach: function( object, state ){
    // the name of the method that we're going to invoke on the passed in object
    var callback = this.expectedCallbacks[ state ];
    // the collection of callbacks that we're going to push this new callback onto
    var callbackCollection = state + 'Callbacks';

    try{
      // make sure the object implements the ajaxCallback method
      if ( !object[ callback ] ) throw 'Object does not implement ' + callback + ' method, cannot register.';
      // push it on the collection and make sure to bind it to itself so the scope doesn't get f'ed up
      this[ callbackCollection ].push( object[ callback ].bind( object ) );
    } catch ( exception ){
      if ( Application.inDevelopment() ) console.log( exception ); // don't worry, we add in the console object (near the top of this file) if it's not defined
    }
  },
  _onCreate: function( ajaxRequest ){ /* not implemented yet */ },
  _onComplete: function( ajaxRequest ) {
    // only render ajax callbacks if we have a response (empty responses have no new content)
    if ( ajaxRequest.transport.responseText != '' ){
      // invoke all of the callbacks for this state
      this.onCompleteCallbacks.each( function( callback ){
        try{ callback();}
        catch( err ){
          if ( Application.inDevelopment() ) console.log( "Error invoking onComplete method for object: " + err );
        }
      }.bind( this ));
    }
  }
};

var WebClip = {
  context:null,
  writeTo:null,
  source:null,
  prepare: function( writeTo, source ){
    this.writeTo = $(writeTo);
    this.source = source;
    if (this.writeTo.hide) this.writeTo.setStyle( { visibility:'hidden' } );
  },
  deferredReload: function(context){
    Event.observe(document, 'dom:loaded', this.reload.curry(context).bindAsEventListener(this));
  },
  reload: function(context){
    this.context = context + ',default';
    this.writeTo.update().setStyle( { visibility:'hidden' } );
    DynamicScriptInclude.load( this.source + '?cobranded=' + Application.cobranded + '&custom_install=' + Application.customInstall + '&uuid=' + Application.uuid + '&what=' + escape( this.context ), true );
  },
  render: function(htmlSnippet){
    if ( this.context && this.writeTo && htmlSnippet && htmlSnippet != '' ) this.writeTo.update(htmlSnippet).setStyle({ visibility:'visible' });
  },
  hide: function(){ this.writeTo.setStyle( { visibility:'hidden' } ); }
};

var CollapsableSection = Class.create({
  initialize: function(wrapper){
    this.wrapper = wrapper;
    this.collapsed = this.wrapper.hasClassName('collapsable-collapsed');

    this._appendClassNameHooks();
    this._boundTogglerClicked = this.togglerClicked.bindAsEventListener(this);
    this.toggler = this._setupToggleControl();
    this.toggler.observe('click', this._boundTogglerClicked);
  },
  togglerClicked: function(event){ this.collapsed ? this._expand() : this._contract(); },
  destroy: function(){
    this.toggler.stopObserving('click', this._boundTogglerClicked);
    this.wrapper = this.toggler = this._boundTogglerClicked = null;
  },
  _expand: function(){
    this.wrapper.removeClassName('collapsable-collapsed');
    this.toggler.setAttribute('title', 'Collapse this section');
    this.collapsed = false;
  },
  _contract: function(){
    this.wrapper.addClassName('collapsable-collapsed');
    this.toggler.setAttribute('title', 'Expand this section');
    this.collapsed = true;
  },
  _appendClassNameHooks: function(){
    this.wrapper.select('table thead tr th:first-child').invoke('addClassName', 'first_column');
    this.wrapper.select('table thead tr th:last-child').invoke('addClassName', 'last_column');
  },
  _setupToggleControl: function(){
    var toggler = this.wrapper.down('span.toggler');
    toggler.setAttribute('title', (this.collapsed ? 'Expand this section' : 'Collapse this section'));
    return toggler;
  }
});

var CollapsableSectionManager = {
  initialize: function(){
    this.elements = $H();
    this._addNew();
  },
  ajaxOnComplete: function(){
    this._removeOrphaned();
    this._addNew();
  },
  _removeOrphaned: function(){
    this.elements.each(function(pair){
      if (pair.value.wrapper.isOrphaned()){
        pair.value.destroy();
        this.elements.unset(pair.key);
      }
    }.bind(this));
  },
  _addNew: function(){
    $$('div.collapsable').each(function(element){
      if (!element.id) element.setAttribute('id', 'collapsable-' + parseInt(Math.random() * 10000000, 10));
      if (!this.elements.get(element.id)) this.elements.set(element.id, new CollapsableSection(element));
    }.bind(this));
  }
};

Event.register(CollapsableSectionManager);

var ProductRating = {
  writeTo:null,
  source:null,
  prepare: function( writeTo, source ){
    this.writeTo = $( writeTo );
    this.source = source;
    if ( this.writeTo && this.writeTo.hide ) this.writeTo.hide();
  },
  reload: function(){
    if ( this.writeTo && this.writeTo.hide ) this.writeTo.hide().update();
    DynamicScriptInclude.load( this.source, true );
  },
  render: function( html_snippet ){
    if ( this.writeTo && html_snippet && html_snippet != '' ) this.writeTo.update( html_snippet ).show();
  }
};

var AdHelper = {
  initialize: function(){
    // poll the ad's iframe container, making sure it's always visible. if it's not visible, we assume an adblocker hid the iframe
    if ( $( 'adbox' ) ){
      this.adCheckingInterval = window.setInterval( function(){
        var adframe = $( 'adframe' );
        // if the ads are hidden, show the "Please Try Spiceworks MyWay to get rid of ads" message
        if ( adframe && !adframe.visible() ) this._displayMyWayImage();
      }.bind( this ), 5000 );
    }
  },
  checkResolutionAndRenderAds: function(url){
    var screenWidth = screen.width;
    var resolution = 'normal';
    if (screenWidth && parseInt(screenWidth, 10) < 1200){
      resolution = 'narrow';
      Application.narrowLayout();
    } else url += '&_w=t';

    // render the ads
    var adframe = $('adframe');
    if (adframe) adframe.src = url;

    // wait an arbitrary amount of time, so we're not immediately flodding the browser with ajax requests
    window.setTimeout(function(){
      new Ajax.Request('/ads/detected_resolution', {parameters: {resolution:resolution} });
    }, 3500);
  },
  _displayMyWayImage: function(){
    // clear the polling interval
    if ( this.adCheckingInterval ) window.clearInterval( this.adCheckingInterval );

    // show the "myway" ad if we have the container to update
    var adbox = $( 'adbox' );
    if (adbox){
      var myWay = new Template('<a class="myway" href="http://www.spiceworks.com/myway/?aba" target="_blank"><img src="/images/other/spiceworks_myway#{large}.png" alt="Have Spiceworks your way, without ads!" /></a>');
      adbox.update( myWay.evaluate({large: Application.narrow ? '' : '_300'}) );
      // kick off this ajax request so we can do other stuff on the server (record stats)
      new Ajax.Request( '/ads/blocking_detected' );
    }
  }
};
Event.register(AdHelper);

var EventHelper = {
  toggleAddNew: function(){
    var form = $('add_event_form');
    var toggler = $('link_to_show_new_event');
    if ( form.visible() ){
      form.hide();
      toggler.show();
    } else {
      $('new_event_id').value = '';
      form.show();
      toggler.hide();
      $('new_event_id').focus();
    }
  },
  showFullMessage: function( eventID ){
    var brief = $( 'brief_message_for_' + eventID );
    var full = $( 'full_message_for_' + eventID );
    brief.hide();
    full.show();
  }
};

var FlashChart = { diskUsage:null, networkOverview:null, active:null };

var Search = {
  loadCommunityTab: function(query){
    var cookieName = 'community_results_count_' + query;
    var count = Cookie.get(cookieName);
    if(count){
      $('community_results_count').update('(' + count + ')');
    } else{
      new Ajax.Request(Delegate.encode('/search/app_count'), {
        parameters: {
          'api_version':1,
          'query':query
        },
        onSuccess: function(transport){
          Cookie.set(cookieName, transport.responseText);
          $('community_results_count').update('(' + transport.responseText + ')');
        }
      });
    }
  },
  loadCommunityResults: function(query, page){
    if (!page) { page = 1; }
    new Ajax.Request(Delegate.encode('/search/app'), {
      parameters: {
        'api_version':1,
        'query':query,
        'page':page
      },
      onSuccess: function(transport){
        $('community_results').update(transport.responseText);
      },
      onFailure: function(transport){
        $('community_results').update('<p>Unable to connect to Community.</p>');
      }
    });
  }
};

/* Helper functions for dealing with attachments to objects. */
var Attachment = {  
  flip_view:function(show_attachment){
    if (show_attachment){
      $('add_description').hide();
      $('add_attachment').show();
      $('attachment_description_body').value = $('description_body').value;
    } else {
      $('add_description').show();
      $('add_attachment').hide();
      $('description_body').value = $('attachment_description_body').value;
      $('attachment_file').value = '';
    }
  }
};


/* Helper functions for dealing with activity streams. */
var Activity = {
  /* hide details of an activity stream item */
  hideDetails:function(elem, imediate){
    elem = $(elem);
    elem.removeClassName('open');
    if( imediate ){
      elem.down('.detail').hide();
    }else{
      new Effect.BlindUp(elem.down('.detail'),  {duration:0.25}); 
    }
  },

  /* show details of an activity stream item */
  showDetails:function(elem, imediate){
    elem = $(elem);
    elem.addClassName('open');

    if( imediate ){
      elem.down('.detail').show();
    }else{
      new Effect.BlindDown(elem.down('.detail'), {duration:0.25});
    }
  },

  toggleDetails:function(elem, imediate){
    if( elem.down('.detail').visible() ){
      Activity.hideDetails(elem, imediate);
    }else{
      Activity.showDetails(elem, imediate);
    }
  },

  showAllDetails:function(){
    $$('ul.activities li').each(function(elem){
      Activity.showDetails(elem, true);
    });
  },

  hideAllDetails:function(){
    $$('ul.activities li').each(function(elem){
      Activity.hideDetails(elem, true);
    });
  },
  toggleAllDetails:function(){
    if( $$('ul.activities li div.detail')[0].visible() ){
      Activity.hideAllDetails();
    }else{
      Activity.showAllDetails();
    }
  },

  mouseOver:function(element){
    element = $(element);
    element.addClassName('hover');
    // element.down('.controls').show();
  },
  mouseOut:function(element){
    element = $(element);
    element.removeClassName('hover');
    // element.down('.controls').hide();
  },

  /* Dismiss an item from the activity stream */
  dismiss:function(elem){
    var id = elem.id.split('_')[1];
    new Ajax.Request('/activities/' + id, { evalScripts:true, method:'delete' });
  },

  remove:function(elem){
    elem = $(elem);
    new Effect.Fade( elem );
    setTimeout(function(){
      Element.remove(elem);
      }, 500);
    }
  };

var ActivityElement = {test:function(){}}; /* jp: I removed this because it clearly does nothing, but now it's back again. weird */


var Extension = {
  registered_elements: new Array(),

  register:function( selector, methods ){
    Extension.registered_elements[Extension.registered_elements.length] = [selector, methods];
  },
  attach:function(){
    Extension.registered_elements.each( function(info){
      selector = info[0];
      methods = info[1];
      $$(selector).each( function(elem){
        Object.extend(elem, methods);
      });
    });
  }
};

var PrettyDate = {
  initialize: function() {
    this.pretty();
  },
  ajaxOnComplete:function() {
    this.pretty();
  },
  pretty: function() {
    $$('span.pretty-date').each(function(elem) {
      var prettied = this.format(elem.innerHTML);
      if (prettied) {
        elem.update(prettied);
      }
    }.bind(this));
  },
  /*
   * JavaScript Pretty Date
   * Copyright (c) 2008 John Resig (jquery.com)
   * Licensed under the MIT license.
   */

   // Takes an ISO time and returns a string representing how
   // long ago the date represents.
   format:function(time){
     var date = new Date((time || "").replace(/-/g,"/").replace(/[TZ]/g," ")),
     diff = (((new Date()).getTime() - date.getTime()) / 1000),
     day_diff = Math.floor(diff / 86400);
     if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
     return;

     return day_diff == 0 && (
       diff < 60 && "just now" ||
       diff < 120 && "1 minute ago" ||
       diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
       diff < 7200 && "1 hour ago" ||
       diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
       day_diff == 1 && "Yesterday" ||
       day_diff < 7 && day_diff + " days ago" ||
       day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
     }
};

// This object is here because the event_data.rxml script was mangling the javascript
// to fetch a device (replacing a + with %20)
var eventLoader = {
  loadEvents: function(params) {
    new Ajax.Request('/events', {asynchronous:true, evalScripts:true, parameters:params});
  }
};

Event.register(PrettyDate);
AjaxCallbacks.register(PrettyDate, 'onComplete');

// This will ensure that all AJAX posts have an authenticity token so we won't
// cause rails to throw an ActionController::InvalidAuthenticityToken exception.
Ajax.Responders.register({
  onCreate:function(request) {
    if (request.method == 'post' && Application.authenticity_token) {
      // If we don't have a postBody, force one.  This is our only chance
      // to change what gets posted, because Ajax.Request will always use
      // the postBody if present and it's too late to add to request.options.parameters.
      if (!request.options.postBody) {
        request.options.postBody = Object.toQueryString(request.options.parameters);
      }
      if (! request.options.postBody.match(/authenticity_token\=/)) {
          request.options.postBody += "&authenticity_token=" + Application.authenticity_token;
      }
    }
  }
});