/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS Kernel Streaming
 * FILE:            drivers/wdm/audio/backpln/portcls/registry.cpp
 * PURPOSE:         Registry access object
 * PROGRAMMER:      Johannes Anderwald
 */

#include "private.hpp"

#ifndef YDEBUG
#define NDEBUG
#endif

#include <debug.h>

class CRegistryKey : public IRegistryKey
{
public:
    STDMETHODIMP QueryInterface( REFIID InterfaceId, PVOID* Interface);

    STDMETHODIMP_(ULONG) AddRef()
    {
        InterlockedIncrement(&m_Ref);
        return m_Ref;
    }
    STDMETHODIMP_(ULONG) Release()
    {
        InterlockedDecrement(&m_Ref);

        if (!m_Ref)
        {
            delete this;
            return 0;
        }
        return m_Ref;
    }

    IMP_IRegistryKey;
    CRegistryKey(IUnknown * OuterUnknown, HANDLE hKey, BOOL CanDelete) : m_hKey(hKey), m_Deleted(FALSE), m_CanDelete(CanDelete), m_Ref(0){}
    virtual ~CRegistryKey();

protected:

    HANDLE m_hKey;
    BOOL m_Deleted;
    BOOL m_CanDelete;
    LONG m_Ref;
};

CRegistryKey::~CRegistryKey()
{
    if (!m_Deleted)
    {
         // close key only when has not been deleted yet
         ZwClose(m_hKey);
    }
}


NTSTATUS
NTAPI
CRegistryKey::QueryInterface(
    IN  REFIID refiid,
    OUT PVOID* Output)
{
    UNICODE_STRING GuidString;

    DPRINT("CRegistryKey::QueryInterface entered\n");
    if (IsEqualGUIDAligned(refiid, IID_IRegistryKey) ||
        IsEqualGUIDAligned(refiid, IID_IUnknown))
    {
        *Output = PVOID(PREGISTRYKEY(this));
        PUNKNOWN(*Output)->AddRef();
        return STATUS_SUCCESS;
    }

    if (RtlStringFromGUID(refiid, &GuidString) == STATUS_SUCCESS)
    {
        DPRINT1("CRegistryKey::QueryInterface no interface!!! iface %S\n", GuidString.Buffer);
        RtlFreeUnicodeString(&GuidString);
    }

    return STATUS_UNSUCCESSFUL;
}

NTSTATUS
NTAPI
CRegistryKey::DeleteKey()
{
    NTSTATUS Status;
    PC_ASSERT_IRQL_EQUAL(PASSIVE_LEVEL);

    if (m_Deleted)
    {
        // key already deleted
        return STATUS_INVALID_HANDLE;
    }

    if (!m_CanDelete)
    {
        // only general keys can be deleted
        return STATUS_ACCESS_DENIED;
    }

    // delete key
    Status = ZwDeleteKey(m_hKey);
    if (NT_SUCCESS(Status))
    {
        m_Deleted = TRUE;
        m_hKey = NULL;
    }
    return Status;
}

NTSTATUS
NTAPI
CRegistryKey::EnumerateKey(
    IN ULONG  Index,
    IN KEY_INFORMATION_CLASS  KeyInformationClass,
    OUT PVOID  KeyInformation,
    IN ULONG  Length,
    OUT PULONG  ResultLength)
{
    PC_ASSERT_IRQL_EQUAL(PASSIVE_LEVEL);

    if (m_Deleted)
    {
        return STATUS_INVALID_HANDLE;
    }

    return ZwEnumerateKey(m_hKey, Index, KeyInformationClass, KeyInformation, Length, ResultLength);
}

NTSTATUS
NTAPI
CRegistryKey::EnumerateValueKey(
    IN ULONG  Index,
    IN KEY_VALUE_INFORMATION_CLASS  KeyValueInformationClass,
    OUT PVOID  KeyValueInformation,
    IN ULONG  Length,
    OUT PULONG  ResultLength)
{
    PC_ASSERT_IRQL_EQUAL(PASSIVE_LEVEL);

    if (m_Deleted)
    {
        return STATUS_INVALID_HANDLE;
    }

    return ZwEnumerateValueKey(m_hKey, Index, KeyValueInformationClass, KeyValueInformation, Length, ResultLength);
}

NTSTATUS
NTAPI
CRegistryKey::NewSubKey(
    OUT PREGISTRYKEY  *RegistrySubKey,
    IN PUNKNOWN  OuterUnknown,
    IN ACCESS_MASK  DesiredAccess,
    IN PUNICODE_STRING  SubKeyName,
    IN ULONG  CreateOptions,
    OUT PULONG  Disposition  OPTIONAL)
{
    OBJECT_ATTRIBUTES Attributes;
    NTSTATUS Status;
    HANDLE hKey;
    CRegistryKey * RegistryKey;

    PC_ASSERT_IRQL_EQUAL(PASSIVE_LEVEL);

    DPRINT("CRegistryKey::NewSubKey entered %S\n", SubKeyName->Buffer);

    if (m_Deleted)
    {
        return STATUS_INVALID_HANDLE;
    }

    InitializeObjectAttributes(&Attributes, SubKeyName, OBJ_INHERIT | OBJ_CASE_INSENSITIVE | OBJ_OPENIF | OBJ_KERNEL_HANDLE, m_hKey, NULL);
    Status = ZwCreateKey(&hKey, DesiredAccess, &Attributes, 0, NULL, CreateOptions, Disposition);
    if (!NT_SUCCESS(Status))
    {
        DPRINT("CRegistryKey::NewSubKey failed with %x\n", Status);
        return Status;
    }

    RegistryKey = new(NonPagedPool, TAG_PORTCLASS)CRegistryKey(OuterUnknown, hKey, TRUE);
    if (!RegistryKey)
        return STATUS_INSUFFICIENT_RESOURCES;

    Status = RegistryKey->QueryInterface(IID_IRegistryKey, (PVOID*)RegistrySubKey);

    if (!NT_SUCCESS(Status))
    {
        delete RegistryKey;
        return Status;
    }

    DPRINT("CRegistryKey::NewSubKey RESULT %p\n", *RegistrySubKey);
    return STATUS_SUCCESS;
}

NTSTATUS
NTAPI
CRegistryKey::QueryKey(
    IN KEY_INFORMATION_CLASS  KeyInformationClass,
    OUT PVOID  KeyInformation,
    IN ULONG  Length,
    OUT PULONG  ResultLength)
{
    PC_ASSERT_IRQL_EQUAL(PASSIVE_LEVEL);

    if (m_Deleted)
    {
        return STATUS_INVALID_HANDLE;
    }

    return ZwQueryKey(m_hKey, KeyInformationClass, KeyInformation, Length, ResultLength);
}

NTSTATUS
NTAPI
CRegistryKey::QueryRegistryValues(
    IN PRTL_QUERY_REGISTRY_TABLE  QueryTable,
    IN PVOID  Context  OPTIONAL)
{
    PC_ASSERT_IRQL_EQUAL(PASSIVE_LEVEL);

    if (m_Deleted)
    {
        return STATUS_INVALID_HANDLE;
    }

    return RtlQueryRegistryValues(RTL_REGISTRY_HANDLE, (PCWSTR)m_hKey, QueryTable, Context, NULL);
}

NTSTATUS
NTAPI
CRegistryKey::QueryValueKey(
    IN PUNICODE_STRING  ValueName,
    IN KEY_VALUE_INFORMATION_CLASS  KeyValueInformationClass,
    OUT PVOID  KeyValueInformation,
    IN ULONG  Length,
    OUT PULONG  ResultLength)
{
    NTSTATUS Status;

    PC_ASSERT_IRQL_EQUAL(PASSIVE_LEVEL);

    if (m_Deleted)
    {
        return STATUS_INVALID_HANDLE;
    }

    Status = ZwQueryValueKey(m_hKey, ValueName, KeyValueInformationClass, KeyValueInformation, Length, ResultLength);
    DPRINT("CRegistryKey::QueryValueKey entered %p value %wZ Status %x\n", this, ValueName, Status);
    return Status;
}

NTSTATUS
NTAPI
CRegistryKey::SetValueKey(
    IN PUNICODE_STRING  ValueName  OPTIONAL,
    IN ULONG  Type,
    IN PVOID  Data,
    IN ULONG  DataSize
    )
{
    DPRINT("CRegistryKey::SetValueKey entered %S\n", ValueName->Buffer);
    PC_ASSERT_IRQL_EQUAL(PASSIVE_LEVEL);

    if (m_Deleted)
    {
        return STATUS_INVALID_HANDLE;
    }

    return ZwSetValueKey(m_hKey, ValueName, 0, Type, Data, DataSize);
}

NTSTATUS
NTAPI
PcNewRegistryKey(
    OUT PREGISTRYKEY* OutRegistryKey,
    IN  PUNKNOWN OuterUnknown OPTIONAL,
    IN  ULONG RegistryKeyType,
    IN  ACCESS_MASK DesiredAccess,
    IN  PVOID DeviceObject OPTIONAL,
    IN  PVOID SubDevice OPTIONAL,
    IN  POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN  ULONG CreateOptions OPTIONAL,
    OUT PULONG Disposition OPTIONAL)
{
    HANDLE hHandle;
    NTSTATUS Status = STATUS_UNSUCCESSFUL;
    CRegistryKey * RegistryKey;
    PPCLASS_DEVICE_EXTENSION DeviceExt;
    PSUBDEVICE_DESCRIPTOR SubDeviceDescriptor;
    ISubdevice * Device;
    PSYMBOLICLINK_ENTRY SymEntry;
    BOOL CanDelete = FALSE;

    DPRINT("PcNewRegistryKey entered\n");

    if (!OutRegistryKey)
        return STATUS_INVALID_PARAMETER;

    if (RegistryKeyType != GeneralRegistryKey &&
        RegistryKeyType != DeviceRegistryKey &&
        RegistryKeyType != DriverRegistryKey &&
        RegistryKeyType != HwProfileRegistryKey &&
        RegistryKeyType != DeviceInterfaceRegistryKey)
    {
        return STATUS_INVALID_PARAMETER;
    }

    // check for the key type
    if (RegistryKeyType == GeneralRegistryKey)
    {
        // do we have the required object attributes
        if (!ObjectAttributes)
        {
            // object attributes is mandatory
            return STATUS_INVALID_PARAMETER;
        }
        // try to create the key
        Status = ZwCreateKey(&hHandle, DesiredAccess, ObjectAttributes, 0, NULL, CreateOptions, Disposition);

        // key can be deleted
        CanDelete = TRUE;
    }
    else if (RegistryKeyType == DeviceRegistryKey ||
             RegistryKeyType == DriverRegistryKey ||
             RegistryKeyType == HwProfileRegistryKey)
    {
        // check for HwProfileRegistryKey case
        if (RegistryKeyType == HwProfileRegistryKey)
        {
             // IoOpenDeviceRegistryKey used different constant
            RegistryKeyType = PLUGPLAY_REGKEY_CURRENT_HWPROFILE | PLUGPLAY_REGKEY_DEVICE;
        }

        // obtain the new device extension
        DeviceExt = (PPCLASS_DEVICE_EXTENSION) ((PDEVICE_OBJECT)DeviceObject)->DeviceExtension;

        Status = IoOpenDeviceRegistryKey(DeviceExt->PhysicalDeviceObject, RegistryKeyType, DesiredAccess, &hHandle);
    }
    else if (RegistryKeyType == DeviceInterfaceRegistryKey)
    {
        if (SubDevice == NULL)
        {
            // invalid parameter
            return STATUS_INVALID_PARAMETER;
        }

        // look up our undocumented interface
        Status = ((PUNKNOWN)SubDevice)->QueryInterface(IID_ISubdevice, (LPVOID*)&Device);

        if (!NT_SUCCESS(Status))
        {
            DPRINT("No ISubdevice interface\n");
            // invalid parameter
            return STATUS_INVALID_PARAMETER;
        }

        // get the subdevice descriptor
        Status = Device->GetDescriptor(&SubDeviceDescriptor);
        if (!NT_SUCCESS(Status))
        {
            DPRINT("Failed to get subdevice descriptor %x\n", Status);
            ((PUNKNOWN)SubDevice)->Release();
            return STATUS_UNSUCCESSFUL;
        }

        // is there an registered device interface
        if (IsListEmpty(&SubDeviceDescriptor->SymbolicLinkList))
        {
            DPRINT("No device interface registered\n");
            ((PUNKNOWN)SubDevice)->Release();
            return STATUS_UNSUCCESSFUL;
        }

        // get the first symbolic link
        SymEntry = (PSYMBOLICLINK_ENTRY)CONTAINING_RECORD(SubDeviceDescriptor->SymbolicLinkList.Flink, SYMBOLICLINK_ENTRY, Entry);

        // open device interface
        Status = IoOpenDeviceInterfaceRegistryKey(&SymEntry->SymbolicLink, DesiredAccess, &hHandle);

        // release subdevice interface
        ((PUNKNOWN)SubDevice)->Release();
    }

    // check for success
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("PcNewRegistryKey failed with %lx\n", Status);
        return Status;
    }

    // allocate new registry key object
    RegistryKey = new(NonPagedPool, TAG_PORTCLASS)CRegistryKey(OuterUnknown, hHandle, CanDelete);
    if (!RegistryKey)
    {
        // not enough memory
        ZwClose(hHandle);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    // query for interface
    Status = RegistryKey->QueryInterface(IID_IRegistryKey, (PVOID*)OutRegistryKey);

    if (!NT_SUCCESS(Status))
    {
        // out of memory
        delete RegistryKey;
    }

    DPRINT("PcNewRegistryKey result %p\n", *OutRegistryKey);
    return STATUS_SUCCESS;
}
