/*
 JavaScript for Sweety to wrap the standard template around the API.
 */


/**
 * The UI Manager object for setting up the interface.
 * @author Chris Corbyn
 * @constructor
 */
function SweetyUIManager() {
  
  var _this = this;
  
  /** Packages toggled on or off */
  var _pkgs = { };
  
  /** Test cases within packages */
  var _pkgTests = { };
  
  /** An element cache */
  var _cached = { };
  
  /**
   * Initialize the user interface.
   */
  this.initialize = function initialize() {
    _this.showFilterBox();
    _this.loadTestList();
    _this.resetMessageDiv();
    _this.resetTotals();
  }
  
  /**
   * Show or hide an entire package.
   * @param {String} pkg
   * @param {Boolean} onoff
   */
  this.togglePackage = function togglePackage(pkg, onOff) {
    if (typeof _pkgs[pkg] == "undefined") {
      _pkgs[pkg] = true;
    }
    
    //Toggle if not overridden
    _pkgs[pkg] = (typeof onOff == "undefined") ? !_pkgs[pkg] : onOff;
    
    var pkgState = _pkgs[pkg] ? "1" : "0";
    
    var date = new Date();
    date.setTime(date.getTime() + (30 * 24 * 60 * 60 * 1000));
    		
    document.cookie = escape("sweetyPkg" + pkg) + "=" + pkgState +
      "; expires=" + date.toGMTString() +
      "; path=/";
    
    var pkgRegex = new RegExp("^" + pkg + "_[^_]+$");
    for (var testCase in sweetyTestCases) {
      if (testCase.match(pkgRegex)) {
        var testDiv = _getElementById(testCase);
        if (_pkgs[pkg]) {
          if (sweetyTestCases[testCase]) {
            testDiv.style.display = "block";
          }
        } else {
          testDiv.style.display = "none";
        }
      }
    }
    var headerImg;
    if (headerImg = _getElementById("sweety-pkg-img-" + pkg))
    {
      if (_pkgs[pkg]) {
        headerImg.src = "templates/sweety/images/darr.gif";
      } else {
        headerImg.src = "templates/sweety/images/rarr.gif";
      }
    }
  }
  
  /**
   * Enable or disable user input.
   * @param {Boolean} on
   */
  this.allowInteractivity = function allowInteractivity(on) {
    if (!on) {
      _paintFilterDisabled(true);
      _getRunButton().value = "Stop Tests";
      _getRunButton().onclick = function() {
        try {
          SweetyTestRunner.getCurrentInstance().cancelTesting(true);
          _this.allowInteractivity(true);
        } catch (e) { }
      };
      for (var testCase in sweetyTestCases) {
        _getElementById(testCase).onclick = function() {
          return false;
        };
      }
    } else {
      _paintFilterDisabled(false);
      _getRunButton().value = "Run Tests";
      _getRunButton().onclick = function() {
        _this.initialize();
        sweetyRunner.runAll();
      };
      for (var testCase in sweetyTestCases) {
        _getElementById(testCase).onclick = function() {
          _this.initialize();
          sweetyRunner.runTestCase(this.id);
        };
      }
    }
  }
  
  /**
   * Display the filter box.
   */
  this.showFilterBox = function showFilterBox() {
    _getFilter().style.visibility = 'visible';
  }
  
  /**
   * Restore the UI on a new page load or reload.
   */
  this.restore = function restore() {
    for (var testName in sweetyTestCases) {
      var pkgName = _pkgFor(testName);
      if (typeof _pkgTests[pkgName] == "undefined") {
        _pkgTests[pkgName] = { };
      }
      _pkgTests[pkgName][testName] = true;
    }
    this.hideCheckboxes();
    _loadPkgsFromCookie();
    for (var pkg in _pkgs) {
      this.togglePackage(pkg, _pkgs[pkg]);
    }
  }
  
  /**
   * Hide all the checkboxes which are only applicable to the non-JS version.
   */
  this.hideCheckboxes = function hideCheckboxes() {
    var inputs = document.getElementsByTagName("input");
    for (var i = 0, len = inputs.length; i < len; i++) {
      if (inputs.item(i).className == "sweety-check") {
        inputs.item(i).style.display = "none";
      }
    }
    delete inputs;
  }
  
  /**
   * Load the available test case list in the UI.
   */
  this.loadTestList = function loadTestList() {
    var caseBox = _getListContainer();

    //Show or hide any tests
    for (var testCase in sweetyTestCases) {
      var pkgName = _pkgFor(testCase);
      _pkgTests[pkgName][testCase] = sweetyTestCases[testCase];
      
      this.paintTestCaseIdle(testCase);
      
      var pkg = _pkgFor(testCase);
      this.paintPkgIdle(pkg);
      
      var testDiv = _getElementById(testCase);
      
      //Make it look idle
      testDiv.className = "sweety-test sweety-idle";
      
      if (sweetyTestCases[testCase]) {
        if (typeof _pkgs[pkg] == "undefined") {
          testDiv.style.display = "block";
        } else if (_pkgs[pkg]) {
          testDiv.style.display = "block";
        }
      } else {
        testDiv.style.display = "none";
      }
    }
    
    //Show or hide any packages
    for (var pkgName in _pkgTests) {
      var display = false;
      var len = 0;
      for (var testCase in _pkgTests[pkgName]) {
        if (_pkgTests[pkgName][testCase]) {
          display = true;
          //break;
          len++;
        }
      }
      this.showPkgCount(pkgName, len);
      this.showHidePkg(pkgName, display);
    }
  }
  
  /**
   * Shows or hides the headers for the given package.
   * @param {String} pkg
   * @param {Boolean} show
   */
  this.showHidePkg = function showHidePkg(pkg, show) {
    var pkgDiv = _getElementById("sweety-package-" + pkg);
    if (show) {
      pkgDiv.style.display = "block";
    } else {
      pkgDiv.style.display = "none";
    }
  }
  
  this.showPkgCount = function showPkgCount(pkg, n) {
    var countBox = _getElementById("sweety-pkg-count-" + pkg);
    _setContent(countBox, "(" + n + ")");
  }
  
  /**
   * Reset all the aggregate results in the UI.
   */
  this.resetTotals = function resetTotals() {
    _this.paintNumCases(0);
    _this.paintNumRun(0);
    _this.paintNumPasses(0);
    _this.paintNumFails(0);
    _this.paintNumExceptions(0);
    
    _this.paintAllIdle();
  }
  
  /**
   * Paint or unpaint the networking icon to indicate communication with the server.
   * @param {Boolean} on
   */
  this.paintNetworking = function paintNetworking(on) {
    if (on) {
      _getCommIcon().style.display = "block";
    } else {
      _getCommIcon().style.display = "none";
    }
  }
  
  /**
   * Flush the contents of the assertion message area.
   */
  this.resetMessageDiv = function resetMessageDiv() {
    _getMessages().innerHTML = "";
    _getElementById("sweety-smoke-images").innerHTML = "";
  }
  
  /**
   * Marks a given package as running (yellow) in the UI.
   * @param {String} pkg
   */
  this.paintPkgRunning = function paintPkgRunning(pkg) {
    _getElementById("sweety-package-" + pkg).className = "sweety-package-header sweety-running";
  }
  
  /**
   * Marks a given package as idle (grey) in the UI.
   * @param {String} pkg
   */
  this.paintPkgIdle = function paintPkgIdle(pkg) {
    _getElementById("sweety-package-" + pkg).className = "sweety-package-header sweety-pkg-idle";
  }
  
  /**
   * Marks a given package as passed (green) in the UI.
   * @param {String} pkg
   */
  this.paintPkgPassed = function paintPkgPassed(pkg) {
    _getElementById("sweety-package-" + pkg).className = "sweety-package-header sweety-pass";
  }
  
  /**
   * Marks a given package as failed (red) in the UI.
   * @param {String} pkg
   */
  this.paintPkgFailed = function paintPkgFailed(pkg) {
    _getElementById("sweety-package-" + pkg).className = "sweety-package-header sweety-fail";
  }
  
  /**
   * Marks a given test case as running (yellow) in the UI.
   * @param {String} testCase
   */
  this.paintTestCaseRunning = function paintTestCaseRunning(testCase) {
    _getElementById(testCase).className = "sweety-test sweety-running";
  }
  
  /**
   * Marks a given test case as idle (grey) in the UI.
   * @param {String} testCase
   */
  this.paintTestCaseIdle = function paintTestCaseIdle(testCase) {
    _getElementById(testCase).className = "sweety-test sweety-idle";
  }
  
  /**
   * Marks a given test case as failed (red) in the UI.
   * @param {String} testCase
   */
  this.paintTestCaseFailed = function paintTestCaseFailed(testCase) {
    _getElementById(testCase).className = "sweety-test sweety-fail";
  }
  
  /**
   * Marks a given test case as passed (green) in the UI.
   * @param {String} testCase
   */
  this.paintTestCasePassed = function paintTestCasePassed(testCase) {
    _getElementById(testCase).className = "sweety-test sweety-pass";
  }
  
  /**
   * Paints a skipped testcase message to the message area.
   * @param {String} message
   * @param {String} path
   */
  this.paintSkip = function paintSkip(message, path) {
    var skipDiv = document.createElement("div");
    skipDiv.className = "sweety-message";
    
    var skipLabel = _createSkipLabel("Skip");
    skipDiv.appendChild(skipLabel);
    
    var messageSpan = document.createElement("strong");
    _setContent(messageSpan, ": " + message);
    skipDiv.appendChild(messageSpan);
    
    var pathDiv = _createPathDiv(path);
    skipDiv.appendChild(pathDiv);
    
    _getMessages().appendChild(skipDiv);
  }
  
  /**
   * Paints an unexpected exception notice to the message area.
   * @param {String} message
   * @param {String} path
   */
  this.paintException = function paintException(message, path) {
    var exceptionDiv = document.createElement("div");
    exceptionDiv.className = "sweety-message";
    
    var exceptionLabel = _createFailLabel("Exception");
    exceptionDiv.appendChild(exceptionLabel);
    
    var messageSpan = document.createElement("strong");
    _setContent(messageSpan, ": " + message);
    exceptionDiv.appendChild(messageSpan);
    
    var pathDiv = _createPathDiv(path);
    exceptionDiv.appendChild(pathDiv);
    
    _getMessages().appendChild(exceptionDiv);
  }
  
  /**
   * Paints a failed assertion message to the message area.
   * @param {String} message
   * @param {String} path
   */
  this.paintFail = function paintFail(message, path) {
    var failDiv = document.createElement("div");
    failDiv.className = "sweety-message";
    
    var failLabel = _createFailLabel("Fail");
    failDiv.appendChild(failLabel);
    
    var messageSpan = document.createElement("span");
    _setContent(messageSpan, ": " + message);
    failDiv.appendChild(messageSpan);
    
    var pathDiv = _createPathDiv(path);
    failDiv.appendChild(pathDiv);
    
    _getMessages().appendChild(failDiv);
  }
  
  /**
   * Paints dump() output to the message area.
   * @param {String} output
   * @param {String} path
   */
  this.paintOutput = function paintOutput(output, path) {
    var refs;
    if (refs = /^\{image @ (.*?)\}$/.exec(output)) {
      this.paintSmokeImage(refs[1]);
    } else {
      var outputPane = document.createElement("pre");
      outputPane.className = "sweety-raw-output";
      _setContent(outputPane, output);
    
      _getMessages().appendChild(outputPane);
    }
  }
  
  this.paintSmokeImage = function paintSmokeImage(imageSrc) {
    var imagePane = _getElementById("sweety-smoke-images");
    var smokeImg = document.createElement("img");
    smokeImg.title = 'Smoke test image';
    smokeImg.src = imageSrc;
    smokeImg.style.cursor = 'pointer';
    smokeImg.onclick = function() { window.open(imageSrc); };
    imagePane.appendChild(smokeImg);
  }
  
  /**
   * Paints an internal message to the message area.
   * @param {String} message
   * @param {String} path
   */
  this.paintMessage = function paintMessage(message) {
    var messageDiv = document.createElement("div");
    messageDiv.className = "sweety-message sweety-running";
    _setContent(messageDiv, message);
    
    _getMessages().appendChild(messageDiv);
  }
  
  /**
   * Paints the current number of test cases to the summary bar.
   * @param {Number} num
   */
  this.paintNumCases = function paintNumCases(num) {
    _setContent(_getCases(), num);
  }
  
  /**
   * Paints the current number of finished test cases to the summary bar.
   * @param {Number} num
   */
  this.paintNumRun = function paintNumRun(num) {
    _setContent(_getComplete(), num);
  }
  
  /**
   * Paints the current number of passing assertions to the summary bar.
   * @param {Number} num
   */
  this.paintNumPasses = function paintNumPasses(num) {
    _setContent(_getPasses(), num);
  }
  
  /**
   * Paints the current number of failing assertions to the summary bar.
   * @param {Number} num
   */
  this.paintNumFails = function paintNumFails(num) {
    _setContent(_getFails(), num);
  }
  
  /**
   * Paints the current number of exceptions to the summary bar.
   * @param {Number} num
   */
  this.paintNumExceptions = function paintNumExceptions(num) {
    _setContent(_getExceptions(), num);
  }
  
  /**
   * Paints the summary bar (green) as passed.
   */
  this.paintConclusionPassed = function paintConclusionPassed() {
    _getResultsBar().className = "sweety-pass";
  }
  
  /**
   * Paints the summary bar (red) as failed.
   */
  this.paintConclusionFailed = function paintConclusionFailed() {
    _getResultsBar().className = "sweety-fail";
  }
  
  /**
   * Paints the summary bar (yellow) as running.
   */
  this.paintAllRunning = function paintAllRunning() {
    _getResultsBar().className = "sweety-running";
  }
  
  /**
   * Paints the summary bar (grey) as idle.
   */
  this.paintAllIdle = function paintAllIdle() {
    _getResultsBar().className = "sweety-idle";
  }
  
  /**
   * Puts the filter box in searching L&F.
   */
  this.paintSearching = function paintSearching() {
    _getFilter().className = "sweety-text sweety-waiting";
  }
  
  /**
   * Returns the filter box the idle L&F.
   */
  this.paintSearchComplete = function paintSearchComplete() {
    _getFilter().className = "sweety-text";
  }
  
  /**
   * Apply data to a page element.
   * @param {Element} el
   * @param {String} content
   */
  var _setContent = function _setContent(el, content) {
    if (typeof el.textContent != "undefined") {
      el.textContent = content;
    } else {
      el.innerHTML = content;
    }
  }
  
  /**
   * Create a label used at the start of a message to indicate a skipped test case.
   * @param {String} label
   * @returns HTMLSpanElement
   */
  var _createSkipLabel = function _createSkipLabel(label) {
    var skipLabel = document.createElement("span");
    skipLabel.className = "sweety-skip-text";
    _setContent(skipLabel, label);
    return skipLabel;
  }
  
  /**
   * Create a label used at the start of a message to indicate failure.
   * @param {String} label
   * @returns HTMLSpanElement
   */
  var _createFailLabel = function _createFailLabel(label) {
    var failLabel = document.createElement("span");
    failLabel.className = "sweety-fail-text";
    _setContent(failLabel, label);
    return failLabel;
  }
  
  /**
   * Creates the text which shows the complete pathway to a test method.
   * The path includes all groups, the test case and the test method.
   * @param {String} path
   * @returns HTMLDivElement
   */
  var _createPathDiv = function _createPathDiv(path) {
    var pathDiv = document.createElement("div");
    pathDiv.className = "sweety-test-path";
    _setContent(pathDiv, "in " + path);
    return pathDiv;
  }
  
  var _paintFilterDisabled = function _paintFilterDisabled(disabled) {
    if (disabled) {
      _getFilter().disabled = true;
      _getFilter().className = "sweety-text sweety-disabled";
    } else {
      _getFilter().disabled = false;
      _getFilter().className = "sweety-text";
    }
  }
  
  /**
   * A caching wrapper around document.getElementById().
   * @param {String} id
   * @returns Element
   */
  var _getElementById = function _getElementById(elId) {
    if (!_cached[elId]) {
      _cached[elId] = document.getElementById(elId);
    }
    return _cached[elId];
  }
  
  /**
   * Get the icon which shows network activity.
   * @returns Element
   */
  var _getCommIcon = function _getCommIcon() {
    return _getElementById("sweety-communication");
  }
  
  /**
   * Get the container which holds the list of test cases.
   * @returns Element
   */
  var _getListContainer = function _getListContainer() {
    return _getElementById("sweety-testlist-container");
  }
  
  /**
   * Get the container where all assertion messages go.
   * @returns Element
   */
  var _getMessages = function _getMessages() {
    return _getElementById("sweety-messages");
  }
  
  /**
   * Get the element for number of test cases.
   * @returns Element
   */
  var _getCases = function _getCases() {
    return _getElementById("sweety-num-cases");
  }
  
  /**
   * Get the container for number of test cases finished.
   * @returns Element
   */
  var _getComplete = function _getComplete() {
    return _getElementById("sweety-num-run");
  }
  
  /**
   * Get the container for number of exceptions.
   * @returns Element
   */
  var _getExceptions = function _getExceptions() {
    return _getElementById("sweety-num-exceptions");
  }
  
  /**
   * Get the container for number of fails.
   * @returns Element
   */
  var _getFails = function _getFails() {
    return _getElementById("sweety-num-fails");
  }
  
  /**
   * Get the container for number of passes.
   * @returns Element
   */
  var _getPasses = function _getPasses() {
    return _getElementById("sweety-num-passes");
  }
  
  
  /**
   * Get the bar showing aggregate results.
   * @returns Element
   */
  var _getResultsBar = function _getResutsBar() {
    return _getElementById("sweety-results");
  }
  
  /**
   * Get the filter input box.
   * @returns Element
   */
  var _getFilter = function _getFilter() {
    return _getElementById("sweety-filter");
  }
  
  /**
   * Get the button which operates the filter.
   * @returns Element
   */
  var _getRunButton = function _getRunButton() {
    return _getElementById("sweety-run-button");
  }
  
  var _loadPkgsFromCookie = function _loadPkgsFromCookie() {
    for (var testCase in sweetyTestCases) {
      var pkg = _pkgFor(testCase);
      _pkgs[pkg] = false;
    }
    var cookieBits = document.cookie.split(/\s*;\s*/g);
    for (var i in cookieBits) {
      if (cookieBits[i].substring(0, 9) != "sweetyPkg")
      {
        continue;
      }
      var nvp = cookieBits[i].substring(9).split('=');
      _pkgs[unescape(nvp[0])] = (nvp[1] == "0") ? false : true;
      //alert(unescape(nvp[0]) +  " => " + _pkgs[unescape(nvp[0])]);
    }
  }
  
  var _pkgFor = function _pkgFor(testName) {
    return testName.replace(/_?[^_]+$/, "");
  }
  
}

//Create an instance of the UI Manager for usage
var sweetyUI = new SweetyUIManager();


/**
 * A filter to hide/show test cases in the list.
 * @author Chris Corbyn
 * @consructor
 */
function SweetyFilter() {
  
  var _this = this;
  
  /** Asynchronous page timer (so nothing happens whilst typing) */
  var _timer;
  
  /** The sweety-filter element, lazy loaded */
  var _filter = null;
  
  /**
   * Update the display once the search is complete.
   */
  this.repaintUI = function repaintUI() {
    sweetyUI.initialize();
    sweetyUI.paintSearchComplete();
  }
  
  /**
   * Search for matching test cases.
   */
  this.search = function search() {
    sweetyUI.paintSearching();
    
    var query = _getFilterInput().value.toLowerCase();
    var queryBits = query.split(/[^\!a-zA-Z0-9_]+/g);
    
    //Cancel searching if still typing
    try {
      window.clearTimeout(_timer);
    } catch (e) { }
    
    for (var testCase in sweetyTestCases) {
      for (var i in queryBits) {
        var testFor = queryBits[i];
        var isNegated = ("!" == testFor.charAt(0));
        if (isNegated) {
          testFor = testFor.substring(1);
        }
        
        if (!isNegated && 0 > testCase.toLowerCase().indexOf(testFor)) {
          sweetyTestCases[testCase] = false;
          break;
        } else if (isNegated && 0 < testCase.toLowerCase().indexOf(testFor)) {
          sweetyTestCases[testCase] = false;
          break;
        } else {
          sweetyTestCases[testCase] = true;
        }
      }
    }
    
    //Only apply the search in 500ms, since user may be typing
    _timer = window.setTimeout(_this.repaintUI, 500);
  }
  
  /**
   * Get a lazy loaded reference to the input element.
   * @return HTMLInputElement
   */
  var _getFilterInput = function _getFilterInput() {
    if (!_filter) {
      _filter = document.getElementById("sweety-filter");
    }
    return _filter;
  }
  
}

//Create a new instance of the filter
var sweetyFilter = new SweetyFilter();

/**
 * The reporter which gathers aggregate results and displays a summary.
 * @author Chris Corbyn
 * @constructor
 * @param {Boolean} reportPkgs if package status should be reported
 */
function SweetyTemplateAggregateReporter(testCaseList, reportPkgs) {
  
  var _this = this;
  
  /** True if this reporter instance is running now */
  var _started = false;
  
  /** Aggregate totals */
  var _aggregates = { cases : 0, run: 0, passes : 0, fails : 0, exceptions : 0 };
  
  /** Aggregates per-package */
  var _pkgs = { };
  
  /** Currently running package */
  var _currentPkg;
  
  /**
   * Creates a reporter for the given testCase.
   * @param {String} testCase
   * @returns SweetyReporter
   */
  this.getReporterFor = function getReporterFor(testCase) {
    _aggregates.cases++;
    
    if (reportPkgs) {
      var pkg = _getPkgName(testCase);
      sweetyUI.paintPkgRunning(pkg);
      
      _pkgs[pkg].cases++;
      
      if (_currentPkg && _currentPkg != pkg) {
        _updatePkgStatus(_currentPkg);
      }
      
      _currentPkg = pkg;
    }
    
    sweetyUI.paintNumCases(_aggregates.cases);
    
    var reporter = new SweetyTemplateCaseReporter(testCase, _this);
    return reporter;
  }
  
  /**
   * Updates the UI with the new aggregate totals.
   */
  this.notifyEnded = function notifyEnded(testCase) {
    _aggregates.run++;
    
    if (reportPkgs) {
      var pkg = _getPkgName(testCase);
      _pkgs[pkg].run++;
    }
    
    //Update the UI with new totals
    sweetyUI.paintNumRun(_aggregates.run);
    sweetyUI.paintNumPasses(_aggregates.passes);
    sweetyUI.paintNumFails(_aggregates.fails);
    sweetyUI.paintNumExceptions(_aggregates.exceptions);
  }
  
  /**
   * Returns true if this reporter instance is running.
   * @returns Boolean
   */
  this.isStarted = function isStarted() {
    return _started;
  }
  
  /**
   * Start reporting.
   */
  this.start = function start() {
    _started = true;
    
    if (reportPkgs)
    {
      for (var i = 0, len = testCaseList.length; i < len; i++) {
        var testCase = testCaseList[i];
        var pkg = _getPkgName(testCase);
        if (typeof _pkgs[pkg] == "undefined") {
          _pkgs[pkg] = { cases : 0, run : 0, passes : 0, fails : 0, exceptions : 0 };
        }
      }
    }
    
    sweetyUI.allowInteractivity(false);
    sweetyUI.paintNetworking(true);
    sweetyUI.paintAllRunning();
  }
  
  /**
   * Report a skipped test case.
   * @param {String} message
   * @param {String} path
   */
  this.reportSkip = function reportSkip(message, path) {
    sweetyUI.paintSkip(message, path);
  }
  
  /**
   * Report a passing assertion.
   * @param {String} message
   * @param {String} path
   */
  this.reportPass = function reportPass(message, path) {
    _aggregates.passes++;
    
    if (reportPkgs) {
      _pkgs[_currentPkg].passes++;
    }
  }
  
  /**
   * Report a failing assertion.
   * @param {String} message
   * @param {String} path
   */
  this.reportFail = function reportFail(message, path) {
    _aggregates.fails++;
    
    if (reportPkgs) {
      _pkgs[_currentPkg].fails++;
    }
    
    sweetyUI.paintFail(message, path);
  }
  
  /**
   * Report an unexpected exception.
   * @param {String} message
   * @param {String} path
   */
  this.reportException = function reportException(message, path) {
    _aggregates.exceptions++;
    
    if (reportPkgs) {
      _pkgs[_currentPkg].exceptions++;
    }
    
    sweetyUI.paintException(message, path);
  }
  
  /**
   * Handle test case output from something like a dump().
   * @param {String} output
   * @param {String} path
   */
  this.reportOutput = function reportOutput(output, path) {
    sweetyUI.paintOutput(output, path);
  }
  
  /**
   * End reporting.
   * This method is used to come to a conclusion about the test results in the UI.
   */
  this.finish = function finish() {
    _started = false;
    
    if (reportPkgs) {
      _updatePkgStatus(_currentPkg);
    }
    
    sweetyUI.allowInteractivity(true);
    
    sweetyUI.paintNetworking(false);
    
    if ((!_aggregates.fails && !_aggregates.exceptions)
        && (_aggregates.cases == _aggregates.run)) {
      sweetyUI.paintConclusionPassed();
    } else {
      sweetyUI.paintConclusionFailed();
    }
    
    var incompleteCount = _aggregates.cases - _aggregates.run;
    
    //Check if all tests actually got fully parsed (i.e. finished)
    if (0 < incompleteCount) {
      sweetyUI.paintMessage(
        incompleteCount + " test case(s) did not complete." +
        " This may be because invalid XML was output during the test run" +
        " and/or because an error occured." +
        " Incomplete test cases are shown in yellow.  Click the HTML link " +
        "next to the test for more detail.");
    }
  }
  
  var _getPkgName = function _getPkgName(testCase) {
    return testCase.replace(/_?[^_]+$/, "");
  }
  
  var _updatePkgStatus = function _updatePkgStatus(pkg) {
    if ((!_pkgs[pkg].fails && !_pkgs[pkg].exceptions)
        && (_pkgs[pkg].cases == _pkgs[pkg].run)) {
      sweetyUI.paintPkgPassed(pkg);
    } else if (_pkgs[pkg].cases == _pkgs[pkg].run) {
      sweetyUI.paintPkgFailed(pkg);
    }
  }
  
}
SweetyTemplateAggregateReporter.prototype = new SweetyReporter();

/**
 * The reporter class per-test case.
 * @author Chris Corbyn
 * @consructor
 */
function SweetyTemplateCaseReporter(testCase, reporter) {
  
  var _this = this;
  
  /** Aggregate totals */
  var _aggregates = { passes : 0, fails : 0, exceptions : 0 };
  
  /** The DIV element showing this test case */
  var _testCaseDiv = document.getElementById(testCase);
  
  /** True only if this reporter is running */
  var _started = false;
  
  /**
   * Stubbed only to return itself.
   * @returns SweetyReporter
   */
  this.getReporterFor = function getReporterFor(testCase) {
    return _this;
  }
  
  /**
   * Returns true when the reporter is started.
   * @returns Boolean
   */
  this.isStarted = function isStarted() {
    return _started;
  }
  
  /**
   * Start reporting.
   */
  this.start = function start() {
    _started = true;
    sweetyUI.paintTestCaseRunning(testCase);
  }
  
  /**
   * Report a skipped test case.
   * @param {String} message
   * @param {String} path
   */
  this.reportSkip = function reportSkip(message, path) {
    reporter.reportSkip(message, path);
  }
  
  /**
   * Report a passing assertion.
   * @param {String} message
   * @param {String} path
   */
  this.reportPass = function reportPass(message, path) {
    _aggregates.passes++;
    reporter.reportPass(message, path);
  }
  
  /**
   * Report a failing assertion.
   * @param {String} message
   * @param {String} path
   */
  this.reportFail = function reportFail(message, path) {
    _aggregates.fails++;
    reporter.reportFail(message, path);
  }
  
  /**
   * Report an unexpected exception.
   * @param {String} message
   * @param {String} path
   */
  this.reportException = function reportException(message, path) {
    _aggregates.exceptions++;
    reporter.reportException(message, path);
  }
  
  /**
   * Handle output from a test case in the form of something like a dump().
   * @param {String} output
   * @param {string} path
   */
  this.reportOutput = function reportOutput(output, path) {
    reporter.reportOutput(output, path);
  }
  
  /**
   * End reporting.
   */
  this.finish = function finish() {
    _started = false;
    
    if (!_aggregates.fails && !_aggregates.exceptions) {
      sweetyUI.paintTestCasePassed(testCase);
    } else {
      sweetyUI.paintTestCaseFailed(testCase);
    }
    
    reporter.notifyEnded(testCase);
  }
  
}
SweetyTemplateCaseReporter.prototype = new SweetyReporter();

/**
 * Wraps the invokation of SweetyTestRunner.
 * @author Chris Corbyn
 * @constructor
 */
function SweetyTestWrapper() {
  
  var _this = this;
  
  /**
   * Run a single test case.
   * @param {String} testClass
   */
  this.runTestCase = function runTestCase(testClass) {
    var testCaseList = new Array();
    testCaseList.push(testClass);
    
    var reporter = new SweetyTemplateAggregateReporter(testCaseList);
    
    var runner = new SweetyTestRunner();
    runner.runTests(testCaseList, reporter);
  }
  
  /**
   * Run all selected test cases.
   */
  this.runAll = function runAll(pkg) {
    var pkgRegex;
    if (pkg) {
      pkgRegex = new RegExp("^" + pkg + "_[^_]+$");
    }
    
    var testCaseList = new Array();
    
    for (var testCase in sweetyTestCases) {
      if (!sweetyTestCases[testCase] || (pkg && !testCase.match(pkgRegex))) {
        continue;
      }
      testCaseList.push(testCase);
    }
    
    var reporter = new SweetyTemplateAggregateReporter(testCaseList, true);
    
    var runner = new SweetyTestRunner();
    runner.runTests(testCaseList, reporter);
  }
  
}

//Create an instance of the test runner for usage
var sweetyRunner = new SweetyTestWrapper();

if (typeof document.onreadystatechange != "undefined") { //IE 6/7
  document.onreadystatechange = function() {
    if (document.readyState == "complete") {
      sweetyUI.restore(); sweetyUI.initialize();
    }
  };
} else { //Fallback
  window.onload = function() {
    sweetyUI.restore(); sweetyUI.initialize();
  };
  
  try { //FF
    document.addEventListener("DOMContentLoaded", window.onload, false);
  } catch (e) {
  }
}