require 'lotus/utils/class'

module Lotus
  module Model
    module Config
      # Raised when an adapter class does not exist
      #
      # @since 0.2.0
      class AdapterNotFound < Lotus::Model::Error
        def initialize(adapter_name)
          super "Cannot find Lotus::Model adapter #{adapter_name}"
        end
      end

      # Configuration for the adapter
      #
      # Lotus::Model has its own global configuration that can be manipulated
      # via `Lotus::Model.configure`.
      #
      # New adapter configuration can be registered via `Lotus::Model.adapter`.
      #
      # @see Lotus::Model.adapter
      #
      # @example
      #   require 'lotus/model'
      #
      #   Lotus::Model.configure do
      #     adapter type: :sql, uri: 'postgres://localhost/database'
      #   end
      #
      #   Lotus::Model.configuration.adapter_config
      #   # => Lotus::Model::Config::Adapter(type: :sql, uri: 'postgres://localhost/database')
      #
      # By convention, Lotus inflects type to find the adapter class
      # For example, if type is :sql, derived class will be `Lotus::Model::Adapters::SqlAdapter`
      #
      # @since 0.2.0
      class Adapter
        # @return [Symbol] the adapter name
        #
        # @since 0.2.0
        attr_reader :type

        # @return [String] the adapter URI
        #
        # @since 0.2.0
        attr_reader :uri

        # @return [Hash] a list of non-mandatory options for the adapter
        #
        attr_reader :options

        # @return [String] the adapter class name
        #
        # @since 0.2.0
        attr_reader :class_name

        # Initialize an adapter configuration instance
        #
        # @param options [Hash] configuration options
        # @option options [Symbol] :type adapter type name
        # @option options [String] :uri adapter URI
        #
        # @return [Lotus::Model::Config::Adapter] a new apdapter configuration's
        #   instance
        #
        # @since 0.2.0
        def initialize(**options)
          opts     = options.dup

          @type    = opts.delete(:type)
          @uri     = opts.delete(:uri)
          @options = opts

          @class_name ||= Lotus::Utils::String.new("#{@type}_adapter").classify
        end

        # Initialize the adapter
        #
        # @param mapper [Lotus::Model::Mapper] the mapper instance
        #
        # @return [Lotus::Model::Adapters::SqlAdapter, Lotus::Model::Adapters::MemoryAdapter] an adapter instance
        #
        # @see Lotus::Model::Adapters
        #
        # @since 0.2.0
        def build(mapper)
          load_adapter
          instantiate_adapter(mapper)
        end

        private

        def load_adapter
          begin
            require "lotus/model/adapters/#{type}_adapter"
          rescue LoadError => e
            raise LoadError.new("Cannot find Lotus::Model adapter '#{type}' (#{e.message})")
          end
        end

        def instantiate_adapter(mapper)
          begin
            klass = Lotus::Utils::Class.load!(class_name, Lotus::Model::Adapters)
            klass.new(mapper, uri, options)
          rescue NameError
            raise AdapterNotFound.new(class_name)
          rescue => e
            raise "Cannot instantiate adapter of #{klass} (#{e.message})"
          end
        end

      end
    end
  end
end