module CORL
module Vagrant
  
  #
  # Since we can execute CORL actions from within Vagrant on a combination of
  # Vagrant VMs and remote server instances we need a way to tap into the 
  # Vagrant environment and operate on CORL configured Vagrant machines.
  # 
  # This command is set in the CORL launcher Vagrant command plugin execute 
  # method.  It is then accessible anywhere within CORL if we have used that
  # Vagrant command as an execution gateway.  If not it will be nil, giving us
  # a convienient method for checking whether we are executing through Vagrant
  # which is used in the CORL Vagrant {Node} and {Machine} plugins.
  #  
  @@command = nil
  
  def self.command=command
    @@command = command
  end
  
  def self.command
    @@command
  end  
end

module Plugin
class CloudAction < CORL.plugin_class(:action)
  
  #-----------------------------------------------------------------------------
  # Property accessor / modifiers
  
  def namespace
    :corl
  end
  
  #---
    
  def configure
    super do
      node_config
      yield if block_given?
    end
  end
          
  #-----------------------------------------------------------------------------
  # Settings
  
  def node_config
    node_plugins = CORL.loaded_plugins(:node)
    
    register :parallel, :bool, true, 'corl.core.action.options.parallel'
    register :net_remote, :str, :edit, 'corl.core.action.options.net_remote'
    register :net_provider, :str, :default, 'corl.core.action.options.net_provider' do |value|
      value           = value.to_sym
      network_plugins = CORL.loaded_plugins(:network)
      
      unless network_plugins.keys.include?(value)
        warn('corl.core.action.errors.network_provider', { :value => value, :choices => network_plugins.keys.join(", ") })
        next false 
      end
      true
    end
    register :node_provider, :str, :local, 'corl.core.action.options.node_provider' do |value|
      value          = value.to_sym      
      node_providers = node_plugins.keys
      
      unless CORL.vagrant? || node_providers.include?(value)
        warn('corl.core.action.errors.node_provider', { :value => value, :choices => node_providers.join(", ") })
        next false
      end
      true  
    end
    register :nodes, :array, [], 'corl.core.action.options.nodes' do |values|
      success = true
      values.each do |value|
        if info = CORL.plugin_class(:node).translate_reference(value)
          if ! node_plugins.keys.include?(info[:provider].to_sym) || info[:name].empty?
            warn('corl.core.action.errors.nodes', { :value => value, :provider => info[:provider],  :name => info[:name] })
            success = false
          end
        end
      end
      success
    end
  end
  
  #---
         
  def node_ignore
    [ :parallel, :net_remote, :net_provider, :node_provider, :nodes ]
  end
  
  #-----------------------------------------------------------------------------
  # Operations
  
  def validate(node, network)
    super(node, network)
  end
  
  #---
   
  def execute
    super(true, true) do
      node_exec do |node, network|
        hook_config = { :node => node, :network => network }
        
        yield(node, network) if block_given? && extension_check(:exec_init, hook_config)
        myself.status = extension_set(:exec_exit, status, hook_config)
      end
    end
  end
  
  #---
        
  def node_exec
    network = init_network
    
    #
    # A fork in the road...
    #
    if network && network.has_nodes? && ! settings[:nodes].empty?
      # Execute action on remote nodes 
      success = network.batch(settings[:nodes], settings[:node_provider], settings[:parallel]) do |node|
        exec_config = Config.new(settings)
        exec_config.delete(:parallel)
        exec_config.delete(:nodes)
        exec_config.delete(:node_provider)
              
        result = node.action(plugin_provider, exec_config) do |op, data|
          execute_remote(node, network, op, data)
        end
        result.status == code.success 
      end
      myself.status = code.batch_error unless success
    else
      # Execute statement locally
      node = nil
      node = network.local_node if network
      
      if validate(node, network)
        yield(node, network) if block_given?
      else
        puts "\n" + I18n.t('corl.core.exec.help.usage') + ': ' + help + "\n" unless quiet?
        myself.status = code.validation_failed 
      end
    end
  end
  
  #---
  
  def init_network(path = nil)
    # Get network configuration path
    if CORL.admin?
      network_path = lookup(:corl_network)
      Dir.mkdir(network_path) unless File.directory?(network_path)
    else
      network_path = ( path.nil? ? Dir.pwd : File.expand_path(path) )
    end
    
    # Load network if it exists
    network_config = extended_config(:network, { :directory => network_path })
    network        = CORL.network(network_path, network_config, settings[:net_provider])
    network  
  end
  
  #---
  
  def execute_remote(node, network, op, data)
    # Implement in sub classes if needed
    data 
  end
  
  #---
  
  def ensure_network(network, &block)
    codes :network_failure
    
    if network
      block.call
    else
      myself.status = code.network_failure
    end
  end
  
  def ensure_node(node, &block)
    codes :node_failure
    
    if node
      block.call
    else
      myself.status = code.node_failure
    end
  end
end
end
end