/*
 * PROJECT:         ReactOS Kernel
 * LICENSE:         GPL - See COPYING in the top level directory
 * FILE:            ntoskrnl/po/events.c
 * PURPOSE:         Power Manager
 * PROGRAMMERS:     Hervé Poussineau (hpoussin@reactos.org)
 */

/* INCLUDES ******************************************************************/

#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>

/* GLOBALS *******************************************************************/

typedef struct _SYS_BUTTON_CONTEXT
{
	PDEVICE_OBJECT DeviceObject;
	PIO_WORKITEM WorkItem;
	KEVENT Event;
	IO_STATUS_BLOCK IoStatusBlock;
	ULONG SysButton;
} SYS_BUTTON_CONTEXT, *PSYS_BUTTON_CONTEXT;

static VOID
NTAPI
PopGetSysButton(
	IN PDEVICE_OBJECT DeviceObject,
	IN PVOID Context);

PKWIN32_POWEREVENT_CALLOUT PopEventCallout;
extern PCALLBACK_OBJECT SetSystemTimeCallback;

/* FUNCTIONS *****************************************************************/

VOID
NTAPI
PoNotifySystemTimeSet(VOID)
{
    KIRQL OldIrql;

    /* Check if Win32k registered a notification callback */
    if (PopEventCallout)
    {
        /* Raise to dispatch */
        KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);

        /* Notify the callback */
        ExNotifyCallback(SetSystemTimeCallback, NULL, NULL);

        /* Lower IRQL back */
        KeLowerIrql(OldIrql);
    }
}

static NTSTATUS
NTAPI
PopGetSysButtonCompletion(
	IN PDEVICE_OBJECT DeviceObject,
	IN PIRP Irp,
	IN PVOID Context)
{
	PSYS_BUTTON_CONTEXT SysButtonContext = Context;
	ULONG SysButton;

	/* The DeviceObject can be NULL, so use the one we stored */
	DeviceObject = SysButtonContext->DeviceObject;

	/* FIXME: What do do with the sys button event? */
	SysButton = *(PULONG)Irp->AssociatedIrp.SystemBuffer;
	{
		DPRINT1("A device reported the event 0x%x (", SysButton);
		if (SysButton & SYS_BUTTON_POWER) DbgPrint(" POWER");
		if (SysButton & SYS_BUTTON_SLEEP) DbgPrint(" SLEEP");
		if (SysButton & SYS_BUTTON_LID) DbgPrint(" LID");
		if (SysButton == 0) DbgPrint(" WAKE");
		DbgPrint(" )\n");
        
        if (SysButton & SYS_BUTTON_POWER)
        {
            /* FIXME: Read registry for the action we should perform here */
            DPRINT1("Initiating shutdown after power button event\n");
            
            ZwShutdownSystem(ShutdownNoReboot);
        }
	}

	/* Allocate a new workitem to send the next IOCTL_GET_SYS_BUTTON_EVENT */
	SysButtonContext->WorkItem = IoAllocateWorkItem(DeviceObject);
	if (!SysButtonContext->WorkItem)
	{
		DPRINT("IoAllocateWorkItem() failed\n");
		ExFreePool(SysButtonContext);
		return STATUS_SUCCESS;
	}
	IoQueueWorkItem(
		SysButtonContext->WorkItem,
		PopGetSysButton,
		DelayedWorkQueue,
		SysButtonContext);

	return STATUS_SUCCESS /* STATUS_CONTINUE_COMPLETION */;
}

static VOID
NTAPI
PopGetSysButton(
	IN PDEVICE_OBJECT DeviceObject,
	IN PVOID Context)
{
	PSYS_BUTTON_CONTEXT SysButtonContext = Context;
	PIO_WORKITEM CurrentWorkItem = SysButtonContext->WorkItem;
	PIRP Irp;

	/* Get button pressed (IOCTL_GET_SYS_BUTTON_EVENT) */
	KeInitializeEvent(&SysButtonContext->Event, NotificationEvent, FALSE);
	Irp = IoBuildDeviceIoControlRequest(
		IOCTL_GET_SYS_BUTTON_EVENT,
		DeviceObject,
		NULL,
		0,
		&SysButtonContext->SysButton,
		sizeof(SysButtonContext->SysButton),
		FALSE,
		&SysButtonContext->Event,
		&SysButtonContext->IoStatusBlock);
	if (Irp)
	{
		IoSetCompletionRoutine(
			Irp,
			PopGetSysButtonCompletion,
			SysButtonContext,
			TRUE,
			FALSE,
			FALSE);
		IoCallDriver(DeviceObject, Irp);
	}
	else
	{
		DPRINT1("IoBuildDeviceIoControlRequest() failed\n");
		ExFreePool(SysButtonContext);
	}

	IoFreeWorkItem(CurrentWorkItem);
}

NTSTATUS
NTAPI
PopAddRemoveSysCapsCallback(IN PVOID NotificationStructure,
                            IN PVOID Context)
{
	PDEVICE_INTERFACE_CHANGE_NOTIFICATION Notification;
	PSYS_BUTTON_CONTEXT SysButtonContext;
	OBJECT_ATTRIBUTES ObjectAttributes;
	HANDLE FileHandle;
	PDEVICE_OBJECT DeviceObject;
	PFILE_OBJECT FileObject;
	PIRP Irp;
	IO_STATUS_BLOCK IoStatusBlock;
	KEVENT Event;
	BOOLEAN Arrival;
	ULONG Caps;
	NTSTATUS Status;
    UNICODE_STRING DeviceName;
	UNICODE_STRING DeviceNamePrefix = RTL_CONSTANT_STRING(L"\\??\\");

	DPRINT("PopAddRemoveSysCapsCallback(%p %p)\n",
		NotificationStructure, Context);

	Notification = (PDEVICE_INTERFACE_CHANGE_NOTIFICATION)NotificationStructure;
	if (Notification->Version != 1)
		return STATUS_REVISION_MISMATCH;
	if (Notification->Size != sizeof(DEVICE_INTERFACE_CHANGE_NOTIFICATION))
		return STATUS_INVALID_PARAMETER;
	if (RtlCompareMemory(&Notification->Event, &GUID_DEVICE_INTERFACE_ARRIVAL, sizeof(GUID)) == sizeof(GUID))
		Arrival = TRUE;
	else if (RtlCompareMemory(&Notification->Event, &GUID_DEVICE_INTERFACE_REMOVAL, sizeof(GUID)) == sizeof(GUID))
		Arrival = FALSE;
	else
		return STATUS_INVALID_PARAMETER;

	if (Arrival)
	{
		DPRINT("Arrival of %wZ\n", Notification->SymbolicLinkName);

        DeviceName.Length = 0;
		DeviceName.MaximumLength = Notification->SymbolicLinkName->MaximumLength + DeviceNamePrefix.MaximumLength;
		DeviceName.Buffer = ExAllocatePool(PagedPool, DeviceName.MaximumLength);
		if (!DeviceName.Buffer) return STATUS_INSUFFICIENT_RESOURCES;

		RtlCopyUnicodeString(&DeviceName, &DeviceNamePrefix);
		RtlAppendUnicodeStringToString(&DeviceName, Notification->SymbolicLinkName);

		DPRINT("Opening handle to %wZ\n", &DeviceName);

		/* Open the device */
		InitializeObjectAttributes(
			&ObjectAttributes,
			&DeviceName,
			OBJ_KERNEL_HANDLE,
			NULL,
			NULL);
		Status = ZwOpenFile(
			&FileHandle,
			FILE_READ_DATA,
			&ObjectAttributes,
			&IoStatusBlock,
			FILE_SHARE_READ | FILE_SHARE_WRITE,
			0);
        RtlFreeUnicodeString(&DeviceName);
		if (!NT_SUCCESS(Status))
		{
			DPRINT1("ZwOpenFile() failed with status 0x%08lx\n", Status);
			return Status;
		}
		Status = ObReferenceObjectByHandle(
			FileHandle,
			FILE_READ_DATA,
			IoFileObjectType,
			KernelMode,
			(PVOID*)&FileObject,
			NULL);
		if (!NT_SUCCESS(Status))
		{
			DPRINT1("ObReferenceObjectByHandle() failed with status 0x%08lx\n", Status);
			ZwClose(FileHandle);
			return Status;
		}
		DeviceObject = IoGetRelatedDeviceObject(FileObject);
		ObDereferenceObject(FileObject);

		/* Get capabilities (IOCTL_GET_SYS_BUTTON_CAPS) */
		KeInitializeEvent(&Event, NotificationEvent, FALSE);
		Irp = IoBuildDeviceIoControlRequest(
			IOCTL_GET_SYS_BUTTON_CAPS,
			DeviceObject,
			NULL,
			0,
			&Caps,
			sizeof(Caps),
			FALSE,
			&Event,
			&IoStatusBlock);
		if (!Irp)
		{
			DPRINT1("IoBuildDeviceIoControlRequest() failed\n");
			ZwClose(FileHandle);
			return STATUS_INSUFFICIENT_RESOURCES;
		}
		Status = IoCallDriver(DeviceObject, Irp);
		if (Status == STATUS_PENDING)
		{
			DPRINT("IOCTL_GET_SYS_BUTTON_CAPS pending\n");
			KeWaitForSingleObject(&Event, Suspended, KernelMode, FALSE, NULL);
			Status = IoStatusBlock.Status;
		}
		if (!NT_SUCCESS(Status))
		{
			DPRINT1("Sending IOCTL_GET_SYS_BUTTON_CAPS failed with status 0x%08x\n", Status);
			ZwClose(FileHandle);
			return STATUS_INSUFFICIENT_RESOURCES;
		}

		/* FIXME: What do do with the capabilities? */
		{
			DPRINT("Device capabilities: 0x%x (", Caps);
			if (Caps & SYS_BUTTON_POWER) DPRINT(" POWER");
			if (Caps & SYS_BUTTON_SLEEP) DPRINT(" SLEEP");
			if (Caps & SYS_BUTTON_LID) DPRINT(" LID");
			DPRINT(" )\n");
		}

		SysButtonContext = ExAllocatePool(NonPagedPool, sizeof(SYS_BUTTON_CONTEXT));
		if (!SysButtonContext)
		{
			DPRINT1("ExAllocatePool() failed\n");
			ZwClose(FileHandle);
			return STATUS_INSUFFICIENT_RESOURCES;
		}

		/* Queue a work item to get sys button event */
		SysButtonContext->WorkItem = IoAllocateWorkItem(DeviceObject);
		SysButtonContext->DeviceObject = DeviceObject;
		if (!SysButtonContext->WorkItem)
		{
			DPRINT1("IoAllocateWorkItem() failed\n");
			ZwClose(FileHandle);
			ExFreePool(SysButtonContext);
			return STATUS_INSUFFICIENT_RESOURCES;
		}
		IoQueueWorkItem(
			SysButtonContext->WorkItem,
			PopGetSysButton,
			DelayedWorkQueue,
			SysButtonContext);

		ZwClose(FileHandle);
		return STATUS_SUCCESS;
	}
	else
	{
		DPRINT1("Removal of a power capable device not implemented\n");
		return STATUS_NOT_IMPLEMENTED;
	}
}
