require "backup/config/dsl"
require "backup/config/helpers"

module Backup
  module Config
    class Error < Backup::Error; end

    DEFAULTS = {
      config_file: "config.rb",
      data_path: ".data",
      tmp_path: ".tmp"
    }

    class << self
      include Utilities::Helpers

      attr_reader :user, :root_path, :config_file, :data_path, :tmp_path

      # Loads the user's +config.rb+ and all model files.
      def load(options = {})
        update(options) # from the command line

        unless File.exist?(config_file)
          raise Error, "Could not find configuration file: '#{config_file}'."
        end

        config = File.read(config_file)
        version = Backup::VERSION.split(".").first
        unless config =~ /^# Backup v#{ version }\.x Configuration$/
          raise Error, <<-EOS
            Invalid Configuration File
            The configuration file at '#{config_file}'
            does not appear to be a Backup v#{version}.x configuration file.
            If you have upgraded to v#{version}.x from a previous version,
            you need to upgrade your configuration file.
            Please see the instructions for upgrading in the Backup documentation.
          EOS
        end

        dsl = DSL.new
        dsl.instance_eval(config, config_file)

        update(dsl._config_options)  # from config.rb
        update(options)              # command line takes precedence

        Dir[File.join(File.dirname(config_file), "models", "*.rb")].each do |model|
          dsl.instance_eval(File.read(model), model)
        end
      end

      def hostname
        @hostname ||= run(utility(:hostname))
      end

      private

      # If :root_path is set in the options, all paths will be updated.
      # Otherwise, only the paths given will be updated.
      def update(options = {})
        root_path = options[:root_path].to_s.strip
        new_root = root_path.empty? ? false : set_root_path(root_path)

        DEFAULTS.each do |name, ending|
          set_path_variable(name, options[name], ending, new_root)
        end
      end

      # Sets the @root_path to the given +path+ and returns it.
      # Raises an error if the given +path+ does not exist.
      def set_root_path(path)
        # allows #reset! to set the default @root_path,
        # then use #update to set all other paths,
        # without requiring that @root_path exist.
        return @root_path if path == @root_path

        path = File.expand_path(path)
        unless File.directory?(path)
          raise Error, <<-EOS
            Root Path Not Found
            When specifying a --root-path, the path must exist.
            Path was: #{path}
          EOS
        end
        @root_path = path
      end

      def set_path_variable(name, path, ending, root_path)
        # strip any trailing '/' in case the user supplied this as part of
        # an absolute path, so we can match it against File.expand_path()
        path = path.to_s.sub(/\/\s*$/, "").lstrip
        new_path = false
        # If no path is given, the variable will not be set/updated
        # unless a root_path was given. In which case the value will
        # be updated with our default ending.
        if path.empty?
          new_path = File.join(root_path, ending) if root_path
        else
          # When a path is given, the variable will be set/updated.
          # If the path is relative, it will be joined with root_path (if given),
          # or expanded relative to PWD.
          new_path = File.expand_path(path)
          unless path == new_path
            new_path = File.join(root_path, path) if root_path
          end
        end
        instance_variable_set(:"@#{name}", new_path) if new_path
      end

      def reset!
        @user      = ENV["USER"] || Etc.getpwuid.name
        @root_path = File.join(File.expand_path(ENV["HOME"] || ""), "Backup")
        update(root_path: @root_path)
      end
    end

    reset! # set defaults on load
  end
end
