# frozen_string_literal: true

require 'concurrent/executor/executor_service'

module GoodJob
  class SharedExecutor
    # Allow posting tasks directly to instances
    include Concurrent::ExecutorService

    MAX_THREADS = 2

    # @!attribute [r] instances
    #   @!scope class
    #   List of all instantiated SharedExecutor in the current process.
    #   @return [Array<GoodJob::SharedExecutor>, nil]
    cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false

    attr_reader :executor

    def initialize
      @mutex = Mutex.new

      self.class.instances << self
    end

    def post(*args, &task)
      unless running?
        @mutex.synchronize do
          next if running?

          create_executor
        end
      end

      @executor&.post(*args, &task)
    end

    def running?
      @executor&.running?
    end

    # Tests whether the scheduler is shutdown and no tasks are running.
    # @return [Boolean, nil]
    def shutdown?
      @executor.nil? || (@executor.shutdown? && !@executor.shuttingdown?)
    end

    # Shut down the SharedExecutor.
    # Use {#shutdown?} to determine whether threads have stopped.
    # @param timeout [Numeric, nil] Seconds to wait for active threads.
    #   * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
    #   * +-1+, the scheduler will wait until the shutdown is complete.
    #   * +0+, the scheduler will immediately shutdown and stop any threads.
    #   * A positive number will wait that many seconds before stopping any remaining active threads.
    # @return [void]
    def shutdown(timeout: -1)
      return if @executor.nil? || (@executor.shutdown? && !@executor.shuttingdown?)

      @executor.shutdown if @executor.running?

      if @executor.shuttingdown? && timeout # rubocop:disable Style/GuardClause
        executor_wait = timeout.negative? ? nil : timeout
        return if @executor.wait_for_termination(executor_wait)

        @executor.kill
        @executor.wait_for_termination
      end
    end

    def restart(timeout: -1)
      shutdown(timeout: timeout) if running?
      create_executor
    end

    private

    def create_executor
      @executor = Concurrent::ThreadPoolExecutor.new(
        min_threads: 0,
        max_threads: MAX_THREADS,
        auto_terminate: true,
        idletime: 60,
        max_queue: 0,
        fallback_policy: :discard
      )
    end
  end
end