/*
 * Copyright 2013-2026, Seqera Labs
 *
 * 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.
 */

package nextflow.executor

import java.nio.file.Path

import groovy.transform.CompileStatic
import groovy.transform.Memoized
import groovy.util.logging.Slf4j
import nextflow.Session
import nextflow.SysEnv
import nextflow.file.FileHelper
import nextflow.processor.TaskHandler
import nextflow.processor.TaskMonitor
import nextflow.processor.TaskRun
import nextflow.script.ScriptType
/**
 * Declares methods have to be implemented by a generic
 * execution strategy
 *
 * @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
 */
@Slf4j
@CompileStatic
@SupportedScriptTypes( [ScriptType.SCRIPTLET] )
abstract class Executor {

    /**
     * The current session object
     */
    Session session

    /**
     * The `executor` configuration settings
     */
    ExecutorConfig config

    /**
     * The executor simple name
     */
    String name

    /**
     * The queue holder that keep track of all tasks for this executor.
     */
    private TaskMonitor monitor

    TaskMonitor getMonitor() { monitor }

    protected String getDisplayName() {
        return fusionEnabled ? "$name (fusion enabled)" : name
    }

    /**
     * Allows to post-initialize the executor
     */
    final void init() {
        log.debug "[warm up] executor > ${getDisplayName()}"
        monitor = createTaskMonitor()
        monitor.start()
        register()
    }

    protected void register() { }

    void signal() {
        monitor.signal()
    }

    /**
     * Submit the specified task for execution to the underlying system
     * and add it to the queue of tasks to be monitored.
     *
     * @param task A {@code TaskRun} instance
     */
    void submit( TaskRun task ) {
        log.trace "Scheduling process: ${task}"

        if( session.isTerminated() ) {
            new IllegalStateException("Session terminated - Cannot add process to execution queue: ${task}")
        }

        final handler = createTaskHandler(task)

        /*
         * Add the task to the queue for processing
         * Note: queue is implemented as a fixed size blocking queue, when
         * there's not space the *put* operation will block until some other tasks finish
         */
        monitor.schedule(handler)
    }

    /**
     * The path where scratch data is written for the current executor.
     *
     * @return The executor base work directory
     */
    Path getWorkDir() {
       session.getWorkDir()
    }

    /**
     * The path where project bin directory are stored
     *
     * @return The executor base bin directory
     */
    Path getBinDir() {
       return session.getBinDir()
    }

    @Memoized
    Path getStageDir() {
        return getWorkDir().resolve("stage-${getSession().uniqueId}")
    }

    boolean isForeignFile(Path path) {
        path.scheme != getStageDir().scheme
    }

    /**
     * Temporary work directory relative to the executor work directory
     *
     * @return The temporary directory path
     */
    Path getTempDir( String name = null, boolean create = true ) {
        def path = FileHelper.createTempFolder(getWorkDir())
        if( name )
            path = path.resolve(name)

        if( !path.exists() && create && !path.mkdirs() )
            throw new IOException("Unable to create directory: $path -- Check file system permission" )

        return path
    }

    /**
     * @return Create a new instance of the {@code TaskQueueHolder} component
     */
    abstract protected TaskMonitor createTaskMonitor()

    /**
     * @return A reference to the current {@code #queueHolder} object
     */
    TaskMonitor getTaskMonitor()  { monitor }

    /**
     * @return Create a new {@code TaskHandler} to manage the scheduling
     * actions for this task
     */
    abstract TaskHandler createTaskHandler(TaskRun task)

    /**
     * @return {@code true} whenever the containerization is managed by the executor itself
     */
    boolean isContainerNative() {
        return false
    }

    /**
     * Determines which container engine settings in the nextflow config file
     * will be used by this executor e.g. {@code 'docker'}, {@code 'singularity'}, etc.
     *
     * When {@code null} is returned the setting for the current engine marked as 'enabled' will be used.
     *
     * @return
     *      {@code docker} when {#link #isContainerNative} is {@code true} and {@code null} otherwise
     *
     */
    String containerConfigEngine() {
        return null
    }

    /**
     * @return {@code true} whenever the secrets handling is managed by the executing platform itself
     */
    boolean isSecretNative() {
        return false
    }

    /**
     * Determines whether large staging scripts should be written to a separate `.command.stage` file
     * instead of being inlined in the main wrapper script.
     *
     * This is enabled by default only for HPC grid schedulers (extending {@link AbstractGridExecutor})
     * because they use a shared filesystem where the stage file can be reliably accessed.
     * For cloud executors using object storage (e.g. S3, GCS), the stage file would need to be
     * written to remote storage and then fetched, which may not work reliably in all cases.
     *
     * Cloud executors that support this feature (e.g. Google Batch with gcsfuse mounts) can
     * override this method to return {@code true}.
     *
     * Can be controlled via the {@code NXF_WRAPPER_STAGE_FILE_THRESHOLD} environment variable.
     *
     * @return {@code true} when the executor supports writing large staging scripts to a separate file
     * @see <a href="https://github.com/nextflow-io/nextflow/issues/4279">GitHub issue #4279</a>
     */
    boolean isStageFileEnabled() {
        return isStageFileEnabled0(this)
    }

    @Memoized
    static private boolean isStageFileEnabled0(Executor executor) {
        final flag = SysEnv.get('NXF_WRAPPER_STAGE_FILE_THRESHOLD')
        return flag==null
            ? executor instanceof AbstractGridExecutor
            : true
    }

    /**
     * @return {@code true} when the executor uses fusion file system
     */
    boolean isFusionEnabled() {
        return false
    }

    /**
     * Allow graceful termination of executor resources
     */
    void shutdown() { }
}
