/**
   * Writes the result HTML pages for the evaluation of multiple jobs/algorithms
   *
   * @param results The NemaEvaluationResultSet to write results pages for.
   * @param jobIDToResultPlotFileList map of a jobId to the results plots for that job.
   * @param summaryCsv the summary csv file that summarizes all jobs.
   * @param jobIDToPerTrackCSV map of jobId to individual per-track results csv files for that job.
   * @param jobIDToTgz map of jobId to the tar-balls of individual job results.
   * @param outputDir directory to write the HTML pages to.
   */
  private void writeHtmlAnalysisPages(NemaEvaluationResultSet results, File outputDir) {
    String jobId;
    Map<NemaTrackList, List<NemaData>> sysResults;
    List<Page> resultPages = new ArrayList<Page>();
    List<PageItem> items;
    Page aPage;
    int numJobs = results.getJobIds().size();

    TableItem legendTable = createLegendTable(results);

    // do intro page to describe task
    {
      resultPages.add(createIntroHtmlPage(results, legendTable));
    }

    /* Do per system pages */
    {
      for (Iterator<String> it = results.getJobIds().iterator(); it.hasNext(); ) {
        jobId = it.next();
        items = new ArrayList<PageItem>();
        sysResults = results.getPerTrackEvaluationAndResults(jobId);

        /* Plot onset transcription against GT for each track result for each system */
        PageItem[] plots = plotTranscriptionForJob(jobId, results);
        for (int i = 0; i < plots.length; i++) {
          items.add(plots[i]);
        }

        aPage =
            new Page(
                results.getJobName(jobId) + "_results", results.getJobName(jobId), items, true);
        resultPages.add(aPage);
      }
    }

    if (results.getJobIds().size() > 1) {
      // do comparative plot page
      {
        getLogger().info("Creating comparison plots page...");
        items = new ArrayList<PageItem>();
        PageItem[] plots = plotTranscriptionForAllJobs(results);
        for (int i = 0; i < plots.length; i++) {
          items.add(plots[i]);
        }
        getLogger().info("\tdone.");
        aPage = new Page("comparisonPlots", "Comparative plots", items, true);
        resultPages.add(aPage);
      }
    }

    Page.writeResultPages(results.getTask().getName(), outputDir, resultPages);
  }
  /**
   * Plots the onset transcriptions for each job, for each file
   *
   * @param jobId the jobId we wish to plot results for.
   * @param results The results Object containing the data to plot.
   * @return an array of page items that will produce the plots.
   */
  private PageItem[] plotTranscriptionForJob(String jobId, NemaEvaluationResultSet results) {
    NemaData result, groundtruth = null;

    /* Plot each result */
    Map<NemaTrackList, List<NemaData>> job_results = results.getPerTrackEvaluationAndResults(jobId);
    List<PageItem> plotItems = new ArrayList<PageItem>();

    for (Iterator<NemaTrackList> foldIt = job_results.keySet().iterator(); foldIt.hasNext(); ) {
      NemaTrackList testSet = foldIt.next();
      for (Iterator<NemaData> iterator = job_results.get(testSet).iterator();
          iterator.hasNext(); ) {
        result = iterator.next();
        if (results.getTrackIDToGT() != null) {
          groundtruth = results.getTrackIDToGT().get(result.getId());
        }
        if (groundtruth == null) {
          getLogger()
              .warning("No ground-truth found for '" + result.getId() + "' to be used in plotting");
        }

        //				File plotFile = new File(sysDir.getAbsolutePath()
        //						+ File.separator + "track_" + result.getId() + MELODY_PLOT_EXT);
        //				plotItems.add(plotFile);

        double[][] rawData2D =
            result.get2dDoubleArrayMetadata(NemaDataConstants.ONSET_DETECTION_DATA);
        double[][] rawGtData2D = null;
        if (groundtruth != null) {
          rawGtData2D =
              groundtruth.get2dDoubleArrayMetadata(NemaDataConstants.ONSET_DETECTION_DATA);
        }
        String[] annotators = null;
        if (groundtruth != null
            && groundtruth.hasMetadata(NemaDataConstants.ONSET_DETECTION_ANNOTATORS)) {
          annotators =
              groundtruth.getStringArrayMetadata(NemaDataConstants.ONSET_DETECTION_ANNOTATORS);
        }
        // setup time line for for X-axis
        double startTimeSecs = 0.0;
        double endTimeSecs = 0.0;

        // Load in the prediction data
        double[] rawData = new double[rawData2D.length];
        for (int i = 0; i < rawData.length; i++) {
          rawData[i] = rawData2D[i][0];
          if (rawData2D[i][0] > endTimeSecs) {
            endTimeSecs = rawData2D[i][0];
          }
        }

        // setup hash map for to plot
        Map<String, double[]> series = new HashMap<String, double[]>(rawGtData2D[0].length + 1);
        List<String> seriesNames = new ArrayList<String>(rawGtData2D[0].length + 1);
        List<Boolean> isGroundtruth = new ArrayList<Boolean>(rawGtData2D[0].length + 1);
        if (groundtruth != null) {
          for (int curGT = 0; curGT < rawGtData2D[0].length; curGT++) {
            ArrayList<Double> gtDataArr = new ArrayList<Double>();
            for (int t = 0; t < rawGtData2D.length; t++) {
              double onTime = rawGtData2D[t][curGT];
              if (!Double.isNaN(onTime)) {
                gtDataArr.add(new Double(onTime));
                if (onTime > endTimeSecs) {
                  endTimeSecs = onTime;
                }
              }
            }
            double[] rawGtData = new double[gtDataArr.size()];
            for (int t = 0; t < rawGtData.length; t++) {
              rawGtData[t] = gtDataArr.get(t).doubleValue();
            }
            if (groundtruth.hasMetadata(NemaDataConstants.ONSET_DETECTION_ANNOTATORS)) {
              String annotator = annotators[curGT];
              series.put(annotator, rawGtData);
              seriesNames.add(annotator);
            } else {
              series.put("Ground-truth " + curGT, rawGtData);
              seriesNames.add("Ground-truth " + curGT);
            }
            isGroundtruth.add(true);
          }
        }

        series.put(results.getJobName(jobId), rawData);
        seriesNames.add(results.getJobName(jobId));
        isGroundtruth.add(false);

        try {
          ProtovisOnsetPlotItem plot =
              new ProtovisOnsetPlotItem(
                  // plotname
                  results.getJobName(jobId) + "_onset_transcript_" + result.getId(),
                  // plot caption
                  results.getJobName(jobId) + ": Onset transcription for track " + result.getId(),
                  // start time for x axis
                  startTimeSecs,
                  // end time for x axis
                  endTimeSecs,
                  // map of annotator/system names to the data
                  series,
                  // ordered list of annotator/system names
                  seriesNames,
                  // flags indicating whether a series is ground-truth
                  isGroundtruth,
                  // Directory to write JS data files to
                  outputDir);
          plotItems.add(plot);
        } catch (IOException e) {
          getLogger()
              .log(
                  Level.SEVERE,
                  "Failed to plot results for job "
                      + results.getJobName(jobId)
                      + " ("
                      + jobId
                      + ") for track "
                      + result.getId(),
                  e);
        }
      }
    }
    return plotItems.toArray(new PageItem[plotItems.size()]);
  }
  /**
   * Writes the result HTML pages for the evaluation of multiple jobs/algorithms
   *
   * @param results The NemaEvaluationResultSet to write results pages for.
   * @param jobIDToResultPlotFileList map of a jobId to the results plots for that job.
   * @param summaryCsv the summary csv file that summarizes all jobs.
   * @param jobIDToPerTrackCSV map of jobId to individual per-track results csv files for that job.
   * @param jobIDToTgz map of jobId to the tar-balls of individual job results.
   * @param outputDir directory to write the HTML pages to.
   */
  private void writeResultHtmlPages(
      NemaEvaluationResultSet results,
      List<String> classNames,
      /*Map<String, File[]> jobIDToResultPlotFileList, */ File summaryCsv,
      File perClassFMeasureCsv,
      File perClassPrecisionCsv,
      File perClassRecallCsv,
      Map<String, File> jobIDToPerTrackCSV,
      Map<String, File> jobIDToTgz,
      File outputDir) {
    String jobId;
    Map<NemaTrackList, List<NemaData>> sysResults;
    List<Page> resultPages = new ArrayList<Page>();
    List<PageItem> items;
    Page aPage;
    int numJobs = results.getJobIds().size();

    TableItem legendTable = createLegendTable(results);

    // do intro page to describe task
    {
      resultPages.add(createIntroHtmlPage(results, legendTable));
    }

    /* Do summary page */
    {
      items = new ArrayList<PageItem>();
      items.add(legendTable);

      List<String> metrics = new ArrayList<String>();
      metrics.add(NemaDataConstants.ONSET_DETECTION_AVG_FMEASURE);
      metrics.add(NemaDataConstants.ONSET_DETECTION_AVG_PRECISION);
      metrics.add(NemaDataConstants.ONSET_DETECTION_AVG_RECALL);

      Table summaryTable =
          WriteCsvResultFiles.prepSummaryTable(
              results.getJobIdToOverallEvaluation(), results.getJobIdToJobName(), metrics);

      items.add(
          new TableItem(
              "summary_results",
              "Summary Results",
              summaryTable.getColHeaders(),
              summaryTable.getRows()));
      aPage = new Page("summary", "Summary", items, false);
      resultPages.add(aPage);
    }

    /* Do per class page */
    {
      items = new ArrayList<PageItem>();
      items.add(legendTable);

      Table perClassFMeasureTable =
          WriteCsvResultFiles.prepTableDataOverClassArrays(
              results.getJobIdToOverallEvaluation(),
              results.getJobIdToJobName(),
              classNames,
              NemaDataConstants.ONSET_DETECTION_AVG_FMEASURE_BY_CLASS);

      Table perClassPrecisionTable =
          WriteCsvResultFiles.prepTableDataOverClassArrays(
              results.getJobIdToOverallEvaluation(),
              results.getJobIdToJobName(),
              classNames,
              NemaDataConstants.ONSET_DETECTION_AVG_PRECISION_BY_CLASS);

      Table perClassRecallTable =
          WriteCsvResultFiles.prepTableDataOverClassArrays(
              results.getJobIdToOverallEvaluation(),
              results.getJobIdToJobName(),
              classNames,
              NemaDataConstants.ONSET_DETECTION_AVG_RECALL_BY_CLASS);

      items.add(
          new TableItem(
              "fmeasure_class",
              "F-Measure per Class",
              perClassFMeasureTable.getColHeaders(),
              perClassFMeasureTable.getRows()));

      items.add(
          new TableItem(
              "precision_class",
              "Pecision per Class",
              perClassPrecisionTable.getColHeaders(),
              perClassPrecisionTable.getRows()));

      items.add(
          new TableItem(
              "recall_class",
              "Recall per Class",
              perClassRecallTable.getColHeaders(),
              perClassRecallTable.getRows()));

      aPage = new Page("results_per_class", "Results per Class", items, false);
      resultPages.add(aPage);
    }

    /* Do per system pages */
    {
      for (Iterator<String> it = results.getJobIds().iterator(); it.hasNext(); ) {
        jobId = it.next();
        items = new ArrayList<PageItem>();
        TableItem filtLegend = filterLegendTable(legendTable, jobId);
        if (filtLegend != null) {
          items.add(filtLegend);
        }
        sysResults = results.getPerTrackEvaluationAndResults(jobId);

        /* Add per track table */
        Table perTrackTable =
            WriteCsvResultFiles.prepTableDataOverTracks(
                results.getTestSetTrackLists(),
                sysResults,
                results.getTrackEvalMetricsAndResultsKeys());

        items.add(
            new TableItem(
                results.getJobName(jobId) + "_results",
                results.getJobName(jobId) + " Per Track Results",
                perTrackTable.getColHeaders(),
                perTrackTable.getRows()));

        /* Plot onset transcription against GT for each track result for each system */
        PageItem[] plots = plotTranscriptionForJob(jobId, results);
        for (int i = 0; i < plots.length; i++) {
          items.add(plots[i]);
        }

        aPage =
            new Page(
                results.getJobName(jobId) + "_results", results.getJobName(jobId), items, true);
        resultPages.add(aPage);
      }
    }

    if (results.getJobIds().size() > 1) {
      // do comparative plot page
      {
        getLogger().info("Creating comparison plots page...");
        items = new ArrayList<PageItem>();
        items.add(legendTable);
        PageItem[] plots = plotTranscriptionForAllJobs(results);
        for (int i = 0; i < plots.length; i++) {
          items.add(plots[i]);
        }
        getLogger().info("\tdone.");
        aPage = new Page("comparisonPlots", "Comparative plots", items, true);
        resultPages.add(aPage);
      }
    }

    /* Do files page */
    {
      items = new ArrayList<PageItem>();

      /* CSVs */
      List<String> CSVPaths = new ArrayList<String>(numJobs + 4);
      CSVPaths.add(IOUtil.makeRelative(summaryCsv, outputDir));
      CSVPaths.add(IOUtil.makeRelative(perClassFMeasureCsv, outputDir));
      CSVPaths.add(IOUtil.makeRelative(perClassPrecisionCsv, outputDir));
      CSVPaths.add(IOUtil.makeRelative(perClassRecallCsv, outputDir));
      for (Iterator<String> it = results.getJobIds().iterator(); it.hasNext(); ) {
        jobId = it.next();
        CSVPaths.add(IOUtil.makeRelative(jobIDToPerTrackCSV.get(jobId), outputDir));
      }

      items.add(new FileListItem("dataCSVs", "CSV result files", CSVPaths));

      /* System tar-balls */
      List<String> tarballPaths = new ArrayList<String>(numJobs);
      for (Iterator<String> it = results.getJobIds().iterator(); it.hasNext(); ) {
        jobId = it.next();
        tarballPaths.add(IOUtil.makeRelative(jobIDToTgz.get(jobId), outputDir));
      }
      items.add(new FileListItem("tarballs", "Per algorithm evaluation tarball", tarballPaths));
      aPage = new Page("files", "Raw data files", items, true);
      resultPages.add(aPage);
    }

    Page.writeResultPages(results.getTask().getName(), outputDir, resultPages);
  }