module QuickbooksWebConnector
  class Job

    attr_accessor :response_xml

    def initialize(payload)
      @payload = payload
    end

    # Creates a job by placing it on the queue. Expects a request builder class
    # name, a response handler class name, and an optional array of arguments to
    # pass to the class' `perform` method.
    #
    # Raises an exception if no class is given.
    def self.create(request_builder, response_handler, *args)
      QuickbooksWebConnector.push(
        'request_builder_class' => request_builder.to_s,
        'response_handler_class' => response_handler.to_s,
        'args' => args
      )
    end

    # Destroys a job on the queue. Expects a request builder class name, a
    # response handler class name, and an optional array of arguments to pass
    # to the class' `perform` method.
    def self.destroy(request_builder, response_handler, *args)
      QuickbooksWebConnector.remove(
        'request_builder_class' => request_builder.to_s,
        'response_handler_class' => response_handler.to_s,
        'args' => args
      )
    end

    # Returns an instance of QuickbooksWebConnector::Job
    # if any jobs are available. If not, returns nil.
    def self.reserve
      return unless payload = QuickbooksWebConnector.pop
      new(payload)
    end

    # Return an instance of QuickbooksWebConnector::job if any jobs are
    # available, without removing the job from the queue
    def self.peek
      return unless payload = QuickbooksWebConnector.peek
      new(payload)
    end

    # Find jobs from the queue.
    #
    # Returns the list of jobs queued.
    #
    # This method can be potentially very slow and memory intensive,
    # depending on the size of your queue, as it loads all jobs into
    # a Ruby array.
    def self.queued
      QuickbooksWebConnector.list_range(:queue, 0, -1).map do |item|
        new(item)
      end
    end

    # Attempts to perform the work represented by this job instance.
    # Calls #perform on the class given in the payload with the
    # Quickbooks response and the arguments given in the payload..
    def perform
      begin
        job = response_handler_class

        # Execute the job.
        job.perform(response_xml, *job_args)
      rescue Object => ex
        fail(ex)
      end
    end

    # Returns the request XML from the payload.
    def request_xml
      begin
        xml = request_builder_class.perform(*job_args)

        # Replace non-ascii characters with decimal entities
        xml.gsub!(/[^\u{20}-\u{7E}]/) do |char|
          "&##{char.codepoints.first};"
        end

        xml
      rescue Object => ex
        fail(ex)
        :failed
      end
    end

    # Returns the actual class constant for building the request from the job's payload.
    def request_builder_class
      @request_builder_class ||= @payload['request_builder_class'].constantize
    end

    # Returns the actual class constant represented in this job's payload.
    def response_handler_class
      @response_handler_class ||= @payload['response_handler_class'].constantize
    end

    # Returns an array of args represented in this job's payload.
    def args
      @payload['args']
    end

    def job_args
      args || []
    end

    # Given an exception object, hands off the needed parameters to the Failure
    # module.
    def fail(exception)
      Failure.create(
        payload: @payload,
        exception: exception
      )
    end

  end
end