/*
 * Copyright (C) 2010 Damjan Jovanovic
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdarg.h>

#define NONAMELESSUNION

#include "ntstatus.h"
#define WIN32_NO_STATUS
#include "windef.h"
#include "winbase.h"
#include "winternl.h"
#include "ddk/wdm.h"
#include "ddk/usb.h"
#include "ddk/usbdlib.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(usbd);

PURB WINAPI USBD_CreateConfigurationRequest(
        PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor, PUSHORT Siz )
{
    URB *urb = NULL;
    USBD_INTERFACE_LIST_ENTRY *interfaceList;
    ULONG interfaceListSize;
    USB_INTERFACE_DESCRIPTOR *interfaceDesc;
    int i;

    TRACE( "(%p, %p)\n", ConfigurationDescriptor, Siz );

    /* http://www.microsoft.com/whdc/archive/usbfaq.mspx
     * claims USBD_CreateConfigurationRequest doesn't support > 1 interface,
     * but is this on Windows 98 only or all versions?
     */

    *Siz = 0;
    interfaceListSize = (ConfigurationDescriptor->bNumInterfaces + 1) * sizeof(USBD_INTERFACE_LIST_ENTRY);
    interfaceList = ExAllocatePool( NonPagedPool, interfaceListSize );
    if (interfaceList)
    {
        RtlZeroMemory( interfaceList,  interfaceListSize );
        interfaceDesc = (PUSB_INTERFACE_DESCRIPTOR) USBD_ParseDescriptors(
            ConfigurationDescriptor, ConfigurationDescriptor->wTotalLength,
            ConfigurationDescriptor, USB_INTERFACE_DESCRIPTOR_TYPE );
        for (i = 0; i < ConfigurationDescriptor->bNumInterfaces && interfaceDesc != NULL; i++)
        {
            interfaceList[i].InterfaceDescriptor = interfaceDesc;
            interfaceDesc = (PUSB_INTERFACE_DESCRIPTOR) USBD_ParseDescriptors(
                ConfigurationDescriptor, ConfigurationDescriptor->wTotalLength,
                interfaceDesc + 1, USB_INTERFACE_DESCRIPTOR_TYPE );
        }
        urb = USBD_CreateConfigurationRequestEx( ConfigurationDescriptor, interfaceList );
        if (urb)
            *Siz = urb->u.UrbHeader.Length;
        ExFreePool( interfaceList );
    }
    return urb;
}

PURB WINAPI USBD_CreateConfigurationRequestEx(
        PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor,
        PUSBD_INTERFACE_LIST_ENTRY InterfaceList )
{
    URB *urb;
    ULONG size = 0;
    USBD_INTERFACE_LIST_ENTRY *interfaceEntry;
    ULONG interfaceCount = 0;

    TRACE( "(%p, %p)\n", ConfigurationDescriptor, InterfaceList );

    size = sizeof(struct _URB_SELECT_CONFIGURATION);
    for (interfaceEntry = InterfaceList; interfaceEntry->InterfaceDescriptor; interfaceEntry++)
    {
        ++interfaceCount;
        size += (interfaceEntry->InterfaceDescriptor->bNumEndpoints - 1) *
            sizeof(USBD_PIPE_INFORMATION);
    }
    size += (interfaceCount - 1) * sizeof(USBD_INTERFACE_INFORMATION);

    urb = ExAllocatePool( NonPagedPool, size );
    if (urb)
    {
        USBD_INTERFACE_INFORMATION *interfaceInfo;

        RtlZeroMemory( urb, size );
        urb->u.UrbSelectConfiguration.Hdr.Length = size;
        urb->u.UrbSelectConfiguration.Hdr.Function = URB_FUNCTION_SELECT_CONFIGURATION;
        urb->u.UrbSelectConfiguration.ConfigurationDescriptor = ConfigurationDescriptor;
        interfaceInfo = &urb->u.UrbSelectConfiguration.Interface;
        for (interfaceEntry = InterfaceList; interfaceEntry->InterfaceDescriptor; interfaceEntry++)
        {
            ULONG i;
            USB_INTERFACE_DESCRIPTOR *currentInterface;
            USB_ENDPOINT_DESCRIPTOR *endpointDescriptor;
            interfaceInfo->InterfaceNumber = interfaceEntry->InterfaceDescriptor->bInterfaceNumber;
            interfaceInfo->AlternateSetting = interfaceEntry->InterfaceDescriptor->bAlternateSetting;
            interfaceInfo->Class = interfaceEntry->InterfaceDescriptor->bInterfaceClass;
            interfaceInfo->SubClass = interfaceEntry->InterfaceDescriptor->bInterfaceSubClass;
            interfaceInfo->Protocol = interfaceEntry->InterfaceDescriptor->bInterfaceProtocol;
            interfaceInfo->NumberOfPipes = interfaceEntry->InterfaceDescriptor->bNumEndpoints;
            currentInterface = USBD_ParseConfigurationDescriptorEx(
                ConfigurationDescriptor, ConfigurationDescriptor,
                interfaceEntry->InterfaceDescriptor->bInterfaceNumber, -1, -1, -1, -1 );
            endpointDescriptor = (PUSB_ENDPOINT_DESCRIPTOR) USBD_ParseDescriptors(
                ConfigurationDescriptor, ConfigurationDescriptor->wTotalLength,
                currentInterface, USB_ENDPOINT_DESCRIPTOR_TYPE );
            for (i = 0; i < interfaceInfo->NumberOfPipes && endpointDescriptor; i++)
            {
                interfaceInfo->Pipes[i].MaximumPacketSize = endpointDescriptor->wMaxPacketSize;
                interfaceInfo->Pipes[i].EndpointAddress = endpointDescriptor->bEndpointAddress;
                interfaceInfo->Pipes[i].Interval = endpointDescriptor->bInterval;
                switch (endpointDescriptor->bmAttributes & USB_ENDPOINT_TYPE_MASK)
                {
                case USB_ENDPOINT_TYPE_CONTROL:
                    interfaceInfo->Pipes[i].PipeType = UsbdPipeTypeControl;
                    break;
                case USB_ENDPOINT_TYPE_BULK:
                    interfaceInfo->Pipes[i].PipeType = UsbdPipeTypeBulk;
                    break;
                case USB_ENDPOINT_TYPE_INTERRUPT:
                    interfaceInfo->Pipes[i].PipeType = UsbdPipeTypeInterrupt;
                    break;
                case USB_ENDPOINT_TYPE_ISOCHRONOUS:
                    interfaceInfo->Pipes[i].PipeType = UsbdPipeTypeIsochronous;
                    break;
                }
                endpointDescriptor = (PUSB_ENDPOINT_DESCRIPTOR) USBD_ParseDescriptors(
                    ConfigurationDescriptor, ConfigurationDescriptor->wTotalLength,
                    endpointDescriptor + 1, USB_ENDPOINT_DESCRIPTOR_TYPE );
            }
            interfaceInfo->Length = sizeof(USBD_INTERFACE_INFORMATION) +
                (i - 1) * sizeof(USBD_PIPE_INFORMATION);
            interfaceEntry->Interface = interfaceInfo;
            interfaceInfo = (USBD_INTERFACE_INFORMATION*)(((char*)interfaceInfo)+interfaceInfo->Length);
        }
    }
    return urb;
}

VOID WINAPI USBD_GetUSBDIVersion(
        PUSBD_VERSION_INFORMATION VersionInformation )
{
    TRACE( "(%p)\n", VersionInformation );
    /* Emulate Windows 2000 (= 0x300) for now */
    VersionInformation->USBDI_Version = 0x300;
    VersionInformation->Supported_USB_Version = 0x200;
}

PUSB_INTERFACE_DESCRIPTOR WINAPI USBD_ParseConfigurationDescriptorEx(
        PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor,
        PVOID StartPosition, LONG InterfaceNumber,
        LONG AlternateSetting, LONG InterfaceClass,
        LONG InterfaceSubClass, LONG InterfaceProtocol )
{
    /* http://blogs.msdn.com/usbcoreblog/archive/2009/12/12/
     *        what-is-the-right-way-to-validate-and-parse-configuration-descriptors.aspx
     */

    PUSB_INTERFACE_DESCRIPTOR interface;

    TRACE( "(%p, %p, %d, %d, %d, %d, %d)\n", ConfigurationDescriptor,
            StartPosition, InterfaceNumber, AlternateSetting,
            InterfaceClass, InterfaceSubClass, InterfaceProtocol );

    interface = (PUSB_INTERFACE_DESCRIPTOR) USBD_ParseDescriptors(
        ConfigurationDescriptor, ConfigurationDescriptor->wTotalLength,
        StartPosition, USB_INTERFACE_DESCRIPTOR_TYPE );
    while (interface != NULL)
    {
        if ((InterfaceNumber == -1 || interface->bInterfaceNumber == InterfaceNumber) &&
            (AlternateSetting == -1 || interface->bAlternateSetting == AlternateSetting) &&
            (InterfaceClass == -1 || interface->bInterfaceClass == InterfaceClass) &&
            (InterfaceSubClass == -1 || interface->bInterfaceSubClass == InterfaceSubClass) &&
            (InterfaceProtocol == -1 || interface->bInterfaceProtocol == InterfaceProtocol))
        {
            return interface;
        }
        interface = (PUSB_INTERFACE_DESCRIPTOR) USBD_ParseDescriptors(
            ConfigurationDescriptor, ConfigurationDescriptor->wTotalLength,
            interface + 1, USB_INTERFACE_DESCRIPTOR_TYPE );
    }
    return NULL;
}

PUSB_INTERFACE_DESCRIPTOR WINAPI USBD_ParseConfigurationDescriptor(
        PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor, UCHAR InterfaceNumber,
        UCHAR AlternateSetting )
{
    TRACE( "(%p, %u, %u)\n", ConfigurationDescriptor, InterfaceNumber, AlternateSetting );
    return USBD_ParseConfigurationDescriptorEx( ConfigurationDescriptor, ConfigurationDescriptor,
                                                InterfaceNumber, AlternateSetting, -1, -1, -1 );
}

PUSB_COMMON_DESCRIPTOR WINAPI USBD_ParseDescriptors(
        PVOID DescriptorBuffer,
        ULONG TotalLength,
        PVOID StartPosition,
        LONG DescriptorType )
{
    PUSB_COMMON_DESCRIPTOR common;

    TRACE( "(%p, %u, %p, %d)\n", DescriptorBuffer, TotalLength, StartPosition, DescriptorType );

    for (common = (PUSB_COMMON_DESCRIPTOR)DescriptorBuffer;
         ((char*)common) + sizeof(USB_COMMON_DESCRIPTOR) <= ((char*)DescriptorBuffer) + TotalLength;
         common = (PUSB_COMMON_DESCRIPTOR)(((char*)common) + common->bLength))
    {
        if (StartPosition <= (PVOID)common && common->bDescriptorType == DescriptorType)
            return common;
    }
    return NULL;
}

USBD_STATUS WINAPI USBD_ValidateConfigurationDescriptor(
        PUSB_CONFIGURATION_DESCRIPTOR descr,
        ULONG length,
        USHORT level,
        PUCHAR *offset,
        ULONG tag )
{
    FIXME( "(%p, %u, %u, %p, %u) partial stub!\n", descr, length, level, offset, tag );

    if (offset) *offset = 0;

    if (!descr ||
        length < sizeof(USB_CONFIGURATION_DESCRIPTOR) ||
        descr->bLength < sizeof(USB_CONFIGURATION_DESCRIPTOR) ||
        descr->wTotalLength < descr->bNumInterfaces * sizeof(USB_CONFIGURATION_DESCRIPTOR)
       ) return USBD_STATUS_ERROR;

    return USBD_STATUS_SUCCESS;
}

ULONG WINAPI USBD_GetInterfaceLength(
        PUSB_INTERFACE_DESCRIPTOR InterfaceDescriptor,
        PUCHAR BufferEnd )
{
    PUSB_COMMON_DESCRIPTOR common;
    ULONG total = InterfaceDescriptor->bLength;

    TRACE( "(%p, %p)\n", InterfaceDescriptor, BufferEnd );

    for (common = (PUSB_COMMON_DESCRIPTOR)(InterfaceDescriptor + 1);
         (((PUCHAR)common) + sizeof(USB_COMMON_DESCRIPTOR)) <= BufferEnd &&
             common->bDescriptorType != USB_INTERFACE_DESCRIPTOR_TYPE;
         common = (PUSB_COMMON_DESCRIPTOR)(((char*)common) + common->bLength))
    {
        total += common->bLength;
    }
    return total;
}

NTSTATUS WINAPI DriverEntry( DRIVER_OBJECT *driver, UNICODE_STRING *path )
{
    TRACE( "(%p, %s)\n", driver, debugstr_w(path->Buffer) );
    return STATUS_SUCCESS;
}