module Celluloid
  module Supervision
    class Configuration
      class << self
        def deploy(options = {})
          define(options).deploy
        end

        def define(options = {})
          new(options)
        end
      end

      extend Forwardable

      def_delegators :current_instance,
                     :delete,
                     :key?,
                     :set,
                     :get,
                     :[],
                     :[]=,
                     :injection!,
                     :injections!

      attr_accessor :instances

      def initialize(options = {})
        @instances = [Instance.new]
        @branch = :services
        @i = 0 # incrementer of instances in this branch
        resync_accessors
        @configuration = options

        if options.is_a? Hash
          options[:configuration] ||= Container::Behavior.configure(options)
          @configuration = instance_eval(&options[:configuration])
          @supervisor ||= @configuration.fetch(:supervisor, :"Celluloid.services")
        end
        @supervisor ||= :"Celluloid.services"

        define(@configuration) if (@configuration.is_a?(Hash) || @configuration.is_a?(Array)) && @configuration.any?
      end

      def provider
        @provider ||= if @supervisor.is_a? Hash
                        @supervisor[:type].run!(@supervisor)
                      elsif @supervisor.is_a? Symbol
                        @supervisor = Object.module_eval(@supervisor.to_s)
                        provider
                      elsif @supervisor.is_a? Class
                        @supervisor.run!
                      elsif @supervisor.respond_to? :supervise
                        @supervisor
                      else
                        raise Error::InvalidSupervisor
                      end
      end

      def deploy(options = {})
        define(options) if options.any?
        @instances.each do |instance|
          provider.add instance.merge(branch: @branch)
        end
        provider
      end

      def count
        @instances.count
      end

      def each(&block)
        @instances.each(&block)
      end

      def resync_accessors
        # methods for setting and getting the usual defaults
        Configuration.parameters(:mandatory, :optional, :plugins, :meta).each do |key|
          [:"#{key}!", :"#{key}="].each do |m|
            self.class.instance_eval do
              remove_method :"#{m}" rescue nil # avoid warnings in tests
              define_method(m) { |p| current_instance.send(m, p) }
            end
          end
          [:"#{key}?", :"#{key}"].each do |m|
            self.class.instance_eval do
              remove_method :"#{m}" rescue nil # avoid warnings in tests
              define_method(m) { current_instance.send(m) }
            end
          end
        end

        Configuration.aliases.each do |_alias, _original|
          ["!", :"=", :"?", :""]. each do |m|
            self.class.instance_eval do
              remove_method :"#{_alias}#{m}" rescue nil # avoid warnings in tests
              alias_method :"#{_alias}#{m}", :"#{_original}#{m}"
            end
          end
        end
      end

      def merge!(values)
        if values.is_a?(Configuration) || values.is_a?(Hash)
          current_instance.merge!(values)
        else
          raise Error::Invalid
        end
      end

      def merge(values)
        if values.is_a?(Configuration) || values.is_a?(Hash)
          current_instance.merge(values)
        else
          raise Error::Invalid
        end
      end

      def export
        return current_instance.to_hash if @i == 0
        @instances.map(&:export)
      end

      def include?(name)
        @instances.map(&:name).include? name
      end

      def define(configuration, fail = false)
        if configuration.is_a? Array
          configuration.each { |c| define(c, fail) }
        else
          unless include? configuration[:as]
            begin
              current_instance.define(configuration, fail)
            rescue Error::AlreadyDefined
              increment
              retry
            end
          end
        end
        self
      end

      def increment
        @i += 1
      end
      alias another increment

      def add(options)
        define(options)
        provider.supervise options if Configuration.valid? options
      end

      def shutdown
        @provider.shutdown
      end

      private

      def current_instance
        @instances[@i] ||= Instance.new
      end

      def invoke_injection(_point)
        # de puts "injection? #{point}"
      end
    end
  end
end
