require 'pp'

module MCollective
  # Toolset to create a standard interface of client and agent using
  # an RPC metaphor, standard compliant agents will make it easier
  # to create generic clients like web interfaces etc
  module RPC
    autoload :ActionRunner, "mcollective/rpc/actionrunner"
    autoload :Agent, "mcollective/rpc/agent"
    autoload :Audit, "mcollective/rpc/audit"
    autoload :Client, "mcollective/rpc/client"
    autoload :Helpers, "mcollective/rpc/helpers"
    autoload :Progress, "mcollective/rpc/progress"
    autoload :Reply, "mcollective/rpc/reply"
    autoload :Request, "mcollective/rpc/request"
    autoload :Result, "mcollective/rpc/result"
    autoload :Stats, "mcollective/rpc/stats"

    # Creates a standard options hash, pass in a block to add extra headings etc
    # see Optionparser
    def rpcoptions
      oparser = MCollective::Optionparser.new({:verbose => false, :progress_bar => true}, "filter")

      options = oparser.parse do |parser, options|
        if block_given?
          yield(parser, options)
        end

        Helpers.add_simplerpc_options(parser, options)
      end

      return options
    end

    # Wrapper to create clients, supposed to be used as
    # a mixin:
    #
    # include MCollective::RPC
    #
    # exim = rpcclient("exim")
    # printrpc exim.mailq
    #
    # or
    #
    # rpcclient("exim") do |exim|
    #    printrpc exim.mailq
    # end
    #
    # It will take a few flags:
    #    :configfile => "etc/client.cfg"
    #    :options => options
    #    :exit_on_failure => true
    #
    # Options would be a build up options hash from the Optionparser
    # you can use the rpcoptions helper to create this
    #
    # :exit_on_failure is true by default, and causes the application to
    # exit if there is a failure constructing the RPC client. Set this flag
    # to false to cause an Exception to be raised instead.
    def rpcclient(agent, flags = {})
      configfile = flags[:configfile] || "/usr/pkg/etc/mcollective/client.cfg"
      options = flags[:options] || nil

      if flags.key?(:exit_on_failure)
        exit_on_failure = flags[:exit_on_failure]
      else
        # We exit on failure by default for CLI-friendliness
        exit_on_failure = true
      end

      begin
        if options
          rpc = Client.new(agent, :configfile => options[:config], :options => options)
          @options = rpc.options
        else
          rpc = Client.new(agent, :configfile => configfile)
          @options = rpc.options
        end
      rescue Exception => e
        if exit_on_failure
          puts("Could not create RPC client: #{e}")
          exit!
        else
          raise e
        end
      end

      if block_given?
        yield(rpc)
      else
        return rpc
      end
    end

    # means for other classes to drop stats into this module
    # its a bit hacky but needed so that the mixin methods like
    # printrpcstats can easily get access to it without
    # users having to pass it around in params.
    def self.stats(stats)
      @@stats = stats
    end

    # means for other classes to drop discovered hosts into this module
    # its a bit hacky but needed so that the mixin methods like
    # printrpcstats can easily get access to it without
    # users having to pass it around in params.
    def self.discovered(discovered)
      @@discovered = discovered
    end

    # Prints stats, requires stats to be saved from elsewhere
    # using the MCollective::RPC.stats method.
    #
    # If you've passed -v on the command line a detailed stat block
    # will be printed, else just a one liner.
    #
    # You can pass flags into it:
    #
    #   printrpcstats :caption => "Foo", :summarize => true
    #
    # This will use "Foo" as the caption to the stats in verbose
    # mode and print out any aggregate summary information if present
    def printrpcstats(flags={})
      return unless @options[:output_format] == :console

      flags = {:summarize => false, :caption => "rpc stats"}.merge(flags)

      verbose = @options[:verbose] rescue verbose = false

      begin
        stats = @@stats
      rescue
        puts("no stats to display")
        return
      end

      puts stats.report(flags[:caption], flags[:summarize], verbose)
    end

    # Prints the result of an RPC call.
    #
    # In the default quiet mode - no flattening or verbose - only results
    # that produce an error will be printed
    #
    # To get details of each result run with the -v command line option.
    def printrpc(result, flags = {})
      verbose = @options[:verbose] rescue verbose = false
      verbose = flags[:verbose] || verbose
      flatten = flags[:flatten] || false
      format = @options[:output_format]
      forced_mode = @options[:force_display_mode] || false

      result_text =  Helpers.rpcresults(result, {:verbose => verbose, :flatten => flatten, :format => format, :force_display_mode => forced_mode})

      if result.is_a?(Array) && format == :console
        puts "\n%s\n" % [ result_text ]
      else
        # when we get just one result to print dont pad them all with
        # blank spaces etc, just print the individual result with no
        # padding
        puts result_text unless result_text == ""
      end
    end

    # Wrapper for MCollective::Util.empty_filter? to make clients less fugly
    # to write - ticket #18
    def empty_filter?(options)
      if options.include?(:filter)
        Util.empty_filter?(options[:filter])
      else
        Util.empty_filter?(options)
      end
    end

    def self.const_missing(const_name)
      super unless const_name == :DDL

      Log.warn("MCollective::RPC::DDL is deprecatd, please use MCollective::DDL instead")
      MCollective::DDL
    end
  end
end
