/**
 * This file is part of Eclipse Steady.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors
 */
package org.eclipse.steady.java.goals;

import java.io.IOException;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;

import org.apache.logging.log4j.Logger;
import org.eclipse.steady.core.util.CoreConfiguration;
import org.eclipse.steady.goals.AbstractAppGoal;
import org.eclipse.steady.goals.AbstractGoal;
import org.eclipse.steady.goals.GoalConfigurationException;
import org.eclipse.steady.java.ArchiveAnalysisManager;
import org.eclipse.steady.shared.enums.GoalType;
import org.eclipse.steady.shared.json.model.Application;
import org.eclipse.steady.shared.util.FileSearch;
import org.eclipse.steady.shared.util.FileUtil;
import org.eclipse.steady.shared.util.ThreadUtil;

/**
 * <p>InstrGoal class.</p>
 */
public class InstrGoal extends AbstractAppGoal {

  private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(InstrGoal.class);

  private Path libPath = null;

  private Path inclPath = null;

  private Path targetPath = null;

  private Set<Path> instrPaths = new HashSet<Path>();

  /**
   * <p>Constructor for InstrGoal.</p>
   */
  public InstrGoal() {
    super(GoalType.INSTR);
  }

  /**
   * <p>Getter for the field <code>libPath</code>.</p>
   *
   * @return a {@link java.nio.file.Path} object.
   */
  public Path getLibPath() {
    return this.libPath;
  }

  /**
   * <p>Setter for the field <code>libPath</code>.</p>
   *
   * @param _p a {@link java.nio.file.Path} object.
   * @throws java.lang.IllegalArgumentException if any.
   */
  public void setLibPath(Path _p) throws IllegalArgumentException {
    if (FileUtil.isAccessibleDirectory(_p) || FileUtil.isAccessibleFile(_p)) this.libPath = _p;
  }

  /**
   * <p>hasLibPath.</p>
   *
   * @return a boolean.
   */
  public boolean hasLibPath() {
    return this.getLibPath() != null;
  }

  /**
   * <p>Getter for the field <code>inclPath</code>.</p>
   *
   * @return a {@link java.nio.file.Path} object.
   */
  public Path getInclPath() {
    return this.inclPath;
  }

  /**
   * <p>Setter for the field <code>inclPath</code>.</p>
   *
   * @param _p a {@link java.nio.file.Path} object.
   * @throws java.lang.IllegalArgumentException if any.
   */
  public void setInclPath(Path _p) throws IllegalArgumentException {
    if (FileUtil.isAccessibleDirectory(_p) || FileUtil.isAccessibleFile(_p)) this.inclPath = _p;
  }

  /**
   * <p>hasInclPath.</p>
   *
   * @return a boolean.
   */
  public boolean hasInclPath() {
    return this.getInclPath() != null;
  }

  /**
   * <p>Getter for the field <code>targetPath</code>.</p>
   *
   * @return a {@link java.nio.file.Path} object.
   */
  public Path getTargetPath() {
    return this.targetPath;
  }

  /**
   * <p>Setter for the field <code>targetPath</code>.</p>
   *
   * @param _p a {@link java.nio.file.Path} object.
   * @throws java.lang.IllegalArgumentException if any.
   */
  public void setTargetPath(Path _p) throws IllegalArgumentException {
    this.targetPath = _p;
  }

  /**
   * <p>hasTargetPath.</p>
   *
   * @return a boolean.
   */
  public boolean hasTargetPath() {
    return this.getTargetPath() != null;
  }

  /**
   * <p>Getter for the field <code>instrPaths</code>.</p>
   *
   * @return a {@link java.util.Set} object.
   */
  public Set<Path> getInstrPaths() {
    return this.instrPaths;
  }

  /**
   * <p>addInstrPath.</p>
   *
   * @param _p a {@link java.nio.file.Path} object.
   * @throws java.lang.IllegalArgumentException if any.
   */
  public void addInstrPath(Path _p) throws IllegalArgumentException {
    if (!FileUtil.isAccessibleDirectory(_p) && !FileUtil.isAccessibleFile(_p))
      log.warn("[" + _p + "] is not an accessible file or directory");
    else if (this.getInstrPaths().contains(_p))
      log.debug(
          "["
              + _p
              + "] is already part of intrumentation paths, and will not be added another time");
    else this.instrPaths.add(_p);
  }

  /**
   * <p>addInstrPaths.</p>
   *
   * @param _paths a {@link java.util.Set} object.
   * @throws java.lang.IllegalArgumentException if any.
   */
  public void addInstrPaths(Set<Path> _paths) throws IllegalArgumentException {
    for (Path p : _paths) this.addInstrPath(p);
  }

  /**
   * <p>hasInstrPaths.</p>
   *
   * @return a boolean.
   */
  public boolean hasInstrPaths() {
    return this.getInstrPaths() != null && !this.getInstrPaths().isEmpty();
  }

  /**
   * {@inheritDoc}
   *
   * Checks whether one or more {@link Path}s with application constructs, and one or more {@link Path}s
   * with dependencies are available.
   */
  @Override
  protected void prepareExecution() throws GoalConfigurationException {

    super.prepareExecution();

    try {
      // Lib path
      this.setLibPath(
          FileUtil.getPath(
              this.getConfiguration()
                  .getConfiguration()
                  .getString(CoreConfiguration.INSTR_LIB_DIR, null)));

      // Warn if there's no lib path
      if (!this.hasLibPath()) log.warn("No library path");

      // Include path
      this.setInclPath(
          FileUtil.getPath(
              this.getConfiguration()
                  .getConfiguration()
                  .getString(CoreConfiguration.INSTR_INCLUDE_DIR, null)));

      // Warn if there's no include path
      if (!this.hasInclPath()) log.warn("No path with to-be-included JAR files");

      // Where the instrumented archives will be written to
      this.setTargetPath(
          FileUtil.getPath(
              this.getConfiguration()
                  .getConfiguration()
                  .getString(CoreConfiguration.INSTR_TARGET_DIR, null),
              true));

      // Warn if there's no target path
      if (!this.hasTargetPath()) {
        this.setTargetPath(FileUtil.createTmpDir("instr"));
        log.warn("No target path specified, using [" + this.getTargetPath() + "]");
      }

      // Instrumentation paths?
      this.addInstrPaths(
          FileUtil.getPaths(
              this.getConfiguration().getStringArray(CoreConfiguration.INSTR_SRC_DIR, null)));

      // Warn if there's no app path
      if (!this.hasInstrPaths()) {
        log.warn("No path(s) with instrumentation sources, take application paths instead");
        final Set<Path> paths = new HashSet<Path>();
        paths.addAll(this.getAppPaths());
        this.addInstrPaths(paths);
      }
    }
    // Thrown by all methods related to updating/adding paths
    catch (IllegalArgumentException e) {
      throw new GoalConfigurationException(e.getMessage());
    } catch (IOException ioe) {
      throw new GoalConfigurationException(ioe.getMessage());
    }
  }

  /** {@inheritDoc} */
  @Override
  protected void executeTasks() throws Exception {
    final Application app = this.getApplicationContext();

    // TODO: Check how to use packaging information from the Maven plugin

    final long timeout =
        this.getConfiguration().getConfiguration().getLong(CoreConfiguration.JAR_TIMEOUT, -1);
    final int no_threads = ThreadUtil.getNoThreads(this.getConfiguration(), 2);

    final ArchiveAnalysisManager mgr = new ArchiveAnalysisManager(no_threads, timeout, true, app);
    mgr.setRename(true);

    // Set the lib, include and work directories (if any)
    // if(this.inclPathsFileUtil.isAccessibleDirectory(includeDir) &&
    // this.app.getPackaging().toLowerCase().equals("war"))
    if (this.hasInclPath()) mgr.setIncludeDir(this.inclPath);

    if (this.hasLibPath()) mgr.setLibDir(this.getLibPath());

    mgr.setWorkDir(this.getTargetPath(), true);

    // Search source archives and go
    final FileSearch vis = new FileSearch(AbstractGoal.JAR_WAR_EXT);
    final int search_depth =
        this.getConfiguration()
                .getConfiguration()
                .getBoolean(CoreConfiguration.INSTR_SEARCH_RECURSIVE, false)
            ? Integer.MAX_VALUE
            : 1;
    mgr.startAnalysis(vis.search(getInstrPaths(), search_depth), null);

    // Add goal stats
    // this.addGoalStats("instr.archivesAnalyzed", mgr.countArchivesAnalyzed());
    // this.addGoalStats("instr.archivesOriginalFileSizeTotal", mgr.getFileSize());
    // this.addGoalStats("instr.archivesInstrumentedFileSizeTotal", mgr.getInstrumentedFileSize());
    // The following probably makes no sense, since we always instrument one JAR or WAR in the
    // context of this Maven goal
    //			this.addGoalStats("instr.archivesFileSizeMax", ...);
    //			this.addGoalStats("instr.archivesFileSizeAvg", ...);

    // Number of classes in the JAR or WAR. For WARs, also the libs in WB/INF/lib are considered.
    // this.addGoalStats("instr.classesInstrumentedTotal", mgr.countClassesTotal());
    // this.addGoalStats("instr.classesInstrumentedAlready", mgr.countClassesInstrumentedAlready());
    // this.addGoalStats("instr.classesInstrumentedSuccess", mgr.countClassesInstrumentedSuccess());
    // this.addGoalStats("instr.classesInstrumentedFailure", mgr.countClassesInstrumentedFailure());

    // TODO: Keep track of single constructs instrumented
    // exe.addGoalStats("instr.noDepConstructs", mgr.countConstructsIdentified());*/
  }
}
