using System;
using System.Net;
using System.Xml;
using System.Xml.Serialization;
using System.Globalization;
using Mercury.Messaging.Serialization;
using Mercury.Messaging.Routing;
using Mercury.Messaging.Core;

namespace Mercury.Messaging.Instrumentation
{
    /// <summary>
    /// Provides information on a message in transit.
    /// </summary>
    public class MessageInfo
        : IXmlSerializable
    {
        /// <summary>
        /// Initializes a default instance of the <see cref="Mercury.Messaging.Instrumentation.MessageInfo"/> class.
        /// </summary>
        public MessageInfo()
        {
        }

        internal MessageInfo(IRoutingContext context, IPEndPoint receiverEP, string receiverId)
        {
            // Get basic properties
            this.PostedType = context.MessageType;
            if (context.Message != null)
                this.ActualType = context.Message.GetType();
            if (context.Channel != null)
                this.ChannelType = context.Channel.GetType();
            this.RouteId = context.RouteId;
            this.IsSynchronous = context.IsSynchronous;

            // Get XML
            this.Xml = ContextInfo.TrySerialize(context.Message as IXmlSerializable);

            // Get receiver info
            this.ReceiverEndPoint = receiverEP;
            this.ReceiverId = receiverId;
        }

        /// <summary>
        /// Gets the type of the message posted to the receiver.
        /// </summary>
        public Type PostedType { get; private set; }

        /// <summary>
        /// Gets the actual type of the message.
        /// </summary>
        public Type ActualType { get; private set; }

        /// <summary>
        /// Gets the type of the channel over which the message was sent.
        /// </summary>
        public Type ChannelType { get; private set; }

        /// <summary>
        /// Gets the route identifier.
        /// </summary>
        public string RouteId { get; private set; }

        /// <summary>
        /// Gets a value indicating whether message delivery was synchronous.
        /// </summary>
        public bool IsSynchronous { get; private set; }

        /// <summary>
        /// Gets the XML generated by the posted message.
        /// </summary>
        public string Xml { get; private set; }

        /// <summary>
        /// Gets or sets the IP end point for a receiving node.
        /// </summary>
        public IPEndPoint ReceiverEndPoint { get; set; }

        /// <summary>
        /// Gets or sets a unique identifier for a receiver of this context.
        /// </summary>
        public string ReceiverId { get; set; }

        /// <summary>
        /// Returns a System.String that represents the current <see cref="Mercury.Messaging.Instrumentation.MessageInfo"/>.
        /// </summary>
        /// <returns>A System.String that represents the current <see cref="Mercury.Messaging.Instrumentation.MessageInfo"/>.</returns>
        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture, "MessageInfo: {1}{0}Posted Type={2}{0}Actual Type={3}{0}Channel Type={4}{0}Is Synchronous={5}{0}Message XML={6}{0}Remote IP={7}{0}Receiver ID={8}",
                Environment.NewLine, this.RouteId, this.PostedType,
                this.ActualType != null ? this.ActualType.FullName : string.Empty,
                this.ChannelType != null ? this.ChannelType.FullName : string.Empty,
                this.IsSynchronous, this.Xml != null ? this.Xml : string.Empty,
                this.ReceiverEndPoint != null ? RemotingUtil.GetRemoteFormat(this.ReceiverEndPoint) : string.Empty,
                this.ReceiverId);
        }

        /// <summary>
        /// Returns the XML schema.
        /// </summary>
        /// <returns>Default value is null.</returns>
        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Deserializes the message info from the XML reader.
        /// </summary>
        /// <param name="reader">The XML reader from which to read.</param>
        public void ReadXml(XmlReader reader)
        {
            if (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.LocalName == "PostedType")
                    {
                        string xml = reader.ReadInnerXml();
                        if (!string.IsNullOrEmpty(xml))
                            this.PostedType = RuntimeSerializer.DeserializeType(xml);
                    }
                    if (reader.LocalName == "ActualType")
                    {
                        string xml = reader.ReadInnerXml();
                        if (!string.IsNullOrEmpty(xml))
                            this.ActualType = RuntimeSerializer.DeserializeType(xml);
                    }
                    if (reader.LocalName == "ChannelType")
                    {
                        string xml = reader.ReadInnerXml();
                        if (!string.IsNullOrEmpty(xml))
                            this.ChannelType = RuntimeSerializer.DeserializeType(xml);
                    }
                    if (reader.LocalName == "RouteId")
                    {
                        this.RouteId = reader.ReadString();
                        reader.Read();
                    }
                    if (reader.LocalName == "IsSynchronous")
                    {
                        var syncStr = reader.ReadString();
                        bool sync;
                        if (!string.IsNullOrEmpty(syncStr) && bool.TryParse(syncStr, out sync))
                            this.IsSynchronous = sync;
                        reader.Read();
                    }
                    if (reader.LocalName == "Xml")
                    {
                        this.Xml = reader.ReadString();
                        reader.Read();
                    }
                    if (reader.LocalName == "ReceiverEndPoint")
                    {
                        var epStr = reader.ReadString();
                        this.ReceiverEndPoint = RemotingUtil.ParseRemoteFormat(epStr);
                        reader.Read();
                    }
                    if (reader.LocalName == "ReceiverId")
                    {
                        this.ReceiverId = reader.ReadString();
                        reader.Read();
                    }
                }
            }
            reader.Read();
        }

        /// <summary>
        /// Serializes the message info to the XML writer.
        /// </summary>
        /// <param name="writer">The XML writer on which to write.</param>
        public void WriteXml(XmlWriter writer)
        {
            if (this.PostedType != null)
            {
                writer.WriteStartElement("PostedType");
                RuntimeSerializer.SerializeType(this.PostedType, writer);
                writer.WriteEndElement();
            }
            if (this.ActualType != null)
            {
                writer.WriteStartElement("ActualType");
                RuntimeSerializer.SerializeType(this.ActualType, writer);
                writer.WriteEndElement();
            }
            if (this.ChannelType != null)
            {
                writer.WriteStartElement("ChannelType");
                RuntimeSerializer.SerializeType(this.ChannelType, writer);
                writer.WriteEndElement();
            }

            if (!string.IsNullOrEmpty(this.RouteId))
                writer.WriteElementString("RouteId", this.RouteId);
            if (this.IsSynchronous)
                writer.WriteElementString("IsSynchronous", this.IsSynchronous.ToString());
            if (!string.IsNullOrEmpty(this.Xml))
                writer.WriteElementString("Xml", this.Xml);
            if (this.ReceiverEndPoint != null)
                writer.WriteElementString("ReceiverEndPoint", RemotingUtil.GetRemoteFormat(this.ReceiverEndPoint));
            if (!string.IsNullOrEmpty(this.ReceiverId))
                writer.WriteElementString("ReceiverId", this.ReceiverId);
        }
    }
}
