require_dependency 'mixin/rails_logger'
require_dependency 'mixin/start_finish_logger'

class Sequencer
  include ::Mixin::RailsLogger
  include ::Mixin::StartFinishLogger

  attr_reader :sequence

  # Convenience wrapper for instant processing with the given attributes.
  #
  # @example
  #  Sequencer.process('Example::Sequence')
  #
  # @example
  #  Sequencer.process('Example::Sequence',
  #    parameters: {
  #      some: 'value',
  #    },
  #    expecting: [:result, :answer]
  #  )
  #
  # @return [Hash{Symbol => Object}] the final result state attributes and values
  def self.process(sequence, *args)
    new(sequence, *args).process
  end

  # Provides the log level definition for the requested Sequencer component.
  #
  # @example
  #  Sequencer.log_level_for(:state)
  #  #=> { get: :debug, set: :debug, ... }
  #
  # @return [ActiveSupport::HashWithIndifferentAcces] the log level definition
  def self.log_level_for(component)
    Setting.get('sequencer_log_level').with_indifferent_access[component]
  end

  # Initializes a new Sequencer instance for the given Sequence with parameters and expecting result.
  #
  # @example
  #  Sequencer.new('Example::Sequence')
  #
  # @example
  #  Sequencer.new('Example::Sequence',
  #    parameters: {
  #      some: 'value',
  #    },
  #    expecting: [:result, :answer]
  #  )
  def initialize(sequence, parameters: {}, expecting: nil)
    @sequence   = Sequencer::Sequence.constantize(sequence)
    @parameters = parameters
    @expecting  = expecting

    # fall back to sequence default expecting if no explicit
    # expecting was given for this sequence
    return if !@expecting.nil?

    @expecting = @sequence.expecting
  end

  # Processes the Sequence the instance was initialized with.
  #
  # @example
  #  sequence.process
  #
  # @return [Hash{Symbol => Object}] the final result state attributes and values
  def process
    log_start_finish(log_level[:start_finish], "Sequence '#{sequence.name}'") do

      sequence.units.each_with_index do |unit, index|

        state.process do

          log_start_finish(log_level[:unit], "Sequence '#{sequence.name}' Unit '#{unit.name}' (index: #{index})") do
            unit.process(state)
          end
        end
      end
    end

    state.to_h.tap do |result|
      logger.public_send(log_level[:result]) { "Returning Sequence '#{sequence.name}' result: #{result.inspect}" }
    end
  end

  private

  def state
    @state ||= Sequencer::State.new(sequence,
                                    parameters: @parameters,
                                    expecting:  @expecting)
  end

  def log_level
    @log_level ||= self.class.log_level_for(:sequence)
  end
end
