using System;
using System.Text;
using System.Xml;
using System.Diagnostics.CodeAnalysis;
namespace Wayne.Lib.Log
{
    /// <summary>
    /// An generic entry to be logged containing details regarding what to log,
    /// the datetime and who was performing the logging etc.
    /// 
    /// This class is inherited by DebugLogEntry, EventLogEntry and ErrorLogEntry
    /// which adds on more specific properties.
    /// </summary>
    public class LogEntry
    {
        #region Fields

        private readonly EntityCategory entityCategory;
        private readonly DateTime dateTime;
        private readonly UInt64 logEntryIndex;
        private readonly object logObject;

        #endregion

        #region Constructors

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="entity">The entity that performed the logging.</param>
        /// <param name="logObject">The object to log.</param>
        internal LogEntry(IIdentifiableEntity entity, object logObject)
            : this(entity, logObject, string.Empty)
        {
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="entity">The entity that performed the logging.</param>
        /// <param name="logObject">The object to log.</param>
        /// <param name="category">The category of the log object.</param>
        internal LogEntry(IIdentifiableEntity entity, object logObject, object category)
        {
            dateTime = DateTime.Now;
            if (Logger.GlobalLogEntryCounter != null)
                logEntryIndex = Logger.GlobalLogEntryCounter.GetNextValue();

            this.logObject = logObject;

            if (Logger.IsClosed || Logger.DebugConfig == null)
            {
                entityCategory = new EntityCategory(entity, category);
            }
            else
            {
                entityCategory = Logger.DebugConfig.GetEntityCategory(entity, category);
            }
        }

        /// <summary>
        /// Deserialization constructor.
        /// </summary>
        /// <param name="logEntryNode"></param>
        [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", MessageId = "System.Xml.XmlNode")]
        internal protected LogEntry(XmlElement logEntryNode)
        {
            //Deserialize the dateTime
            dateTime = XmlConvert.ToDateTime(logEntryNode.Attributes["DateTime"].Value, XmlDateTimeSerializationMode.Unspecified);
            logEntryIndex = XmlConvert.ToUInt64(logEntryNode.Attributes["LogEntryIndex"].Value);

            //Create the entity category
            string category = logEntryNode.Attributes["Category"].Value;

            IIdentifiableEntity entity = null;
            if (logEntryNode["Entity", EventLogXml.Ns] != null)
            {
                entity = new InternalEntity(logEntryNode["Entity", EventLogXml.Ns]);
            }

            entityCategory = Logger.DebugConfig.GetEntityCategory(entity, category);

            //Create the log object as a sting...
            logObject = logEntryNode["LogObjectString", EventLogXml.Ns].InnerText;
        }

        #endregion

        #region Properties

        /// <summary>
        /// The EntityCategory that performed the logging.
        /// </summary>
        public EntityCategory EntityCategory
        {
            get { return entityCategory; }
        }

        /// <summary>
        /// The object to log.
        /// </summary>
        public object LogObject
        {
            get { return logObject; }
        }

        /// <summary>
        /// The date time of the logging.
        /// </summary>
        public DateTime DateTime
        {
            get { return dateTime; }
        }

        #endregion

        #region Methods: AppendTextToStringBuilder

        /// <summary>
        /// Appends the object to log to a StringBuilder-output.
        /// </summary>
        /// <param name="logWriter">The logwriter to be used for logging.</param>
        /// <param name="output">The StringBuilder.</param>
        /// <param name="indentLength">The indent to be used if many lines.</param>
        /// <param name="isFirstLine">Is this the first line to log?</param>
        /// <param name="indent">A string holding a generated indent-text (=a number of spaces). Use AppendIndent() to append the indent.</param>
        internal virtual void AppendToStringBuilder(LogWriter logWriter, StringBuilder output,
            int indentLength, ref bool isFirstLine, ref string indent)
        {
            StringLogObject.AppendObjectToStringBuilder(logObject, output, logWriter, indentLength, ref isFirstLine, ref indent);
        }

        /// <summary>
        /// Returns the datetime as a string, using the given date time format.
        /// </summary>
        /// <param name="dateTimeFormat">The requested date time format.</param>
        /// <returns></returns>
        public string GetDateTimeString(string dateTimeFormat)
        {
            return Logger.DateTimeToString(dateTime, dateTimeFormat, logEntryIndex);
        }

        #endregion

        #region Debug methods

        /// <summary>
        /// Presents the class as a string.
        /// </summary>
        /// <returns></returns>
        public virtual string ToString(string format, IFormatProvider provider)
        {
            return string.Concat("LogEntry EntityCategory=", entityCategory, ", LogObject=", (logObject != null) ? logObject.ToString() : "null");
        }

        /// <summary>
        /// Presents the class as a string using the specified culture-specific format information.
        /// </summary>
        /// <returns></returns>
        public virtual string ToString(IFormatProvider provider)
        {
            return ToString(string.Empty, provider);
        }

        /// <summary>
        /// Presents the class as a string using a format string.
        /// </summary>
        /// <returns></returns>
        public virtual string ToString(string format)
        {
            return ToString(format, System.Globalization.CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Presents the class as a string using a format string and the specified culture-specific format information.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return ToString(string.Empty, System.Globalization.CultureInfo.InvariantCulture);
        }

        #endregion

        #region XML

        /// <summary>
        /// Serializes this object into the specified xmlWriter.
        /// </summary>
        /// <param name="xmlWriter"></param>
        /// <param name="prefix"></param>
        public void WriteXml(XmlWriter xmlWriter, string prefix)
        {
            xmlWriter.WriteStartElement(prefix, "LogEntry", EventLogXml.Ns);
            xmlWriter.WriteAttributeString("Category", this.entityCategory.CategoryString);
            xmlWriter.WriteAttributeString("DateTime", XmlConvert.ToString(dateTime, XmlDateTimeSerializationMode.Unspecified));
            xmlWriter.WriteAttributeString("LogEntryIndex", XmlConvert.ToString(logEntryIndex));

            WriteInternalEntity(xmlWriter, prefix, this.entityCategory.Entity);


            string logObjectString;
            if (logObject != null)
                logObjectString = logObject.ToString();
            else
                logObjectString = string.Empty;

            xmlWriter.WriteElementString(prefix, "LogObjectString", EventLogXml.Ns, logObjectString);

            xmlWriter.WriteStartElement(prefix, "LogData", EventLogXml.Ns);

            WriteLogObjectData(xmlWriter);

            xmlWriter.WriteEndElement(); //LogData

            xmlWriter.WriteEndElement(); //EventLogEntry
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xmlWriter"></param>
        protected virtual void WriteLogObjectData(XmlWriter xmlWriter)
        {

        }


        private static void WriteInternalEntity(XmlWriter xmlWriter, string prefix, IIdentifiableEntity entity)
        {
            if (entity == null)
                return;

            xmlWriter.WriteStartElement(prefix, "Entity", EventLogXml.Ns);
            xmlWriter.WriteAttributeString("EntityType", entity.EntityType);
            xmlWriter.WriteAttributeString("EntitySubType", entity.EntitySubType);
            xmlWriter.WriteAttributeString("Id", XmlConvert.ToString(entity.Id));

            xmlWriter.WriteStartElement(prefix, "Parent", EventLogXml.Ns);
            if (entity.ParentEntity != null)
                WriteInternalEntity(xmlWriter, prefix, entity.ParentEntity);//Write the parent entities recursively.
            xmlWriter.WriteEndElement(); //Parent

            xmlWriter.WriteEndElement(); //Entity
        }

        #endregion
    }
}