/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License 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.
 */

using System;
using System.Collections;
using Apache.Qpid.Proton.Client.Implementation;
using Apache.Qpid.Proton.Types.Messaging;

namespace Apache.Qpid.Proton.Client
{
   /// <summary>
   /// A single AMQP Message instance used by senders and receivers to provide a high
   /// level abstraction around an AMQP message.
   /// </summary>
   /// <typeparam name="T">The type that comprises the message body</typename>
   public interface IMessage<T>
   {
      #region Static Message Factory Methods

      /// <summary>
      /// Create and return an IMessage that will carry no body section unless one
      /// is assigned by the caller.
      /// </summary>
      /// <typeparam name="E">The type that the message body will be</typeparam>
      /// <returns>a new message instance with an empty body.</returns>
      static IMessage<T> Create()
      {
         return ClientMessage<T>.Create();
      }

      /// <summary>
      /// Create and return an IMessage that will carry the body section provided
      /// </summary>
      /// <returns>a new message instance with the provided body.</returns>
      static IMessage<T> Create(T value)
      {
         return ClientMessage<T>.Create(new AmqpValue(value));
      }

      /// <summary>
      /// Create and return an IMessage that will carry the body section provided
      /// as an AMQP Data section that carries the provided bytes.
      /// </summary>
      /// <param name="value">The byte array to wrap in the AMQP message body</param>
      /// <returns>a new message instance with the provided body.</returns>
      static IMessage<byte[]> Create(byte[] value)
      {
         return ClientMessage<byte[]>.Create(new Data(value));
      }

      /// <summary>
      /// Create and return an IMessage that will carry the body section provided
      /// as an AMQP Sequence section that carries the provided list entries.
      /// </summary>
      /// <param name="value">The list to wrap in the AMQP message body</param>
      /// <returns>a new message instance with the provided body.</returns>
      static IMessage<IList> Create(IList value)
      {
         return ClientMessage<IList>.Create(new AmqpSequence(value));
      }

      /// <summary>
      /// Create and return an IMessage that will carry the body section provided
      /// as an AMQP Value section that carries the provided map entries.
      /// </summary>
      /// <param name="value">The map to wrap in the AMQP message body</param>
      /// <returns>a new message instance with the provided body.</returns>
      static IMessage<IDictionary> Create<K, V>(IDictionary value)
      {
         return ClientMessage<IDictionary>.Create(new AmqpValue(value));
      }

      #endregion

      #region Conversion helper to Advanced Message handling

      /// <summary>
      /// Safely converts this message to an advanced message instance which allows lower level
      /// access to AMQP message constructs.
      ///
      /// The default implementation first checks if the current instance is already of the correct
      /// type before performing a brute force conversion of the current message to the client's
      /// own internal IAdvancedMessage implementation.  Users should override this method if the
      /// internal conversion implementation is insufficient to obtain the proper message structure
      /// to encode a meaningful 'on the wire' encoding of their custom implementation.
      /// </summary>
      /// <returns>An advanced message view of the original message</returns>
      /// <exception cref="ClientException">If an error occurs during the conversion</exception>
      IAdvancedMessage<T> ToAdvancedMessage()
      {
         if (this is IAdvancedMessage<T> message)
         {
            return message;
         }
         else
         {
            return ClientMessageSupport.ConvertMessage(this);
         }
      }

      #endregion

      #region AMQP Header access

      /// <summary>
      /// For a message being sent this gets and sets the durability flag on the
      /// message.  For a received message this gets or overwrites the durability
      /// flag set by the original sender (unless already locally updated).
      /// </summary>
      bool Durable { get; set; }

      /// <summary>
      /// For a message being sent this gets and sets the message priority on the
      /// message.  For a received message this gets or overwrites the priority
      /// value set by the original sender (unless already locally updated).
      /// </summary>
      byte Priority { get; set; }

      /// <summary>
      /// For a message being sent this gets and sets the message time to live on
      /// the message.  For a received message this gets or overwrites the time to
      /// live value set by the original sender (unless already locally updated).
      ///
      /// The time to live duration in milliseconds for which the message is to be
      /// considered "live". If this is set then a message expiration time will be
      /// computed based on the time of arrival at an intermediary. Messages that live
      /// longer than their expiration time will be discarded (or dead lettered). When
      /// a message is transmitted by an intermediary that was received with a time to
      /// live, the transmitted message's header SHOULD contain a time to live that is
      /// computed as the difference between the current time and the formerly computed
      /// message expiration time, i.e., the reduced time to live, so that messages
      /// will eventually die if they end up in a delivery loop.
      /// </summary>
      uint TimeToLive { get; set; }

      /// <summary>
      /// For a message being sent this gets and sets the first acquirer flag on the
      /// message.  For a received message this gets or overwrites the first acquirer
      /// flag set by the original sender (unless already locally updated).
      ///
      /// If this value is true, then this message has not been acquired by any other link.
      /// If this value is false, then this message MAY have previously been acquired by
      /// another link or links.
      /// </summary>
      bool FirstAcquirer { get; set; }

      /// <summary>
      /// For a message being sent this gets and sets the message delivery count on
      /// the message.  For a received message this gets or overwrites the delivery
      /// count set by the original sender (unless already locally updated).
      /// </summary>
      uint DeliveryCount { get; set; }

      #endregion

      #region AMQP Properties Access

      /// <summary>
      /// The message Id, if set, uniquely identifies a message within the message system. The
      /// message producer is usually responsible for setting the message-id in such a way that
      /// it is assured to be globally unique. A remote peer MAY discard a message as a duplicate
      /// if the value of the message-id matches that of a previously received message sent to
      /// the same node.
      /// </summary>
      object MessageId { get; set; }

      /// <summary>
      /// The identity of the user responsible for producing the message. The client sets this
      /// value, and it MAY be authenticated by intermediaries.
      /// </summary>
      byte[] UserId { get; set; }

      /// <summary>
      /// The to field identifies the node that is the intended destination of the message. On
      /// any given transfer this might not be the node at the receiving end of the link.
      /// </summary>
      string To { get; set; }

      /// <summary>
      /// The Subject field is a common field for summary information about the message content
      /// and purpose.
      /// </summary>
      string Subject { get; set; }

      /// <summary>
      /// The reply to field identifies a node that is the intended destination for responses
      /// to this message.
      /// </summary>
      string ReplyTo { get; set; }

      /// <summary>
      /// This is a client-specific id that can be used to mark or identify messages between
      /// clients.
      /// </summary>
      object CorrelationId { get; set; }

      /// <summary>
      /// The RFC-2046 MIME type for the message's application-data section (body). As per
      /// RFC-2046 this can contain a charset parameter defining the character encoding used:
      /// e.g., 'text/plain; charset="utf-8"'
      ///
      /// When using an application-data section with a section code other than data,
      /// content-type SHOULD NOT be set.
      /// </summary>
      /// <remarks>
      /// For clarity, as per section 7.2.1 of RFC-2616, where the content type is unknown the
      /// content-type SHOULD NOT be set. This allows the recipient the opportunity to determine
      /// the actual type. Where the section is known to be truly opaque binary data, the
      /// content-type SHOULD be set to application/octet-stream.
      /// </remarks>
      string ContentType { get; set; }

      /// <summary>
      /// The content-encoding property is used as a modifier to the content-type. When present,
      /// its value indicates what additional content encodings have been applied to the
      /// application-data, and thus what decoding mechanisms need to be applied in order to
      /// obtain the media-type referenced by the content-type header field. Content-encoding is
      /// primarily used to allow a document to be compressed without losing the identity of its
      /// underlying content type.
      /// </summary>
      /// <remarks>
      /// <para>
      /// Content-encodings are to be interpreted as per section 3.5 of RFC 2616 [RFC2616]. Valid
      /// content-encodings are registered at IANA [IANAHTTPPARAMS].
      /// </para>
      /// <para>
      /// The content-encoding MUST NOT be set when the application-data section is other than data.
      /// The binary representation of all other application-data section types is defined completely
      /// in terms of the AMQP type system.
      /// </para>
      /// Implementations MUST NOT use the identity encoding. Instead, implementations SHOULD NOT set
      /// this property. Implementations SHOULD NOT use the compress encoding, except as to remain
      /// compatible with messages originally sent with other protocols, e.g. HTTP or SMTP.
      /// <para>
      /// Implementations SHOULD NOT specify multiple content-encoding values except as to be
      /// compatible with messages originally sent with other protocols, e.g. HTTP or SMTP.
      /// </para>
      /// </remarks>
      string ContentEncoding { get; set; }

      /// <summary>
      /// An absolute time when this message is considered to be expired.
      /// </summary>
      ulong AbsoluteExpiryTime { get; set; }

      /// <summary>
      /// An absolute time when this message was created.
      /// </summary>
      ulong CreationTime { get; set; }

      /// <summary>
      /// Identifies the group the message belongs to.
      /// </summary>
      string GroupId { get; set; }

      /// <summary>
      /// The relative position of this message within its group.
      /// </summary>
      uint GroupSequence { get; set; }

      /// <summary>
      /// This is a client-specific id that is used so that client can send replies to
      /// this message to a specific group.
      /// </summary>
      string ReplyToGroupId { get; set; }

      #endregion

      #region AMQP Message Annotations Access

      /// <summary>
      /// Checks if the message carries any annotations.
      /// </summary>
      /// <returns>true if the message instance carries any annotations</returns>
      bool HasAnnotations { get; }

      /// <summary>
      /// Query the message to determine if the message carries the given annotation
      /// keyed value.
      /// </summary>
      /// <returns>true if the message instance carries the annotation</returns>
      bool HasAnnotation(string key);

      /// <summary>
      /// Returns the requested message annotation value from this message if it
      /// exists or returns null otherwise.
      /// </summary>
      /// <param name="key">The message annotation key</param>
      /// <returns>The value that is stored in the message annotation mapping</returns>
      object GetAnnotation(string key);

      /// <summary>
      /// Add the annotation to he set of message annotations or update the value stored
      /// with the given annotation key.
      /// </summary>
      /// <param name="key">The whose value is being added or updated</param>
      /// <param name="value">The value to store with the given key</param>
      /// <returns>This message instance</returns>
      IMessage<T> SetAnnotation(string key, object value);

      /// <summary>
      /// Removes the given annotation from the message if present and returns the value
      /// that was stored within.
      /// </summary>
      /// <param name="key">The annotation key whose value should be removed.</param>
      /// <returns>The annotation value removed or null if not present</returns>
      object RemoveAnnotation(string key);

      /// <summary>
      /// Efficient walk of all the current message annotations contained in this
      /// message.
      /// </summary>
      /// <param name="consumer">Function that will be called for each annotation</param>
      IMessage<T> ForEachAnnotation(Action<string, object> consumer);

      #endregion

      #region AMQP Application Properties Access

      /// <summary>
      /// Checks if the message carries any message properties.
      /// </summary>
      /// <returns>true if the message instance carries any properties</returns>
      bool HasProperties { get; }

      /// <summary>
      /// Query the message to determine if the message carries the given property.
      /// </summary>
      /// <returns>true if the message instance carries the property</returns>
      bool HasProperty(string key);

      /// <summary>
      /// Returns the requested message property value from this message if it
      /// exists or returns null otherwise.
      /// </summary>
      /// <param name="key">The message property key</param>
      /// <returns>The value that is mapped to the given key</returns>
      object GetProperty(string key);

      /// <summary>
      /// Add the property to he set of message properties or update the value stored
      /// with the given mapping.
      /// </summary>
      /// <param name="key">The whose value is being added or updated</param>
      /// <param name="value">The value to store with the given key</param>
      /// <returns>This message object instance</returns>
      IMessage<T> SetProperty(string key, object value);

      /// <summary>
      /// Removes the given property from the message if present and returns the value
      /// that was stored within.
      /// </summary>
      /// <param name="key">The property key which is to be removed</param>
      /// <returns>The property value removed or null if not present</returns>
      object RemoveProperty(string key);

      /// <summary>
      /// Efficient walk of all the current message properties contained in this
      /// message.
      /// </summary>
      /// <param name="consumer"></param>
      IMessage<T> ForEachProperty(Action<string, object> consumer);

      #endregion

      #region AMQP Message body access

      /// <summary>
      /// Access the body of this message. Depending on the current state of the message
      /// an exception might be thrown indicating that the body is not readable or is not
      /// writable.
      /// </summary>
      /// <returns>The message body</returns>
      /// <exception cref="ClientException">If the message body cannot be read or written</exception>
      T Body { get; set; }

      #endregion

      #region AMQP Footer access

      /// <summary>
      /// Checks if the message carries any footers.
      /// </summary>
      /// <returns>true if the message instance carries any footers</returns>
      bool HasFooters { get; }

      /// <summary>
      /// Query the message to determine if the message carries the given footer entry.
      /// </summary>
      /// <returns>true if the message instance carries the footer entry</returns>
      bool HasFooter(string key);

      /// <summary>
      /// Returns the requested message footer value from this message if it
      /// exists or returns null otherwise.
      /// </summary>
      /// <param name="key">The message footer key</param>
      /// <returns>The value that is mapped to the given key</returns>
      object GetFooter(string key);

      /// <summary>
      /// Add the footer to he set of message footers or update the value stored
      /// with the given mapping.
      /// </summary>
      /// <param name="key">The whose value is being added or updated</param>
      /// <param name="value">The value to store with the given key</param>
      /// <returns>This message object instance</returns>
      IMessage<T> SetFooter(string key, object value);

      /// <summary>
      /// Removes the given property from the message if present and returns the value
      /// that was stored within.
      /// </summary>
      /// <param name="key">The property key which is to be removed</param>
      /// <returns>The property value removed or null if not present</returns>
      object RemoveFooter(string key);

      /// <summary>
      /// Efficient walk of all the current message footers contained in this
      /// message.
      /// </summary>
      /// <param name="consumer"></param>
      IMessage<T> ForEachFooter(Action<string, object> consumer);

      #endregion

   }
}