using System;
using System.Runtime.InteropServices;
using winPEAS.TaskScheduler.TaskEditor.Native;

namespace winPEAS.TaskScheduler.V1
{
#pragma warning disable CS0618 // Type or member is obsolete

	#region class HRESULT -- Values peculiar to the task scheduler.
	internal class HResult
	{
		// The task is ready to run at its next scheduled time.
		public const int SCHED_S_TASK_READY = 0x00041300;
		// The task is currently running.
		public const int SCHED_S_TASK_RUNNING = 0x00041301;
		// The task will not run at the scheduled times because it has been disabled.
		public const int SCHED_S_TASK_DISABLED = 0x00041302;
		// The task has not yet run.
		public const int SCHED_S_TASK_HAS_NOT_RUN = 0x00041303;
		// There are no more runs scheduled for this task.
		public const int SCHED_S_TASK_NO_MORE_RUNS = 0x00041304;
		// One or more of the properties that are needed to run this task on a schedule have not been set.
		public const int SCHED_S_TASK_NOT_SCHEDULED = 0x00041305;
		// The last run of the task was terminated by the user.
		public const int SCHED_S_TASK_TERMINATED = 0x00041306;
		// Either the task has no triggers or the existing triggers are disabled or not set.
		public const int SCHED_S_TASK_NO_VALID_TRIGGERS = 0x00041307;
		// Event triggers don't have set run times.
		public const int SCHED_S_EVENT_TRIGGER = 0x00041308;
		// Trigger not found.
		public const int SCHED_E_TRIGGER_NOT_FOUND = unchecked((int)0x80041309);
		// One or more of the properties that are needed to run this task have not been set.
		public const int SCHED_E_TASK_NOT_READY = unchecked((int)0x8004130A);
		// There is no running instance of the task to terminate.
		public const int SCHED_E_TASK_NOT_RUNNING = unchecked((int)0x8004130B);
		// The Task Scheduler Service is not installed on this computer.
		public const int SCHED_E_SERVICE_NOT_INSTALLED = unchecked((int)0x8004130C);
		// The task object could not be opened.
		public const int SCHED_E_CANNOT_OPEN_TASK = unchecked((int)0x8004130D);
		// The object is either an invalid task object or is not a task object.
		public const int SCHED_E_INVALID_TASK = unchecked((int)0x8004130E);
		// No account information could be found in the Task Scheduler security database for the task indicated.
		public const int SCHED_E_ACCOUNT_INFORMATION_NOT_SET = unchecked((int)0x8004130F);
		// Unable to establish existence of the account specified.
		public const int SCHED_E_ACCOUNT_NAME_NOT_FOUND = unchecked((int)0x80041310);
		// Corruption was detected in the Task Scheduler security database; the database has been reset.
		public const int SCHED_E_ACCOUNT_DBASE_CORRUPT = unchecked((int)0x80041311);
		// Task Scheduler security services are available only on Windows NT.
		public const int SCHED_E_NO_SECURITY_SERVICES = unchecked((int)0x80041312);
		// The task object version is either unsupported or invalid.
		public const int SCHED_E_UNKNOWN_OBJECT_VERSION = unchecked((int)0x80041313);
		// The task has been configured with an unsupported combination of account settings and run time options.
		public const int SCHED_E_UNSUPPORTED_ACCOUNT_OPTION = unchecked((int)0x80041314);
		// The Task Scheduler Service is not running.
		public const int SCHED_E_SERVICE_NOT_RUNNING = unchecked((int)0x80041315);
		// The Task Scheduler service must be configured to run in the System account to function properly.  Individual tasks may be configured to run in other accounts.
		public const int SCHED_E_SERVICE_NOT_LOCALSYSTEM = unchecked((int)0x80041316);
	}
	#endregion

	#region Enums

	/// <summary>
	/// Options for a task, used for the Flags property of a Task. Uses the
	/// "Flags" attribute, so these values are combined with |. 
	/// Some flags are documented as Windows 95 only, but they have a
	/// user interface in Windows XP so that may not be true.
	/// </summary>
	[Flags]
	internal enum TaskFlags
	{
		/// <summary>
		/// The interactive flag is set if the task is intended to be displayed to the user. 
		/// If the flag is not set, no user interface associated with the task is presented
		/// to the user when the task is executed.
		/// </summary>
		Interactive = 0x1,
		/// <summary>
		/// The task will be deleted when there are no more scheduled run times.
		/// </summary>
		DeleteWhenDone = 0x2,
		/// <summary>
		/// The task is disabled. This is useful to temporarily prevent a task from running
		/// at the scheduled time(s).
		/// </summary>
		Disabled = 0x4,
		/// <summary>
		/// The task begins only if the computer is not in use at the scheduled start time. Windows 95 only.
		/// </summary>
		StartOnlyIfIdle = 0x10,
		/// <summary>
		/// The task terminates if the computer makes an idle to non-idle transition while the task is running.
		/// The computer is not considered idle until the IdleWait triggers' time elapses with no user input.
		/// Windows 95 only. For information regarding idle triggers, see <see cref="IdleTrigger"/>.
		/// </summary>
		KillOnIdleEnd = 0x20,
		/// <summary>
		/// The task does not start if its target computer is running on battery power. Windows 95 only.
		/// </summary>
		DontStartIfOnBatteries = 0x40,
		/// <summary>
		/// The task ends, and the associated application quits if the task's target computer switches
		/// to battery power. Windows 95 only.
		/// </summary>
		KillIfGoingOnBatteries = 0x80,
		/// <summary>
		/// The task runs only if the system is docked. Windows 95 only.
		/// </summary>
		RunOnlyIfDocked = 0x100,
		/// <summary>
		/// The work item created will be hidden.
		/// </summary>
		Hidden = 0x200,
		/// <summary>
		/// The task runs only if there is currently a valid Internet connection.
		/// This feature is currently not implemented.
		/// </summary>
		RunIfConnectedToInternet = 0x400,
		/// <summary>
		/// The task starts again if the computer makes a non-idle to idle transition before all the
		/// task's task_triggers elapse. (Use this flag in conjunction with KillOnIdleEnd.) Windows 95 only.
		/// </summary>
		RestartOnIdleResume = 0x800,
		/// <summary>
		/// The task runs only if the SYSTEM account is available.
		/// </summary>
		SystemRequired = 0x1000,
		/// <summary>
		/// The task runs only if the user specified in SetAccountInformation is logged on interactively. 
		/// This flag has no effect on work items set to run in the local account.
		/// </summary>
		RunOnlyIfLoggedOn = 0x2000
	}

	/// <summary>
	/// Status values returned for a task.  Some values have been determined to occur although
	/// they do no appear in the Task Scheduler system documentation.
	/// </summary>
	internal enum TaskStatus
	{
		/// <summary>The task is ready to run at its next scheduled time.</summary>
		Ready = HResult.SCHED_S_TASK_READY,
		/// <summary>The task is currently running.</summary>
		Running = HResult.SCHED_S_TASK_RUNNING,
		/// <summary>One or more of the properties that are needed to run this task on a schedule have not been set. </summary>
		NotScheduled = HResult.SCHED_S_TASK_NOT_SCHEDULED,
		/// <summary>The task has not yet run.</summary>
		NeverRun = HResult.SCHED_S_TASK_HAS_NOT_RUN,
		/// <summary>The task will not run at the scheduled times because it has been disabled.</summary>
		Disabled = HResult.SCHED_S_TASK_DISABLED,
		/// <summary>There are no more runs scheduled for this task.</summary>
		NoMoreRuns = HResult.SCHED_S_TASK_NO_MORE_RUNS,
		/// <summary>The last run of the task was terminated by the user.</summary>
		Terminated = HResult.SCHED_S_TASK_TERMINATED,
		/// <summary>Either the task has no triggers or the existing triggers are disabled or not set.</summary>
		NoTriggers = HResult.SCHED_S_TASK_NO_VALID_TRIGGERS,
		/// <summary>Event triggers don't have set run times.</summary>
		NoTriggerTime = HResult.SCHED_S_EVENT_TRIGGER
	}

	/// <summary>Valid types of triggers</summary>
	internal enum TaskTriggerType
	{
		/// <summary>Trigger is set to run the task a single time. </summary>
		RunOnce = 0,
		/// <summary>Trigger is set to run the task on a daily interval. </summary>
		RunDaily = 1,
		/// <summary>Trigger is set to run the work item on specific days of a specific week of a specific month. </summary>
		RunWeekly = 2,
		/// <summary>Trigger is set to run the task on a specific day(s) of the month.</summary>
		RunMonthly = 3,
		/// <summary>Trigger is set to run the task on specific days, weeks, and months.</summary>
		RunMonthlyDOW = 4,
		/// <summary>Trigger is set to run the task if the system remains idle for the amount of time specified by the idle wait time of the task.</summary>
		OnIdle = 5,
		/// <summary>Trigger is set to run the task at system startup.</summary>
		OnSystemStart = 6,
		/// <summary>Trigger is set to run the task when a user logs on. </summary>
		OnLogon = 7
	}

	[Flags]
	internal enum TaskTriggerFlags : uint
	{
		HasEndDate = 0x1,
		KillAtDurationEnd = 0x2,
		Disabled = 0x4
	}

	#endregion

	#region Structs

	[StructLayout(LayoutKind.Sequential)]
	internal struct Daily
	{
		public ushort DaysInterval;
	}

	[StructLayout(LayoutKind.Sequential)]
	internal struct Weekly
	{
		public ushort WeeksInterval;
		public DaysOfTheWeek DaysOfTheWeek;
	}

	[StructLayout(LayoutKind.Sequential)]
	internal struct MonthlyDate
	{
		public uint Days;
		public MonthsOfTheYear Months;
	}

	[StructLayout(LayoutKind.Sequential)]
	internal struct MonthlyDOW
	{
		public ushort WhichWeek;
		public DaysOfTheWeek DaysOfTheWeek;
		public MonthsOfTheYear Months;

		public WhichWeek V2WhichWeek
		{
			get
			{
				return (WhichWeek)(1 << ((short)WhichWeek - 1));
			}
			set
			{
				int idx = Array.IndexOf(new short[] { 0x1, 0x2, 0x4, 0x8, 0x10 }, (short)value);
				if (idx >= 0)
					WhichWeek = (ushort)(idx + 1);
				else
					throw new NotV1SupportedException("Only a single week can be set with Task Scheduler 1.0.");
			}
		}
	}

	[StructLayout(LayoutKind.Explicit)]
	internal struct TriggerTypeData
	{
		[FieldOffset(0)]
		public Daily daily;
		[FieldOffset(0)]
		public Weekly weekly;
		[FieldOffset(0)]
		public MonthlyDate monthlyDate;
		[FieldOffset(0)]
		public MonthlyDOW monthlyDOW;
	}

	[StructLayout(LayoutKind.Sequential)]
	internal struct TaskTrigger
	{
		public ushort TriggerSize;             // Structure size.
		public ushort Reserved1;               // Reserved. Must be zero.
		public ushort BeginYear;               // Trigger beginning date year.
		public ushort BeginMonth;              // Trigger beginning date month.
		public ushort BeginDay;                // Trigger beginning date day.
		public ushort EndYear;                 // Optional trigger ending date year.
		public ushort EndMonth;                // Optional trigger ending date month.
		public ushort EndDay;                  // Optional trigger ending date day.
		public ushort StartHour;               // Run bracket start time hour.
		public ushort StartMinute;             // Run bracket start time minute.
		public uint MinutesDuration;           // Duration of run bracket.
		public uint MinutesInterval;           // Run bracket repetition interval.
		public TaskTriggerFlags Flags;         // Trigger flags.
		public TaskTriggerType Type;           // Trigger type.
		public TriggerTypeData Data;           // Trigger data peculiar to this type (union).
		public ushort Reserved2;               // Reserved. Must be zero.
		public ushort RandomMinutesInterval;   // Maximum number of random minutes after start time.

		public DateTime BeginDate
		{
			get { try { return BeginYear == 0 ? DateTime.MinValue : new DateTime(BeginYear, BeginMonth, BeginDay, StartHour, StartMinute, 0, DateTimeKind.Unspecified); } catch { return DateTime.MinValue; } }
			set
			{
				if (value != DateTime.MinValue)
				{
					DateTime local = value.Kind == DateTimeKind.Utc ? value.ToLocalTime() : value;
					BeginYear = (ushort)local.Year;
					BeginMonth = (ushort)local.Month;
					BeginDay = (ushort)local.Day;
					StartHour = (ushort)local.Hour;
					StartMinute = (ushort)local.Minute;
				}
				else
					BeginYear = BeginMonth = BeginDay = StartHour = StartMinute = 0;
			}
		}

		public DateTime? EndDate
		{
			get { try { return EndYear == 0 ? (DateTime?)null : new DateTime(EndYear, EndMonth, EndDay); } catch { return DateTime.MaxValue; } }
			set
			{
				if (value.HasValue)
				{
					EndYear = (ushort)value.Value.Year;
					EndMonth = (ushort)value.Value.Month;
					EndDay = (ushort)value.Value.Day;
					Flags |= TaskTriggerFlags.HasEndDate;
				}
				else
				{
					EndYear = EndMonth = EndDay = 0;
					Flags &= ~TaskTriggerFlags.HasEndDate;
				}
			}
		}

		public override string ToString() => $"Trigger Type: {Type};\n> Start: {BeginDate}; End: {(EndYear == 0 ? "null" : EndDate?.ToString())};\n> DurMin: {MinutesDuration}; DurItv: {MinutesInterval};\n>";
	}

	#endregion

	[ComImport, Guid("148BD527-A2AB-11CE-B11F-00AA00530503"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), System.Security.SuppressUnmanagedCodeSecurity, CoClass(typeof(CTaskScheduler))]
	internal interface ITaskScheduler
	{
		void SetTargetComputer([In, MarshalAs(UnmanagedType.LPWStr)] string Computer);
		CoTaskMemString GetTargetComputer();
		[return: MarshalAs(UnmanagedType.Interface)]
		IEnumWorkItems Enum();
		[return: MarshalAs(UnmanagedType.Interface)]
		ITask Activate([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string Name, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid);
		void Delete([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string Name);
		[return: MarshalAs(UnmanagedType.Interface)]
		ITask NewWorkItem([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string TaskName, [In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid);
		void AddWorkItem([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string TaskName, [In, MarshalAs(UnmanagedType.Interface)] ITask WorkItem);
		void IsOfType([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string TaskName, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid);
	}

	[Guid("148BD528-A2AB-11CE-B11F-00AA00530503"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), System.Security.SuppressUnmanagedCodeSecurity]
	internal interface IEnumWorkItems
	{
		[PreserveSig()]
		//int Next([In] uint celt, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 0)] out string[] rgpwszNames, [Out] out uint pceltFetched);
		int Next([In] uint RequestCount, [Out] out IntPtr Names, [Out] out uint Fetched);
		void Skip([In] uint Count);
		void Reset();
		[return: MarshalAs(UnmanagedType.Interface)]
		IEnumWorkItems Clone();
	}

#if WorkItem
	// The IScheduledWorkItem interface is actually never used because ITask inherits all of its
	// methods.  As ITask is the only kind of WorkItem (in 2002) it is the only interface we need.
	[Guid("a6b952f0-a4b1-11d0-997d-00aa006887ec"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
	internal interface IScheduledWorkItem
	{
		void CreateTrigger([Out] out ushort NewTriggerIndex, [Out, MarshalAs(UnmanagedType.Interface)] out ITaskTrigger Trigger);
		void DeleteTrigger([In] ushort TriggerIndex);
		void GetTriggerCount([Out] out ushort Count);
		void GetTrigger([In] ushort TriggerIndex, [Out, MarshalAs(UnmanagedType.Interface)] out ITaskTrigger Trigger);
		void GetTriggerString([In] ushort TriggerIndex, out System.IntPtr TriggerString);
		void GetRunTimes([In, MarshalAs(UnmanagedType.Struct)] SystemTime Begin, [In, MarshalAs(UnmanagedType.Struct)] SystemTime End, ref ushort Count, [Out] out System.IntPtr TaskTimes);
		void GetNextRunTime([In, Out, MarshalAs(UnmanagedType.Struct)] ref SystemTime NextRun);
		void SetIdleWait([In] ushort IdleMinutes, [In] ushort DeadlineMinutes);
		void GetIdleWait([Out] out ushort IdleMinutes, [Out] out ushort DeadlineMinutes);
		void Run();
		void Terminate();
		void EditWorkItem([In] uint hParent, [In] uint dwReserved);
		void GetMostRecentRunTime([In, Out, MarshalAs(UnmanagedType.Struct)] ref SystemTime LastRun);
		void GetStatus([Out, MarshalAs(UnmanagedType.Error)] out int Status);
		void GetExitCode([Out] out uint ExitCode);
		void SetComment([In, MarshalAs(UnmanagedType.LPWStr)] string Comment);
		void GetComment(out System.IntPtr Comment);
		void SetCreator([In, MarshalAs(UnmanagedType.LPWStr)] string Creator);
		void GetCreator(out System.IntPtr Creator);
		void SetWorkItemData([In] ushort DataLen, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0, ArraySubType=UnmanagedType.U1)] byte[] Data);
		void GetWorkItemData([Out] out ushort DataLen, [Out] out System.IntPtr Data);
		void SetErrorRetryCount([In] ushort RetryCount);
		void GetErrorRetryCount([Out] out ushort RetryCount);
		void SetErrorRetryInterval([In] ushort RetryInterval);
		void GetErrorRetryInterval([Out] out ushort RetryInterval);
		void SetFlags([In] uint Flags);
		void GetFlags([Out] out uint Flags);
		void SetAccountInformation([In, MarshalAs(UnmanagedType.LPWStr)] string AccountName, [In, MarshalAs(UnmanagedType.LPWStr)] string Password);
		void GetAccountInformation(out System.IntPtr AccountName);
	}
#endif

	[ComImport, Guid("148BD524-A2AB-11CE-B11F-00AA00530503"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), System.Security.SuppressUnmanagedCodeSecurity, CoClass(typeof(CTask))]
	internal interface ITask
	{
		[return: MarshalAs(UnmanagedType.Interface)]
		ITaskTrigger CreateTrigger([Out] out ushort NewTriggerIndex);
		void DeleteTrigger([In] ushort TriggerIndex);
		[return: MarshalAs(UnmanagedType.U2)]
		ushort GetTriggerCount();
		[return: MarshalAs(UnmanagedType.Interface)]
		ITaskTrigger GetTrigger([In] ushort TriggerIndex);
		CoTaskMemString GetTriggerString([In] ushort TriggerIndex);
		void GetRunTimes([In, MarshalAs(UnmanagedType.Struct)] ref NativeMethods.SYSTEMTIME Begin, [In, MarshalAs(UnmanagedType.Struct)] ref NativeMethods.SYSTEMTIME End, ref ushort Count, [In, Out] ref IntPtr TaskTimes);
		[return: MarshalAs(UnmanagedType.Struct)]
		NativeMethods.SYSTEMTIME GetNextRunTime();
		void SetIdleWait([In] ushort IdleMinutes, [In] ushort DeadlineMinutes);
		void GetIdleWait([Out] out ushort IdleMinutes, [Out] out ushort DeadlineMinutes);
		void Run();
		void Terminate();
		void EditWorkItem([In] IntPtr hParent, [In] uint dwReserved);
		[return: MarshalAs(UnmanagedType.Struct)]
		NativeMethods.SYSTEMTIME GetMostRecentRunTime();
		TaskStatus GetStatus();
		uint GetExitCode();
		void SetComment([In, MarshalAs(UnmanagedType.LPWStr)] string Comment);
		CoTaskMemString GetComment();
		void SetCreator([In, MarshalAs(UnmanagedType.LPWStr)] string Creator);
		CoTaskMemString GetCreator();
		void SetWorkItemData([In] ushort DataLen, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0, ArraySubType = UnmanagedType.U1)] byte[] Data);
		void GetWorkItemData(out ushort DataLen, [Out] out IntPtr Data);
		void SetErrorRetryCount([In] ushort RetryCount);
		ushort GetErrorRetryCount();
		void SetErrorRetryInterval([In] ushort RetryInterval);
		ushort GetErrorRetryInterval();
		void SetFlags([In] TaskFlags Flags);
		TaskFlags GetFlags();
		void SetAccountInformation([In, MarshalAs(UnmanagedType.LPWStr)] string AccountName, [In] IntPtr Password);
		CoTaskMemString GetAccountInformation();
		void SetApplicationName([In, MarshalAs(UnmanagedType.LPWStr)] string ApplicationName);
		CoTaskMemString GetApplicationName();
		void SetParameters([In, MarshalAs(UnmanagedType.LPWStr)] string Parameters);
		CoTaskMemString GetParameters();
		void SetWorkingDirectory([In, MarshalAs(UnmanagedType.LPWStr)] string WorkingDirectory);
		CoTaskMemString GetWorkingDirectory();
		void SetPriority([In] uint Priority);
		uint GetPriority();
		void SetTaskFlags([In] uint Flags);
		uint GetTaskFlags();
		void SetMaxRunTime([In] uint MaxRunTimeMS);
		uint GetMaxRunTime();
	}

	[Guid("148BD52B-A2AB-11CE-B11F-00AA00530503"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), System.Security.SuppressUnmanagedCodeSecurity]
	internal interface ITaskTrigger
	{
		void SetTrigger([In, Out, MarshalAs(UnmanagedType.Struct)] ref TaskTrigger Trigger);
		[return: MarshalAs(UnmanagedType.Struct)]
		TaskTrigger GetTrigger();
		CoTaskMemString GetTriggerString();
	}

	[ComImport, Guid("148BD52A-A2AB-11CE-B11F-00AA00530503"), System.Security.SuppressUnmanagedCodeSecurity, ClassInterface(ClassInterfaceType.None)]
	internal class CTaskScheduler
	{
	}

	[ComImport, Guid("148BD520-A2AB-11CE-B11F-00AA00530503"), System.Security.SuppressUnmanagedCodeSecurity, ClassInterface(ClassInterfaceType.None)]
	internal class CTask
	{
	}

	internal sealed class CoTaskMemString : SafeHandle
	{
		public CoTaskMemString() : base(IntPtr.Zero, true) { }
		public CoTaskMemString(IntPtr handle) : this() { SetHandle(handle); }
		public CoTaskMemString(string text) : this() { SetHandle(Marshal.StringToCoTaskMemUni(text)); }

		public static implicit operator string(CoTaskMemString cmem) => cmem.ToString();

		public override bool IsInvalid => handle == IntPtr.Zero;

		protected override bool ReleaseHandle()
		{
			Marshal.FreeCoTaskMem(handle);
			return true;
		}

		public override string ToString() => Marshal.PtrToStringUni(handle);
	}
}
