module Rails
  module DataMapper

    def self.storage
      Storage
    end

    class Storage
      attr_reader :name, :config

      def self.create_all
        with_local_repositories { |config| create_environment(config) }
      end

      def self.drop_all
        with_local_repositories { |config| drop_environment(config) }
      end

      def self.create_environment(config)
        config.each { |repo_name, repo_config| new(repo_name, repo_config).create }
      end

      def self.drop_environment(config)
        config.each { |repo_name, repo_config| new(repo_name, repo_config).drop }
      end

      def self.new(name, config)
        klass = lookup_class(config['adapter'])
        if klass.equal?(self)
          super(name, config)
        else
          klass.new(name, config)
        end
      end

      class << self
      private

        def with_local_repositories
          Rails::DataMapper.configuration.repositories.each_value do |config|
            if config['host'].blank? || %w[ 127.0.0.1 localhost ].include?(config['host'])
              yield(config)
            else
              puts "This task only modifies local databases. #{config['database']} is on a remote host."
            end
          end
        end

        def lookup_class(adapter)
          klass_name = normalized_adapter_name(adapter).camelize.to_sym

          unless Storage.const_defined?(klass_name)
            raise "Adapter #{adapter} not supported (#{klass_name.inspect})"
          end

          const_get(klass_name)
        end

        def normalized_adapter_name(adapter_name)
          adapter_name.to_s == 'sqlite3' ? 'sqlite' : adapter_name
        end

      end

      def initialize(name, config)
        @name, @config = name.to_sym, config
      end

      def create
        puts create_message if _create
      end

      def drop
        puts drop_message if _drop
      end

      def database
        @database ||= config['database'] || config['path']
      end

      def username
        @username ||= config['username'] || ''
      end

      def password
        @password ||= config['password'] || ''
      end

      def charset
        @charset ||= config['charset'] || ENV['CHARSET'] || 'utf8'
      end

      def create_message
        "[datamapper] Created database '#{database}'"
      end

      def drop_message
        "[datamapper] Dropped database '#{database}'"
      end

      # Create the configured database
      #
      # This is a noop so that calling this method
      # won't explode on people who use adapters that
      # don't support creating a storage recepticle
      def _create
        true
      end

      # Drop the configured database
      #
      # This is a noop so that calling this method
      # won't explode on people who use adapters that
      # don't support dropping a storage recepticle
      def _drop
        true
      end

      class Sqlite < Storage

        # This is a noop for sqlite
        #
        # Overwritten solely for documentation purposes
        #
        # Both auto_migrate!/auto_upgrade! will create the actual database
        # if the connection has been setup properly and there actually
        # are statements to execute (i.e. at least one model is declared)
        #
        # DataMapper.setup alone won't create the actual database so there
        # really is no API to simply create an empty database for sqlite3.
        #
        # we return true to indicate success nevertheless
        def _create
          true
        end

        def _drop
          return if in_memory?
          path.unlink if path.file?
        end

        def create_message
          "[datamapper] db:create is a noop for sqlite3, use db:automigrate instead (#{database})"
        end

      private

        def in_memory?
          database == ':memory:'
        end

        def path
          @path ||= Pathname(File.expand_path(database, Rails.root))
        end

      end

      class Mysql < Storage
        def _create
          execute("CREATE DATABASE `#{database}` DEFAULT CHARACTER SET #{charset} DEFAULT COLLATE #{collation}")
        end

        def _drop
          execute("DROP DATABASE IF EXISTS `#{database}`")
        end

      private

        def execute(statement)
          command = [
            'mysql',
            username.present? ? "--user=#{username}"     : nil,
            password.present? ? "--password=#{password}" : nil,
            '-e',
            statement,
          ].compact
          system(*command)
        end

        def collation
          @collation ||= config['collation'] || ENV['COLLATION'] || 'utf8_unicode_ci'
        end

      end

      class Postgres < Storage
        def _create
          system(
            'createdb',
            '-E',
            charset,
            '-U',
            username,
            database
          )
        end

        def _drop
          system(
            'dropdb',
            '-U',
            username,
            database
          )
        end

      end
    end
  end
end
