module RVM
  class Environment

    PREFIX_OPTIONS = [:trace, :json, :yaml]

    def self.default_rvm_path
      value = `bash '#{File.expand_path('../shell/calculate_rvm_path.sh', File.dirname(__FILE__))}'`.strip
      $?.success? && !value.empty? ? File.expand_path(value) : nil
    end

    # Returns the environment identifier for the current environment,
    # as determined from the GEM_HOME.
    def self.current_environment_id
      @current_environment_id ||= begin
        gem_home = ENV['GEM_HOME'].to_s.strip
        if !gem_home.empty? && gem_home =~ /rvm\/gems\//
          File.basename(gem_home)
        else
          matching_path = $:.select { |item| item =~ /rvm\/rubies/ }.first
          matching_path.to_s.gsub(/^.*rvm\/rubies\//, '').split('/')[0] || "system"
        end
      end
    end

    # Returns the ruby string that represents the current environment.
    def self.current_ruby_string
      identifier_to_ruby_string(current_environment_id)
    end

    # Converts a ruby identifier (string + gemset) to just the ruby string.
    def self.identifier_to_ruby_string(identifier)
      identifier.gsub(/@.*$/, '')
    end

    def self.identifier_to_gemset_name(identifier)
      identifier.gsub(/^.*@/, '')
    end

    # Returns the currentl environment.
    # Note that when the ruby is changed, this is reset - Also,
    # if the gemset is changed it will also be reset.
    def self.current
      @current_environment ||= Environment.new(current_environment_id)
    end

    # Sets the current environment back to the currently running ruby
    # or the system env (if it can't be determined from GEM_HOME).
    def self.reset_current!
      @current_environment = nil
    end

    # Lets you build a command up, without needing to see the output.
    # Examples:
    #
    #  rvm :use, "ree@rails3", :install => true
    #  > rvm use ree@rails3 --install
    #
    #  rvm :install, "ree@rails3", :rvm_by_path => true, :rubygems_version => "1.8.23"
    #  > rvm_rubygems_version=1.8.23 $rvm_path/bin/rvm install ree@rails3
    #
    def rvm(*args)
      options = extract_options!(args)
      silent = options.delete(:silent)
      rvm_by_path = options.delete(:rvm_by_path)
      rubygems_version = options.delete(:rubygems_version)
      rearrange_options!(args, options)
      args += hash_to_options(options)
      args.map! { |a| a.to_s }

      rvm_path = config_value_for(:rvm_path, self.class.default_rvm_path, false)
      program = rvm_by_path ? "#{rvm_path}/bin/rvm" : "rvm"
      program = "rvm_rubygems_version=#{rubygems_version} #{program}" if rubygems_version
      if silent
        run_silently(program, *args)
      else
        run(program, *args)
      end
    end

    # Run commands inside the given directory.
    def chdir(dir)
      run_silently :pushd, dir.to_s
      result = Dir.chdir(dir) { yield }
      run_silently :popd
      result
    end

    protected

    # Moves certain options (e.g. yaml, json etc) to the front
    # of the arguments list, making stuff like sets work.
    def rearrange_options!(args, options)
      prefix_options = {}
      (PREFIX_OPTIONS + PREFIX_OPTIONS.map { |o| o.to_s }).each do |k|
        if options.has_key?(k)
          value = options.delete(k)
          prefix_options[k.to_sym] = value
        end
      end
      hash_to_options(prefix_options).reverse.each { |o| args.unshift(o) }
    end

    def ruby_string(result)
      if result && result[:rvm_env_string]
        Environment.identifier_to_ruby_string result[:rvm_env_string]
      else
        self.class.identifier_to_ruby_string(expanded_name)
      end
    end

    # Checks whether the given environment is compatible with the current
    # ruby interpeter.
    def compatible_with_current?(result)
      ruby_string(result) == self.class.current_ruby_string
    end

    # Given an environment identifier, it will add the the given
    # gemset to the end to form a qualified identifier name.
    def self.environment_with_gemset(environment, gemset)
      environment_name, gemset_name = environment.split("@", 2)
      environment_name = "default"     if environment_name.to_s.empty?
      environment_name << "@#{gemset}" unless gemset.to_s.empty?
      environment_name
    end

    # Returns a value, or nil if it is blank.
    def normalize(value)
      value = value.to_s.strip
      value.empty? ? nil : value
    end

    # Normalizes an array, removing blank lines.
    def normalize_array(value)
      value.split("\n").map { |line| line.strip }.reject { |line| line.empty? }
    end

    # Extract options from a hash.
    def extract_options!(args)
      args.last.is_a?(Hash) ? args.pop : {}
    end

    # Converts a hash of options to an array of command line argumets.
    # If the value is false, it wont be added but if it is true only the
    # key will be added. Lastly, when the value is neither true or false,
    # to_s will becalled on it and it shall be added to the array.
    def hash_to_options(options)
      result = []
      options.each_pair do |key, value|
        real_key = "--#{key.to_s.gsub("_", "-")}"
        if value == true
          result << real_key
        elsif value != false
          result << real_key
          result << value.to_s
        end
      end
      result
    end

    # Recursively normalize options.
    def normalize_option_value(value)
      case value
      when Array
        value.map { |option| normalize_option_value(option) }.join(",")
      else
        value.to_s
      end
    end

    def use_env_from_result!(result)
      if compatible_with_current?(result)
        ENV['GEM_HOME']    = result[:GEM_HOME]
        ENV['GEM_PATH']    = result[:GEM_PATH]
        Gem.clear_paths if defined?(Gem)
      else
        raise IncompatibleRubyError.new(result, "The given ruby environment requires #{ruby_string(result)} (versus #{self.class.current_ruby_string})")
      end
    end

  end
end
