require 'observed/observer'
require 'observed/configurable'
require 'forwardable'
require 'logger'

module Observed
  # The DSL to describe Observed's configuration.
  # @example
  # context = ConfigDSL.new(builder: the_builder)
  # context.eval_file observed_conf_file(a.k.a user code describes Observed configuration)
  # context.config #=> can be used to instantiate Observed::System
  class ConfigDSL

    extend Forwardable

    include Observed::Configurable

    def_delegators :@builder, :observe, :translate, :report, :read, :write, :emit, :group, :run_group, :receive

    attribute :builder
    attribute :logger, default: Logger.new(STDOUT)

    def initialize(args)
      args[:builder] || fail("The key :builder must exist in #{args}")
      @builder = args[:builder]

      configure(args)
    end

    def eval_file(file)
      @file = File.expand_path(file)
      working_directory File.dirname(@file)
      logger.debug "Reading the file: #{@file}"
      code = File.read(@file)
      logger.debug "Evaluating: #{code}"
      instance_eval(code, @file)
    end

    # The `current directory` in which `require_relative` finds source files
    def working_directory(wd=nil)
      @working_directory = wd if wd
      @working_directory
    end

    # The replacement for Ruby's built-in `require_relative`.
    # Although the built-in one can not be used in `eval` or `instance_eval` or etc because
    # there is no `current file` semantics in `eval`, this replacement takes the file which is going to be evaluated
    # as the `current file`.
    # Thanks to this method, we can use `require_relative` in observed.conf files both when it is evaluated with `eval`
    # and when it is evaluated in result of `require`.
    def require_relative(lib)
      path = File.expand_path("#{working_directory}/#{lib}")
      logger.debug "Require '#{path}'"
      require path
    end

    # Build and returns the Observed configuration
    # @return [Observed::Config]
    def config
      @builder.build
    end

    # Load the file and evaluate the containing code in context of this object(a.k.a DSL).
    # @param [String|Pathname] file The path to Ruby script containing the code in Observed's configuration DSL,
    # typically `observed.conf`.
    def load!(file)
      eval_file file
    end

  end
end
