//------------------------------------------------------------------------------
// <copyright file="EventLogEntry.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

namespace System.Diagnostics {
    using System.Text;
    using System.Runtime.InteropServices;
    using System.Runtime.Serialization;
    using System.ComponentModel;
    using System.Diagnostics;
    using Microsoft.Win32;
    using Microsoft.Win32.SafeHandles;
    using System;
    using System.Security;
    using System.Security.Permissions;
    using System.IO;
    using System.Globalization;
    using System.Runtime.Versioning;

    /// <devdoc>
    ///    <para>
    ///    <see cref='System.Diagnostics.EventLogEntry'/>
    ///    encapsulates a single record in the NT event log.
    /// </para>
    /// </devdoc>
    [
    ToolboxItem(false),
    DesignTimeVisible(false),
    Serializable,
    ]
    public sealed class EventLogEntry : Component, ISerializable {
        internal byte[] dataBuf;
        internal int bufOffset;
        private EventLogInternal owner;
        private string category;
        private string message;

        // make sure only others in this package can create us
        internal EventLogEntry(byte[] buf, int offset, EventLogInternal log) {
            this.dataBuf = buf;
            this.bufOffset = offset;
            this.owner = log;

            GC.SuppressFinalize(this);
        }

        /// <devdoc>
        /// </devdoc>
        /// <internalonly/>
        private EventLogEntry(SerializationInfo info, StreamingContext context) {
            dataBuf = (byte[])info.GetValue("DataBuffer", typeof(byte[]));
            string logName = info.GetString("LogName");
            string machineName = info.GetString("MachineName");
            owner = new EventLogInternal(logName, machineName, "");
            GC.SuppressFinalize(this);
        }

        /// <devdoc>
        ///    <para>
        ///       Gets the name of the computer on which this entry was generated.
        ///
        ///    </para>
        /// </devdoc>
        [MonitoringDescription(SR.LogEntryMachineName)]
        public string MachineName {
            get {
                // first skip over the source name
                int pos = bufOffset + FieldOffsets.RAWDATA;
                while (CharFrom(dataBuf, pos) != '\0')
                    pos += 2;
                pos += 2;
                char ch = CharFrom(dataBuf, pos);
                StringBuilder buf = new StringBuilder();
                while (ch != '\0') {
                    buf.Append(ch);
                    pos += 2;
                    ch = CharFrom(dataBuf, pos);
                }
                return buf.ToString();
            }
        }

        /// <devdoc>
        ///    <para>
        ///       Gets the binary data associated with the entry.
        ///
        ///    </para>
        /// </devdoc>
        [
        MonitoringDescription(SR.LogEntryData)
        ]
        public byte[] Data {
            get {
                int dataLen = IntFrom(dataBuf, bufOffset + FieldOffsets.DATALENGTH);
                byte[] data = new byte[dataLen];
                Array.Copy(dataBuf, bufOffset + IntFrom(dataBuf, bufOffset + FieldOffsets.DATAOFFSET),
                           data, 0, dataLen);
                return data;
            }
        }

        /*
        /// <summary>
        ///    <para>
        ///       Copies the binary data in the <see cref='System.Diagnostics.EventLogEntry.Data'/> member into an
        ///       array.
        ///    </para>
        /// </summary>
        /// <returns>
        ///    <para>
        ///       An array of type <see cref='System.Byte'/>.
        ///    </para>
        /// </returns>
        /// <keyword term=''/>
        public Byte[] getDataBytes() {
            Byte[] data = new Byte[rec.dataLength];
            for (int i = 0; i < data.Length; i++)
                data[i] = new Byte(rec.buf[i]);
            return data;
        }
        */

        /// <devdoc>
        ///    <para>
        ///       Gets the index of this entry in the event
        ///       log.
        ///    </para>
        /// </devdoc>
        [MonitoringDescription(SR.LogEntryIndex)]
        public int Index {
            get {
                return IntFrom(dataBuf, bufOffset + FieldOffsets.RECORDNUMBER);
            }
        }

        /// <devdoc>
        ///    <para>
        ///       Gets the text associated with the <see cref='System.Diagnostics.EventLogEntry.CategoryNumber'/> for this entry.
        ///
        ///    </para>
        /// </devdoc>
        [MonitoringDescription(SR.LogEntryCategory)]
        public string Category {
            get {
                if (category == null) {
                    string dllName = GetMessageLibraryNames("CategoryMessageFile");
                    string cat = owner.FormatMessageWrapper(dllName, (uint) CategoryNumber, null);
                    if (cat == null)
                        category = "(" + CategoryNumber.ToString(CultureInfo.CurrentCulture) + ")";
                    else
                        category = cat;
                }

                return category;
            }
        }

        /// <devdoc>
        ///    <para>
        ///       Gets the application-specific category number for this entry
        ///
        ///    </para>
        /// </devdoc>
        [
        MonitoringDescription(SR.LogEntryCategoryNumber)
        ]
        public short CategoryNumber {
            get {
                return ShortFrom(dataBuf, bufOffset + FieldOffsets.EVENTCATEGORY);
            }
        }

        /// <devdoc>
        ///    <para>
        ///       Gets the application-specific event indentifier of this entry.
        ///
        ///    </para>
        /// </devdoc>
        [
        MonitoringDescription(SR.LogEntryEventID),
        Obsolete("This property has been deprecated.  Please use System.Diagnostics.EventLogEntry.InstanceId instead.  http://go.microsoft.com/fwlink/?linkid=14202")
        ]
        public int EventID {
            get {
                // Apparently the top 2 bits of this number are not
                // always 0. Strip them so the number looks nice to the user.
                // The problem is, if the user were to want to call FormatMessage(),
                // they'd need these two bits.
                return IntFrom(dataBuf, bufOffset + FieldOffsets.EVENTID) & 0x3FFFFFFF;
            }
        }

        /// <devdoc>
        ///    <para>
        ///
        ///       Gets the type
        ///       of this entry.
        ///
        ///    </para>
        /// </devdoc>
        [MonitoringDescription(SR.LogEntryEntryType)]
        public EventLogEntryType EntryType {
            get {
                return(EventLogEntryType) ShortFrom(dataBuf, bufOffset + FieldOffsets.EVENTTYPE);
            }
        }

        /// <devdoc>
        ///    <para>
        ///       Gets the localized message corresponding to this event entry.
        ///
        ///    </para>
        /// </devdoc>
        [
        MonitoringDescription(SR.LogEntryMessage),
        Editor("System.ComponentModel.Design.BinaryEditor, " + AssemblyRef.SystemDesign, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing)
        ]
        public string Message {
            get {
                if (message == null) {
                    string dllNames = GetMessageLibraryNames("EventMessageFile");
                    int msgId =   IntFrom(dataBuf, bufOffset + FieldOffsets.EVENTID);
                    string msg = owner.FormatMessageWrapper(dllNames, (uint)msgId, ReplacementStrings);
                    if (msg == null) {
                        StringBuilder msgBuf = new StringBuilder(SR.GetString(SR.MessageNotFormatted, msgId, Source));
                        string[] strings = ReplacementStrings;
                        for (int i = 0; i < strings.Length; i++) {
                            if (i != 0)
                                msgBuf.Append(", ");
                            msgBuf.Append("'");
                            msgBuf.Append(strings[i]);
                            msgBuf.Append("'");
                        }
                        msg = msgBuf.ToString();
                    }
                    else
                        msg = ReplaceMessageParameters( msg, ReplacementStrings );
                    message = msg;
                }

                return message;
            }
        }

        /// <devdoc>
        ///    <para>
        ///       Gets the name of the application that generated this event.
        ///    </para>
        /// </devdoc>
        [MonitoringDescription(SR.LogEntrySource)]
        public string Source {
            get {
                StringBuilder buf = new StringBuilder();
                int pos = bufOffset + FieldOffsets.RAWDATA;

                char ch = CharFrom(dataBuf, pos);
                while (ch != '\0') {
                    buf.Append(ch);
                    pos += 2;
                    ch = CharFrom(dataBuf, pos);
                }

                return buf.ToString();
            }
        }

        /// <devdoc>
        ///    <para>
        ///       Gets the replacement strings
        ///       associated with the entry.
        ///
        ///    </para>
        /// </devdoc>
        [MonitoringDescription(SR.LogEntryReplacementStrings)]
        public string[] ReplacementStrings {
            get {
                string[] strings = new string[ShortFrom(dataBuf, bufOffset + FieldOffsets.NUMSTRINGS)];
                int i = 0;
                int bufpos = bufOffset + IntFrom(dataBuf, bufOffset + FieldOffsets.STRINGOFFSET);
                StringBuilder buf = new StringBuilder();
                while (i < strings.Length) {
                    char ch = CharFrom(dataBuf, bufpos);
                    if (ch != '\0')
                        buf.Append(ch);
                    else {
                        strings[i] = buf.ToString();
                        i++;
                        buf = new StringBuilder();
                    }
                    bufpos += 2;
                }
                return strings;
            }
        }

        [
            MonitoringDescription(SR.LogEntryResourceId),
            ComVisible(false)
        ]
        public Int64 InstanceId {
            get {
                return (UInt32)IntFrom(dataBuf, bufOffset + FieldOffsets.EVENTID);
            }
        }

#if false
        internal string StringsBuffer {
            get {
                StringBuilder buf = new StringBuilder();
                int bufpos = bufOffset + IntFrom(dataBuf, bufOffset + FieldOffsets.STRINGOFFSET);
                int i = 0;
                int numStrings = ShortFrom(dataBuf, bufOffset + FieldOffsets.NUMSTRINGS);
                while (i < numStrings) {
                    char ch = CharFrom(dataBuf, bufpos);
                    buf.Append(ch);
                    bufpos += 2;
                    if (ch == '\0')
                        i++;
                }
                return buf.ToString();
            }
        }
#endif

        /// <devdoc>
        ///    <para>
        ///       Gets the time at which this event was generated, in local time.
        ///
        ///    </para>
        /// </devdoc>
        [MonitoringDescription(SR.LogEntryTimeGenerated)]
        public DateTime TimeGenerated {
            get {
                return beginningOfTime.AddSeconds(IntFrom(dataBuf, bufOffset + FieldOffsets.TIMEGENERATED)).ToLocalTime();
            }
        }

        /// <devdoc>
        ///    <para>
        ///       Gets
        ///       the time at which this event was written to the log, in local time.
        ///
        ///    </para>
        /// </devdoc>
        [MonitoringDescription(SR.LogEntryTimeWritten)]
        public DateTime TimeWritten {
            get {
                return beginningOfTime.AddSeconds(IntFrom(dataBuf, bufOffset + FieldOffsets.TIMEWRITTEN)).ToLocalTime();
            }
        }

        /// <devdoc>
        ///    <para>
        ///       Gets the name
        ///       of the user responsible for this event.
        ///    </para>
        /// </devdoc>
        [MonitoringDescription(SR.LogEntryUserName)]
        public string UserName {
            get {
                int sidLen = IntFrom(dataBuf, bufOffset + FieldOffsets.USERSIDLENGTH);
                if (sidLen == 0)
                    return null;
                byte[] sid = new byte[sidLen];                                 
                Array.Copy(dataBuf, bufOffset + IntFrom(dataBuf, bufOffset + FieldOffsets.USERSIDOFFSET),
                           sid, 0, sid.Length);
                
                int userNameLen = 256;
                int domainNameLen = 256;
                int sidNameUse = 0;
                StringBuilder bufUserName = new StringBuilder(userNameLen);
                StringBuilder bufDomainName = new StringBuilder(domainNameLen);
                
                StringBuilder retUserName = new StringBuilder();

                if(UnsafeNativeMethods.LookupAccountSid(MachineName, sid, bufUserName, ref userNameLen, bufDomainName, ref domainNameLen, ref sidNameUse) != 0) {
                    retUserName.Append(bufDomainName.ToString());
                    retUserName.Append("\\");
                    retUserName.Append(bufUserName.ToString());
                }
                
                return retUserName.ToString();
            }
        }

        private char CharFrom(byte[] buf, int offset) {
            return(char) ShortFrom(buf, offset);
        }

        /// <devdoc>
        ///    <para>
        ///       Performs a comparison between two event log entries.
        ///
        ///    </para>
        /// </devdoc>
        public bool Equals(EventLogEntry otherEntry) {
            if (otherEntry == null)
                return false;
            int ourLen = IntFrom(dataBuf, bufOffset + FieldOffsets.LENGTH);
            int theirLen = IntFrom(otherEntry.dataBuf, otherEntry.bufOffset + FieldOffsets.LENGTH);
            if (ourLen != theirLen) {
                return false;
            }
            int min = bufOffset;
            int max = bufOffset + ourLen;
            int j = otherEntry.bufOffset;
            for (int i = min; i < max; i++, j++)
                if (dataBuf[i] != otherEntry.dataBuf[j]) {
                    return false;
                }
            return true;
        }

        private int IntFrom(byte[] buf, int offset) {
            // assumes Little Endian byte order.
            return(unchecked((int)0xFF000000) & (buf[offset+3] << 24)) | (0xFF0000 & (buf[offset+2] << 16)) |
            (0xFF00 & (buf[offset+1] << 8)) | (0xFF & (buf[offset]));
        }

        // Replacing parameters '%n' in formated message using 'ParameterMessageFile' registry key.
        internal string ReplaceMessageParameters( String msg,  string[] insertionStrings )   {

            int percentIdx = msg.IndexOf('%');
            if ( percentIdx < 0 )
                return msg;     // no '%' at all

            int startCopyIdx     = 0;        // start idx of last [....] msg chars to copy
            int msgLength   = msg.Length;
            StringBuilder buf = new StringBuilder();
            string paramDLLNames = GetMessageLibraryNames("ParameterMessageFile");

            while ( percentIdx >= 0 ) {
                string param = null;

                // Convert numeric string after '%' to paramMsgID number.
                int lasNumIdx =  percentIdx + 1;
                while ( lasNumIdx < msgLength && Char.IsDigit(msg, lasNumIdx) )
                    lasNumIdx++;

                uint paramMsgID = 0; 

                // If we can't parse it, leave the paramMsgID as zero.  We'll skip the replacement and just put
                // the %xxxx into the final message. 
                if (lasNumIdx != percentIdx + 1 ) 
                    UInt32.TryParse( msg.Substring(percentIdx + 1, lasNumIdx - percentIdx - 1), out paramMsgID);

                if ( paramMsgID != 0 )
                    param = owner.FormatMessageWrapper( paramDLLNames, paramMsgID, insertionStrings);

                if ( param != null ) {
                    if ( percentIdx > startCopyIdx )
                        buf.Append(msg, startCopyIdx, percentIdx - startCopyIdx);    // original chars from msg
                    buf.Append(param);
                    startCopyIdx = lasNumIdx;
                }

                percentIdx = msg.IndexOf('%', percentIdx + 1);
            }

            if ( msgLength - startCopyIdx > 0 )
                buf.Append(msg, startCopyIdx, msgLength - startCopyIdx);          // last span of original msg
            return buf.ToString();
        }

        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        private static RegistryKey GetSourceRegKey(string logName, string source, string machineName) {
            RegistryKey eventKey = null;
            RegistryKey logKey   = null;

            try {
                eventKey = EventLog.GetEventLogRegKey(machineName, false);
                if (eventKey == null)
                    return null;
                if (logName == null)
                    logKey = eventKey.OpenSubKey("Application", /*writable*/false);
                else
                    logKey = eventKey.OpenSubKey(logName, /*writable*/false);
                if (logKey == null)
                    return null;
                return logKey.OpenSubKey(source, /*writable*/false);
            }
            finally {
                if (eventKey != null) eventKey.Close();
                if (logKey != null) logKey.Close();
            }

        }

        // ------------------------------------------------------------------------------
        // Returns DLL names list.
        // libRegKey can be: "EventMessageFile", "CategoryMessageFile", "ParameterMessageFile"
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        private string GetMessageLibraryNames(string libRegKey ) {
            // get the value stored in the registry

            string fileName = null;
            RegistryKey regKey = null;
            try {
                regKey = GetSourceRegKey(owner.Log, Source, owner.MachineName);
                if (regKey != null) {
                    fileName = (string)regKey.GetValue(libRegKey);
                }
            }
            finally {
                if (regKey != null)
                    regKey.Close();
            }

            if (fileName == null)
                return null;

            // convert any absolute paths on a remote machine to use the \\MACHINENAME\DRIVELETTER$ shares
            // so we pick up message dlls from the remote machine.
            if (owner.MachineName != ".") {

                string[] fileNames = fileName.Split(';');

                StringBuilder result = new StringBuilder();

                for (int i = 0; i < fileNames.Length; i++) {
                    if (fileNames[i].Length >= 2 && fileNames[i][1] == ':') {
                        result.Append(@"\\");
                        result.Append(owner.MachineName);
                        result.Append(@"\");
                        result.Append(fileNames[i][0]);
                        result.Append("$");
                        result.Append(fileNames[i], 2, fileNames[i].Length - 2);
                        result.Append(';');
                    } 
                }

                if (result.Length == 0) {
                    return null;
                } else {
                    return result.ToString(0, result.Length - 1); // Chop of last ";"
                }
            }
            else {
                return fileName;
            }
        }


        private short ShortFrom(byte[] buf, int offset) {
            // assumes little Endian byte order.
            return(short) ((0xFF00 & (buf[offset+1] << 8)) | (0xFF & buf[offset]));
        }

        /// <internalonly/>
        /// <devdoc>
        /// <para>
        /// Saves an entry as a stream of data.
        /// </para>
        /// </devdoc>
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
            int len = IntFrom(dataBuf, bufOffset + FieldOffsets.LENGTH);
            byte[] buf = new byte[len];
            Array.Copy(dataBuf, bufOffset, buf, 0, len);

            info.AddValue("DataBuffer", buf, typeof(byte[]));
            info.AddValue("LogName", owner.Log);
            info.AddValue("MachineName", owner.MachineName);
        }

        /// <devdoc>
        ///     Stores the offsets from the beginning of the record to fields within the record.
        /// </devdoc>
        private static class FieldOffsets {
            /** int */
            internal const int LENGTH = 0;
            /** int */
            internal const int RESERVED = 4;
            /** int */
            internal const int RECORDNUMBER = 8;
            /** int */
            internal const int TIMEGENERATED = 12;
            /** int */
            internal const int TIMEWRITTEN = 16;
            /** int */
            internal const int EVENTID = 20;
            /** short */
            internal const int EVENTTYPE = 24;
            /** short */
            internal const int NUMSTRINGS = 26;
            /** short */
            internal const int EVENTCATEGORY = 28;
            /** short */
            internal const int RESERVEDFLAGS = 30;
            /** int */
            internal const int CLOSINGRECORDNUMBER = 32;
            /** int */
            internal const int STRINGOFFSET = 36;
            /** int */
            internal const int USERSIDLENGTH = 40;
            /** int */
            internal const int USERSIDOFFSET = 44;
            /** int */
            internal const int DATALENGTH = 48;
            /** int */
            internal const int DATAOFFSET = 52;
            /** bytes */
            internal const int RAWDATA = 56;
        }

        // times in the struct are # seconds from midnight 1/1/1970.
        private static readonly DateTime beginningOfTime = new DateTime(1970, 1, 1, 0, 0, 0);

        // offsets in the struct are specified from the beginning, but we have to reference
        // them from the beginning of the array.  This is the size of the members before that.
        private const int OFFSETFIXUP = 4+4+4+4+4+4+2+2+2+2+4+4+4+4+4+4;

    }
}

