# encoding: utf-8

module Backup
  module Config
    # Context for loading user config.rb and model files.
    class DSL
      class Error < Backup::Error; end
      Model = Backup::Model

      class << self
        private

        # List the available database, storage, syncer, compressor, encryptor
        # and notifier constants. These are used to define constant names within
        # Backup::Config::DSL so that users may use a constant instead of a string.
        # Nested namespaces are represented using Hashs. Deep nesting supported.
        #
        # Example, instead of:
        #  database "MySQL" do |mysql|
        #  sync_with "RSync::Local" do |rsync|
        #
        # You can do:
        #  database MySQL do |mysql|
        #  sync_with RSync::Local do |rsync|
        #
        def add_dsl_constants
          create_modules(
            DSL,
            [ # Databases
              ['MySQL', 'PostgreSQL', 'MongoDB', 'Redis', 'Riak', 'OpenLDAP', 'SQLite'],
              # Storages
              ['S3', 'CloudFiles', 'Ninefold', 'Dropbox', 'FTP',
              'SFTP', 'SCP', 'RSync', 'Local'],
              # Compressors
              ['Gzip', 'Bzip2', 'Custom', 'Pbzip2', 'Lzma'],
              # Encryptors
              ['OpenSSL', 'GPG'],
              # Syncers
              [
                { 'Cloud' => ['CloudFiles', 'S3'] },
                { 'RSync' => ['Push', 'Pull', 'Local'] }
              ],
              # Notifiers
              ['Mail', 'Twitter', 'Campfire', 'Prowl',
              'Hipchat', 'PagerDuty', 'Pushover', 'HttpPost', 'Nagios',
              'Slack', 'FlowDock', 'Zabbix', 'Ses']
            ]
          )
        end

        def create_modules(scope, names)
          names.flatten.each do |name|
            if name.is_a?(Hash)
              name.each do |key, val|
                create_modules(get_or_create_empty_module(scope, key), [val])
              end
            else
              get_or_create_empty_module(scope, name)
            end
          end
        end

        def get_or_create_empty_module(scope, const)
          if scope.const_defined?(const)
            scope.const_get(const)
          else
            scope.const_set(const, Module.new)
          end
        end
      end

      add_dsl_constants  # add constants on load

      attr_reader :_config_options

      def initialize
        @_config_options = {}
      end

      # Allow users to set command line path options in config.rb
      [:root_path, :data_path, :tmp_path].each do |name|
        define_method name, lambda {|path| _config_options[name] = path }
      end

      # Allows users to create preconfigured models.
      def preconfigure(name, &block)
        unless name.is_a?(String) && name =~ /^[A-Z]/
          raise Error, "Preconfigured model names must be given as a string " +
                        "and start with a capital letter."
        end

        if DSL.const_defined?(name)
          raise Error, "'#{ name }' is already in use " +
                        "and can not be used for a preconfigured model."
        end

        DSL.const_set(name, Class.new(Model))
        DSL.const_get(name).preconfigure(&block)
      end

    end
  end
end
