#include <stdlib.h>
#include <libopencm3/usb/usbd.h>
#include <libopencm3/usb/cdc.h>

#include <SEGGER_RTT.h>
#include <string.h>

#include "rndis.h"
#include "util.h"
#include "rndis_protocol.h"

/* Buffer to be used for control requests. */
static uint8_t usbd_control_buffer[1025];

static const uint8_t station_hwaddr[6] = { RNDIS_HWADDR };
static const uint8_t permanent_hwaddr[6] = { RNDIS_HWADDR };

static const struct usb_device_descriptor dev = {
        .bLength = USB_DT_DEVICE_SIZE,
        .bDescriptorType = USB_DT_DEVICE,
        .bcdUSB = 0x0200,
        .bDeviceClass = 0xE0,  // Wireless Controller
        .bDeviceSubClass = 0,
        .bDeviceProtocol = 0,
        .bMaxPacketSize0 = 64,
        .idVendor = 0x4E44,
        .idProduct = 0x4953,
        .bcdDevice = 0xffff,
        .iManufacturer = 1,
        .iProduct = 2,
        .iSerialNumber = 3,
        .bNumConfigurations = 1,
};

static const struct usb_endpoint_descriptor comm_endp[] = {{
    .bLength = USB_DT_ENDPOINT_SIZE,
    .bDescriptorType = USB_DT_ENDPOINT,
    .bEndpointAddress = 0x83,
    .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT,
    .wMaxPacketSize = 8,
    .bInterval = 0xf,
}};

static const struct {
    struct usb_cdc_header_descriptor header;
    struct usb_cdc_call_management_descriptor call_mgmt;
    struct usb_cdc_acm_descriptor acm;
    struct usb_cdc_union_descriptor cdc_union;
} __attribute__((packed)) cdcacm_functional_descriptors = {
        .header = {
                .bFunctionLength = sizeof(struct usb_cdc_header_descriptor),
                .bDescriptorType = CS_INTERFACE,
                .bDescriptorSubtype = USB_CDC_TYPE_HEADER,
                .bcdCDC = 0x0110,
        },
        .call_mgmt = {
                .bFunctionLength =
                sizeof(struct usb_cdc_call_management_descriptor),
                .bDescriptorType = CS_INTERFACE,
                .bDescriptorSubtype = USB_CDC_TYPE_CALL_MANAGEMENT,
                .bmCapabilities = 0,
                .bDataInterface = 1,
        },
        .acm = {
                .bFunctionLength = sizeof(struct usb_cdc_acm_descriptor),
                .bDescriptorType = CS_INTERFACE,
                .bDescriptorSubtype = USB_CDC_TYPE_ACM,
                .bmCapabilities = 0,
        },
        .cdc_union = {
                .bFunctionLength = sizeof(struct usb_cdc_union_descriptor),
                .bDescriptorType = CS_INTERFACE,
                .bDescriptorSubtype = USB_CDC_TYPE_UNION,
                .bControlInterface = 0,
                .bSubordinateInterface0 = 1,
        },
};

// RNDIS Communications Control
static const struct usb_interface_descriptor comm_iface[] = {{
    .bLength = USB_DT_INTERFACE_SIZE,
    .bDescriptorType = USB_DT_INTERFACE,
    .bInterfaceNumber = 0,
    .bAlternateSetting = 0,
    .bNumEndpoints = 1,
    // Linux kernel calls this "RNDIS for tethering"
    .bInterfaceClass = 0xE0,  // Wireless Controller Class
    .bInterfaceSubClass = 0x01,
    .bInterfaceProtocol = 0x03,

     // https://docs.microsoft.com/en-us/windows-hardware/drivers/network/communication-class-interface
//    .bInterfaceClass = 0x02,
//    .bInterfaceSubClass = 0x02,
//    .bInterfaceProtocol = 0xff,

    .iInterface = 0,

    .endpoint = comm_endp,

    .extra = &cdcacm_functional_descriptors,
    .extralen = sizeof(cdcacm_functional_descriptors),
}};

static const struct usb_endpoint_descriptor data_endp[] = {{
   .bLength = USB_DT_ENDPOINT_SIZE,
   .bDescriptorType = USB_DT_ENDPOINT,
   .bEndpointAddress = 0x01,
   .bmAttributes = USB_ENDPOINT_ATTR_BULK,
   .wMaxPacketSize = 64,
   .bInterval = 0,
}, {
   .bLength = USB_DT_ENDPOINT_SIZE,
   .bDescriptorType = USB_DT_ENDPOINT,
   .bEndpointAddress = 0x82,
   .bmAttributes = USB_ENDPOINT_ATTR_BULK,
   .wMaxPacketSize = 64,
   .bInterval = 0,
}};

static const struct usb_interface_descriptor data_iface[] = {{
    .bLength = USB_DT_INTERFACE_SIZE,
    .bDescriptorType = USB_DT_INTERFACE,
    .bInterfaceNumber = 1,
    .bAlternateSetting = 0,
    .bNumEndpoints = 2,
    .bInterfaceClass = USB_CLASS_DATA,
    .bInterfaceSubClass = 0,
    .bInterfaceProtocol = 0,
    .iInterface = 0,

    .endpoint = data_endp,
}};

static const struct usb_interface ifaces[] = {{
    .num_altsetting = 1,
    .altsetting = comm_iface,
}, {
    .num_altsetting = 1,
    .altsetting = data_iface,
}};

static const struct usb_config_descriptor config = {
        .bLength = USB_DT_CONFIGURATION_SIZE,
        .bDescriptorType = USB_DT_CONFIGURATION,
        .wTotalLength = 0,
        .bNumInterfaces = 2,
        .bConfigurationValue = 1,
        .iConfiguration = 0,
        .bmAttributes = 0x80,
        .bMaxPower = 0x32,

        .interface = ifaces,
};

static const char *usb_strings[] = {
        "Arti",  // iManufacturer
        "STM32-RNDIS",  // iProduct
        "lrndis demo",  // iSerialNumber
};

static void rndis_query_cmplt32(uint8_t **buf, uint32_t status, uint32_t data)
{
    rndis_query_cmplt_t *c = (rndis_query_cmplt_t *)*buf;
    c->MessageType = REMOTE_NDIS_QUERY_CMPLT;
    c->MessageLength = sizeof(rndis_query_cmplt_t) + 4;
    c->Status = status;
    c->InformationBufferLength = 4;
    c->InformationBufferOffset = 16;
    *(uint32_t *)(c + 1) = data;
}

static void rndis_query_cmplt(uint8_t **buf, uint32_t status, const void *data, uint32_t size){
    rndis_query_cmplt_t *c = (rndis_query_cmplt_t *)*buf;
    c->MessageType = REMOTE_NDIS_QUERY_CMPLT;
    c->MessageLength = sizeof(rndis_query_cmplt_t) + size;
    c->Status = status;
    c->InformationBufferLength = size;
    c->InformationBufferOffset = 16;
    memcpy(c + 1, data, size);
}

static void rndis_handle_query(uint8_t **buf, uint16_t *len)
{
    union {
        void                    *buf;
        rndis_generic_msg_t     *header;
        rndis_query_msg_t       *get;
        rndis_query_cmplt_t     *get_c;
    } u;

    u.buf = *buf;

    SEGGER_RTT_printf(0, "NDIS Query oid: 0x%08x, len: 0x%02x\n", u.get->Oid, u.get->InformationBufferLength);

    switch (u.get->Oid)
    {
        case OID_GEN_PHYSICAL_MEDIUM:
            rndis_query_cmplt32(buf, RNDIS_STATUS_SUCCESS, NDIS_MEDIUM_802_3);
            return;
        case OID_802_3_PERMANENT_ADDRESS:
            rndis_query_cmplt(buf, RNDIS_STATUS_SUCCESS, &permanent_hwaddr, sizeof(permanent_hwaddr));
            return;
        default:
            rndis_query_cmplt(buf, RNDIS_STATUS_FAILURE, NULL, 0);
            return;
    }
}

static enum usbd_request_return_codes rndis_control_request(usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf,
                                                             uint16_t *len, void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req))
{
    (void)req;
    (void)complete;

    union {
        void                        *buf;
        rndis_generic_msg_t         *header;
        rndis_initialize_msg_t      *init;
        rndis_initialize_cmplt_t    *init_c;
        rndis_query_msg_t           *get;
        rndis_query_cmplt_t         *get_c;
        rndis_set_msg_t             *set;
        rndis_set_cmplt_t           *set_c;
        rndis_indicate_status_t     *indic;
    } u;

    //SEGGER_RTT_printf(0, "bmRequestType: 0x%02x, bRequest: 0x%02x\n", req->bmRequestType, req->bRequest);

    u.buf = *buf;
    switch (u.header->MessageType)
    {
        case REMOTE_NDIS_INITIALIZE_MSG:
            SEGGER_RTT_printf(0, "NDIS Init: HOST Ver %d.%d, Max MTU: %dbytes \n", u.init->MajorVersion, u.init->MinorVersion, u.init->MaxTransferSize);
            u.init_c->MessageType = REMOTE_NDIS_INITIALIZE_CMPLT;
            return USBD_REQ_HANDLED;
        case REMOTE_NDIS_INITIALIZE_CMPLT:
            SEGGER_RTT_printf(0, "NDIS Init complete, MTU: %d\n", RNDIS_RX_BUFFER_SIZE);
            u.init_c->MessageType = REMOTE_NDIS_INITIALIZE_CMPLT;
            u.init_c->MessageLength = sizeof(rndis_initialize_cmplt_t);
            // u.int_c->RequestId stays the same
            u.init_c->Status = RNDIS_STATUS_SUCCESS;
            u.init_c->MajorVersion = RNDIS_MAJOR_VERSION;
            u.init_c->MinorVersion = RNDIS_MINOR_VERSION;
            u.init_c->DeviceFlags = RNDIS_DF_CONNECTIONLESS;
            u.init_c->Medium = RNDIS_MEDIUM_802_3;
            u.init_c->MaxPacketsPerTransfer = 1;
            u.init_c->MaxTransferSize = RNDIS_RX_BUFFER_SIZE;
            u.init_c->PacketAlignmentFactor = 0;
            u.init_c->AfListOffset = 0;
            u.init_c->AfListSize = 0;
            //*len = sizeof(rndis_initialize_cmplt_t);
            usbd_ep_write_packet(usbd_dev, 0x83, "\x01\x00\x00\x00\x00\x00\x00\x00", 8);
            return USBD_REQ_HANDLED;
        case REMOTE_NDIS_QUERY_MSG:
            rndis_handle_query(buf, len);
            usbd_ep_write_packet(usbd_dev, 0x83, "\x01\x00\x00\x00\x00\x00\x00\x00", 8);
            //*len = u.get_c->MessageLength;
            return USBD_REQ_HANDLED;
        case REMOTE_NDIS_QUERY_CMPLT:
            SEGGER_RTT_printf(0, "NDIS Query complete\n");
            *len = u.get_c->MessageLength;
            usbd_ep_write_packet(usbd_dev, 0x83, "\x01\x00\x00\x00\x00\x00\x00\x00", 8);
            return USBD_REQ_HANDLED;
        case REMOTE_NDIS_SET_MSG:
            SEGGER_RTT_printf(0, "NDIS Set oid: 0x%08x, len 0x%02x\n", u.set->Oid, u.set->InformationBufferLength);
            u.set_c->MessageType = REMOTE_NDIS_SET_CMPLT;
            u.set_c->MessageLength = sizeof(rndis_set_cmplt_t);
            u.set_c->Status = RNDIS_STATUS_SUCCESS;
            *len = u.set_c->MessageLength;
            usbd_ep_write_packet(usbd_dev, 0x83, "\x01\x00\x00\x00\x00\x00\x00\x00", 8);
            return USBD_REQ_HANDLED;
        case REMOTE_NDIS_SET_CMPLT:
            SEGGER_RTT_printf(0, "NDIS Set complete\n");
            *len = u.set_c->MessageLength;
            usbd_ep_write_packet(usbd_dev, 0x83, "\x01\x00\x00\x00\x00\x00\x00\x00", 8);
            return USBD_REQ_HANDLED;
    }
    SEGGER_RTT_printf(0, "NDIS unknown MessageType: 0x%08x, MessageLength: %d, max len: %d\n", u.header->MessageType, u.header->MessageLength, *len);
    return USBD_REQ_NEXT_CALLBACK;
}

static void rndis_data_rx_cb(usbd_device *usbd_dev, uint8_t ep)
{
    (void)ep;

    char buf[64];
    int len = usbd_ep_read_packet(usbd_dev, 0x01, buf, 64);

    if (len) {
        SEGGER_RTT_printf(0, "RX: %d\n", len);
        hexdump(buf, len, 16);
    }
}

static void rndis_set_config(usbd_device *usbd_dev, uint16_t wValue)
{
    (void)wValue;
    (void)usbd_dev;
    //SEGGER_RTT_printf(0, "USB Set config %d\n", wValue);

    usbd_ep_setup(usbd_dev, 0x01, USB_ENDPOINT_ATTR_BULK, 64, rndis_data_rx_cb);
    usbd_ep_setup(usbd_dev, 0x82, USB_ENDPOINT_ATTR_BULK, 64, NULL);
    // Interrupt endpoint is used to only send notification that some data is ready to read
    usbd_ep_setup(usbd_dev, 0x83, USB_ENDPOINT_ATTR_INTERRUPT, 8, NULL);

    usbd_register_control_callback(usbd_dev,
                                   USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE,
                                   USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
                                   rndis_control_request);
}

usbd_device *start_rdnis(void) {
    usbd_device *usbd_dev;

    usbd_dev = usbd_init(
            &st_usbfs_v1_usb_driver, &dev, &config,
            usb_strings, 3,
            usbd_control_buffer, sizeof(usbd_control_buffer)
    );
    usbd_register_set_config_callback(usbd_dev, rndis_set_config);

    return usbd_dev;
}
