//
// System.Diagnostics.EventLog.cs
//
// Authors:
//	Jonathan Pryor (jonpryor@vt.edu)
//	Andreas Nahr (ClassDevelopment@A-SoftTech.com)
//	Gert Driesen (drieseng@users.sourceforge.net)
//
// Copyright (C) 2002
// Copyright (C) 2003 Andreas Nahr
// Copyright (C) 2006 Novell, Inc (http://www.novell.com)
//
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.Diagnostics;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Globalization;
using System.IO;

namespace System.Diagnostics 
{
	[DefaultEvent ("EntryWritten")]
	[InstallerType (typeof (EventLogInstaller))]
	[Designer ("Microsoft.VisualStudio.Install.EventLogInstallableComponentDesigner, " + Consts.AssemblyMicrosoft_VisualStudio)]
	public class EventLog : Component, ISupportInitialize 
	{
		private string source;
		private string logName;
		private string machineName;
		private bool doRaiseEvents = false;
		private ISynchronizeInvoke synchronizingObject = null;

		// IMPORTANT: also update constants in EventLogTest
		internal const string LOCAL_FILE_IMPL = "local";
		private const string WIN32_IMPL = "win32";
		private const string NULL_IMPL = "null";

		internal const string EVENTLOG_TYPE_VAR = "MONO_EVENTLOG_TYPE";

		private EventLogImpl Impl;

		public EventLog() : this (string.Empty)
		{
		}

		public EventLog(string logName) : this (logName, ".")
		{
		}

		public EventLog(string logName, string machineName)
			: this (logName, machineName, string.Empty)
		{
		}

		public EventLog(string logName, string machineName, string source)
		{
			if (logName == null) {
				throw new ArgumentNullException ("logName");
			}
			if (machineName == null || machineName.Length == 0)
#if NET_2_0
				throw new ArgumentException (string.Format (
					CultureInfo.InvariantCulture, "Invalid value '{0}' for"
					+ " parameter 'machineName'.", machineName));
#else
				throw new ArgumentException (string.Format (
					CultureInfo.InvariantCulture, "Invalid value {0} for"
					+ " parameter MachineName.", machineName));
#endif

			this.source = source;
			this.machineName = machineName;
			this.logName = logName;

			Impl = CreateEventLogImpl (this);
			Impl.EntryWritten += new EntryWrittenEventHandler (EntryWrittenHandler);
		}

		private void EntryWrittenHandler (object sender, EntryWrittenEventArgs e)
		{
			if (doRaiseEvents)
				OnEntryWritten (e.Entry);
		}

		[Browsable (false), DefaultValue (false)]
		[MonitoringDescription ("If enabled raises event when a log is written.")]
		public bool EnableRaisingEvents {
			get {return doRaiseEvents;}
			set {doRaiseEvents = value;}
		}

		[Browsable (false), DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
		[MonitoringDescription ("The entries in the log.")]
		public EventLogEntryCollection Entries {
			get {return new EventLogEntryCollection(Impl);}
		}

		[ReadOnly (true), DefaultValue (""), RecommendedAsConfigurable (true)]
		[TypeConverter ("System.Diagnostics.Design.LogConverter, " + Consts.AssemblySystem_Design)]
		[MonitoringDescription ("Name of the log that is read and written.")]
		public string Log {
			get {
				if (source != null && source.Length > 0)
					return GetLogName ();
				return logName;
			}
			set {
				if (value == null)
					throw new ArgumentNullException ("value");
				logName = value;
			}
		}

		[Browsable (false)]
		public string LogDisplayName {
			get {return Impl.LogDisplayName;}
		}

		[ReadOnly (true), DefaultValue ("."), RecommendedAsConfigurable (true)]
		[MonitoringDescription ("Name of the machine that this log get written to.")]
		public string MachineName {
			get {return machineName;}
			set {machineName = value;}
		}

		[ReadOnly (true), DefaultValue (""), RecommendedAsConfigurable (true)]
		[TypeConverter ("System.Diagnostics.Design.StringValueConverter, " + Consts.AssemblySystem_Design)]
		[MonitoringDescription ("The application name that writes the log.")]
		public string Source {
			get { return source; }
			set { source = (value == null) ? string.Empty : value; }
		}

		[Browsable (false), DefaultValue (null)]
		[MonitoringDescription ("An object that synchronizes event handler calls.")]
		public ISynchronizeInvoke SynchronizingObject {
			get {return synchronizingObject;}
			set {synchronizingObject = value;}
		}

		public void BeginInit ()
		{
			Impl.BeginInit();
		}

		public void Clear ()
		{
			string logName = Log;
			if (logName == null || logName.Length == 0)
				throw new ArgumentException ("Log property value has not been specified.");

			if (!EventLog.Exists (logName, MachineName))
				throw new InvalidOperationException (string.Format (
					CultureInfo.InvariantCulture, "Event Log '{0}'"
					+ " does not exist on computer '{1}'.", logName,
					machineName));

			Impl.Clear ();
		}

		public void Close ()
		{
			Impl.Close();
		}

		public static void CreateEventSource (string source, string logName)
		{
			CreateEventSource (source, logName, ".");
		}

		public static void CreateEventSource (string source, 
			string logName, 
			string machineName)
		{
			CreateEventSource (new EventSourceCreationData (source, logName,
				machineName));
		}

#if NET_2_0
		[MonoTODO ("Support remote machine")]
		public
#else
		private
#endif
		static void CreateEventSource (EventSourceCreationData sourceData)
		{
			if (sourceData.Source == null || sourceData.Source.Length == 0)
				throw new ArgumentException ("Source property value has not been specified.");

			if (sourceData.LogName == null || sourceData.LogName.Length == 0)
				throw new ArgumentException ("Log property value has not been specified.");

			if (SourceExists (sourceData.Source, sourceData.MachineName))
				throw new ArgumentException (string.Format (CultureInfo.InvariantCulture,
					"Source '{0}' already exists on '{1}'.", sourceData.Source,
					sourceData.MachineName));

			EventLogImpl impl = CreateEventLogImpl (sourceData.LogName,
				sourceData.MachineName, sourceData.Source);
			impl.CreateEventSource (sourceData);
		}

		public static void Delete (string logName)
		{
			Delete (logName, ".");
		}

		[MonoTODO ("Support remote machine")]
		public static void Delete (string logName, string machineName)
		{
			if (machineName == null || machineName.Length == 0)
				throw new ArgumentException ("Invalid format for argument"
					+ " machineName.");

			if (logName == null || logName.Length == 0)
				throw new ArgumentException ("Log to delete was not specified.");

			EventLogImpl impl = CreateEventLogImpl (logName, machineName, 
				string.Empty);
			impl.Delete (logName, machineName);
		}

		public static void DeleteEventSource (string source)
		{
			DeleteEventSource (source, ".");
		}

		[MonoTODO ("Support remote machine")]
		public static void DeleteEventSource (string source, string machineName)
		{
			if (machineName == null || machineName.Length == 0)
#if NET_2_0
				throw new ArgumentException (string.Format (
					CultureInfo.InvariantCulture, "Invalid value '{0}' for"
					+ " parameter 'machineName'.", machineName));
#else
				throw new ArgumentException (string.Format (
					CultureInfo.InvariantCulture, "Invalid value {0} for"
					+ " parameter machineName.", machineName));
#endif

			EventLogImpl impl = CreateEventLogImpl (string.Empty, machineName,
				source);
			impl.DeleteEventSource (source, machineName);
		}

		protected override void Dispose (bool disposing)
		{
			if (Impl != null)
				Impl.Dispose (disposing);
		}

		public void EndInit()
		{
			Impl.EndInit();
		}

		public static bool Exists (string logName)
		{
			return Exists (logName, ".");
		}

		[MonoTODO ("Support remote machine")]
		public static bool Exists (string logName, string machineName)
		{
			if (machineName == null || machineName.Length == 0)
				throw new ArgumentException ("Invalid format for argument machineName.");

			if (logName == null || logName.Length == 0)
				return false; 

			EventLogImpl impl = CreateEventLogImpl (logName, machineName,
				string.Empty);
			return impl.Exists (logName, machineName);
		}

		public static EventLog[] GetEventLogs ()
		{
			return GetEventLogs (".");
		}

		[MonoTODO ("Support remote machine")]
		public static EventLog[] GetEventLogs (string machineName)
		{
			EventLogImpl impl = CreateEventLogImpl (new EventLog ());
			return impl.GetEventLogs (machineName);
		}

		[MonoTODO ("Support remote machine")]
		public static string LogNameFromSourceName (string source, string machineName)
		{
			if (machineName == null || machineName.Length == 0)
#if NET_2_0
				throw new ArgumentException (string.Format (
					CultureInfo.InvariantCulture, "Invalid value '{0}' for"
					+ " parameter 'MachineName'.", machineName));
#else
				throw new ArgumentException (string.Format (
					CultureInfo.InvariantCulture, "Invalid value {0} for"
					+ " parameter MachineName.", machineName));
#endif

			EventLogImpl impl = CreateEventLogImpl (string.Empty, machineName,
				source);
			return impl.LogNameFromSourceName (source, machineName);
		}

		public static bool SourceExists (string source)
		{
			return SourceExists (source, ".");
		}

		[MonoTODO ("Support remote machines")]
		public static bool SourceExists (string source, string machineName)
		{
			if (machineName == null || machineName.Length == 0)
				throw new ArgumentException (string.Format (
					CultureInfo.InvariantCulture, "Invalid value '{0}' for"
					+ " parameter 'machineName'.", machineName));

			EventLogImpl impl = CreateEventLogImpl (string.Empty, machineName,
				source);
			return impl.SourceExists (source, machineName);
		}

		public void WriteEntry (string message)
		{
			WriteEntry (message, EventLogEntryType.Information);
		}

		public void WriteEntry (string message, EventLogEntryType type)
		{
			WriteEntry (message, type, 0);
		}

		public void WriteEntry (string message, EventLogEntryType type, 
			int eventID)
		{
			WriteEntry (message, type, eventID, 0);
		}

		public void WriteEntry (string message, EventLogEntryType type, 
			int eventID,
			short category)
		{
			WriteEntry (message, type, eventID, category, null);
		}

		public void WriteEntry (string message, EventLogEntryType type, 
			int eventID,
			short category, byte[] rawData)
		{
			WriteEntry (new string [] { message }, type, eventID,
				category, rawData);
		}

		public static void WriteEntry (string source, string message)
		{
			WriteEntry (source, message, EventLogEntryType.Information);
		}

		public static void WriteEntry (string source, string message, 
			EventLogEntryType type)
		{
			WriteEntry (source, message, type, 0);
		}

		public static void WriteEntry (string source, string message, 
			EventLogEntryType type, int eventID)
		{
			WriteEntry (source, message, type, eventID, 0);
		}

		public static void WriteEntry (string source, string message, 
			EventLogEntryType type, int eventID, short category)
		{
			WriteEntry (source, message, type, eventID, category, null);
		}

		public static void WriteEntry (string source, string message, 
			EventLogEntryType type, int eventID, short category, 
			byte[] rawData)
		{
			using (EventLog eventLog = new EventLog ()) {
				eventLog.Source = source;
				eventLog.WriteEntry (message, type, eventID, category, rawData);
			}
		}

#if NET_2_0
		public void WriteEvent (EventInstance instance, params object [] values)
		{
			WriteEvent (instance, null, values);
		}

		public void WriteEvent (EventInstance instance, byte [] data, params object [] values)
		{
			if (instance == null)
				throw new ArgumentNullException ("instance");

			string [] replacementStrings = null;
			if (values != null) {
				replacementStrings = new string [values.Length];
				for (int i = 0; i < values.Length; i++) {
					object value = values [i];
					if (value == null)
						replacementStrings [i] = string.Empty;
					else
						replacementStrings [i] = values [i].ToString ();
				}
			} else {
				replacementStrings = new string [0];
			}

			WriteEntry (replacementStrings, instance.EntryType, instance
				.InstanceId, (short) instance.CategoryId, data);
		}

		public static void WriteEvent (string source, EventInstance instance, params object [] values)
		{
			WriteEvent (source, instance, null, values);
		}

		public static void WriteEvent (string source, EventInstance instance, byte [] data, params object [] values)
		{
			using (EventLog eventLog = new EventLog ()) {
				eventLog.Source = source;
				eventLog.WriteEvent (instance, data, values);
			}
		}
#endif

		internal void OnEntryWritten (EventLogEntry newEntry)
		{
			if (EntryWritten != null)
				EntryWritten (this, new EntryWrittenEventArgs (newEntry));
		}

		[MonitoringDescription ("Raised for each EventLog entry written.")]
		[MonoTODO ("Use FSM for local file implementation, and NotifyChangeEventLog for win32")]
		public event EntryWrittenEventHandler EntryWritten;

		internal string GetLogName ()
		{
			if (logName != null && logName.Length > 0)
				return logName;

			// if no log name has been set, then use source to determine name of log
			logName = LogNameFromSourceName (source, machineName);
			return logName;
		}

		private static EventLogImpl CreateEventLogImpl (string logName, string machineName, string source)
		{
			EventLog eventLog = new EventLog (logName, machineName, source);
			return CreateEventLogImpl (eventLog);
		}

		private static EventLogImpl CreateEventLogImpl (EventLog eventLog)
		{
			switch (EventLogImplType) {
			case LOCAL_FILE_IMPL:
				return new LocalFileEventLog (eventLog);
			case WIN32_IMPL:
				return new Win32EventLog (eventLog);
			case NULL_IMPL:
				return new NullEventLog (eventLog);
			default:
				// we should never get here
				throw new NotSupportedException (string.Format (
					CultureInfo.InvariantCulture, "Eventlog implementation"
					+ " '{0}' is not supported.", EventLogImplType));
			}
		}

		private static bool Win32EventLogEnabled {
			get {
				return (Environment.OSVersion.Platform == PlatformID.Win32NT);
			}
		}

		// IMPORTANT: also modify corresponding property in EventLogTest
		private static string EventLogImplType
		{
			get {
				string implType = Environment.GetEnvironmentVariable (EVENTLOG_TYPE_VAR);
				if (implType == null) {
					if (Win32EventLogEnabled)
						return WIN32_IMPL;
					implType = NULL_IMPL;
				} else {
					if (Win32EventLogEnabled && string.Compare (implType, WIN32_IMPL, true) == 0)
						implType = WIN32_IMPL;
					else if (string.Compare (implType, NULL_IMPL, true) == 0)
						implType = NULL_IMPL;
					else if (string.Compare (implType, 0, LOCAL_FILE_IMPL, 0, LOCAL_FILE_IMPL.Length, true) == 0)
						implType = LOCAL_FILE_IMPL;
					else
						throw new NotSupportedException (string.Format (
							CultureInfo.InvariantCulture, "Eventlog implementation"
							+ " '{0}' is not supported.", implType));
				}
				return implType;
			}
		}

		private void WriteEntry (string [] replacementStrings, EventLogEntryType type, long instanceID, short category, byte [] rawData)
		{
			if (Source.Length == 0)
				throw new ArgumentException ("Source property was not set"
					+ "before writing to the event log.");

			if (!Enum.IsDefined (typeof (EventLogEntryType), type))
				throw new InvalidEnumArgumentException ("type", (int) type,
					typeof (EventLogEntryType));

#if NET_2_0
			ValidateEventID (instanceID);
#endif

			if (!SourceExists (Source, MachineName)) {
				if (Log == null || Log.Length == 0) {
					Log = "Application";
				}
				CreateEventSource (Source, Log, MachineName);

#if ONLY_1_1
				ValidateEventID (instanceID);
#endif
			} else if (logName != null && logName.Length != 0) {
#if ONLY_1_1
				ValidateEventID (instanceID);
#endif
				string actualLog = LogNameFromSourceName (Source, MachineName);
				if (string.Compare (logName, actualLog, true, CultureInfo.InvariantCulture) != 0)
					throw new ArgumentException (string.Format (
						CultureInfo.InvariantCulture, "The source '{0}' is not"
						+ " registered in log '{1}' (it is registered in log"
						+ " '{2}'). The Source and Log properties must be"
						+ " matched, or you may set Log to the empty string,"
						+ " and it will automatically be matched to the Source"
						+ " property.", Source, logName, actualLog));
			}

#if ONLY_1_1
			ValidateEventID (instanceID);
#endif

			if (rawData == null)
				rawData = new byte [0];

			Impl.WriteEntry (replacementStrings, type, (uint) instanceID, category, rawData);
		}

		private void ValidateEventID (long instanceID)
		{
			int eventID = GetEventID (instanceID);
			if (eventID < ushort.MinValue || eventID > ushort.MaxValue)
				throw new ArgumentException (string.Format (CultureInfo.InvariantCulture,
					"Invalid eventID value '{0}'. It must be in the range between"
					+ " '{1}' and '{2}'.", instanceID, ushort.MinValue, ushort.MaxValue));
		}

		internal static int GetEventID (long instanceID)
		{
			long inst = (instanceID < 0) ? -instanceID : instanceID;

			// MSDN: eventID equals the InstanceId with the top two bits masked
			int eventID = (int) (inst & 0x3fffffff);
			return (instanceID < 0) ? -eventID : eventID;
		}
	}
}
