module Sprout::System

# The abstract base class for all supported system/platform types.
# In general, users are created by calling the +create+ factory method
# on the +System+ module.
#     
#     System.create
#
# Assuming you call the create method, you should wind up with
# a concrete system that matches your system, and these concrete
# users will generally be derived from this base class.
#
class BaseSystem

  ##
  # Get the home path for a system on a particular operating system.
  #
  # This path will be different, depending on which system owns
  # the curren process, and which operating system they are on.
  #
  def home
    @home ||= find_home
  end

  ##
  # Set the home path for a system on a particular operating system.
  # 
  # If you request the home path before setting it, we will
  # attempt to determine the home path of the current system for
  # the current operating system.
  #
  # This is just a simple way to override the default behavior.
  #
  def home=(home)
    @home = home
  end

  ##
  # Some operating systems (like OS X and Windows) have a 
  # specific location where applications are expected to store
  # files for a particular system. This location is generally
  # a subdirectory of +home+.
  # 
  # The value of this location will usually be overridden in
  # concrete System classes.
  #
  def library
    return home
  end

  ##
  # Instantiate and return a new Sprout::ProcessRunner so
  # that we can execute it.
  #
  def get_process_runner
    Sprout::ProcessRunner.new
  end

  def can_execute? platform
    platform == :universal
  end

  ##
  # Creates a new process, executes the command
  # and returns whatever the process wrote to stdout, or stderr.
  #
  # Raises a +Sprout::Errors::ExecutionError+ if the process writes to stderr
  #
  def execute(tool, options='')
    Sprout::Log.puts("#{tool} #{options}")
    runner = get_and_execute_process_runner(tool, options)
    error  = runner.read_err
    result = runner.read

    if(result.size > 0)
      Sprout::Log.puts result
    end

    if(error.size > 0)
      raise Sprout::Errors::ExecutionError.new("[ERROR] #{error}")
    end

    result || error
  end

  ##
  # Creates and returns the process without
  # attempting to read or write to the stream.
  # This is useful for interacting with
  # long-lived CLI processes like FCSH or FDB.
  #
  def execute_silent(tool, options='')
    get_and_execute_process_runner(tool, options)
  end

  ##
  # Execute a new process in a separate thread.
  #
  def execute_thread(tool, options='')
    runner = nil
    Thread.new do
      runner = execute_silent(tool, options)
    end
    # Wait for the runner to be created
    # before returning a nil reference
    # that never gets populated...
    while runner.nil? do
      sleep(0.1)
    end
    runner
  end

  ##
  # Clean the provided +path+ String for the current
  # operating system.
  #
  # Each operating system behaves differently when we
  # attempt to execute a file with spaces in the +path+
  # to the file.
  # 
  # Subclasses will generally override this method and
  # clean the path appropriately for their operating 
  # system.
  #
  def clean_path(path)
  end

  ##
  # Different operating systems will store Application data
  # different default locations.
  #
  # Subclasses will generally override this method and 
  # return the appropriate location for their operating system.
  #
  # +name+ String value of the Application name for which we'd
  # like to store data.
  #
  def application_home(name)
    return File.join(library, format_application_name(name.to_s));
  end

  ##
  # Template method that should be overridden by
  # subclasses.
  #
  def format_application_name(name)
    name
  end

  protected

  def env_homedrive
    ENV['HOMEDRIVE']
  end

  def env_homepath
    ENV['HOMEPATH']
  end

  def env_homedrive_and_homepath
    drive = env_homedrive
    path = env_homepath
    "#{drive}:#{path}" if drive && path
  end

  def env_userprofile
    ENV['USERPROFILE']
  end

  def env_home
    ENV['HOME']
  end

  def tilde_home
    File.expand_path("~")
  end

  def alt_separator?
    File::ALT_SEPARATOR
  end

  def worst_case_home
    return "C:\\" if alt_separator?
    return "/"
  end

  def find_home
    [:env_userprofile, :env_home, :env_homedrive_and_homepath].each do |key|
      value = self.send(key)
      return value unless value.nil?
    end

    begin
      return tilde_home
    rescue StandardError
      worst_case_home
    end
  end

  protected

  ##
  # Get a process runner and execute the provided +executable+,
  # with the provided +options+.
  #
  # +executable+ String path to the external executable file.
  #
  # +options+ String commandline options to send to the +executable+.
  #
  def get_and_execute_process_runner tool, options=nil
    runner = get_process_runner
    runner.execute_open4 clean_path(tool), options
    runner
  end

end

class ThreadMock # :nodoc:
  def alive?
    return false
  end
end

end