package com.blackduck.integration.detect.lifecycle.run.operation;

import static com.blackduck.integration.blackduck.codelocation.signaturescanner.command.ToolsApiScannerInstaller.MIN_ARM_BLACK_DUCK_VERSION;
import static com.blackduck.integration.componentlocator.ComponentLocator.SUPPORTED_DETECTORS;
import static com.blackduck.integration.detect.workflow.componentlocationanalysis.GenerateComponentLocationAnalysisOperation.OPERATION_NAME;
import static com.blackduck.integration.detect.workflow.componentlocationanalysis.GenerateComponentLocationAnalysisOperation.SUPPORTED_DETECTORS_LOG_MSG;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Base64.Encoder;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.http.HttpStatus;
import org.apache.http.entity.ContentType;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.blackduck.integration.bdio.graph.ProjectDependencyGraph;
import com.blackduck.integration.bdio.model.externalid.ExternalId;
import com.blackduck.integration.blackduck.api.generated.discovery.ApiDiscovery;
import com.blackduck.integration.blackduck.api.generated.enumeration.BomStatusScanStatusType;
import com.blackduck.integration.blackduck.api.generated.enumeration.PolicyRuleSeverityType;
import com.blackduck.integration.blackduck.api.generated.view.BomStatusScanView;
import com.blackduck.integration.blackduck.api.generated.view.DeveloperScansScanView;
import com.blackduck.integration.blackduck.api.generated.view.ProjectVersionView;
import com.blackduck.integration.blackduck.bdio2.model.BdioFileContent;
import com.blackduck.integration.blackduck.bdio2.model.GitInfo;
import com.blackduck.integration.blackduck.bdio2.util.Bdio2ContentExtractor;
import com.blackduck.integration.blackduck.bdio2.util.Bdio2Factory;
import com.blackduck.integration.blackduck.codelocation.CodeLocationCreationData;
import com.blackduck.integration.blackduck.codelocation.CodeLocationCreationService;
import com.blackduck.integration.blackduck.codelocation.CodeLocationWaitResult;
import com.blackduck.integration.blackduck.codelocation.binaryscanner.BinaryScanBatchOutput;
import com.blackduck.integration.blackduck.codelocation.signaturescanner.ScanBatch;
import com.blackduck.integration.blackduck.codelocation.signaturescanner.ScanBatchRunner;
import com.blackduck.integration.blackduck.codelocation.signaturescanner.command.ScanCommandOutput;
import com.blackduck.integration.blackduck.codelocation.signaturescanner.command.ScanCommandRunner;
import com.blackduck.integration.blackduck.codelocation.signaturescanner.command.ScanPathsUtility;
import com.blackduck.integration.blackduck.http.BlackDuckRequestBuilder;
import com.blackduck.integration.blackduck.service.BlackDuckApiClient;
import com.blackduck.integration.blackduck.service.BlackDuckServicesFactory;
import com.blackduck.integration.blackduck.service.model.NotificationTaskRange;
import com.blackduck.integration.blackduck.service.model.ProjectVersionWrapper;
import com.blackduck.integration.blackduck.service.request.BlackDuckResponseRequest;
import com.blackduck.integration.blackduck.version.BlackDuckVersion;
import com.blackduck.integration.common.util.finder.FileFinder;
import com.blackduck.integration.componentlocator.beans.Component;
import com.blackduck.integration.detect.configuration.DetectConfigurationFactory;
import com.blackduck.integration.detect.configuration.DetectInfo;
import com.blackduck.integration.detect.configuration.DetectUserFriendlyException;
import com.blackduck.integration.detect.configuration.DetectorToolOptions;
import com.blackduck.integration.detect.configuration.connection.ConnectionFactory;
import com.blackduck.integration.detect.configuration.enumeration.BlackduckScanMode;
import com.blackduck.integration.detect.configuration.enumeration.DetectTool;
import com.blackduck.integration.detect.configuration.enumeration.ExitCodeType;
import com.blackduck.integration.detect.configuration.enumeration.RapidCompareMode;
import com.blackduck.integration.detect.lifecycle.OperationException;
import com.blackduck.integration.detect.lifecycle.autonomous.AutonomousManager;
import com.blackduck.integration.detect.lifecycle.run.DetectFontLoaderFactory;
import com.blackduck.integration.detect.lifecycle.run.data.BlackDuckRunData;
import com.blackduck.integration.detect.lifecycle.run.data.DockerTargetData;
import com.blackduck.integration.detect.lifecycle.run.data.ScanCreationResponse;
import com.blackduck.integration.detect.lifecycle.run.operation.blackduck.BdioUploadResult;
import com.blackduck.integration.detect.lifecycle.run.operation.blackduck.ScassScanInitiationResult;
import com.blackduck.integration.detect.lifecycle.run.singleton.BootSingletons;
import com.blackduck.integration.detect.lifecycle.run.singleton.EventSingletons;
import com.blackduck.integration.detect.lifecycle.run.singleton.UtilitySingletons;
import com.blackduck.integration.detect.lifecycle.run.step.CommonScanStepRunner;
import com.blackduck.integration.detect.lifecycle.run.step.utility.OperationAuditLog;
import com.blackduck.integration.detect.lifecycle.run.step.utility.OperationWrapper;
import com.blackduck.integration.detect.lifecycle.shutdown.ExitCodePublisher;
import com.blackduck.integration.detect.tool.DetectableTool;
import com.blackduck.integration.detect.tool.DetectableToolResult;
import com.blackduck.integration.detect.tool.binaryscanner.BinaryScanFindMultipleTargetsOperation;
import com.blackduck.integration.detect.tool.binaryscanner.BinaryScanOptions;
import com.blackduck.integration.detect.tool.binaryscanner.BinaryUploadOperation;
import com.blackduck.integration.detect.tool.detector.CodeLocationConverter;
import com.blackduck.integration.detect.tool.detector.DetectorEventPublisher;
import com.blackduck.integration.detect.tool.detector.DetectorIssuePublisher;
import com.blackduck.integration.detect.tool.detector.DetectorRuleFactory;
import com.blackduck.integration.detect.tool.detector.DetectorTool;
import com.blackduck.integration.detect.tool.detector.DetectorToolResult;
import com.blackduck.integration.detect.tool.detector.executable.DetectExecutableRunner;
import com.blackduck.integration.detect.tool.detector.extraction.ExtractionEnvironmentProvider;
import com.blackduck.integration.detect.tool.detector.factory.DetectDetectableFactory;
import com.blackduck.integration.detector.accuracy.detectable.DetectableExclusionEvaluator;
import com.blackduck.integration.detect.tool.iac.CalculateIacScanTargetsOperation;
import com.blackduck.integration.detect.tool.iac.IacScanOperation;
import com.blackduck.integration.detect.tool.iac.IacScanReport;
import com.blackduck.integration.detect.tool.iac.IacScannerInstaller;
import com.blackduck.integration.detect.tool.iac.PublishIacScanReportOperation;
import com.blackduck.integration.detect.tool.iac.UploadIacScanResultsOperation;
import com.blackduck.integration.detect.tool.impactanalysis.GenerateImpactAnalysisOperation;
import com.blackduck.integration.detect.tool.impactanalysis.ImpactAnalysisMapCodeLocationsOperation;
import com.blackduck.integration.detect.tool.impactanalysis.ImpactAnalysisNamingOperation;
import com.blackduck.integration.detect.tool.impactanalysis.ImpactAnalysisUploadOperation;
import com.blackduck.integration.detect.tool.impactanalysis.service.ImpactAnalysisBatchOutput;
import com.blackduck.integration.detect.tool.impactanalysis.service.ImpactAnalysisUploadService;
import com.blackduck.integration.detect.tool.signaturescanner.SignatureScanPath;
import com.blackduck.integration.detect.tool.signaturescanner.SignatureScannerCodeLocationResult;
import com.blackduck.integration.detect.tool.signaturescanner.SignatureScannerReport;
import com.blackduck.integration.detect.tool.signaturescanner.operation.CalculateScanPathsOperation;
import com.blackduck.integration.detect.tool.signaturescanner.operation.CalculateWaitableSignatureScanCodeLocations;
import com.blackduck.integration.detect.tool.signaturescanner.operation.CreateScanBatchOperation;
import com.blackduck.integration.detect.tool.signaturescanner.operation.CreateScanBatchRunnerWithBlackDuck;
import com.blackduck.integration.detect.tool.signaturescanner.operation.CreateScanBatchRunnerWithLocalInstall;
import com.blackduck.integration.detect.tool.signaturescanner.operation.CreateSignatureScanReports;
import com.blackduck.integration.detect.tool.signaturescanner.operation.PublishSignatureScanReports;
import com.blackduck.integration.detect.tool.signaturescanner.operation.SignatureScanOperation;
import com.blackduck.integration.detect.tool.signaturescanner.operation.SignatureScanOuputResult;
import com.blackduck.integration.detect.util.bdio.protobuf.DetectProtobufBdioHeaderUtil;
import com.blackduck.integration.detect.util.finder.DetectExcludedDirectoryFilter;
import com.blackduck.integration.detect.workflow.ArtifactResolver;
import com.blackduck.integration.detect.workflow.bdio.AggregateCodeLocation;
import com.blackduck.integration.detect.workflow.bdio.BdioResult;
import com.blackduck.integration.detect.workflow.bdio.CreateAggregateBdio2FileOperation;
import com.blackduck.integration.detect.workflow.bdio.CreateAggregateCodeLocationOperation;
import com.blackduck.integration.detect.workflow.bdio.aggregation.FullAggregateGraphCreator;
import com.blackduck.integration.detect.workflow.blackduck.BlackDuckPostOptions;
import com.blackduck.integration.detect.workflow.blackduck.BomScanWaitOperation;
import com.blackduck.integration.detect.workflow.blackduck.DetectFontLoader;
import com.blackduck.integration.detect.workflow.blackduck.bdio.IntelligentPersistentUploadOperation;
import com.blackduck.integration.detect.workflow.blackduck.codelocation.CodeLocationWaitCalculator;
import com.blackduck.integration.detect.workflow.blackduck.codelocation.CodeLocationWaitData;
import com.blackduck.integration.detect.workflow.blackduck.codelocation.WaitableCodeLocationData;
import com.blackduck.integration.detect.workflow.blackduck.developer.RapidModeConfigFindOperation;
import com.blackduck.integration.detect.workflow.blackduck.developer.RapidModeGenerateJsonOperation;
import com.blackduck.integration.detect.workflow.blackduck.developer.RapidModeLogReportOperation;
import com.blackduck.integration.detect.workflow.blackduck.developer.RapidModeUploadOperation;
import com.blackduck.integration.detect.workflow.blackduck.developer.RapidModeWaitOperation;
import com.blackduck.integration.detect.workflow.blackduck.developer.RapidScanDetectResult;
import com.blackduck.integration.detect.workflow.blackduck.developer.RapidScanOptions;
import com.blackduck.integration.detect.workflow.blackduck.developer.aggregate.RapidScanResultAggregator;
import com.blackduck.integration.detect.workflow.blackduck.developer.aggregate.RapidScanResultSummary;
import com.blackduck.integration.detect.workflow.blackduck.developer.blackduck.DetectRapidScanService;
import com.blackduck.integration.detect.workflow.blackduck.integratedmatching.CorrelatedScanCountUploadService;
import com.blackduck.integration.detect.workflow.blackduck.integratedmatching.model.ScanCountsPayload;
import com.blackduck.integration.detect.workflow.blackduck.policy.PolicyChecker;
import com.blackduck.integration.detect.workflow.blackduck.project.AddTagsToProjectOperation;
import com.blackduck.integration.detect.workflow.blackduck.project.AddUserGroupsToProjectOperation;
import com.blackduck.integration.detect.workflow.blackduck.project.FindCloneByLatestOperation;
import com.blackduck.integration.detect.workflow.blackduck.project.FindCloneByNameOperation;
import com.blackduck.integration.detect.workflow.blackduck.project.FindLicenseUrlOperation;
import com.blackduck.integration.detect.workflow.blackduck.project.FindProjectGroupOperation;
import com.blackduck.integration.detect.workflow.blackduck.project.MapToParentOperation;
import com.blackduck.integration.detect.workflow.blackduck.project.SetApplicationIdOperation;
import com.blackduck.integration.detect.workflow.blackduck.project.SyncProjectOperation;
import com.blackduck.integration.detect.workflow.blackduck.project.UpdateCustomFieldsOperation;
import com.blackduck.integration.detect.workflow.blackduck.project.customfields.CustomFieldDocument;
import com.blackduck.integration.detect.workflow.blackduck.project.options.CloneFindResult;
import com.blackduck.integration.detect.workflow.blackduck.project.options.FindCloneOptions;
import com.blackduck.integration.detect.workflow.blackduck.project.options.ParentProjectMapOptions;
import com.blackduck.integration.detect.workflow.blackduck.project.options.ProjectGroupFindResult;
import com.blackduck.integration.detect.workflow.blackduck.project.options.ProjectGroupOptions;
import com.blackduck.integration.detect.workflow.blackduck.project.options.ProjectVersionLicenseFindResult;
import com.blackduck.integration.detect.workflow.blackduck.project.options.ProjectVersionLicenseOptions;
import com.blackduck.integration.detect.workflow.blackduck.report.ReportData;
import com.blackduck.integration.detect.workflow.blackduck.report.service.ReportService;
import com.blackduck.integration.detect.workflow.codelocation.CodeLocationEventPublisher;
import com.blackduck.integration.detect.workflow.codelocation.CodeLocationNameManager;
import com.blackduck.integration.detect.workflow.codelocation.DetectCodeLocation;
import com.blackduck.integration.detect.workflow.componentlocationanalysis.BdioToComponentListTransformer;
import com.blackduck.integration.detect.workflow.componentlocationanalysis.GenerateComponentLocationAnalysisOperation;
import com.blackduck.integration.detect.workflow.componentlocationanalysis.ScanResultToComponentListTransformer;
import com.blackduck.integration.detect.workflow.event.EventSystem;
import com.blackduck.integration.detect.workflow.file.DirectoryManager;
import com.blackduck.integration.detect.workflow.phonehome.PhoneHomeManager;
import com.blackduck.integration.detect.workflow.project.DetectToolProjectInfo;
import com.blackduck.integration.detect.workflow.project.ProjectEventPublisher;
import com.blackduck.integration.detect.workflow.project.ProjectNameVersionDecider;
import com.blackduck.integration.detect.workflow.project.ProjectNameVersionOptions;
import com.blackduck.integration.detect.workflow.report.util.ReportConstants;
import com.blackduck.integration.detect.workflow.result.DetectResult;
import com.blackduck.integration.detect.workflow.result.ReportDetectResult;
import com.blackduck.integration.detect.workflow.status.FormattedCodeLocation;
import com.blackduck.integration.detect.workflow.status.OperationSystem;
import com.blackduck.integration.detect.workflow.status.Status;
import com.blackduck.integration.detect.workflow.status.StatusEventPublisher;
import com.blackduck.integration.detect.workflow.status.StatusType;
import com.blackduck.integration.detector.accuracy.detectable.DetectableEvaluator;
import com.blackduck.integration.detector.accuracy.directory.DirectoryEvaluator;
import com.blackduck.integration.detector.accuracy.entrypoint.DetectorRuleEvaluator;
import com.blackduck.integration.detector.accuracy.search.SearchEvaluator;
import com.blackduck.integration.detector.accuracy.search.SearchOptions;
import com.blackduck.integration.detector.base.DetectorType;
import com.blackduck.integration.detector.finder.DirectoryFinder;
import com.blackduck.integration.detector.rule.DetectorRuleSet;
import com.blackduck.integration.exception.IntegrationException;
import com.blackduck.integration.log.IntLogger;
import com.blackduck.integration.log.Slf4jIntLogger;
import com.blackduck.integration.rest.HttpUrl;
import com.blackduck.integration.rest.body.FileBodyContent;
import com.blackduck.integration.rest.exception.IntegrationRestException;
import com.blackduck.integration.rest.response.Response;
import com.blackduck.integration.sca.upload.rest.status.BinaryUploadStatus;
import com.blackduck.integration.util.IntEnvironmentVariables;
import com.blackduck.integration.util.IntegrationEscapeUtil;
import com.blackduck.integration.util.NameVersion;
import com.blackduck.integration.util.OperatingSystemType;
import com.blackducksoftware.bdio2.Bdio;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;

public class OperationRunner {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final DetectDetectableFactory detectDetectableFactory;
    private final DetectFontLoaderFactory detectFontLoaderFactory;

    private final Gson htmlEscapeDisabledGson;
    private final CodeLocationConverter codeLocationConverter;
    private final ExtractionEnvironmentProvider extractionEnvironmentProvider;

    private final StatusEventPublisher statusEventPublisher;
    private final ExitCodePublisher exitCodePublisher;
    private final CodeLocationEventPublisher codeLocationEventPublisher;
    private final DetectorEventPublisher detectorEventPublisher;

    private final CodeLocationNameManager codeLocationNameManager;

    private final DirectoryManager directoryManager;
    private final DetectConfigurationFactory detectConfigurationFactory;
    private final EventSystem eventSystem;
    private final FileFinder fileFinder;
    private final DetectInfo detectInfo;
    private final RapidScanResultAggregator rapidScanResultAggregator;
    private final ProjectEventPublisher projectEventPublisher;
    private final DetectExecutableRunner executableRunner;
    private final OperationAuditLog auditLog;
    private static final int[] LIMITED_FIBONACCI_SEQUENCE = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55};
    private static final int MIN_POLLING_INTERVAL_THRESHOLD_IN_SECONDS = 5;
    private static final String DEVELOPER_SCAN_ENDPOINT = ApiDiscovery.DEVELOPER_SCANS_PATH.getPath();
    private static final String DEVELOPER_SCAN_CONTENT_TYPE = "application/vnd.blackducksoftware.scan-evidence-1+protobuf";
    private static final String INTELLIGENT_SCAN_ENDPOINT = ApiDiscovery.INTELLIGENT_PERSISTENCE_SCANS_PATH.getPath();
    private static final String INTELLIGENT_SCAN_CONTENT_TYPE = "application/vnd.blackducksoftware.intelligent-persistence-scan-3+protobuf";
    private static final String INTELLIGENT_SCAN_SCASS_CONTENT_TYPE = "application/vnd.blackducksoftware.intelligent-persistence-scan-4+protobuf-jsonld";
    private static final String ALPINE_LINUX_OS = "ALPINE_LINUX";
    private static final String ALPINE_LINUX_ENV_VARIABLE = "SCAN_CLI_OS";
    private static final String OS_ARCHITECTURE_CONSTANT = "x64";
    public static final ImmutableList<Integer> RETRYABLE_AFTER_WAIT_HTTP_EXCEPTIONS = ImmutableList.of(408, 429, 502, 503, 504);
    public static final ImmutableList<Integer> RETRYABLE_WITH_BACKOFF_HTTP_EXCEPTIONS = ImmutableList.of(425, 500);
    private List<File> binaryUserTargets = new ArrayList<>();
    BinaryScanFindMultipleTargetsOperation binaryScanFindMultipleTargetsOperation;

    //Internal: Operation -> Action
    //Leave OperationSystem, but it becomes 'user facing groups of actions or steps'
    public OperationRunner(
        DetectDetectableFactory detectDetectableFactory,
        DetectFontLoaderFactory detectFontLoaderFactory,
        BootSingletons bootSingletons,
        UtilitySingletons utilitySingletons,
        EventSingletons eventSingletons
    ) {
        this.detectDetectableFactory = detectDetectableFactory;
        this.detectFontLoaderFactory = detectFontLoaderFactory;

        statusEventPublisher = eventSingletons.getStatusEventPublisher();
        exitCodePublisher = eventSingletons.getExitCodePublisher();
        codeLocationEventPublisher = eventSingletons.getCodeLocationEventPublisher();
        detectorEventPublisher = eventSingletons.getDetectorEventPublisher();
        projectEventPublisher = eventSingletons.getProjectEventPublisher();

        directoryManager = bootSingletons.getDirectoryManager();
        detectConfigurationFactory = bootSingletons.getDetectConfigurationFactory();
        eventSystem = bootSingletons.getEventSystem();
        fileFinder = bootSingletons.getFileFinder();
        detectInfo = bootSingletons.getDetectInfo();
        executableRunner = utilitySingletons.getExecutableRunner();

        OperationSystem operationSystem = utilitySingletons.getOperationSystem();
        codeLocationNameManager = utilitySingletons.getCodeLocationNameManager();

        //My Managed Dependencies
        this.htmlEscapeDisabledGson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
        this.codeLocationConverter = new CodeLocationConverter(utilitySingletons.getExternalIdFactory());
        this.extractionEnvironmentProvider = new ExtractionEnvironmentProvider(directoryManager);
        this.rapidScanResultAggregator = new RapidScanResultAggregator();
        this.auditLog = new OperationAuditLog(utilitySingletons.getOperationWrapper(), operationSystem);
    }

    public CodeLocationNameManager getCodeLocationNameManager() {
        return codeLocationNameManager;
    }
    
    public OperationAuditLog getAuditLog() {
        return auditLog;
    }

    public final Optional<DetectableTool> checkForDocker() throws OperationException {//TODO: refactor bazel+docker out of detectable
        return auditLog.namedInternal("Check For Docker", () -> {
            DetectableTool detectableTool = new DetectableTool(detectDetectableFactory::createDockerDetectable,
                extractionEnvironmentProvider, codeLocationConverter, "DOCKER", DetectTool.DOCKER,
                statusEventPublisher, exitCodePublisher
            );

            if (detectableTool.initializeAndCheckForApplicable(directoryManager.getSourceDirectory())) {
                return Optional.of(detectableTool);
            } else {
                return Optional.empty();
            }
        });
    }

    public final Optional<DetectableTool> checkForBazel() throws OperationException {//TODO: refactor bazel+docker out of detectable
        return auditLog.namedInternal("Check For Bazel", () -> {
            DetectableTool detectableTool = new DetectableTool(detectDetectableFactory::createBazelDetectable,
                extractionEnvironmentProvider, codeLocationConverter, "BAZEL", DetectTool.BAZEL,
                statusEventPublisher, exitCodePublisher
            );

            if (detectableTool.initializeAndCheckForApplicable(directoryManager.getSourceDirectory())) {
                return Optional.of(detectableTool);
            } else {
                return Optional.empty();
            }
        });
    }

    public DetectableToolResult executeDocker(DetectableTool detectableTool) throws OperationException {//TODO: refactor bazel+docker out of detectable
        return auditLog.namedPublic("Execute Docker", "Docker", detectableTool::extract);
    }

    public DetectableToolResult executeBazel(DetectableTool detectableTool) throws OperationException {//TODO: refactor bazel+docker out of detectable
        return auditLog.namedPublic("Execute Bazel", "Bazel", detectableTool::extract);
    }

    public final DetectorToolResult executeDetectors() throws OperationException {
        return auditLog.namedPublic("Execute Detectors", "Detectors", () -> {
            DetectorToolOptions detectorToolOptions = detectConfigurationFactory.createDetectorToolOptions();
            SearchOptions searchOptions = detectConfigurationFactory.createDetectorSearchOptions();
            DetectorRuleFactory detectorRuleFactory = new DetectorRuleFactory();
            DetectorRuleSet detectRuleSet = detectorRuleFactory.createRules(detectDetectableFactory);
            List<String> excludedDetectors = detectConfigurationFactory.getExcludedDetectors();
            DetectableExclusionEvaluator detectableExclusionEvaluator = new DetectableExclusionEvaluator(excludedDetectors);
            DetectorRuleEvaluator detectorRuleEvaluator = new DetectorRuleEvaluator(
                new SearchEvaluator(searchOptions),
                new DetectableEvaluator(),
                detectableExclusionEvaluator
            );
            DirectoryEvaluator directoryEvaluator = new DirectoryEvaluator(
                detectorRuleEvaluator,
                extractionEnvironmentProvider::createExtractionEnvironment
            );

            DetectorTool detectorTool = new DetectorTool(
                new DirectoryFinder(),
                codeLocationConverter,
                new DetectorIssuePublisher(),
                statusEventPublisher,
                exitCodePublisher,
                detectorEventPublisher,
                directoryEvaluator
            );
            return detectorTool.performDetectors(
                directoryManager.getSourceDirectory(),
                detectRuleSet,
                detectConfigurationFactory.createDetectorFinderOptions(),
                detectorToolOptions.getProjectBomTool(),
                detectorToolOptions.getRequiredDetectors(),
                detectorToolOptions.getRequiredAccuracyTypes(),
                fileFinder
            );
        });
    }

    public final void phoneHome(BlackDuckRunData blackDuckRunData) throws OperationException {
        auditLog.namedPublic("Phone Home", () -> blackDuckRunData.getPhoneHomeManager().ifPresent(PhoneHomeManager::startPhoneHome));
    }

    public DirectoryManager getDirectoryManager() {
        return directoryManager;
    }

    //Rapid
    public UUID initiateStatelessBdbaScan(BlackDuckRunData blackDuckRunData) throws OperationException {
        return auditLog.namedInternal("Initial Stateless BDBA Scan", () -> {
            File bdioHeader = new File(directoryManager.getBdioOutputDirectory() + "/bdio-header.pb");

            if (!bdioHeader.exists()) {
                throw new DetectUserFriendlyException(
                        "Unble to locate BDIO header from BDBA scan.",
                        ExitCodeType.FAILURE_SCAN
                    );
            }

            String uploadHeaderOperationName = "Upload BDIO Header to Initiate Scan";
            return uploadBdioHeaderToInitiateScan(blackDuckRunData, bdioHeader, uploadHeaderOperationName);
        });
    }


    public String getScanServicePostEndpoint() {
        if (detectConfigurationFactory.createScanMode() == BlackduckScanMode.INTELLIGENT) {
            return INTELLIGENT_SCAN_ENDPOINT;
        }
        return DEVELOPER_SCAN_ENDPOINT;
    }

    public String getScanServicePostContentType() {
        if (detectConfigurationFactory.createScanMode() == BlackduckScanMode.INTELLIGENT) {
            return INTELLIGENT_SCAN_CONTENT_TYPE;
        }
        return DEVELOPER_SCAN_CONTENT_TYPE;
    }

    public Optional<String> getContainerScanFilePath() {
        return detectConfigurationFactory.getContainerScanFilePath();
    }
    
    public File downloadContainerImage(Gson gson, File downloadDirectory, String containerImageUri) throws DetectUserFriendlyException, IntegrationException, IOException {
        ConnectionFactory connectionFactory = new ConnectionFactory(detectConfigurationFactory.createConnectionDetails());
        ArtifactResolver artifactResolver = new ArtifactResolver(connectionFactory, gson);
        return artifactResolver.parseFileNameAndDownloadArtifact(downloadDirectory, containerImageUri);
    }

    public File getContainerScanImage(Gson gson, File downloadDirectory) throws OperationException {
        return auditLog.namedPublic("Retrieve Container Scan Image File", () -> {
            Optional<String> containerImageFilePath = getContainerScanFilePath();
            File containerImageFile = null;
            if (containerImageFilePath.isPresent()) {
                String containerImageUri = containerImageFilePath.get();

                if (containerImageUri.startsWith("http://") || containerImageUri.startsWith("https://")) {
                    containerImageFile = downloadContainerImage(gson, downloadDirectory, containerImageUri);
                } else {
                    containerImageFile = new File(containerImageUri);
                }
            }
            return containerImageFile;
        });
    }

    public List<File> getMultiBinaryTargets() {
        return binaryScanFindMultipleTargetsOperation.getMultipleBinaryTargets();
    }

    public void updateBinaryUserTargets(File file) {
        binaryUserTargets.add(file);
    }

    public void saveAutonomousScanSettingsFile(AutonomousManager autonomousManager) throws OperationException {
        autonomousManager.updateUserProvidedBinaryScanTargets(binaryUserTargets);
        if (autonomousManager.getAutonomousScanEnabled()) {
            auditLog.namedPublic("Generate Autonomous Scan Settings File", () -> {
                autonomousManager.writeScanSettingsModelToTarget();
            });
        }
    }

    public JsonObject createScanMetadata(UUID scanId, NameVersion projectNameVersion, String type) {
        String scanPersistence = detectConfigurationFactory.createScanMode() == BlackduckScanMode.INTELLIGENT ? "STATEFUL" : "STATELESS";
        String projectGroupName = detectConfigurationFactory.createProjectGroupOptions().getProjectGroup();

        JsonObject imageMetadataObject = new JsonObject();
        imageMetadataObject.addProperty("scanId", scanId.toString());
        imageMetadataObject.addProperty("scanType", type);
        imageMetadataObject.addProperty("scanPersistence", scanPersistence);
        imageMetadataObject.addProperty("projectName", projectNameVersion.getName());
        imageMetadataObject.addProperty("projectVersionName", projectNameVersion.getVersion());
        imageMetadataObject.addProperty("projectGroupName", projectGroupName);

        return imageMetadataObject;
    }
    
    // Generic method to POST a file to /api/storage/containers endpoint of storage service
    public Response uploadFileToStorageService(BlackDuckRunData blackDuckRunData, String storageServiceEndpoint, File payloadFile, String postContentType, String operationName)
        throws OperationException {
        return uploadFileToStorageServiceWithHeaders(blackDuckRunData, storageServiceEndpoint, payloadFile, postContentType, operationName, null);
    }
    
    public Response uploadFileToStorageServiceWithHeaders(BlackDuckRunData blackDuckRunData, String storageServiceEndpoint, File payloadFile, String postContentType, String operationName, Map<String, String> headers) 
            throws OperationException {
        return auditLog.namedPublic(operationName, () -> {
            BlackDuckServicesFactory blackDuckServicesFactory = blackDuckRunData.getBlackDuckServicesFactory();
            BlackDuckApiClient blackDuckApiClient = blackDuckServicesFactory.getBlackDuckApiClient();

            HttpUrl postUrl = blackDuckRunData.getBlackDuckServerConfig().getBlackDuckUrl().appendRelativeUrl(storageServiceEndpoint);
            
            BlackDuckRequestBuilder requestBuilder = new BlackDuckRequestBuilder()
                    .postFile(payloadFile, ContentType.create(postContentType));
            
            if (headers != null) {
                for (String headerName : headers.keySet()) {
                    requestBuilder.addHeader(headerName, headers.get(headerName));
                }
            }
            
            BlackDuckResponseRequest buildBlackDuckResponseRequest = requestBuilder
                    .buildBlackDuckResponseRequest(postUrl);
            
            try (Response response = blackDuckApiClient.execute(buildBlackDuckResponseRequest)) {
                return response;
            } catch (IntegrationException e) {
                logger.trace("Could not execute file upload request to storage service.");
                throw new IntegrationException("Could not execute file upload request to storage service.", e);
            } catch (IOException e) {
                logger.trace("I/O error occurred during file upload request.");
