class Rubish::Workspace < Rubish::Mu

  class << self
    # this is the default workspace (used by the singleton context
    def singleton
      @singleton ||= Rubish::Workspace.new
    end

    alias_method :global, :singleton
  end

  module Base
    
    # TODO move this to context?
    def cd(dir,&block)
      if block
        begin
          old_dir = FileUtils.pwd
          FileUtils.cd File.expand_path(dir)
          # hmmm.. calling instance_eval has weird effects, dunno why
          #self.instance_eval &block
          return block.call
        ensure
          FileUtils.cd old_dir
        end
      else
        FileUtils.cd File.expand_path(dir)
      end
    end

    def puts(*args)
      current_context.o.puts(*args)
      return Rubish::Null
    end

    def pp(obj)
      PP.pp(obj,current_context.o)
      return Rubish::Null
    end

    def cmd(method,*args)
      Rubish::Command.new(method,args)
    end

    def p(cmds_or_workspace=nil,&block)
      # self is the workspace
      case cmds_or_workspace
      when Array
        raise "build pipe from an array of commands or a block" if block
        cmds = cmds_or_workspace
        Rubish::Pipe.new(cmds)
      when Rubish::Workspace
        workspace = cmds_or_workspace
        Rubish::Pipe.build(workspace.derive,&block)
      when nil
        # actually redundant (since pipe builder
        # uses the current workspace when given
        # workspace is nil anyway. But it's nice
        # to be explicit).
        Rubish::Pipe.build(current_workspace.derive,&block)
      end
    end

    def exec(*exes)
      __exec(:exec,exes)
    end

    def exec!(*exes)
      __exec(:exec!,exes)
    end

    # current context on the dynamic context stack
    def current_context
      Rubish::Context.current
    end
    alias_method :context, :current_context

    def current_workspace
      Rubish::Context.current.workspace
    end
    alias_method :workspace, :current_workspace

    
    # TODO should clone a context (as well as workspace)
    def with(ws_or_context=nil,i=nil,o=nil,e=nil,&block)
      case ws_or_context
      when Rubish::Workspace
        ws = ws_or_context
        # derive from current context but use a different workspace
        c = current_context.derive(ws,i,o,e)
      when Rubish::Context
        parent = ws_or_context
        c = parent.derive(nil,i,o,e)
      when nil
        c = current_context.derive(nil,i,o,e)
      else
        raise "can't create scope with: #{ws_or_context}"
      end
      if block
        c.eval &block
      else
        c
      end
    end
    alias_method :scope, :with

    def batch(ws_or_context=nil,i=nil,o=nil,e=nil,&block)
      Rubish::BatchExecutable.new(with(ws_or_context,i,o,e),&block)
    end

    # job control methods
    def wait(*jobs)
      job_control.wait(*jobs)
    end

    def waitall
      job_control.waitall
    end

    def kill(job)
      job_control.kill(job)
    end

    def jobs
      job_control.jobs
    end

    def job_control
      current_context.job_control
    end

    private

    def __exec(exec_method,exes)
      exes.map do |exe|
        raise "not an exeuctable: #{exe}" unless exe.is_a?(Rubish::Executable)
        exe.send(exec_method)
      end
    end
    
  end

  include Base

  attr_accessor :command_factory_hook
  
  def initialize
    # this is a hack for pipe... dunno if there's a better way to do it.
    @command_factory_hook = nil
    @modules = []
  end

  def extend(*modules,&block)
    @modules.concat modules
    modules.each do |m|
      self.__extend(m)
    end
    # extend with anonymous module
    if block
      mod = Module.new(&block)
      self.__extend mod
      @modules << mod
    end
    self
  end

  # creates a cloned workspace
  def derive(*modules,&block)
    new_ws = self.__clone
    new_ws.extend(*modules,&block)
  end

  def eval(__string=nil,&__block)
    raise "should be either a string or a block" if __string && __block
    if __block
      self.__instance_eval(&__block) 
    else
      self.__instance_eval(__string)
    end
  end

  def method_missing(method,*args,&block)
    cmd = Rubish::Command.new(method,args)
    if @command_factory_hook.is_a?(Proc)
      @command_factory_hook.call(cmd)
    else
      cmd
    end
  end

  def clone
    self.__clone
  end

  def methods
    self.__methods.reject { |m| m =~ /^__/ }
  end

  def to_s
    "#<#{self.__class}:#{self.__object_id}>"
  end

  def inspect
    to_s
  end

  def to_ary
    [to_s]
  end
  
end
