/**
 * @author TobiasTrozowski
 * @constructor
 * @returns {TuneUpIntegrator}
 */
function TuneUpIntegrator( )
{
  AbstractTuneUpClass.apply(this, arguments);
}
TuneUpIntegrator.prototype = new AbstractTuneUpClass( );

AbstractTuneUpClass
    .extend(
      TuneUpIntegrator.prototype,
      {
        trialEnabled : false,

        Settings : {

          /**
           * Launch the SettingCenter with System config as entry-point
           */
          System : function( )
          {
            tuDispatcher.trigger("launch", "SettingCenter.exe", "system");
          },

          /**
           * Launch the SettingCenter with Tray config as entry-point
           */
          Tray : function( )
          {
            tuDispatcher.trigger("launch", "SettingCenter.exe", "tray");
          },

          /**
           * Launch the SettingCenter with Balloon config as entry-point
           */
          Balloon : function( )
          {
            tuDispatcher.trigger("launch", "SettingCenter.exe", "balloon");
          },

          /**
           * Launch the SettingCenter with Update config as entry-point
           */
          Update : function( )
          {
            tuDispatcher.trigger("launch", "SettingCenter.exe", "update");
          },

          /**
           * Launch the SettingCenter with Rescue Center config as entry-point
           */
          RescueCenter : function( )
          {
            tuDispatcher.trigger("launch", "SettingCenter.exe", "rescue");
          },

          /**
           * Launch the SettingCenter with Password config as entry-point
           */
          Password : function( )
          {
            tuDispatcher.trigger("launch", "SettingCenter.exe", "pass");
          },

          /**
           * Launch the SettingCenter with Live-Optimization config as entry-point
           */
          LiveOptimization : function( )
          {
            tuDispatcher.trigger("launch", "SettingCenter.exe", "live");
          },

          /**
           * Launch the SettingCenter with One-Click-Maintenance config as entry-point
           */
          OCM : function( )
          {
            tuDispatcher.trigger("launch", "SettingCenter.exe", "ocm");
          },

          /**
           * Launch the SettingCenter with Automatic-Background-Maintenance config as entry-point
           */
          ABM : function( )
          {
            tuDispatcher.trigger("launch", "SettingCenter.exe", "abm");
          },

          /**
           * Launch the SettingCenter with turbomode config as entry-point
           */
          Turbo : function( )
          {
            tuDispatcher.trigger("launch", "SettingCenter.exe", "turbo");
          },

          /**
           * Launch the energy optimizer config
           */
          Eco : function( )
          {
            tuDispatcher.trigger("launch", "EnergyOptimizer.exe", "eco");
          },

          /**
           * Launch the SettingCenter with Disc-Space-Explorer config as entry-point
           */
          DSE : function( )
          {
            tuDispatcher.trigger("launch", "SettingCenter.exe", "dse");
          },

          /**
           * Launch the SettingCenter with Program Deactivator config as entry-point
           */
          ProgramDeactivator : function( )
          {
            tuDispatcher.trigger("launch", "SettingCenter.exe", "sandy");
          }

        },

        /**
         * Send the load command to the TUService
         */
        onload : function( )
        {
          console.debug("TuneUpIntegrator.onload()");
          return this.send("#onload");
        },

        /**
         * Close the Integrator
         */
        close : function( )
        {
          console.debug("TuneUpIntegrator.close()");
          return this.send('#header:close');
        },

        /**
         * Minimize the Integrator
         */
        minimize : function( )
        {
          console.debug("TuneUpIntegrator.minimize()");
          return this.send('#header:minimize');
        },

        /**
         * Move the Integrator
         */
        move : function( )
        {
          console.debug("TuneUpIntegrator.move()");
          return this.send('#header:move');
        },

        /**
         * Launch an application with the given parameters
         * 
         * @param {application_or_event}
         * @param opt
         *          optional
         */
        launch : function(evt, application)
        {
          console.debug("TuneUpIntegrator.launch(", arguments, ")");
          if (evt === undefined && application === undefined) {
            throw new URIError("Cannot launch a undefined application.");
          }
          var cmdString = param = paramString = null, args = Array.prototype.slice.call(arguments);

          // if we got an event so use arguments within the event
          if (evt !== undefined && evt.args !== undefined && evt.type !== undefined) {
            args = evt.args;
            application = args.shift( ); // strip first argument from array
          } else {
            // {evt} is not our event-object
            param = application;
            application = args.shift( );
          }

          cmdString = String.format("#launch:%s", application);
          // if any argument is out there
          if (args.length > 0) {
            paramString = "/" + args.join(":/");
            cmdString = String.format("%s:%s", cmdString, paramString);
          }
          return this.send(cmdString);
        },

        /**
         * Launch the Styler with {target} as entry point
         * 
         * @param target
         */
        style : function(target)
        {
          console.debug("TuneUpIntegrator.style(", arguments, ")");
          this.send(String.format("#style:%s", target));
        },

        /**
         * Launch the specified window of gaindiskspace module
         * 
         * @param type
         */
        gaindiskspace : function(type)
        {
          console.debug("TuneUpIntegrator.gaindiskspace(", arguments, ")");
          this.send(String.format("#gaindiskspace:%s", type));
        },

        /**
         * Show or hide the specified tooltip {type}
         * 
         * @param state
         * @param type
         */
        tooltip : function(evt, state, type, left, top, width, height)
        {
          console.debug("TuneUpIntegrator.tooltip(", arguments, ")");

          if (type == 'TODO') {
            console.warn("Unknown Tooltip found: ", type);
          } else {
            if (left !== undefined && top !== undefined && width !== undefined && height !== undefined) {
              this.send(String.format("#tooltip:%d:%s:%d:%d:%d:%d", state, type, left, top, width, height));
            } else {
              this.send(String.format("#tooltip:%d:%s", state, type));
            }
          }
          return this;
        },

        enableTrial : function(text)
        {
          console.debug("TuneUpIntegrator.enableTrial(", arguments, ")");
          if (text !== undefined && text !== null && ( text.trim( ) != "" )) {
            jQuery("body").addClass("trial");
            // show trial
            jQuery(".footer .trial .text").html(text);
            this.trialEnabled = true;
            console.warn("TuneUp Utilities is in trial-mode!");
          } else {
            jQuery("body").removeClass("trial");
            this.trialEnabled = false;
          }

          // do not invoke rendering if the tuning status was not rendered yet
          if (tuTuningStatus.isRendered( )) {
            tuTuningStatus.render( );
          }
        },

        isTrialEnabled : function( )
        {
          return this.trialEnabled;
        },

        /**
         * Show or hide the specified popupmenu {type}
         * 
         * @param evt
         * @param enable
         */
        popupmenu : function(evt, el, type)
        {
          console.debug("TuneUpIntegrator.popupmenu(", arguments, ")");

          var elementPos = el.offset( );
          elementPos.width = el.width( );
          elementPos.height = el.height( );

          this.send(String.format("#popupmenu:%d:%d:%d:%d:%d", type, elementPos.left, ( elementPos.top - 1 ), elementPos.width,
            elementPos.height));

          return this;
        },

        /**
         * Inform the Integrator about a new selected tab
         * 
         * @param tabId
         * @returns void
         */
        tabChanged : function(tabId)
        {
          console.debug("TuneUpIntegrator.tabChanged(", tabId, ")");
          if (tabId === undefined) {
            throw new ReferenceError("TabID is missing.");
          }
          return this.send("#tab:" + tabId);
        },

        /**
         * @returns {TuneUpIntegrator}
         */
        initialize : function( )
        {
          console.debug("TuneUpIntegrator.initialize()");

          // singleton implementation
          if (TuneUpIntegrator.instance !== undefined) {
            return TuneUpIntegrator.instance;
          }

          tuDispatcher.bind('close', function( )
          {
            this.close( );
          }, this).bind('minimize', function( )
          {
            this.minimize( );
          }, this).bind('onload', function(evt)
          {
            this.onload( );
          }, this).bind('launch', function(evt, app)
          {
            this.launch(evt, app);
          }, this).bind('style', function(evt, target)
          {
            this.style(target);
          }, this).bind('tooltip', function(evt, state, type, left, top, width, height)
          {
            this.tooltip(evt, state, type, left, top, width, height);
          }, this).bind('enableTrial', function(evt, text)
          {
            this.enableTrial(text);
          }, this).bind('popupmenu', function(evt, state, type)
          {
            this.popupmenu(evt, state, type);
          }, this).bind('gaindiskspace', function(evt, type)
          {
            this.gaindiskspace(type);
          }, this).bind('updatePillar', function(evt, data)
          {
            this.updatePillar(evt, data);
          }, this).bind('updateSystemload', function(evt, type, text)
          {
            this.updateSystemload(type, text);
          }, this).bind('updateStyleSettings', function(evt, target, linkList)
          {
            this.updateStyleSettings(target, linkList);
          }, this).bind('gdsUpdateDriveInfo', function(evt, drives)
          {
            this.gdsUpdateDriveInfo(drives);
          }, this).bind('gdsDriveSelected', function(evt, selectedOption)
          {
            this.gdsDriveSelected(selectedOption);
          }, this).bind('updateLiveOptimization', function(evt, type, value)
          {
            this.updateLiveOptimization(type, value);
          }, this).bind('tabSelected', function(evt, id)
          {
            this.tabSelected(id);
          }, this);

          jQuery("#MoveAreaMap1").bind('mousedown', jQuery.proxy(function(evt)
          {
            if (evt.button == 0) {
              this.move( );
            }
          }, this));

          tuDispatcher.bind('onload', jQuery.proxy(function( )
          {
            this.onload( );
          }, this));

          TuneUpIntegrator.instance = this;
          return TuneUpIntegrator.instance;
        },

        /**
         * This function should always be used to select a tab. It initializes the tab by calling the registered init
         * handler, hides possibly problematic controls of the old tab and then makes the new tab visible (eventually
         * using cross-fading).
         * 
         * @param tabId
         */
        tabSelected : function(tabId)
        {
          console.debug("TuneUpIntegrator.tabSelected(", arguments, ")");
          var newTab = newPanel = oldTab = oldPanel = changingPanel = changingTab = null;
          newTab = jQuery('#' + tabId);

          // get tab from panel
          newPanel = jQuery(newTab.find('a').first( ).attr('href'));

          oldTab = jQuery('#tabs li.selected');
          oldPanel = jQuery('#tab-panels .panel.selected');

          if (!newTab.length || !newPanel.length) {
            return;
          }

          changingPanel = ( newPanel.attr('id') != oldPanel.attr('id') );

          // Prepare the tab's panel for displaying
          if (changingPanel) {
            newPanel.addClass('before-selected');
          }

          // Call registered "initpanel" handler of the new panel (if necessary).
          // Note that we intentionally call this handler also if the panel was
          // already visible by default (= no check for changingPanel)
          if (!newPanel.hasClass('initpanel-done')) {
            newPanel.triggerHandler('initpanel');
            newPanel.addClass('initpanel-done');
          }

          // Display the new panel
          if (changingPanel) {
            newPanel.triggerHandler('showpanel');

            // Special handling: Switching to the "all functions" panel
            if (newPanel.attr('id') == 'panel6') {
              // jQuery( 'body' ).css( "background-image", "url(images/panel6/ig-background.png)" );
              jQuery('#tab-panels.tab-panels').removeClass('background');
              jQuery('#tuningarea').addClass('hidden');
              jQuery('#footer').addClass('panel6');
              jQuery('#tabs').addClass('selected-only');
              // Make the "all functions" tab link to the previously displayed panel
              newTab.find('a').attr('href', '#' + oldPanel.attr('id'));

            }
            // Special handling: Leaving the "all functions" panel
            else if (oldPanel.attr('id') == 'panel6') {
              // jQuery( 'body' ).css( "background-image", "url(images/ig-background.png)" );
              jQuery('#tab-panels.tab-panels').addClass('background');
              jQuery('#tuningarea').removeClass('hidden');
              jQuery('#footer').removeClass('panel6');
              jQuery('#tabs').removeClass('selected-only');
              // Reset the "all functions" tab link to its own panel
              oldTab.find('a').attr('href', '#' + oldPanel.attr('id'));
              // Find the corresponding tab for the new panel
              newTab = jQuery('#tabs li').has('a[href="#' + newPanel.attr('id') + '"]').first( );
            }

            // Set the correct CSS classes to make the panel visible
            newPanel.addClass('selected');
            newPanel.removeClass('before-selected');
            oldPanel.removeClass('selected');
          }

          // Check if the tab panel was changed or if no change event was sent yet
          if (changingPanel || !jQuery('#tabs').attr('initdone')) {
            // Remember that we sent the first tab change event
            if (!changingPanel) {
              jQuery('#tabs').attr('initdone', '1');
            }
            // Inform Integrator about the tab change
            this.tabChanged(newTab.attr('id'));
          }

          // Set the correct CSS classes to make the tab look selected
          changingTab = ( newTab.attr('id') != oldTab.attr('id') );
          if (changingTab) {
            oldTab.removeClass('selected');
            newTab.addClass('selected');
          }
        },

        /**
         * Updates the pillars on panel #1
         * 
         * @param evt
         * @param data
         * @returns {TuneUpIntegrator}
         */
        updatePillar : function(evt, obj)
        {
          console.debug("TuneUpIntegrator.updatePillar(", arguments, ")");
          if (obj === undefined || obj === null) {
            console.warn("Cannot update pillar cause the data object is empty.");
            return;
          }
          var selector = cfgButton = null;

          // now check if this object contains all properties we need.
          if (obj.state === undefined || obj.pillar === undefined || obj.textState === undefined || obj.button === undefined
            || obj.button.target === undefined || obj.button.text === undefined)
          {
            throw new TypeError("One or more properties are missing.");
          }

          selector = String.format(".panel > .pillar.%s", obj.pillar);

          // set status
          jQuery(selector).removeClass("good warning critical").addClass(obj.state);
          jQuery(String.format("%s .status .text span", selector)).text(String.format("- %s -", obj.textState));
          jQuery(String.format("%s button.button", selector)).html(obj.button.text);
          jQuery(String.format("%s > .text", selector)).html(obj.infoText);

          // change icon-classes for panel 6
          jQuery(String.format("#panel6 #allfunctions-%s", obj.pillar)).attr({
            src : String.format("images/panel6/%s-%s.png", obj.pillar, obj.state)
          });

          // performance and rescue pillars have static targets
          if (obj.pillar !== 'maintenance' && obj.pillar !== 'rescue') { // for health and performance pillars
            // bind listener
            jQuery(String.format("%s .status .icon, %s .status .text span", selector, selector)).unbind("click.status").bind(
              'click.status', jQuery.proxy(function( )
              {
                // Should be used like this:
                // tuDispatcher.trigger( 'dashboard', obj.button.target );
                this.send(obj.button.target);
              }, this));

            // set item-onclick in panel6 to same target like the pillar buttons
            jQuery(String.format("#panel6 a#%s", obj.pillar)).unbind('click.status').bind("click.status", jQuery.proxy(function( )
            {
              this.send(obj.button.target);
            }, this));
            var configContainer = jQuery(String.format("#panel6 a#%s", obj.pillar)).parent( )
                .find(".pillarcontent-config-button-container");

            if (configContainer.length !== undefined && configContainer.length !== 0) {
              configContainer.unbind("click.status").bind("click.status", jQuery.proxy(function( )
              {
                this.send(obj.config.target);
              }, this));
            }

          }

          // tooltips of performance pillar depends on the target
          if (obj.pillar === 'performance') {
            jQuery(String.format("%s button.button", selector)).attr(
              {
                tooltip : ( obj.button.target.indexOf("#") === 0 ) ? obj.button.target.substring(1).replace(":", "-") : obj.button.target
                    .replace(":", "-")
              });
          }

          // bind button to target
          jQuery(String.format("%s button.button", selector)).unbind('click.status').bind('click.status', jQuery.proxy(function( )
          {
            // Should be used like this:
            // tuDispatcher.trigger( 'dashboard', obj.button.target );
            this.send(obj.button.target);
          }, this));

          cfgButton = jQuery(String.format("%s button.config", selector));
          if (cfgButton !== undefined && cfgButton !== null && obj.config !== undefined && obj.config.target !== undefined
            && obj.config.target.length !== undefined && obj.config.target.length !== 0)
          {
            // tooltips of performance pillar depends on the target
            cfgButton.attr({
              tooltip : ( obj.config.target.indexOf("#") === 0 ) ? obj.config.target.substring(1).replace(":", "-") : obj.config.target
                  .replace(":", "-")
            });

            // if config button exists and if we have target data for them
            cfgButton.unbind("click.status").bind("click.status", jQuery.proxy(function( )
            {
              // Should be used like this:
              // tuDispatcher.trigger( 'dashboard', obj.button.target );
              this.send(obj.config.target);
            }, this));
            cfgButton.removeClass("no-display");
          } else {
            if (cfgButton !== undefined && cfgButton !== null) {
              // hide if config button exists cause we have no target data for them
              console.warn("Hiding config button cause there was no target data.");
              cfgButton.addClass("no-display");
            }
          }
          return this;
        },

        /**
         * Updates the last used settings in Styler on panel 5
         * 
         * @param target
         * @param linkList
         * @returns {TuneUpIntegrator}
         */
        updateStyleSettings : function(target, linkList)
        {
          console.debug("TuneUpIntegrator.updateStyleSettings(", arguments, ")");
          var targetObj = jQuery(String.format(".pillar .text > .%s", target)), i = 0, l = linkList.length, obj = div = null, imgPath = ( target == 'repair' )
            ? 'panel4' : 'panel5';
          if (targetObj === undefined || targetObj === null || ( l < 1 )) {
            console.warn("Cannot update style-settings.");
            return this;
          }
          targetObj.html("");
          for (; i < l; i++) {
            obj = linkList[ i ];
            // .pillar > .text .used-settings.%s
            targetObj.append(jQuery(document.createElement('div')).css({
              backgroundImage : String.format("url('images/%s/%s')", imgPath, obj.image),
              cursor : 'pointer'
            }).attr({
              link : obj.link
            }).unbind("click.deeplink").bind("click.deeplink", jQuery.proxy(function(evt)
            {
              this.send(jQuery(evt.currentTarget).attr("link"));
            }, this)).append(jQuery(document.createElement('a')).text(obj.text)));
          }

        },

        /**
         * Update the Program Deactivator info-text on panel 2
         * 
         * @param type
         * @param text
         * @returns {TuneUpIntegrator}
         */
        updateSystemload : function(type, text)
        {
          console.debug("TuneUpIntegrator.updateSystemload(", arguments, ")");
          jQuery(String.format("#panel2 .pillar > .text.%s", type)).html(text);
          return this;
        },

        /**
         * Updates the Gain Disk Space information on panel 3
         * 
         * @param drives
         * @returns {TuneUpIntegrator}
         */
        gdsUpdateDriveInfo : function(drives)
        {
          console.debug("TuneUpIntegrator.gdsUpdateDriveInfo(", arguments, ")");

          // Get a reference to the HTML select list containing all drives
          var selectList = jQuery('#gaindiskspace'), oldContents = oldRootPath = selectedOption = htmlFilter = newContents = null, i = 0;
          if (!selectList.length) {
            console.error('Could not find HTML select list "gaindiskspace".');
            return this;
          }

          // Serialize old selectList contents to be able to detect changes
          oldContents = selectList.html( );

          // Remember the current selection (if any)
          oldRootPath = selectList.children('option[selected]').attr('rootpath');

          // Remove all options from the HTML select list
          selectList.children('option').remove( );

          // Now go through the list "drives" and add all drives as new options
          // to the HTML select list
          for (; i < drives.length; i++) {
            selectList.append(String.format(
              '<option class="listitem-drive" rootpath="%s" icon="%s" title="%s" percentused="%s" categories="%s" gds="%s">%s</option>',
              drives[ i ].rootPath, ( drives[ i ].rootPath ? 'icon:24:' + drives[ i ].rootPath : '../ico_alpha_MultipleDrives_48x48.png' ),
              String.format("<localized>%s von %s frei</localized>", drives[ i ].freeSpaceStr, drives[ i ].totalSpaceStr), Math
                  .round(drives[ i ].totalSpace ? 100 - ( 100 * drives[ i ].freeSpace / drives[ i ].totalSpace ) : 0), escape(JSON
                  .stringify(drives[ i ].categories)), escape(JSON.stringify(drives[ i ])), drives[ i ].displayName));
          }

          // Determine the drive option that should be selected:
          // Try to find the previously selected option first
          selectedOption = selectList.children(String.format('option[rootpath="%s"]', oldRootPath));
          // If the previously selected option does not exist, use the first one
          if (!selectedOption.length) {
            selectedOption = selectList.children('option:first-child');
          }
          if (selectedOption.length) {
            // Now mark the chosen option as "selected"
            selectedOption.attr('selected', 'selected');
            // Add special "tuselected" attribute to allow selection change
            // detection through a newContents vs. oldContents comparison
            selectedOption.attr('tuselected', '1');

            // Trigger the gdsDriveSelected event to ensure that the detail texts
            // on the category buttons are properly updated
            tuDispatcher.trigger('gdsDriveSelected', selectedOption);
          }

          // Check if the selectList has already been initialized
          if (jQuery('#gaindiskspace-button').length) {
            // Define a filter that will be applied to oldContents and newContents
            // before comparing them. It removes the "categories" attribute from
            // the html code.
            htmlFilter = /\scategories\s*="[^"]*"/g;

            // If the contents of selectList have changed, update the
            // selectmenu UI control with the new options
            newContents = selectList.html( );
            if (newContents.replace(htmlFilter, '') != oldContents.replace(htmlFilter, '')) {
              selectList.selectmenu( );
            }
          }

          return this;
        },

        /**
         * Updates the Free Space Potential information on panel 3
         * 
         * @param drives
         * @returns {TuneUpIntegrator}
         */
        gdsUpdateFreeSpacePotential : function(gds)
        {
          console.debug("TuneUpIntegrator.gdsUpdateFreeSpacePotential(", gds, ")");
          // Go through the list "drives" and get the total free space in bytes
          var freeSpace = 0, freeSpaceText = "";

          // calculate the free space in MB
          freeSpace = gds.gainableSpace / 1048576.0; // freeSpace / (1024 * 1024)

          jQuery('.discspace-potential .summary').html(gds.gainableSpaceStr);

          // base values for calculation
          var hourOfVideoInMB = 2073.6, songInMB = 2.0, pictureInMB = 0.3;

          freeSpaceText = "<localized>Schafft Platz für:</localized><br />", numberOfVideoHours = this
              .roundFreeSpaceEquivalentValue(freeSpace / hourOfVideoInMB), numberOfSongs = this.roundFreeSpaceEquivalentValue(freeSpace
            / songInMB), numberOfPictures = this.roundFreeSpaceEquivalentValue(freeSpace / pictureInMB);

          // if space >= 1 hour of video, show videos
          if (freeSpace >= hourOfVideoInMB) {
            freeSpaceText = freeSpaceText
              + String.format("%s,<br /><localized>%d Fotos oder<br />%d Lieder</localized>", String.format(numberOfVideoHours == 1
                ? "<localized>%d Stunde Video</localized>" : "<localized>%d Stunden Video</localized>", numberOfVideoHours),
                numberOfPictures, numberOfSongs);
            // if space >= 20 songs but < 1 hour of video, show songs & photos
          } else if (freeSpace >= ( 20.0 * songInMB )) {
            freeSpaceText = freeSpaceText
              + String.format("<localized>%d Fotos oder<br />%d Lieder</localized>", numberOfPictures, numberOfSongs);
            // if space < 20 songs but >= 2 photos, show photos only
          } else if (freeSpace >= ( 2.0 * pictureInMB )) {
            freeSpaceText = freeSpaceText + String.format("<localized>%d Fotos</localized>", numberOfPictures);
            // if space < 2 photos, don't show anything
          } else {
            // do nothing
            freeSpaceText = '';
          }
          jQuery('.discspace-potential .detail').html(freeSpaceText);
          return this;
        },

        /**
         * Round the number of video hours, songs or photos to the appropriate value
         * 
         * @param Float
         *          value
         * @returns Integer result
         */
        roundFreeSpaceEquivalentValue : function(value)
        {
          console.debug("TuneUpIntegrator.roundFreeSpaceEquivalentValue() called with value=" + value);
          // number of significant figures
          var numberSF;

          // remove the decimal part (careful to round down)
          value = Math.floor(value);

          if (value >= 1000) {
            // if number is 1000 or more round to 3sf
            numberSF = 3;
          } else if (( value >= 100 ) && ( value < 1000 )) {
            // if number is between 100 & 1000 round to 2sf
            numberSF = 2;
          } else {
            // if value < 100 do nothing
            console.debug("TuneUpIntegrator.roundFreeSpaceEquivalentValue() returning value=" + value);
            return value;
          }

          // round down to the number of significant figures
          var result = Math.pow(10, ( ( value + '' ).length - numberSF ))
            * Math.floor(value / Math.pow(10, ( ( value + '' ).length - numberSF )));
          console.debug("TuneUpIntegrator.roundFreeSpaceEquivalentValue() returning value=" + result);
          return result;
        },

        /**
         * Updates the Gain Disk Space category buttons on panel 3
         * 
         * @param selectedOption
         * @returns {TuneUpIntegrator}
         */
        gdsDriveSelected : function(selectedOption)
        {
          console.debug("TuneUpIntegrator.gdsDriveSelected(", selectedOption, ")");

          // Initialize the categories array with status "invalid" to ensure
          // that all buttons are updated even if selectedOption is invalid
          // or the JSON parsing fails
          var categories = [
            {
              categoryId : "unneeded-files",
              scanStatus : "invalid"
            }, {
              categoryId : "backups",
              scanStatus : "invalid"
            }, {
              categoryId : "windows-functions",
              scanStatus : "invalid"
            }
          ], gds = null;

          // Now try to replace the categories array with valid data
          if (selectedOption && selectedOption.length) {
            try {
              // Try to parse the JSON data attached to the HTML option node
              categories = JSON.parse(unescape(selectedOption.attr('categories')));
              gds = JSON.parse(unescape(selectedOption.attr('gds')));
            } catch (err) {
              console.error('Failed to parse JSON data attached to drive item.');
            }
          } else {
            console.error('The selectedOption passed to gdsDriveSelected is invalid.');
          }

          // Refresh contents of category buttons
          var catButton = catDetail = tmplName = tmplText = placeholderMatch = placeholderCount = oldRootPath = newRootPath = null, i = 0;
          for (; i < categories.length; i++) {
            if (categories[ i ].scanStatus == 'invalid') {
              continue;
            }
            catButton = jQuery('#gds-' + categories[ i ].categoryId);
            catDetail = catButton.find('.detail');
            if (catButton.length && catDetail.length) {
              // Find the detail text template for the category scan status
              tmplName = '.' + categories[ i ].scanStatus;
              if (categories[ i ].scanStatus == 'finished') {
                tmplName += ( categories[ i ].elementCount == 1 ? '.single' : '.multiple' );
              }
              tmplText = catButton.find(String.format('.detail-template %s', tmplName)).text( );

              if (categories[ i ].elementCount == 1) {
                catDetail.text(String.format("%s, %s", tmplText, categories[ i ].gainableSpaceStr));
              } else {
                catDetail.text(String.format("%s, %s", String.format(tmplText, categories[ i ].elementCount),
                  categories[ i ].gainableSpaceStr));
              }
            } else {
              console.error('Button or detail div for category "' + categories[ i ].categoryId + '" is missing in HTML code.');
            }
          }

          // If the selected drive has changed since the last call (if any),
          // inform Integrator about the drive selection
          oldRootPath = jQuery('#gaindiskspace').attr('selrootpath');
          newRootPath = selectedOption.attr('rootpath');
          if (newRootPath != oldRootPath) {
            jQuery('#gaindiskspace').attr('selrootpath', newRootPath);
            this.send("#gds-drive-selected:" + newRootPath);
          }

          // update the text that says how many videos, pictures, songs the free space corresponds to
          for (i = 0; i < categories.length; i++) {
            // dont update unless the scan is still in progress
            if (categories[ i ].scanStatus != 'finished') {
              return this;
            }
          }
          this.gdsUpdateFreeSpacePotential(gds);
          return this;
        },

        /**
         * Update the Live-Optimization text
         * 
         * @param type
         * @param value
         */
        updateLiveOptimization : function(type, value)
        {
          console.debug("TuneUpIntegrator.updateLiveOptimization(", arguments, ")");
          jQuery(String.format('.pillar.%s > .text.%s', type, type)).html(
            ( value == 2 ) ? "<span class=\"on\"><localized>Beide Optionen der Live-Optimierung sind aktiviert.</localized></span>"
              : ( ( value == 1 ) ? "<span class=\"on\"><localized>Die Live-Optimierung ist teilweise aktiviert.</localized></span>"
                : "<span class=\"off\"><localized>Die Live-Optimierung ist deaktiviert.</localized></span>" ));
        }
      });

/*
 * NAB 08.06.2011: this variable will be used to handle the pressed/clicked state of a button if the mouse was outed and
 * entered again on an element; the value is false if mousebutton is not pressed or object with coords of the element
 * where mousebutton was pressed
 */
var mouseDownPressed = false;

jQuery(document)
    .ready(
      function($)
      {

        window.tuIntegrator = new TuneUpIntegrator( );

        // This one will select all elements where a 'tooltip'-Attribute is present and will use their
        // content to trigger the tooltip.
        $("[tooltip]").not('[class~="tooltipped"]').addClass('tooltipped').mouseenter(function( )
        {
          if (mouseDownPressed) {
            return;
          }
          var tooltip = null;
          if (( tooltip = jQuery(this).attr("tooltip") ) !== null) {
            // Don't show tooltips if we are just launching a module
            if ($('#content').is('.launching')) {
              return;
            }

            tuDispatcher.trigger("tooltip", 1, tooltip);
          }
        }).mouseleave(function( )
        {
          var tooltip = null;
          if (( tooltip = jQuery(this).attr("tooltip") ) !== null) {
            tuDispatcher.trigger("tooltip", 0, tooltip);
          }
        });
        /*
         * Tab-function to navigate via tabulator throught the integrator:
         */
        // $( '[tabindex]' ).each( function( ) {
        // $( this ).keydown( function( event ) {
        // if ( event.keyCode == 13 ) // on
        // return
        // {
        // $( this ).click( );
        // }
        // } ).focus( function( ) {
        // if ( $.keyboardNavigation ) {
        // $( this ).toggleOutline( true );
        // } else {
        // $( this ).toggleOutline( false );
        // }
        // } ).blur( function( ) {
        // $( this ).toggleOutline( false );
        // } ).mousedown( function( ) {
        // $( this ).toggleOutline( false );
        // } );
        // } );#
        // set mouseDownPressed to false if mousebutton was released
        $('#content').bind('mouseup', function(event)
        {
          if (( event.button == 0 ) || ( event.button == 1 )) {
            mouseDownPressed = false;
          }
        });
        $('body').mouseleave(function( )
        {
          mouseDownPressed = false;
        });

        /**
         * stuff we need for panel 1
         */
        jQuery(".pillar-button").bind("mouseover mouseout", function(evt)
        {
          jQuery(this).toggleClass("hover");
        }).bind("mousedown mouseup", function(evt)
        {
          if (evt.type == "mousedown") {
            jQuery(this).addClass("active");
          } else {
            jQuery(this).removeClass("active");
            // jQuery(this).find("button.button").trigger("click");
          }
        }).bind("mouseleave", function( )
        {
          jQuery(this).removeClass("active hover");
        });

        jQuery(".pillar-button button.config").bind("mouseover mouseout", function(evt)
        {
          jQuery(this).toggleClass("hover").parent( ).removeClass("active hover");

        }).bind("mousedown mouseup", function(evt)
        {
          if (evt.type == "mousedown") {
            jQuery(this).addClass("active").parent( ).removeClass("active hover");
          } else {
            jQuery(this).removeClass("active").parent( ).removeClass("active hover");
          }
        }).bind("mouseleave", function( )
        {
          jQuery(this).removeClass("active hover").parent( ).removeClass("active hover");
        });

        jQuery(".pillar-button button.config > img")
            .bind(
              // create an area map for each config image and add the maps to their image
              "load",
              function(evt)
              {
                var image = jQuery(this), button = image.parent( );
                var id = String.format("config-image-%d-%d-%d-%d", Math.random( ) * Math.pow(10, 17), Math.random( ) * Math.pow(10, 17),
                  Math.random( ) * Math.pow(10, 17), Math.random( ) * Math.pow(10, 17));
                var map = jQuery(document.createElement("map"))
                    .attr({
                      name : id
                    })
                    .append(jQuery(document.createElement("area")).attr({
                      shape : "poly",
                      coords : "0, 0, 0, 21, 21, 0"
                    }))
                    .bind("click",
                    // button
                    jQuery.proxy(function(evt)
                    {
                      // delegate all events to the button-element
                      jQuery(this).parent( ).parent( ).find("button.button").triggerHandler(evt.type);
                      evt.stopPropagation( );
                    }, this))
                    .append(
                      // config-button
                      jQuery(document.createElement("area"))
                          .attr({
                            shape : "poly",
                            coords : "0, 21, 21, 21, 21, 0"
                          })
                          .bind(
                            "blur focus focusin focusout click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave keydown keypress keyup error",
                            jQuery.proxy(function(evt)
                            {
                              // delegate all events to the config-element
                              jQuery(this).parent( ).triggerHandler(evt.type);
                              evt.stopPropagation( );
                            }, this)));

                button.parent( ).prepend(map);
                image.attr({
                  useMap : "#" + id
                });
              });

      });

// coords of element where mousebutton was pressed
function getClientRect(elt)
{
  var rect = elt.getBoundingClientRect( ), x = rect.left, y = rect.top, w = rect.right - rect.left, h = rect.bottom - rect.top;
  var clientRect = {
    "leftmin" : x,
    "topmin" : y,
    "leftmax" : x + w,
    "topmax" : y + h
  };
  return clientRect;
}
// whether the current mouse position is inside of event rectangle
function isMouseInRect(event)
{
  if (!mouseDownPressed) {
    return false;
  }
  var p1 = ( event.pageX < mouseDownPressed.leftmax ) && ( event.pageX >= mouseDownPressed.leftmin );
  var p2 = ( event.pageY <= mouseDownPressed.topmax ) && ( event.pageY >= mouseDownPressed.topmin );
  return ( p1 && p2 );

}

/**
 * @deprecated
 * @param elements
 */
function addHoverPressedEvents(elements)
{
  // NAB: seems to be used for $('#turbomode .settings') and
  // $('#rescuecenter')
  elements.mousedown(function(e)
  {
    if (e.button == 1) {
      $(this).addClass("locked");
      $(this).find(".hover").stop(false, true).show( );
      $(this).find(".pressed").stop(false, true).show( );
    }
  });

  elements.mouseenter(function( )
  {
    if (mouseDownPressed) {
      return;
    }
    $(this).addClass("active");
    $(this).find(".hover").stop(false, true).show( );
    if (!$(this).hasClass("locked")) {
      $(this).find(".pressed").stop(false, true).hide( );
    }
  });

  elements.mouseleave(function( )
  {
    $(this).removeClass("active");
    $(this).find(".hover").stop(false, true).hide( );
    if (!$(this).hasClass("locked")) {
      $(this).find(".pressed").stop(false, true).hide( );
    }
  });
}

/**
 * @deprecated
 */
function slideLeft( )
{
  // NAB 15.06.2011: this function seems to be not in use?
  $("#maincontent").animate({
    left : "-1000px"
  }, 750);
}

/**
 * @deprecated
 * @param selector
 */
function activateTooltip(selector)
{
  $(selector).not('[class~="tooltipped"]').addClass('tooltipped').mouseenter(function( )
  {
    if (mouseDownPressed) {
      return;
    }
    if (getTooltipId($(this))) {
      // Don't show tooltips if we are just launching a module
      if ($('#content').is('.launching')) {
        return;
      }
      tuDispatcher.trigger('tooltip', 1, getTooltipId($(this)));
    }
  }).mouseleave(function( )
  {
    if (getTooltipId($(this))) {
      tuDispatcher.trigger('tooltip', 0, getTooltipId($(this)));
    }
  });
}

/**
 * @deprecated
 * @param element
 * @returns
 */
function getTooltipId(element)
{
  if (element.attr('tooltip')) {
    return element.attr('tooltip');
  } else if (element.attr('onclick')) {
    var onclick = String(element.attr('onclick')), matches = onclick.match(/call\((.*)\)/), tooltipId = id = null;
    if (matches) {
      // strip quotes
      matches[ 1 ] = matches[ 1 ].substr(1, ( matches[ 1 ].length - 2 ));
      // stip hash
      tooltipId = matches[ 1 ].substr(1, ( matches[ 1 ].length - 1 ));
      if (tooltipId.substr(0, 7) == 'launch:') {
        tooltipId = tooltipId.substr(7, ( tooltipId.length - 7 ));
      }
      return tooltipId.replace(/:/g, '-');
    } else if (onclick.match(/pillar2/)) {
      return getTooltipId($('#pillar2 .pillarlinks a:first'));
    } else if (onclick.match(/pillar3/)) {
      return getTooltipId($('#pillar3 .pillarlinks a:first'));
    } else if (onclick.match(/tab\-3/)) {
      return 'gaindiskspace';
    }
  } else if (element.hasClass('tuindex')) {
    id = element.attr('id');
    if (id.substr(0, 6) == 'helper') {
      id = id.substr(6);
    }
    return id;
  }
}
