# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
#     http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

require 'aws/model'

module AWS
  class SQS

    # Represents a message received from an Amazon SQS Queue.
    class ReceivedMessage

      include Model

      # @return [Queue] The queue from which this message was
      #   received.
      attr_reader :queue

      # @return [String] The ID of the message.
      attr_reader :id

      # @return [String] A string associated with this specific
      #   instance of receiving the message.
      attr_reader :handle

      # @return [String] The message's contents.
      attr_reader :body

      # @return [String] An MD5 digest of the message body.
      attr_reader :md5

      # @private
      attr_reader :attributes

      # @private
      ATTRIBUTE_ALIASES = {
        :sent_at => :sent_timestamp,
        :receive_count => :approximate_receive_count,
        :first_received_at => :approximate_first_receive_timestamp
      }

      # @private
      def initialize(queue, id, handle, opts = {})
        @queue = queue
        @id = id
        @handle = handle
        @body = opts[:body]
        @md5 = opts[:md5]
        @attributes = opts[:attributes] || {}
        super
      end

      # When SNS publishes messages to SQS queues the message body is
      # formatted as a json message and then base 64 encoded.
      # An easy way to work with SNS messages is to call this method:
      #
      #   sns_msg = message.as_sns_message
      # 
      #   sns_msg.topic
      #   #=> <AWS::SNS::Topic ...>
      #
      #   sns_msg.to_h.inspect
      #   #=> { :body => '...', :topic_arn => ... }
      #
      # @return [ReceivedSNSMessage]
      def as_sns_message
        ReceivedSNSMessage.new(body, :config => config)
      end

      # Deletes the message from the queue.  Even if the message is
      # locked by another reader due to the visibility timeout
      # setting, it is still deleted from the queue.  If you leave a
      # message in the queue for more than 4 days, SQS automatically
      # deletes it.
      #
      # If you use {Queue#poll} or {Queue#receive_message} in block
      # form, the messages you receive will be deleted automatically
      # unless an exception occurs while you are processing them.
      # You can still use this method if you want to delete a
      # message early and then continue processing it.
      #
      # @note It is possible you will receive a message even after
      #   you have deleted it. This might happen on rare occasions
      #   if one of the servers storing a copy of the message is
      #   unavailable when you request to delete the message. The
      #   copy remains on the server and might be returned to you
      #   again on a subsequent receive request. You should create
      #   your system to be idempotent so that receiving a
      #   particular message more than once is not a problem.
      #
      # @return [nil]
      def delete
        client.delete_message(
          :queue_url => queue.url, 
          :receipt_handle => handle
        )
        nil
      end

      # Changes the visibility timeout of a specified message in a
      # queue to a new value. The maximum allowed timeout value you
      # can set the value to is 12 hours. This means you can't
      # extend the timeout of a message in an existing queue to more
      # than a total visibility timeout of 12 hours. (For more
      # information visibility timeout, see {Visibility
      # Timeout}[http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/IntroductionArticle.html#AboutVT]
      # in the Amazon SQS Developer Guide.)
      #
      # For example, let's say the timeout for the queue is 30
      # seconds, and you receive a message. Once you're 20 seconds
      # into the timeout for that message (i.e., you have 10 seconds
      # left), you extend it by 60 seconds by calling this method
      # with +timeout+ set to 60 seconds. You have then changed the
      # remaining visibility timeout from 10 seconds to 60 seconds.
      #
      # @note If you attempt to set the timeout to an amount more
      #   than the maximum time left, Amazon SQS returns an
      #   error. It will not automatically recalculate and increase
      #   the timeout to the maximum time remaining.
      #
      # @note Unlike with a queue, when you change the visibility
      #   timeout for a specific message, that timeout value is
      #   applied immediately but is not saved in memory for that
      #   message. If you don't delete a message after it is
      #   received, the visibility timeout for the message the next
      #   time it is received reverts to the original timeout value,
      #   not the value you set with this method.
      #
      # @return Returns the timeout argument as passed.
      #
      def visibility_timeout=(timeout)
        client.change_message_visibility(
          :queue_url => queue.url,
          :receipt_handle => handle,
          :visibility_timeout => timeout
        )
        timeout
      end

      # @return [String] The AWS account number (or the IP address,
      #   if anonymous access is allowed) of the sender.
      def sender_id
        attributes["SenderId"]
      end

      # @return [Time] The time when the message was sent.
      def sent_timestamp
        @sent_at ||=
          (timestamp = attributes["SentTimestamp"] and
           Time.at(timestamp.to_i / 1000.0)) || nil
      rescue RangeError => e
        p [timestamp, timestamp.to_i]
      end
      alias_method :sent_at, :sent_timestamp

      # @return [Integer] The number of times a message has been
      #   received but not deleted.
      def approximate_receive_count
        (count = attributes["ApproximateReceiveCount"] and
         count.to_i) or nil
      end
      alias_method :receive_count, :approximate_receive_count

      # @return [Time] The time when the message was first received.
      def approximate_first_receive_timestamp
        @received_at ||=
          (timestamp = attributes["ApproximateFirstReceiveTimestamp"] and
           Time.at(timestamp.to_i / 1000.0)) || nil
      end
      alias_method :first_received_at, :approximate_first_receive_timestamp

    end

  end
end