// An ApplicableList is a structure used to keep track of which rulesets
// were applied, and which ones weren't but might have been, to the contents
// of a given page (top level nsIDOMWindow)

var serial_number = 0;

function ApplicableList(logger, uri) {
  this.log = logger;
  this.uri = uri.clone();
  if (!this.uri) {
    this.log(WARN,"NULL CLONING URI " + doc);
    if (uri) {
      this.log(WARN,"NULL CLONING URI " + uri.spec);
    }
  }
  this.home = uri.spec; // what doc we're housekeeping for
  this.active = {};
  this.breaking = {}; // rulesets with redirection loops
  this.inactive = {};
  this.moot={};  // rulesets that might be applicable but uris are already https
  this.all={};  // active + breaking + inactive + moot
  serial_number += 1;
  this.serial = serial_number;
  this.log(DBUG,"Alist serial #" + this.serial + " for " + this.home);
}

ApplicableList.prototype = {

  empty: function() {
    // Empty everything, used when toggles occur in order to ensure that if
    // the reload fails, the resulting list is not eroneous
    this.active = {};
    this.breaking = {}; 
    this.inactive = {};
    this.moot={};  
    this.all={};  
  },

  active_rule: function(ruleset) {
    this.log(INFO,"active rule " + ruleset.name +" in "+ this.home +" -> " +
             this.uri.spec+ " serial " + this.serial);
    this.active[ruleset.name] = ruleset;
    this.all[ruleset.name] = ruleset;
  },

  breaking_rule: function(ruleset) {
    this.log(NOTE,"breaking rule " + ruleset.name +" in "+ this.home +" -> " +
             this.uri.spec+ " serial " + this.serial);
    this.breaking[ruleset.name] = ruleset;
    this.all[ruleset.name] = ruleset;
  },

  inactive_rule: function(ruleset) {

    this.log(INFO,"inactive rule " + ruleset.name +" in "+ this.home +" -> " +
             this.uri.spec+ " serial " + this.serial);
    this.inactive[ruleset.name] = ruleset;
    this.all[ruleset.name] = ruleset;
  },

  moot_rule: function(ruleset) {
    this.log(INFO,"moot rule " + ruleset.name +" in "+ this.home + " serial " + this.serial);
    this.moot[ruleset.name] = ruleset;
    this.all[ruleset.name] = ruleset;
  },

  dom_handler: function(operation,key,data,src,dst) {
    // See https://developer.mozilla.org/En/DOM/UserDataHandler
    if (src && dst) 
      dst.setUserData(key, data, this.dom_handler);
  },

  populate_list: function() {
    // The base URI of the dom tends to be loaded from some /other/
    // ApplicableList, so pretend we're loading it from here.
    HTTPSEverywhere.instance.https_rules.rewrittenURI(this, this.uri);
    this.log(DBUG, "populating using alist #" + this.serial);
  },

  populate_menu: function(document, menupopup, weird) {
    this.populate_list();
    this.document = document;
    
    var https_everywhere = CC["@eff.org/https-everywhere;1"].getService(Components.interfaces.nsISupports).wrappedJSObject;   
   
    // get the menu popup
    this.menupopup = menupopup;

    // empty it all of its menuitems
    while(this.menupopup.firstChild.tagName != "menuseparator") {
      this.menupopup.removeChild(this.menupopup.firstChild);
    }
    
    // add global enable/disable toggle button  
    var strings = document.getElementById("HttpsEverywhereStrings");
    
    var enableLabel = document.createElement('menuitem');
    var text = strings.getString("https-everywhere.menu.globalDisable");
    if(!https_everywhere.prefs.getBoolPref("globalEnabled"))
      text = strings.getString("https-everywhere.menu.globalEnable");

    enableLabel.setAttribute('label', text);
    enableLabel.setAttribute('command', 'https-everywhere-menuitem-globalEnableToggle');
    this.prepend_child(enableLabel);

    // add the label at the top
    var any_rules = false;
    for(var x in this.all) {
      any_rules = true;  // how did JavaScript get this ugly?
      break;
    }
    // This label just describes the fact that the items underneath it enable
    // and disable rules.
    var label = document.createElement('menuitem');
    label.setAttribute('label', strings.getString('https-everywhere.menu.enableDisable'));
    label.setAttribute('disabled', 'true');
    label.setAttribute('class', 'menuitem-non-iconic');
    label.setAttribute('style', 'color:#000000;');
    var label2 = false;
    if (!any_rules) {
      label2 = document.createElement('menuitem');
      if (!weird) text = strings.getString('https-everywhere.menu.noRules');
      else        text = strings.getString('https-everywhere.menu.unknownRules');
      label2.setAttribute('label', text);
      label2.setAttribute('command', 'https-everywhere-menuitem-preferences');
      label2.setAttribute('style', 'color:#909090;');
    }

    // create a commandset if it doesn't already exist
    this.commandset = document.getElementById('https-everywhere-commandset');
    if(!this.commandset) {
      this.commandset = document.createElement('commandset');
      this.commandset.setAttribute('id', 'https-everywhere-commandset');
      var win = document.getElementById('main-window');
      win.appendChild(this.commandset);
    } else {
      // empty commandset
      while(this.commandset.firstChild) 
        this.commandset.removeChild(this.commandset.firstChild);
    }

    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                     .getService(Components.interfaces.nsIWindowMediator);
                     
    var browser = wm.getMostRecentWindow("navigator:browser").gBrowser.selectedBrowser;
    var location = browser.currentURI.asciiSpec; //full url, including about:certerror details
    
    if(location.substr(0, 6) == "about:"){
      //"From" portion of the rule is retrieved from the location bar via document.getElementById("urlbar").value
        
      var fromHost = document.getElementById("urlbar").value;  
      
      //scheme must be trimmed out to check for applicable rulesets       
      if(fromHost.indexOf("://") != -1)
        fromHost = fromHost.substr(fromHost.indexOf("://") + 3, fromHost.length);
          
      //trim off any page locations - we only want the host - e.g. domain.com
      if(fromHost.indexOf("/") != -1)
        fromHost = fromHost.substr(0, fromHost.indexOf("/"));
                     
      // Search for applicable rulesets for the host listed in the location bar
      var plist = HTTPSRules.potentiallyApplicableRulesets(fromHost);     
      for (var i = 0 ; i < plist.length ; i++){
        //For each applicable rulset, determine active/inactive, and append to proper list.
        var ruleOn = false;
        try {
          if(https_everywhere.rule_toggle_prefs.getBoolPref(plist[i].name))
            ruleOn = true;
        } catch(e) {
          if(https_everywhere.https_rules.rulesetsByName[plist[i].name].active)
            ruleOn = true;
        }
        if(ruleOn)
          this.active_rule(plist[i]);
        else
          this.inactive_rule(plist[i]);                   
      }   
    }   
    
    // add all applicable commands
    for(var x in this.breaking) 
      this.add_command(this.breaking[x]); 
    for(var x in this.active) 
      this.add_command(this.active[x]); 
    for(var x in this.moot)
      this.add_command(this.moot[x]);
    for(var x in this.inactive) 
      this.add_command(this.inactive[x]);

    if(https_everywhere.prefs.getBoolPref("globalEnabled")){
       // add all the menu items
       for (var x in this.inactive)
          this.add_menuitem(this.inactive[x], 'inactive');
       // rules that are active for some uris are not really moot
       for (var x in this.moot) 
          if (!(x in this.active))   
              this.add_menuitem(this.moot[x], 'moot');
       // break once break everywhere
       for (var x in this.active) 
          if (!(x in this.breaking))
              this.add_menuitem(this.active[x], 'active');
       for (var x in this.breaking)
          this.add_menuitem(this.breaking[x], 'breaking');
          
       if (label2) this.prepend_child(label2);
       this.prepend_child(label);
    }
    
  },

  prepend_child: function(node) {
    this.menupopup.insertBefore(node, this.menupopup.firstChild);
  },

  add_command: function(rule) {
      var command = this.document.getElementById("https-everywhere-menuitem-rule-toggle-template").cloneNode();
      command.setAttribute('id', JSON.stringify(rule.id)+'-command');
      command.setAttribute('data-id', JSON.stringify(rule.id));
      command.setAttribute('label', rule.name);
      this.commandset.appendChild(command);
  },

  // add a menu item for a rule -- type is "active", "inactive", "moot",
  // or "breaking"

  add_menuitem: function(rule, type) {
    // create the menuitem
    var item = this.document.createElement('menuitem');
    item.setAttribute('command', rule.id+'-command');
    item.setAttribute('class', type+'-item menuitem-iconic');
    item.setAttribute('type', 'checkbox');
    item.setAttribute('label', rule.name);

    // we can get confused if rulesets have their state changed after the
    // ApplicableList was constructed
    if (!rule.active && (type == 'active' || type == 'moot'))
      type = 'inactive';
    if (rule.active && type == 'inactive')
      type = 'moot';
    
    // set the icon
    var image_src;
    if (type == 'active') image_src = 'tick.png';
    else if (type == 'inactive') image_src = 'cross.png';
    else if (type == 'moot') image_src = 'tick-moot.png';
    else if (type == 'breaking') image_src = 'loop.png';
    item.setAttribute('image', 'chrome://https-everywhere/skin/'+image_src);

    // all done
    this.prepend_child(item);
  },

  show_applicable: function() {
    this.log(WARN, "Applicable list number " + this.serial);
    for (var x in this.active) 
      this.log(WARN,"Active: " + this.active[x].name);

    for (var x in this.breaking) 
      this.log(WARN,"Breaking: " + this.breaking[x].name);
  
    for (x in this.inactive) 
      this.log(WARN,"Inactive: " + this.inactive[x].name);

    for (x in this.moot) 
      this.log(WARN,"Moot: " + this.moot[x].name);
    
  }
};

