/**
   * Returns control when task is complete.
   *
   * @param json
   * @param logger
   */
  private String waitForDeploymentCompletion(JSON json, OctopusApi api, Log logger) {
    final long WAIT_TIME = 5000;
    final double WAIT_RANDOM_SCALER = 100.0;
    JSONObject jsonObj = (JSONObject) json;
    String id = jsonObj.getString("TaskId");
    Task task = null;
    String lastState = "Unknown";
    try {
      task = api.getTask(id);
    } catch (IOException ex) {
      logger.error("Error getting task: " + ex.getMessage());
      return null;
    }

    logger.info("Task info:");
    logger.info("\tId: " + task.getId());
    logger.info("\tName: " + task.getName());
    logger.info("\tDesc: " + task.getDescription());
    logger.info("\tState: " + task.getState());
    logger.info("\n\nStarting wait...");
    boolean completed = task.getIsCompleted();
    while (!completed) {
      try {
        task = api.getTask(id);
      } catch (IOException ex) {
        logger.error("Error getting task: " + ex.getMessage());
        return null;
      }

      completed = task.getIsCompleted();
      lastState = task.getState();
      logger.info("Task state: " + lastState);
      if (completed) {
        break;
      }
      try {
        Thread.sleep(WAIT_TIME + (long) (Math.random() * WAIT_RANDOM_SCALER));
      } catch (InterruptedException ex) {
        logger.info("Wait interrupted!");
        logger.info(ex.getMessage());
        completed = true; // bail out of wait loop
      }
    }
    logger.info("Wait complete!");
    return lastState;
  }
  public ArrayList<String> readFile(String filePath)
      throws java.io.FileNotFoundException, java.io.IOException {
    ArrayList<String> aList = new ArrayList<String>();

    try {
      final InputStream is = this.getClass().getResourceAsStream(filePath);
      try {
        final Reader r = new InputStreamReader(is);
        try {
          final BufferedReader br = new BufferedReader(r);
          try {
            String line = null;
            while ((line = br.readLine()) != null) {
              aList.add(line);
            }
            br.close();
            r.close();
            is.close();
          } finally {
            try {
              br.close();
            } catch (IOException e) {
              e.printStackTrace();
            }
          }
        } finally {
          try {
            r.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      } finally {
        try {
          is.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    } catch (IOException e) {
      // failure
      e.printStackTrace();
    }

    return aList;
  }
  @Override
  public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) {
    // This method deserves a refactor and cleanup.
    boolean success = true;
    Log log = new Log(listener);
    if (Result.FAILURE.equals(build.getResult())) {
      log.info("Not deploying due to job being in FAILED state.");
      return success;
    }

    logStartHeader(log);
    // todo: getting from descriptor is ugly. refactor?
    getDescriptorImpl().setGlobalConfiguration();
    OctopusApi api = getDescriptorImpl().api;

    VariableResolver resolver = build.getBuildVariableResolver();
    EnvVars envVars;
    try {
      envVars = build.getEnvironment(listener);
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Failed to retrieve environment variables for this build - '%s'", ex.getMessage()));
      return false;
    }
    EnvironmentVariableValueInjector envInjector =
        new EnvironmentVariableValueInjector(resolver, envVars);
    // NOTE: hiding the member variables of the same name with their env-injected equivalents
    String project = envInjector.injectEnvironmentVariableValues(this.project);
    String releaseVersion = envInjector.injectEnvironmentVariableValues(this.releaseVersion);
    String environment = envInjector.injectEnvironmentVariableValues(this.environment);
    String variables = envInjector.injectEnvironmentVariableValues(this.variables);

    com.octopusdeploy.api.Project p = null;
    try {
      p = api.getProjectByName(project);
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Retrieving project name '%s' failed with message '%s'", project, ex.getMessage()));
      success = false;
    }
    com.octopusdeploy.api.Environment env = null;
    try {
      env = api.getEnvironmentByName(environment);
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Retrieving environment name '%s' failed with message '%s'",
              environment, ex.getMessage()));
      success = false;
    }
    if (p == null) {
      log.fatal("Project was not found.");
      success = false;
    }
    if (env == null) {
      log.fatal("Environment was not found.");
      success = false;
    }
    if (!success) // Early exit
    {
      return success;
    }
    Set<com.octopusdeploy.api.Release> releases = null;
    try {
      releases = api.getReleasesForProject(p.getId());
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Retrieving releases for project '%s' failed with message '%s'",
              project, ex.getMessage()));
      success = false;
    }
    if (releases == null) {
      log.fatal("Releases was not found.");
      return false;
    }
    Release releaseToDeploy = null;
    for (Release r : releases) {
      if (releaseVersion.equals(r.getVersion())) {
        releaseToDeploy = r;
        break;
      }
    }
    if (releaseToDeploy == null) // early exit
    {
      log.fatal(
          String.format(
              "Unable to find release version %s for project %s", releaseVersion, project));
      return false;
    }
    Properties properties = new Properties();
    try {
      properties.load(new StringReader(variables));
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Unable to load entry variables failed with message '%s'", ex.getMessage()));
      success = false;
    }

    // TODO: Can we tell if we need to call? For now I will always try and get variable and use if I
    // find them
    Set<com.octopusdeploy.api.Variable> variablesForDeploy = null;

    try {
      String releaseId = releaseToDeploy.getId();
      String environmentId = env.getId();
      variablesForDeploy =
          api.getVariablesByReleaseAndEnvironment(releaseId, environmentId, properties);
    } catch (Exception ex) {
      log.fatal(
          String.format(
              "Retrieving variables for release '%s' to environment '%s' failed with message '%s'",
              releaseToDeploy.getId(), env.getName(), ex.getMessage()));
      success = false;
    }
    try {
      String results =
          api.executeDeployment(releaseToDeploy.getId(), env.getId(), variablesForDeploy);
      if (isTaskJson(results)) {
        JSON resultJson = JSONSerializer.toJSON(results);
        String urlSuffix = ((JSONObject) resultJson).getJSONObject("Links").getString("Web");
        String url = getDescriptorImpl().octopusHost;
        if (url.endsWith("/")) {
          url = url.substring(0, url.length() - 2);
        }
        log.info("Deployment executed: \n\t" + url + urlSuffix);
        build.addAction(
            new BuildInfoSummary(
                BuildInfoSummary.OctopusDeployEventType.Deployment, url + urlSuffix));
        if (waitForDeployment) {

          log.info("Waiting for deployment to complete.");
          String resultState = waitForDeploymentCompletion(resultJson, api, log);
          if (resultState == null) {
            log.info("Marking build failed due to failure in waiting for deployment to complete.");
            success = false;
          }

          if ("Failed".equals(resultState)) {
            log.info("Marking build failed due to deployment task status.");
            success = false;
          }
        }
      }
    } catch (IOException ex) {
      log.fatal("Failed to deploy: " + ex.getMessage());
      success = false;
    }

    return success;
  }
  @Override
  public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)
      throws InterruptedException {
    listener.getLogger().println("[htmlpublisher] Archiving HTML reports...");

    // Grab the contents of the header and footer as arrays
    ArrayList<String> headerLines;
    ArrayList<String> footerLines;
    try {
      headerLines = this.readFile("/htmlpublisher/HtmlPublisher/header.html");
      footerLines = this.readFile("/htmlpublisher/HtmlPublisher/footer.html");
    } catch (FileNotFoundException e1) {
      e1.printStackTrace();
      return false;
    } catch (IOException e1) {
      e1.printStackTrace();
      return false;
    }

    for (int i = 0; i < this.reportTargets.size(); i++) {
      // Create an array of lines we will eventually write out, initially the header.
      ArrayList<String> reportLines = new ArrayList<String>(headerLines);
      HtmlPublisherTarget reportTarget = this.reportTargets.get(i);
      boolean keepAll = reportTarget.getKeepAll();

      FilePath archiveDir =
          build
              .getWorkspace()
              .child(resolveParametersInString(build, listener, reportTarget.getReportDir()));
      FilePath targetDir = reportTarget.getArchiveTarget(build);

      String levelString = keepAll ? "BUILD" : "PROJECT";
      listener
          .getLogger()
          .println(
              "[htmlpublisher] Archiving at "
                  + levelString
                  + " level "
                  + archiveDir
                  + " to "
                  + targetDir);

      // The index name might be a comma separated list of names, so let's figure out all the pages
      // we should index.
      String[] csvReports =
          resolveParametersInString(build, listener, reportTarget.getReportFiles()).split(",");
      ArrayList<String> reports = new ArrayList<String>();
      for (int j = 0; j < csvReports.length; j++) {
        String report = csvReports[j];
        report = report.trim();

        // Ignore blank report names caused by trailing or double commas.
        if (report.equals("")) {
          continue;
        }

        reports.add(report);
        String tabNo = "tab" + (j + 1);
        // Make the report name the filename without the extension.
        int end = report.lastIndexOf(".");
        String reportName;
        if (end > 0) {
          reportName = report.substring(0, end);
        } else {
          reportName = report;
        }
        String tabItem =
            "<li id=\""
                + tabNo
                + "\" class=\"unselected\" onclick=\"updateBody('"
                + tabNo
                + "');\" value=\""
                + report
                + "\">"
                + reportName
                + "</li>";
        reportLines.add(tabItem);
      }
      // Add the JS to change the link as appropriate.
      String hudsonUrl = Hudson.getInstance().getRootUrl();
      AbstractProject job = build.getProject();
      reportLines.add(
          "<script type=\"text/javascript\">document.getElementById(\"hudson_link\").innerHTML=\"Back to "
              + job.getName()
              + "\";</script>");
      // If the URL isn't configured in Hudson, the best we can do is attempt to go Back.
      if (hudsonUrl == null) {
        reportLines.add(
            "<script type=\"text/javascript\">document.getElementById(\"hudson_link\").onclick = function() { history.go(-1); return false; };</script>");
      } else {
        String jobUrl = hudsonUrl + job.getUrl();
        reportLines.add(
            "<script type=\"text/javascript\">document.getElementById(\"hudson_link\").href=\""
                + jobUrl
                + "\";</script>");
      }

      reportLines.add(
          "<script type=\"text/javascript\">document.getElementById(\"zip_link\").href=\"*zip*/"
              + reportTarget.getSanitizedName()
              + ".zip\";</script>");

      try {
        if (!archiveDir.exists()) {
          listener.error("Specified HTML directory '" + archiveDir + "' does not exist.");
          build.setResult(Result.FAILURE);
          return true;
        } else if (!keepAll) {
          // We are only keeping one copy at the project level, so remove the old one.
          targetDir.deleteRecursive();
        }

        if (archiveDir.copyRecursiveTo("**/*", targetDir) == 0) {
          listener.error(
              "Directory '" + archiveDir + "' exists but failed copying to '" + targetDir + "'.");
          if (build.getResult().isBetterOrEqualTo(Result.UNSTABLE)) {
            // If the build failed, don't complain that there was no coverage.
            // The build probably didn't even get to the point where it produces coverage.
            listener.error("This is especially strange since your build otherwise succeeded.");
          }
          build.setResult(Result.FAILURE);
          return true;
        }
      } catch (IOException e) {
        Util.displayIOException(e, listener);
        e.printStackTrace(listener.fatalError("HTML Publisher failure"));
        build.setResult(Result.FAILURE);
        return true;
      }

      reportTarget.handleAction(build);

      // Now add the footer.
      reportLines.addAll(footerLines);
      // And write this as the index
      try {
        writeFile(reportLines, new File(targetDir.getRemote(), reportTarget.getWrapperName()));
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

    return true;
  }