(function() {
  var KEY_PREFIX = "linkchecker";
  var KEY_BGC = KEY_PREFIX + "_saved_bgc";

  // http://stackoverflow.com/a/6700/533198
  Object.size = function(obj) {
    var size = 0, key;
    for (key in obj) {
      if (obj.hasOwnProperty(key)) size++;
    }
    return size;
  };

  // Save the job status and notify the the popup
  function updateJobStatus(value) {
    chrome.storage.sync.set({"linkchecker-ongoing": value}, function() {
      chrome.runtime.sendMessage(null, {action: "updatedStatus"});
    });
  }

  function Job(selection) {
    this.selection = selection;
    this.anchors = [];
    this.uris = [];
    this.checked = {};
    this.bad = 0;

    this.addSelection(selection);
  }
  Job.prototype = new Object();
  Job.prototype.begin = function() {
    var uris = this.uris;
    var checkMessage = {
      "base": window.location.href,
      "action": "checkLinks",
      "uris": uris
    };
    for (var i = 0; i < uris.length; i++) {
      var anchor = this.anchors[i];
      anchor.data(KEY_BGC, anchor.css('background-color'));
      anchor.css("background-color", "#ffa");
    }
    chrome.extension.sendMessage(checkMessage);
  };
  Job.prototype.addSelection = function(selection) {
    var self = this;
    selection.each(function() { self.addAnchor($(this)); });
  };
  Job.prototype.addAnchor = function(anchor) {
    var uri = anchor.attr('href');
    if (!uri) {
    } else {
      this.anchors.push(anchor);
      this.uris.push(uri);
    }
  };
  Job.prototype.addStatus = function(id, reqstat) {
    var anchor = this.anchors[id];
    var uri = this.uris[id];
    var bad = false;
    if (200 <= reqstat && reqstat < 400) {
      anchor.css("background-color", "#afa");
    } else {
      var msg = "",
          color = "#faa";
      if (reqstat == 0) {
        // UNSENT, OPENED or error
        msg = "XHR timed out or UNSENT/OPENED";
        color = "#aaf";
        bad = true;
      } else if (reqstat == -1) {
        // blacklisted
        msg = "blacklisted link";
        color = "#ddd";
      } else if (reqstat == 1) {
        // no URI given
        msg = "no URI given";
        color = "#aaf";
      } else if (reqstat == 2) {
        // invalid action
        msg = "invalid method HEAD";
        color = "#aaf";
      } else {
        msg = reqstat;
        color = "#faa";
      }

      if (anchor === undefined) {
        console.error('LC: link', id, 'is undefined');
      } else {
        console.log("LC: " + msg, anchor[0]);
        anchor.css("background-color", color);
      }
    }
    this.checked[id] = bad;
  };
  Job.prototype.countBad = function() {
    var count = 0;
    for (var i = 0; i < this.checked.length; i++) {
      if (this.checked[i]) {
        count += 1;
      }
    }
    return count;
  };
  Job.prototype.percentComplete = function() {
    return Math.round(
        Object.size(this.checked) / Object.size(this.anchors) * 100);
  };
  Job.prototype.clearMarkings = function() {
    this.selection.each(function () {
      $(this).css('background-color', $(this).data(KEY_BGC));
    });
  };
  Job.prototype.updateChecked = function() {
    var percent = this.percentComplete();
    var badge = percent + "%";
    var color = "#000";

    if (percent >= 100) {
      var bad = this.countBad();
      if (bad > 0) {
        badge = ":(";
        color = "#f44";
      } else {
        badge = ":)";
        color = "#4f4";
      }
      updateJobStatus(bad);
    }
    chrome.runtime.sendMessage(
      null, {action: "setBadge", text: badge, color: color});
  };

  var job;
  chrome.runtime.onMessage.addListener(function(request, sender) {
    if (request.action == "startCheck") {
      if (job !== undefined) {
        job.clearMarkings();
        delete job;
      }
      anchors = $("a");
      job = new Job(anchors);
      job.begin();
      updateJobStatus(-1);
    } else if (request.action == "linkStatuses") {
      var lstatuses = request.statuses;
      for (var i = 0; i < lstatuses.length; i++) {
        var lstatus = lstatuses[i];
        job.addStatus(lstatus.id, lstatus.status);
      }
      job.updateChecked();
    } else if (request.action == "clearMarkings") {
      if (job) {
        job.clearMarkings();
      }
      updateJobStatus(null);
    }
  });
  window.onbeforeunload = function() {
    chrome.storage.sync.set({"linkchecker-ongoing": null});
  };
})();