#region  --------------- Copyright Dresser Wayne Pignone -------------
/*
 * $Log: /Wrk/WayneLibraries/Wrk/Log/StringLogObject.cs $
 * 
 * 5     08-02-13 9:30 Mattias.larsson
 * FxCop fixes.
 * 
 * 4     07-03-19 17:12 roger.m�nsson
 * Allow null as LogObject.
 */
#endregion

using System;
using System.Text;
using System.Diagnostics.CodeAnalysis;
namespace Wayne.Lib.Log
{
    /// <summary>
    /// The StringLogObject-class serves as a helpclass to convert one or more
    /// objects into one or more strings to log. Also provides format abilities.
    /// </summary>
    public class StringLogObject : ILogObject
    {
        #region Fields

        private object[] logObjects;
        private string format;
        private IFormatProvider provider;

        #endregion

        #region Construction

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="logObjects">A number of objects to log.</param>
        [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "objects")]
        public StringLogObject(params object[] logObjects)
        {
            this.logObjects = logObjects;
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="format">A format-string to format the items of an array.</param>
        /// <param name="provider">An IFormatProvider to format the items of an array.</param>
        /// <param name="array">An array of objects to log.</param>
        public StringLogObject(string format, IFormatProvider provider, Array array)
        {
            this.format = format;
            this.provider = provider;
            this.logObjects = new object[] { array };
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="format">A format-string to format the items of an array.</param>
        /// <param name="provider">An IFormatProvider to format the items of an array.</param>
        /// <param name="logObjects">A number of objects to log.</param>
        [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "objects")]
        public StringLogObject(string format, IFormatProvider provider, params object[] logObjects)
        {
            this.format = format;
            this.provider = provider;
            this.logObjects = logObjects;
        }

        #endregion

        #region Properties

        bool ILogObject.UsesFormat
        {
            get { return true; }
        }

        string ILogObject.Format
        {
            get { return format; }
            set { format = value; }
        }

        IFormatProvider ILogObject.Provider
        {
            get { return provider; }
            set { provider = value; }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Appends this object's logObjects to a StringBuilder-output.
        /// </summary>
        /// <param name="output">The StringBuilder.</param>
        /// <param name="logWriter">The LogWriter to write the logObject.</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>
        void ILogObject.AppendToStringBuilder(LogWriter logWriter, StringBuilder output,
            int indentLength, ref bool isFirstLine, ref string indent)
        {
            foreach (object logObject in logObjects)
                AppendObjectToStringBuilder(logObject, output, logWriter, indentLength, ref isFirstLine, ref indent, format, provider);
        }

        #endregion

        #region Static Support Methods

        /// <summary>
        /// Appends an object to a StringBuilder-output.
        /// </summary>
        /// <param name="logObject">The object to log.</param>
        /// <param name="output">The StringBuilder.</param>
        /// <param name="logWriter">The LogWriter to write the logObject.</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 static void AppendObjectToStringBuilder(object logObject, StringBuilder output, LogWriter logWriter,
            int indentLength, ref bool isFirstLine, ref string indent)
        {
            AppendObjectToStringBuilder(logObject, output, logWriter, indentLength,
                ref isFirstLine, ref indent, null, null);
        }

        /// <summary>
        /// Appends an object to a StringBuilder-output.
        /// </summary>
        /// <param name="logObject">The object to log.</param>
        /// <param name="output">The StringBuilder.</param>
        /// <param name="logWriter">The LogWriter to write the logObject.</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>
        /// <param name="format">A format-string to format the objects.</param>
        /// <param name="provider">An IFormatProvider to format the objects.</param>
        private static void AppendObjectToStringBuilder(object logObject, StringBuilder output, LogWriter logWriter,
            int indentLength, ref bool isFirstLine, ref string indent, string format, IFormatProvider provider)
        {
            // Check if the logObject is an ILogObject.
            ILogObject iLogObject = logObject as ILogObject;
            if (iLogObject != null)
            {
                if (iLogObject.UsesFormat && (format != null) && (iLogObject.Format == null))
                {
                    // Copy my format to the stringLogObject.
                    iLogObject.Format = format;
                    iLogObject.Provider = provider;
                }
                iLogObject.AppendToStringBuilder(logWriter, output, indentLength, ref isFirstLine, ref indent);
            }
            else
            {
                string[] logLines = logObject as string[];
                if (logLines != null)
                {
                    // The logObject is an array of strings.
                    AppendStringsToStringBuilder(logLines, output, logWriter, indentLength, ref isFirstLine, ref indent);
                }
                else
                {
                    Array logObjectArray = logObject as Array;
                    if (logObjectArray != null)
                    {
                        // This is an array. Was there a format provided?
                        if (format != null)
                        {
                            // There is a format provided. In this case, write all the items in one string.
                            StringBuilder arrayBuilder = new StringBuilder();
                            bool firstItem = true;
                            foreach (object logObjectItem in logObjectArray)
                            {
                                // Append a separator character.
                                if (firstItem)
                                {
                                    firstItem = false;
                                    arrayBuilder.Append("   ");
                                }
                                else
                                    arrayBuilder.Append(", ");
                                // try to format the object with the provided format and IFormatProvider.
                                IFormattable formattableItem = logObjectItem as IFormattable;
                                if (formattableItem != null)
                                    arrayBuilder.Append(formattableItem.ToString(format, provider));
                                else
                                    arrayBuilder.Append(logObjectItem.ToString());
                            }
                            AppendStringToStringBuilder(arrayBuilder.ToString(), output, logWriter, indentLength, ref isFirstLine, ref indent);
                        }
                        else
                        {
                            // No format provided. Just write the objects one by one in a normal fashion.
                            foreach (object arrayItem in logObjectArray)
                                AppendObjectToStringBuilder(arrayItem, output, logWriter, indentLength, ref isFirstLine, ref indent);
                        }
                    }
                    else
                    {
                        if (format != null)
                        {
                            IFormattable formattableItem = logObject as IFormattable;
                            if (formattableItem != null)
                            {
                                // Format the logObject with the given format-string and IFormatProvider.
                                try
                                {
                                    AppendStringToStringBuilder(formattableItem.ToString(format, provider), output, logWriter, indentLength, ref isFirstLine, ref indent);
                                }
                                catch (FormatException)
                                {
                                    // Simply convert the logObject to a string.
                                    AppendStringToStringBuilder(logObject.ToString(), output, logWriter, indentLength, ref isFirstLine, ref indent);
                                }
                            }
                            else
                            {
                                // Simply convert the logObject to a string.
                                AppendStringToStringBuilder(logObject.ToString(), output, logWriter, indentLength, ref isFirstLine, ref indent);
                            }
                        }
                        else
                        {
                            // Simply convert the logObject to a string.
                            if (logObject != null)
                                AppendStringToStringBuilder(logObject.ToString(), output, logWriter, indentLength, ref isFirstLine, ref indent);
                            else
                                AppendStringToStringBuilder("null", output, logWriter, indentLength, ref isFirstLine, ref indent);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Appends a string to a StringBuilder-output.
        /// </summary>
        /// <param name="logString">The string to log.</param>
        /// <param name="output">The StringBuilder.</param>
        /// <param name="logWriter">The LogWriter to write the logObject.</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 static void AppendStringToStringBuilder(string logString, StringBuilder output,
            LogWriter logWriter, int indentLength, ref bool isFirstLine, ref string indent)
        {
            // Check if the string contains new line-characters.
            if (logString.IndexOf("\r\n", StringComparison.Ordinal) > -1)
            {
                // Create an array of strings cutting after each new line-character.
                string[] logLines = logString.Replace("\r\n", "\uFFFF").Split('\uFFFF');
                AppendStringsToStringBuilder(logLines, output, logWriter, indentLength, ref isFirstLine, ref indent);
            }
            else
            {
                if (isFirstLine)
                    isFirstLine = false;
                else
                    AppendIndent(output, indentLength, ref indent);
                output.Append(logString); // A normal single line string.
                output.Append("\r\n");
            }
        }

        /// <summary>
        /// Appends a string-array to a StringBuilder-output.
        /// </summary>
        /// <param name="logLines">The array of strings.</param>
        /// <param name="output">The StringBuilder.</param>
        /// <param name="logWriter">The logwriter to be used for logging.</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 static void AppendStringsToStringBuilder(string[] logLines, StringBuilder output,
            LogWriter logWriter, int indentLength, ref bool isFirstLine, ref string indent)
        {
            // Remove any empty lines at the end.
            int logLinesLength = logLines.Length;
            for (int i = logLines.Length - 1; i >= 0; i--)
            {
                if (string.IsNullOrEmpty(logLines[i]))
                    logLinesLength--;
                else
                    break; // The first non-empty line from the end is found. Stop.
            }

            // Log an array of strings.
            if (logLinesLength > 1)
            {
                for (int i = 0; i < logLinesLength; i++)
                    AppendStringToStringBuilder(logLines[i], output, logWriter, indentLength, ref isFirstLine, ref indent);
            }
            else if (logLinesLength == 1)
                AppendStringToStringBuilder(logLines[0], output, logWriter, indentLength, ref isFirstLine, ref indent);
        }

        /// <summary>
        /// Appends the indent to a StringBuilder-output.
        /// (After this call the 'indent' is defined.)
        /// </summary>
        /// <param name="indentLength">The indent to be used if many lines.</param>
        /// <param name="indent">A string holding a generated indent-text (=a number of spaces). Use AppendIndent() to append the indent.</param>
        internal static void EnsureIndent(int indentLength, ref string indent)
        {
            if (indent == null)
                indent = Strings.Indent(indentLength, true);
        }

        /// <summary>
        /// Appends the indent to a StringBuilder-output.
        /// (After this call the 'indent' is defined.)
        /// </summary>
        /// <param name="output">The StringBuilder.</param>
        /// <param name="indentLength">The indent to be used if many lines.</param>
        /// <param name="indent">A string holding a generated indent-text (=a number of spaces). Use AppendIndent() to append the indent.</param>
        internal static void AppendIndent(StringBuilder output, int indentLength, ref string indent)
        {
            EnsureIndent(indentLength, ref indent);
            output.Append(indent);
        }

        #endregion
    }
}