/*
 * Copyright (c) 2000-2009 TeamDev Ltd. All rights reserved.
 * TeamDev PROPRIETARY and CONFIDENTIAL.
 * Use is subject to license terms.
 */
package com.jniwrapper.win32.system.eventlog;

import com.jniwrapper.*;
import com.jniwrapper.win32.FunctionName;
import com.jniwrapper.win32.Handle;
import com.jniwrapper.win32.LastError;
import com.jniwrapper.win32.LastErrorException;
import com.jniwrapper.win32.registry.RegistryKey;
import com.jniwrapper.win32.registry.RegistryKeyType;
import com.jniwrapper.win32.registry.RegistryKeyValues;
import com.jniwrapper.win32.registry.RegistryValueTransformer;
import com.jniwrapper.win32.system.AdvApi32;
import com.jniwrapper.win32.system.Kernel32;
import com.jniwrapper.win32.ui.User32;

import javax.security.auth.login.FailedLoginException;
import java.awt.image.Kernel;
import java.io.File;
import java.text.MessageFormat;
import java.util.*;

/**
 * This class provides functionality for working with system event logs.
 *
 * @author Vladimir Kondrashchenko
 */
public class EventLog extends Handle
{
    private static final FunctionName FUNCTION_OPENEVENTLOG = new FunctionName("OpenEventLog");
    private static final FunctionName FUNCTION_BACKUPEVENTLOG = new FunctionName("BackupEventLog");
    private static final FunctionName FUNCTION_CLEAREVENTLOG = new FunctionName("ClearEventLog");
    private static final String FUNCTION_CLOSEEVENTLOG = "CloseEventLog";
    private static final String FUNCTION_GETNUMBEROFRECORDS = "GetNumberOfEventLogRecords";
    private static final String FUNCTION_GETOLDESTRECORD = "GetOldestEventLogRecord";
    private static final FunctionName FUNCTION_OPENBACKUPEVENTLOG = new FunctionName("OpenBackupEventLog");
    private static final FunctionName FUNCTION_READEVENTLOG = new FunctionName("ReadEventLog");
    private static final FunctionName FUNCTION_REGISTEREVENTSOURCE = new FunctionName("RegisterEventSource");
    private static final FunctionName FUNCTION_DEREGISTEREVENTSOURCE = new FunctionName("DeregisterEventSource");
    private static final FunctionName FUNCTION_REPORTEVENT = new FunctionName("ReportEvent");
    private static final FunctionName FUNCTION_LOOKUPACCOUNTSID = new FunctionName("LookupAccountSid");
    private static final FunctionName FUNCTION_LOADLIBRARY = new FunctionName("LoadLibrary");
    private static final FunctionName FUNCTION_LOADLIBRARYEX = new FunctionName("LoadLibraryEx");
    private static final FunctionName FUNCTION_FORMATMESSAGE = new FunctionName("FormatMessage");

    private static final String NO_FORMAT_FILE_MESSAGE = "The description for Event ID {0} from source {1} cannot be found. Either the component that raises this event is not installed on your local computer or the installation is corrupted. You can install or repair the component on the local computer.\n\nIf the event originated on another computer, the display information had to be saved with the event.\n\nThe following information was included with the event: \n\n{2}";

    //ReadEventLog constants
    private static final long EVENTLOG_SEQUENTIAL_READ = 0x0001;
    private static final long EVENTLOG_SEEK_READ = 0x0002;
    private static final long EVENTLOG_FORWARDS_READ = 0x0004;
    private static final long EVENTLOG_BACKWARDS_READ = 0x0008;

    //FormatMessage constants
    private static final long FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
    private static final long FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000;
    private static final long FORMAT_MESSAGE_FROM_HMODULE = 0x00000800;
    private static final long FORMAT_MESSAGE_FROM_STRING = 0x00000400;
    private static final long FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
    private static final long FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
    private static final long FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF;

    //LoadLibraryEx constants
    private static final long LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020;
    private static final long LOAD_LIBRARY_AS_DATAFILE = 0x00000002;

    private static final int BUFFER_SIZE = 65536;
    private static final int MAXIMUM_MESSAGE_STRING_COUNT = 99;

    private String _name;
    private String _server;
    private RegistryKey _registryKey;
    private HashMap _keysMap;

    private EventLog()
    {
    }

    /**
     * Opens an event log for reading. If the specified event log cannot be found,
     * the Application log will be opened.
     *
     * @param logName specifies the name of the log to be opened.
     */
    public EventLog(String logName)
    {
        this(null, logName);
    }

    /**
     * Opens a remote event log for reading. If the specified event log cannot be found,
     * the Application log will be opened.
     *
     * @param serverName is the UNC name of the server.
     * @param logName specifies the name of the log to be opened.
     */
    public EventLog(String serverName, String logName)
    {
        Function function = AdvApi32.getInstance().getFunction(FUNCTION_OPENEVENTLOG.toString());

        Parameter server;
        if (serverName == null)
        {
            server = new Pointer(null, true);
        }
        else
        {
            server = new Str(serverName);
        }

        long result = function.invoke(this, server, new Str(logName));

        if (getValue() == 0)
        {
            throw new RuntimeException(LastError.getMessage(result));
        }
        _name = logName;
        _server = serverName;

        RegistryKeyValues.registerAssociation(RegistryKeyType.EXPAND_SZ, RegistryValueTransformer.STRING_EXPAND_TRANSFORMER);
        _registryKey = RegistryKey.LOCAL_MACHINE.openSubKey("SYSTEM\\CurrentControlSet\\services\\eventlog\\" + _name);

        _keysMap = new HashMap();
    }

    /**
     * Returns the name of the opened log.
     *
     * @return the name of the opened log.
     */
    public String getLogName()
    {
        return _name;
    }

    /**
     * If a remote event log has been opened, then the function returns the name of the remote server.
     * Otherwise, the function returns <code>null</code>.
     *
     * @return the function returns the name of the remote server.
     */
    public String getServerName()
    {
        return _server;
    }

    /**
     * Opens the event log that was backed up.
     *
     * @param backup specifies the backup file.
     * @return opened event log.
     */
    public static EventLog openBackup(File backup)
    {
        if (backup == null || !backup.isFile())
        {
            throw new IllegalArgumentException("Illegal backup file.");
        }

        Function function = AdvApi32.getInstance().getFunction(FUNCTION_OPENBACKUPEVENTLOG.toString());

        EventLog eventLog = new EventLog();

        long result = function.invoke(eventLog, new Pointer(null, true), new Str(backup.getAbsolutePath()));

        if (result != 0)
        {
            throw new RuntimeException(LastError.getMessage(result));
        }

        return eventLog;
    }

    /**
     * Backs up the opened event log to a file.
     *
     * @param file specifies the backup file.
     */
    public void backup(File file)
    {
        if (file == null)
        {
            throw new IllegalArgumentException("Illegal backup file.");
        }

        Function function = AdvApi32.getInstance().getFunction(FUNCTION_BACKUPEVENTLOG.toString());

        long result = function.invoke(null, this, new Str(file.getAbsolutePath()));

        if (result != 0)
        {
            throw new LastErrorException(result);
        }
    }

    /**
     * Closes the event log handle.
     */
    public void close()
    {
        Function function = AdvApi32.getInstance().getFunction(FUNCTION_CLOSEEVENTLOG);

        long result = function.invoke(null, this);

        if (result != 0 && result != 38)
        {
            throw new LastErrorException(result);
        }
    }

    /**
     * Clears the opened event log.
     */
    public void clear()
    {
        Function function = AdvApi32.getInstance().getFunction(FUNCTION_CLEAREVENTLOG.toString());

        long result = function.invoke(null, this, new Pointer(null, true));

        if (result != 0 && result != 38)
        {
            throw new LastErrorException(result);
        }
    }

    /**
     * Returns the number of records in the event log.
     *
     * @return the number of records in the event log.
     */
    public int getRecordsCount()
    {
        Function function = AdvApi32.getInstance().getFunction(FUNCTION_GETNUMBEROFRECORDS);

        UInt32 count = new UInt32();
        IntBool retVal = new IntBool();
        long result = function.invoke(retVal, this, new Pointer(count));

        if (retVal.getValue() == 0)
        {
            throw new LastErrorException(result);
        }

        return (int) count.getValue();
    }

    private int getOldestRecordNumber()
    {
        Function function = AdvApi32.getInstance().getFunction(FUNCTION_GETOLDESTRECORD);

        UInt32 count = new UInt32();
        IntBool retVal = new IntBool();
        long result = function.invoke(retVal, this, new Pointer(count));
        if (retVal.getValue() != 0)
        {
            // TODO [Jazz]: investigate why this exception can be thrown
            //throw new LastErrorException(result);
        }

        return (int) count.getValue();
    }

    private void flush()
    {
        Function function = AdvApi32.getInstance().getFunction(FUNCTION_READEVENTLOG.toString());

        function.invoke(null, new Parameter[] {
            this,
            new UInt32(EVENTLOG_SEEK_READ | EVENTLOG_FORWARDS_READ),
            new UInt32(getOldestRecordNumber()),
            new Pointer(new UInt8()),
            new UInt32(0),
            new Pointer(new UInt32()),
            new Pointer(new UInt32())});
    }

    /**
     * Returns the list of event log messages. Each element of the list is an
     * instance of {@link EventLogMessage}.
     *
     * @return the list of event log messages.
     * @see EventLogMessage
     */
    public List getMessages()
    {
        flush();

        /* reads a whole number of entries from the specified event log.
          BOOL ReadEventLog(
          HANDLE hEventLog, // Handle to the event log to be read.
          DWORD dwReadFlags, // Options for how the read operation is to proceed.
          DWORD dwRecordOffset, // Log-entry record number at which the read operation should start.
          LPVOID lpBuffer, // Pointer to a buffer for the data read from the event log. The buffer will be filled with an EVENTLOGRECORD structure.
          DWORD nNumberOfBytesToRead, // Size of the buffer, in bytes.
          DWORD* pnBytesRead, // Pointer to a variable that receives the number of bytes read by the function.
          DWORD* pnMinNumberOfBytesNeeded // Pointer to a variable that receives the number of bytes required for the next log entry.
        );*/
        Function function = AdvApi32.getInstance().getFunction(FUNCTION_READEVENTLOG.toString());
        List result = new LinkedList();
        UInt32 read;

        if (getRecordsCount() == 0)
        {
            return result;
        }

        do
        {
            read = new UInt32(); // the number of bytes read by the function.
            PrimitiveArray array = new PrimitiveArray(UInt8.class, BUFFER_SIZE); // buffer for the data read from the event log.

            int offset = 0;
            IntBool retVal = new IntBool();
            long error = function.invoke(retVal, new Parameter[] {
                this,
                new UInt32(EVENTLOG_SEQUENTIAL_READ | EVENTLOG_FORWARDS_READ),
                new UInt32(0),
                new Pointer(array),
                new UInt32(BUFFER_SIZE),
                new Pointer(read),
                new Pointer(new UInt32())});
            if (retVal.getValue() == 0 && error != 0 && error != 38 && error != 122)
            {
                throw new LastErrorException(error);
            }
            if (read.getValue() == 0)
            {
                break;
            }
            byte[] bytes = array.getBytes();
            int total = (int) read.getValue();
            UInt32 dwSize = new UInt32();
            do
            {
                byte[] bSize = new byte[4];
                System.arraycopy(bytes, offset, bSize, 0, 4);
                dwSize.read(bSize, 0);  // read length of current record (record is a EVENTLOGRECORD structure).
                int size = (int) dwSize.getValue();
                byte[] data = new byte[size];
                System.arraycopy(bytes, offset, data, 0, size);
                result.add(parseBytes(data));
                offset += size;
            } while (offset != total);
        } while (true);

        flushKeysMap();

        return result;
    }

    private void flushKeysMap()
    {
        Function freeLibrary = Kernel32.getInstance().getFunction("FreeLibrary");
        for (Iterator iterator = _keysMap.entrySet().iterator(); iterator.hasNext(); )
        {
            Handle loadedLibraryFile = (Handle) ((Map.Entry) iterator.next()).getValue();
            freeLibrary.invoke(null, loadedLibraryFile);
        }
        _keysMap.clear();
    }

    private EventLogMessage parseBytes(byte[] bytes)
    {
        Function accountSID = AdvApi32.getInstance().getFunction(FUNCTION_LOOKUPACCOUNTSID.toString());

        EventLogMessageInt logMessageInt = new EventLogMessageInt();
        logMessageInt.read(bytes, 0);
        EventLogMessage eventLogMessage = new EventLogMessage();

        eventLogMessage.setRecordNumber((int) logMessageInt.getRecordNumber());
        eventLogMessage.setEventID(logMessageInt.getEventID() & 0xFFFF);
        eventLogMessage.setDate(new Date(logMessageInt.getTimeWritten()*1000));
        eventLogMessage.setCategory((int) logMessageInt.getEventCategory());
        eventLogMessage.setEventType(new EventLogMessage.Type((int) logMessageInt.getEventType()));

        int sourceSize;
        if (logMessageInt.getUserSidOffset() != 0)
        {
            sourceSize = logMessageInt.getUserSidOffset() - 1 - logMessageInt.getLength();
        }
        else
        {
            sourceSize = logMessageInt.getStringOffset() - 1 - logMessageInt.getLength();
        }

        byte[] bSource = new byte[sourceSize];
        System.arraycopy(bytes, logMessageInt.getLength(), bSource, 0, sourceSize);

        String source = new String(bSource);
        int bound = getStringSeparatorPos(source);
        String strSource = source.substring(0, bound).replaceAll("\0", "");
        eventLogMessage.setSource(strSource);

        String computerName = getNullTerminatedString(source.substring(bound + 2));
        eventLogMessage.setComputer(computerName.replaceAll("\0",""));

        int messageSize = logMessageInt.getDataOffset() - logMessageInt.getStringOffset();

        // number of strings in message
        int stringsCount = (int)logMessageInt._numStrings.getValue();
        if (stringsCount > 0)
        {
            byte[] messageBytes = new byte[messageSize];
            System.arraycopy(bytes, logMessageInt.getStringOffset(), messageBytes, 0, messageSize);
            String message = retrieveMessage(messageBytes, strSource, logMessageInt._eventID,stringsCount);
            eventLogMessage.setMessage(message);
        }

        // dataLength - Size of the event-specific data (at the position indicated by DataOffset), in bytes.
        int dataLength = logMessageInt.getDataLength();

        if (dataLength > 0)
        {
            byte[] data = new byte[dataLength];
            System.arraycopy(bytes, logMessageInt.getDataOffset(), data, 0, dataLength);
            eventLogMessage.setData(data);
        }

        byte[] userSid = new byte[logMessageInt.getUserSidLength()];
        System.arraycopy(bytes, logMessageInt.getUserSidOffset(), userSid, 0, logMessageInt.getUserSidLength());

        if (userSid.length != 0)
        {
            Str name = new Str(255);
            Int id = new Int();
            accountSID.invoke(null, new Parameter[] {
                new Pointer(null, true),
                new Pointer(new PrimitiveArray(userSid)),
                new Pointer(name),
                new Pointer(new UInt32(255)),
                new Pointer(new Str(255)),
                new Pointer(new UInt32(255)),
                new Pointer(id)});
            eventLogMessage.setUser(name.getValue());
        }
        else
        {
            eventLogMessage.setUser(null);
        }

        return eventLogMessage;
    }

    /**
     * Gets the first founded null-terminated string in specified string.
     * @param unicodeStr strng in unicode form.
     * @return null-terminated string in unicode form.
     */
    private String getNullTerminatedString(String unicodeStr)
    {
        int indexOfNull = unicodeStr.indexOf("\0\0");
        return unicodeStr.substring(0, indexOfNull);
    }

    /*
     * Retrieves message of merged null-terminated strings from byte array.
     * @param message byte array of message data.
     * @return message of merged strings.
     */

    /**
     * Retrieves message of merged null-terminated strings from byte array.
     * @param message - byte array of the message strings
     * @param sourceName - name of the event source
     * @param messageID - message ID in the system
     * @param stringsCount - number of strings in the byte array
     * @return - formatted message corresponding to the issue
     */
    private String retrieveMessage(byte[] message, String sourceName, UInt32 messageID, int stringsCount)
    {
        String formattedString = null;
        Handle messageFormatFile = getMessageFileFormat(sourceName);

        /*We use the following procedure to retrieve array of pointers to unicode strings and the set of strings
        in format available for representation depending on whether we have or have not got the message format file*/
        String msg = new String(message);
        int beginIndex = 0;
        int endIndex;
        int curStringIndex = 0;
        //We create more placeholders for strings since MSDN says:"Some event sources seem to set NumStrings to a value less than the number of placeholders in the message file. If you use this value to allocate an array of strings in order to get the full message, the subsequent call to FormatMessage may crash. One possibility is to always assume the maximum number of strings (99) when allocating memory and use memset to ensure that any excess strings will default to NULL."
        Pointer[] addresses = new Pointer[MAXIMUM_MESSAGE_STRING_COUNT];
        StringBuffer messageStrings = new StringBuffer();
        while (curStringIndex < stringsCount)
        {
            endIndex = msg.indexOf("\0\0", beginIndex);
            int length = endIndex - beginIndex;
            //We shall copy the string with the endline zero
            int increment = 2;
            //In case of nonempty string with one byte char we must keep the char after the last value character
            if ((length % 2) == 1)
            {
                increment++;
            }

            length += increment;

            byte[] messageStringBytes = new byte[0];
            messageStringBytes = new byte[length];

            System.arraycopy(message, beginIndex, messageStringBytes, 0, length);

            addresses[curStringIndex] = new Pointer(new PrimitiveArray(messageStringBytes));

            String messageString = (new String(messageStringBytes)).replaceAll("\0", "");
            messageStrings.append(messageString);

            beginIndex = endIndex + increment;
            curStringIndex++;
        }

        byte [] emptyStringBytes = new byte[]{0,0};
        Pointer emptyStringPointer = new Pointer(new PrimitiveArray(emptyStringBytes));
        for(;curStringIndex < MAXIMUM_MESSAGE_STRING_COUNT; curStringIndex++)
        {
            addresses[curStringIndex] = emptyStringPointer;
        }

        //Format message procedure
        if(messageFormatFile != null)
        {
            Str retrievedMessage = new Str("", 65536);
            Function formatMessage = Kernel32.getInstance().getFunction(FUNCTION_FORMATMESSAGE.toString());
            UInt32 flags = new UInt32(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_FROM_SYSTEM);
            UInt32 returnValue = new UInt32();

            formatMessage.invoke(returnValue, new Parameter[]{
                    flags,
                    messageFormatFile.isNull() ? new Pointer.Void() : messageFormatFile,
                    messageID,
                    new UInt32(0),
                    new Pointer(retrievedMessage),
                    new UInt32(retrievedMessage.getMaxLength()),
                    new Pointer((new PrimitiveArray(addresses)))
            });
            formattedString = retrievedMessage.toString();
        }
        if(formattedString == null || formattedString.equals(""))
        {
            formattedString = MessageFormat.format(NO_FORMAT_FILE_MESSAGE,new Object[]{Long.toString(messageID.getValue()),sourceName,messageStrings.toString()});
        }

        return formattedString;
    }

    /**
     * Retrieve the file which holds the message format
     * @param sourceName - name of the event source
     * @return handle to the file containing message format or null
     */
    private Handle getMessageFileFormat(String sourceName)
    {
        final String eventMessageFile = "EventMessageFile";
        Handle messageFormatFile = (Handle) _keysMap.get(sourceName);
        if (messageFormatFile == null && _registryKey.exists(sourceName))
        {
            RegistryKey registryKey = _registryKey.openSubKey(sourceName);
            RegistryKeyValues keyValues = registryKey.values();
            String eventMessageFilePath = (String) keyValues.get(eventMessageFile);
            if (eventMessageFilePath != null)
            {
                Function loadLibraryEx = Kernel32.getInstance().getFunction(FUNCTION_LOADLIBRARYEX.toString());
                messageFormatFile = new Handle();

                Str fileName = new Str(eventMessageFilePath);
                UInt32 flags = new UInt32(LOAD_LIBRARY_AS_IMAGE_RESOURCE | LOAD_LIBRARY_AS_DATAFILE);
                loadLibraryEx.invoke(messageFormatFile, new Parameter[]{fileName, new Pointer.Void(),flags});

                _keysMap.put(sourceName, messageFormatFile);
            }
            registryKey.close();
        }
        return messageFormatFile;
    }

    private int getStringSeparatorPos(String s)
    {
        int pos = 0;
        while ((pos = s.indexOf('\0', pos)) != -1)
        {
            if (s.charAt(pos - 1) == '\0' && s.charAt(pos + 1) == '\0')
            {
                return pos;
            }
            pos++;
        }
        return pos;
    }

    /**
     * Posts the message to an event log.
     *
     * @param message specifies the message to be posted.
     */ 
    public static void reportEvent(EventLogMessage message)
    {
        if (message == null)
        {
            throw new IllegalArgumentException("Illegal message argument.");
        }

        EventLog eventLog = EventLog.registerSource(message.getSource());

        Function function = AdvApi32.getInstance().getFunction(FUNCTION_REPORTEVENT.toString());

        PrimitiveArray array = new PrimitiveArray(Str.class, 1);
        if (message.getMessage() != null)
        {
            array.setElement(0, new Pointer(new Str(message.getMessage())));
        }
        else
        {
            array.setElement(0, new Pointer(new Str()));
        }

        int dataSize = 0;
        Pointer.Void pData = new Pointer.Void();

        if (message.getData() != null)
        {
            dataSize = message.getData().length;
            new Pointer(new PrimitiveArray(message.getData())).castTo(pData);
        }

        function.invoke(null, new Parameter[] {
                eventLog,
                new UInt16(message.getEventType() == null ?
                           EventLogMessage.Type.INFORMATION.getValue() : message.getEventType().getValue()),
                new UInt16(message.getCategory()),
                new UInt32(message.getEventID()),
                new Pointer(null, true),
                new UInt16(1),
                new UInt32(dataSize),
                new Pointer(array),
                pData});

//        eventLog.close();
        deregisterSource(eventLog);
    }

    private static EventLog registerSource(String logName)
    {
        Function function = AdvApi32.getInstance().getFunction(FUNCTION_REGISTEREVENTSOURCE.toString());

        EventLog eventLog = new EventLog();

        long result = function.invoke(eventLog, new Pointer(null, true), new Str(logName));

        if (result != 0)
        {
            throw new LastErrorException(result);
        }

        return eventLog;
    }

    private static void deregisterSource(EventLog eventLog) {
        Function function = AdvApi32.getInstance().getFunction(FUNCTION_DEREGISTEREVENTSOURCE.toString());
        Bool result = new Bool();
        long errorCode = function.invoke(result, eventLog);
        if (!result.getValue()) {
            throw new LastErrorException(errorCode);
        }
    }

    /**
     * EVENTLOGRECORD native structure.
     */
    private static class EventLogMessageInt extends Structure
    {
        private UInt32 _length = new UInt32();
        private UInt32 _reserved= new UInt32();
        private UInt32 _recordNumber = new UInt32();
        private UInt32 _timeGenerated = new UInt32();
        private UInt32 _timeWritten = new UInt32();
        private UInt32 _eventID = new UInt32();
        private UInt16 _eventType = new UInt16();
        private UInt16 _numStrings = new UInt16();
        private UInt16 _eventCategory = new UInt16();
        private UInt16 _reservedFlags = new UInt16();
        private UInt32 _closingRecordNumber = new UInt32();
        private UInt32 _stringOffset = new UInt32();
        private UInt32 _userSidLength = new UInt32();
        private UInt32 _userSidOffset = new UInt32();
        private UInt32 _dataLength = new UInt32();
        private UInt32 _dataOffset = new UInt32();

        public EventLogMessageInt()
        {
            init(new Parameter[] {
                _length,
                _reserved,
                _recordNumber,
                _timeGenerated,
                _timeWritten,
                _eventID,
                _eventType,
                _numStrings,
                _eventCategory,
                _reservedFlags,
                _closingRecordNumber,
                _stringOffset,
                _userSidLength,
                _userSidOffset,
                _dataLength,
                _dataOffset
            });

        }

        public EventLogMessageInt(EventLogMessageInt that)
        {
            this();
            initFrom(that);
        }

        public long getRecordNumber()
        {
            return _recordNumber.getValue();
        }

        public long getTimeGenerated()
        {
            return _timeGenerated.getValue();
        }

        public long getEventID()
        {
            return _eventID.getValue();
        }

        public long getEventType()
        {
            return _eventType.getValue() ;
        }

        public long getEventCategory()
        {
            return _eventCategory.getValue();
        }

        public long getRecordLength()
        {
            return _length.getValue();
        }

        public long getTimeWritten()
        {
            return _timeWritten.getValue();
        }

        public int getUserSidLength()
        {
            return (int) _userSidLength.getValue();
        }

        public int getUserSidOffset()
        {
            return (int) _userSidOffset.getValue();
        }

        public int getStringOffset()
        {
            return (int) _stringOffset.getValue();
        }

        public int getDataLength()
        {
            return (int) _dataLength.getValue();
        }

        public int getDataOffset()
        {
            return (int) _dataOffset.getValue();
        }

        public Object clone()
        {
            return new EventLogMessageInt(this);
        }
    }
}