/*
 * PROJECT:         ReactOS Kernel
 * LICENSE:         GPL - See COPYING in the top level directory
 * FILE:            drivers/battery/battc/battc.c
 * PURPOSE:         Battery Class Driver
 * PROGRAMMERS:     Cameron Gutman (cameron.gutman@reactos.org)
 */

#include <battc.h>

#define NDEBUG
#include <debug.h>

NTSTATUS
NTAPI
DriverEntry(PDRIVER_OBJECT DriverObject,
            PUNICODE_STRING RegistryPath)
{
  DPRINT("Battery class driver initialized\n");

  return STATUS_SUCCESS;
}

BCLASSAPI
NTSTATUS
NTAPI
BatteryClassUnload(PVOID ClassData)
{
  PBATTERY_CLASS_DATA BattClass = ClassData;

  DPRINT("Battery 0x%x is being unloaded\n");

  if (BattClass->InterfaceName.Length != 0)
  {
      IoSetDeviceInterfaceState(&BattClass->InterfaceName, FALSE);
      RtlFreeUnicodeString(&BattClass->InterfaceName);
  }

  ExFreePoolWithTag(BattClass,
                    BATTERY_CLASS_DATA_TAG);

  return STATUS_SUCCESS;
}

BCLASSAPI
NTSTATUS
NTAPI
BatteryClassSystemControl(PVOID ClassData,
                          PVOID WmiLibContext,
                          PDEVICE_OBJECT DeviceObject,
                          PIRP Irp,
                          PVOID Disposition)
{
  UNIMPLEMENTED

  return STATUS_WMI_GUID_NOT_FOUND;
}

BCLASSAPI
NTSTATUS
NTAPI
BatteryClassQueryWmiDataBlock(PVOID ClassData,
                              PDEVICE_OBJECT DeviceObject,
                              PIRP Irp,
                              ULONG GuidIndex,
                              PULONG InstanceLengthArray,
                              ULONG OutBufferSize,
                              PUCHAR Buffer)
{
  UNIMPLEMENTED

  return STATUS_WMI_GUID_NOT_FOUND;
}

BCLASSAPI
NTSTATUS
NTAPI
BatteryClassStatusNotify(PVOID ClassData)
{
  PBATTERY_CLASS_DATA BattClass = ClassData;
  PBATTERY_WAIT_STATUS BattWait = BattClass->EventTriggerContext;
  BATTERY_STATUS BattStatus;
  NTSTATUS Status;

  DPRINT("Received battery status notification from 0x%x\n", ClassData);

  ExAcquireFastMutex(&BattClass->Mutex);
  if (!BattClass->Waiting)
  {
      ExReleaseFastMutex(&BattClass->Mutex);
      return STATUS_SUCCESS;
  }

  switch (BattClass->EventTrigger)
  {
     case EVENT_BATTERY_TAG:
       ExReleaseFastMutex(&BattClass->Mutex);
       DPRINT1("Waiting for battery is UNIMPLEMENTED!\n");
       break;

     case EVENT_BATTERY_STATUS:
       ExReleaseFastMutex(&BattClass->Mutex);
       Status = BattClass->MiniportInfo.QueryStatus(BattClass->MiniportInfo.Context,
                                                    BattWait->BatteryTag,
                                                    &BattStatus);
       if (!NT_SUCCESS(Status))
           return Status;

       ExAcquireFastMutex(&BattClass->Mutex);

       if (!(BattWait->PowerState & BattStatus.PowerState) ||
           (BattWait->HighCapacity > BattStatus.Capacity) ||
           (BattWait->LowCapacity < BattStatus.Capacity))
       {
           KeSetEvent(&BattClass->WaitEvent, IO_NO_INCREMENT, FALSE);
       }

       ExReleaseFastMutex(&BattClass->Mutex);
       break;

     default:
       ExReleaseFastMutex(&BattClass->Mutex);
       ASSERT(FALSE);
       break;
  }

  return STATUS_SUCCESS;
}

BCLASSAPI
NTSTATUS
NTAPI
BatteryClassInitializeDevice(PBATTERY_MINIPORT_INFO MiniportInfo,
                             PVOID *ClassData)
{
  NTSTATUS Status;
  PBATTERY_CLASS_DATA BattClass = ExAllocatePoolWithTag(NonPagedPool,
                                                        sizeof(BATTERY_CLASS_DATA),
                                                        BATTERY_CLASS_DATA_TAG);

  if (!BattClass)
      return STATUS_INSUFFICIENT_RESOURCES;

  RtlZeroMemory(BattClass, sizeof(BATTERY_CLASS_DATA));

  RtlCopyMemory(&BattClass->MiniportInfo,
                MiniportInfo,
                sizeof(BattClass->MiniportInfo));

  KeInitializeEvent(&BattClass->WaitEvent, SynchronizationEvent, FALSE);

  ExInitializeFastMutex(&BattClass->Mutex);

  if (MiniportInfo->Pdo != NULL)
  {
    Status = IoRegisterDeviceInterface(MiniportInfo->Pdo,
                                       &GUID_DEVICE_BATTERY,
                                       NULL,
                                       &BattClass->InterfaceName);
    if (NT_SUCCESS(Status))
    {
        DPRINT("Initialized battery interface: %wZ\n", &BattClass->InterfaceName);
        Status = IoSetDeviceInterfaceState(&BattClass->InterfaceName, TRUE);
        if (Status == STATUS_OBJECT_NAME_EXISTS)
        {
            DPRINT1("Got STATUS_OBJECT_NAME_EXISTS for SetDeviceInterfaceState\n");
            Status = STATUS_SUCCESS;
        }
    }
    else
    {
        DPRINT1("IoRegisterDeviceInterface failed (0x%x)\n", Status);
    }
  }

  *ClassData = BattClass;

  return STATUS_SUCCESS;
}

BCLASSAPI
NTSTATUS
NTAPI
BatteryClassIoctl(PVOID ClassData,
                  PIRP Irp)
{
  PBATTERY_CLASS_DATA BattClass = ClassData;
  PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
  NTSTATUS Status;
  ULONG WaitTime;
  PBATTERY_WAIT_STATUS BattWait;
  PBATTERY_QUERY_INFORMATION BattQueryInfo;
  PBATTERY_SET_INFORMATION BattSetInfo;
  LARGE_INTEGER Timeout;
  PBATTERY_STATUS BattStatus;
  BATTERY_NOTIFY BattNotify;
  ULONG ReturnedLength;

  Irp->IoStatus.Information = 0;

  DPRINT("Received IOCTL %x for 0x%x\n", IrpSp->Parameters.DeviceIoControl.IoControlCode,
         ClassData);

  switch (IrpSp->Parameters.DeviceIoControl.IoControlCode)
  {
    case IOCTL_BATTERY_QUERY_TAG:
      if (IrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(ULONG) ||
          IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(ULONG))
      {
          Status = STATUS_BUFFER_TOO_SMALL;
          break;
      }

      WaitTime = *(PULONG)Irp->AssociatedIrp.SystemBuffer;

      Timeout.QuadPart = Int32x32To64(WaitTime, -1000);

      Status = BattClass->MiniportInfo.QueryTag(BattClass->MiniportInfo.Context,
                                                (PULONG)Irp->AssociatedIrp.SystemBuffer);
      if (!NT_SUCCESS(Status))
      {
          ExAcquireFastMutex(&BattClass->Mutex);
          BattClass->EventTrigger = EVENT_BATTERY_TAG;
          BattClass->Waiting = TRUE;
          ExReleaseFastMutex(&BattClass->Mutex);

          Status = KeWaitForSingleObject(&BattClass->WaitEvent,
                                         Executive,
                                         KernelMode,
                                         FALSE,
                                         WaitTime != -1 ? &Timeout : NULL);

          ExAcquireFastMutex(&BattClass->Mutex);
          BattClass->Waiting = FALSE;
          ExReleaseFastMutex(&BattClass->Mutex);

          if (Status == STATUS_SUCCESS)
          {
              Status = BattClass->MiniportInfo.QueryTag(BattClass->MiniportInfo.Context,
                                                        (PULONG)Irp->AssociatedIrp.SystemBuffer);
              if (NT_SUCCESS(Status))
                  Irp->IoStatus.Information = sizeof(ULONG);
          }
          else
          {
              Status = STATUS_NO_SUCH_DEVICE;
          }
      }
      else
          Irp->IoStatus.Information = sizeof(ULONG);
      break;

    case IOCTL_BATTERY_QUERY_STATUS:
      if (IrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(*BattWait) ||
          IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(BATTERY_STATUS))
      {
          Status = STATUS_BUFFER_TOO_SMALL;
          break;
      }

      BattWait = Irp->AssociatedIrp.SystemBuffer;

      Timeout.QuadPart = Int32x32To64(BattWait->Timeout, -1000);

      Status = BattClass->MiniportInfo.QueryStatus(BattClass->MiniportInfo.Context,
                                                   BattWait->BatteryTag,
                                                   (PBATTERY_STATUS)Irp->AssociatedIrp.SystemBuffer);

      BattStatus = Irp->AssociatedIrp.SystemBuffer;

      if (!NT_SUCCESS(Status) ||
          ((BattWait->PowerState & BattStatus->PowerState) &&
           (BattWait->HighCapacity <= BattStatus->Capacity) &&
           (BattWait->LowCapacity >= BattStatus->Capacity)))
      {
          BattNotify.PowerState = BattWait->PowerState;
          BattNotify.HighCapacity = BattWait->HighCapacity;
          BattNotify.LowCapacity = BattWait->LowCapacity;

          BattClass->MiniportInfo.SetStatusNotify(BattClass->MiniportInfo.Context,
                                                  BattWait->BatteryTag,
                                                  &BattNotify);

          ExAcquireFastMutex(&BattClass->Mutex);
          BattClass->EventTrigger = EVENT_BATTERY_STATUS;
          BattClass->EventTriggerContext = BattWait;
          BattClass->Waiting = TRUE;
          ExReleaseFastMutex(&BattClass->Mutex);

          Status = KeWaitForSingleObject(&BattClass->WaitEvent,
                                         Executive,
                                         KernelMode,
                                         FALSE,
                                         BattWait->Timeout != -1 ? &Timeout : NULL);

          ExAcquireFastMutex(&BattClass->Mutex);
          BattClass->Waiting = FALSE;
          ExReleaseFastMutex(&BattClass->Mutex);

          BattClass->MiniportInfo.DisableStatusNotify(BattClass->MiniportInfo.Context);

          if (Status == STATUS_SUCCESS)
          {
              Status = BattClass->MiniportInfo.QueryStatus(BattClass->MiniportInfo.Context,
                                                           BattWait->BatteryTag,
                                                           (PBATTERY_STATUS)Irp->AssociatedIrp.SystemBuffer);
              if (NT_SUCCESS(Status))
                  Irp->IoStatus.Information = sizeof(ULONG);
          }
          else
          {
              Status = STATUS_NO_SUCH_DEVICE;
          }
      }
      else
          Irp->IoStatus.Information = sizeof(BATTERY_STATUS);
      break;

    case IOCTL_BATTERY_QUERY_INFORMATION:
      if (IrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(*BattQueryInfo))
      {
          Status = STATUS_BUFFER_TOO_SMALL;
          break;
      }

      BattQueryInfo = Irp->AssociatedIrp.SystemBuffer;

      Status = BattClass->MiniportInfo.QueryInformation(BattClass->MiniportInfo.Context,
                                                        BattQueryInfo->BatteryTag,
                                                        BattQueryInfo->InformationLevel,
                                                        BattQueryInfo->AtRate,
                                                        Irp->AssociatedIrp.SystemBuffer,
                                                        IrpSp->Parameters.DeviceIoControl.OutputBufferLength,
                                                        &ReturnedLength);
      Irp->IoStatus.Information = ReturnedLength;
      if (!NT_SUCCESS(Status))
          DPRINT1("QueryInformation failed (0x%x)\n", Status);
      break;
    case IOCTL_BATTERY_SET_INFORMATION:
      if (IrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(*BattSetInfo))
      {
          Status = STATUS_BUFFER_TOO_SMALL;
          break;
      }

      BattSetInfo = Irp->AssociatedIrp.SystemBuffer;

      Status = BattClass->MiniportInfo.SetInformation(BattClass->MiniportInfo.Context,
                                                      BattSetInfo->BatteryTag,
                                                      BattSetInfo->InformationLevel,
                                                      BattSetInfo->Buffer);
      if (!NT_SUCCESS(Status))
          DPRINT1("SetInformation failed (0x%x)\n", Status);
      break;

    default:
      DPRINT1("Received unsupported IRP %x\n", IrpSp->Parameters.DeviceIoControl.IoControlCode);
      /* Do NOT complete the irp */
      return STATUS_NOT_SUPPORTED;
  }

  Irp->IoStatus.Status = Status;
  IoCompleteRequest(Irp, IO_NO_INCREMENT);

  return Status;
}
