module MCollective
  module RPC
    # Simple class to manage compliant results from MCollective::RPC agents
    #
    # Currently it just fakes Hash behaviour to the result to remain backward
    # compatible but it also knows which agent and action produced it so you
    # can associate results to a DDL
    class Result
      attr_reader :agent, :action, :results

      include Enumerable

      def initialize(agent, action, result={})
        @agent = agent
        @action = action
        @results = result

        convert_data_based_on_ddl if ddl
      end

      def ddl
        @_ddl ||= DDL.new(agent)
      rescue
        nil
      end

      def data
        @results[:data] = @results.delete("data") if @results.include?("data")

        self[:data]
      end

      # Converts keys on the supplied data to those listed as outputs
      # in the DDL.  This is to facilitate JSON based transports
      # without forcing everyone to rewrite DDLs and clients to
      # convert symbols to strings, the data will be on symbol keys
      # if the DDL has a symbol and not a string output defined
      def convert_data_based_on_ddl
        interface = ddl.action_interface(action)

        return if interface.fetch(:output, {}).empty?

        interface[:output].each do |output, _properties|
          next if data.include?(output)

          data[output] = data.delete(output.to_s) if output.is_a?(Symbol) && data.include?(output.to_s)
        end
      end

      def compatible_key(key)
        if key.is_a?(Symbol) && @results.include?(key.to_s)
          key.to_s
        else
          key
        end
      end

      def [](key)
        @results[compatible_key(key)]
      end

      def []=(key, item)
        @results[key] = item
      end

      def fetch(key, default)
        @results.fetch(compatible_key(key), default)
      end

      def each(&block)
        @results.each_pair(&block)
      end

      def to_json(*result)
        {:agent => @agent,
         :action => @action,
         :sender => self[:sender],
         :statuscode => self[:statuscode],
         :statusmsg => self[:statusmsg],
         :data => data}.to_json(*result)
      end

      def <=>(other)
        self[:sender] <=> other[:sender]
      end
    end
  end
end
