/*
 * Copyright (c) 2021-2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "usbhost_nosdk_speed.h"

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <osal_sem.h>
#include <osal_thread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

#include "implementation/global_implementation.h"
#include "liteos_ddk_usb.h"
#include "osal_mem.h"
#include "osal_time.h"
#include "usb_pnp_notify.h"

#define USB_DEV_FS_PATH                 "/dev/bus/usb"
#define URB_COMPLETE_PROCESS_STACK_SIZE 8196
#define ENDPOINT_IN_OFFSET              7
#define DEFAULT_BUSNUM                  1
#define DEFAULT_DEVADDR                 2
static int32_t g_speedFlag = 0;
static int32_t g_busNum = DEFAULT_BUSNUM;
static int32_t g_devAddr = DEFAULT_DEVADDR;
static struct OsalSem g_sem;
static uint64_t g_send_count = 0;
static uint64_t g_recv_count = 0;
static uint64_t g_byteTotal = 0;
static struct UsbAdapterUrbs urb[TEST_CYCLE];
static struct urb *g_sendUrb = NULL;
static bool g_printData = false;
static uint8_t g_endNum;
static struct OsalSem g_timeSem;
static struct usb_device *g_fd = NULL;
static uint32_t g_sigCnt = 0;
static UsbAdapterHostEndpoint *g_uhe = NULL;
static bool g_writeOrRead = TEST_WRITE;
static struct AcmDevice *g_acm = NULL;

static void CloseDevice(void)
{
    return;
}

static int32_t OpenDevice(void)
{
    struct UsbGetDevicePara paraData;
    paraData.type = USB_PNP_DEVICE_ADDRESS_TYPE;
    paraData.busNum = (uint8_t)g_busNum;
    paraData.devNum = (uint8_t)g_devAddr;
    g_fd = UsbPnpNotifyGetUsbDevice(paraData);
    if (g_fd == NULL) {
        HDF_LOGE("%{public}s: UsbPnpNotifyGetUsbDevice err", __func__);
        return -1;
    }
    return 0;
}

static int32_t ClaimInterface(int32_t iface)
{
    HDF_LOGI("%{public}s: claim success: iface=%{public}d", __func__, iface);
    return HDF_SUCCESS;
}

static void SpeedPrint(void)
{
    double speed;
    uint64_t count;

    g_sigCnt++;
    count = (uint64_t)g_sigCnt * TEST_PRINT_TIME;
    if (count >= TEST_TIME) {
        g_speedFlag = true;
    }
    speed =
        (g_byteTotal * TEST_DOUBLE_COUNT) / (g_sigCnt * TEST_PRINT_TIME * TEST_BYTE_COUNT_UINT * TEST_BYTE_COUNT_UINT);
    printf("\nSpeed:%f MB/s\n", speed);
}

static int32_t SendProcess(void *argurb)
{
    (void)argurb;
    int32_t i;
    while (!g_speedFlag) {
        OsalSemWait(&g_sem, HDF_WAIT_FOREVER);
        for (i = 0; i < TEST_CYCLE; i++) {
            if (urb[i].inUse == 0) {
                urb[i].inUse = 1;
                break;
            }
        }

        if (i == TEST_CYCLE) {
            i = TEST_CYCLE - 1;
        }
        g_sendUrb = urb[i].urb;
        int32_t err = usb_setup_endpoint(g_fd, g_uhe, TEST_BYTE_COUNT_UINT);
        if (err < 0) {
            DPRINTFN(0, "setup failed err:%d\n", err);
            return err;
        }
        err = usb_submit_urb(g_sendUrb, 0);
        if (err < 0) {
            HDF_LOGI("SubmitBulkRequest: err:%{public}d", err);
            urb[i].inUse = 0;
            continue;
        }
        g_send_count++;
    }
    return 0;
}

static void UrbCompleteHandle(const struct urb *curUrb)
{
    if (g_printData) {
        for (int32_t i = 0; i < curUrb->actual_length; i++) {
            printf("%c", *(((char *)curUrb->transfer_buffer) + i));
        }
        fflush(stdout);
    } else if (g_recv_count % TEST_PRINT_MAX_RANGE == 0) {
        printf("#");
        fflush(stdout);
    }
}

static void UrbComplete(struct urb *curUrb)
{
    int32_t i;
    for (i = 0; i < TEST_CYCLE; i++) {
        if (urb[i].urb == curUrb) {
            if (g_byteTotal == 0) {
                OsalSemPost(&g_timeSem);
            }
            g_recv_count++;
            g_byteTotal += curUrb->actual_length;
            UrbCompleteHandle(curUrb);
            urb[i].inUse = 0;
            OsalSemPost(&g_sem);
            break;
        }
    }
}

static int32_t BeginProcessHandleFirst(void)
{
    char *data = NULL;
    for (int32_t i = 0; i < TEST_CYCLE; i++) {
        if (urb[i].urb == NULL) {
            urb[i].urb = OsalMemCalloc(sizeof(struct urb));
            if (urb[i].urb == NULL) {
                HDF_LOGE("%{public}s:%{public}d urb calloc err", __func__, __LINE__);
                return -1;
            }
        }
        urb[i].inUse = 0;
        urb[i].urb->dev = g_fd;
        urb[i].urb->endpoint = g_uhe;
        urb[i].urb->complete = UrbComplete;

        if (data == NULL) {
            data = OsalMemCalloc(TEST_LENGTH);
            if (data == NULL) {
                HDF_LOGE("%{public}s:%{public}d data calloc err", __func__, __LINE__);
                return -1;
            }
        }

        if (memset_s(data, TEST_LENGTH, 'c', TEST_LENGTH) != EOK) {
            HDF_LOGE("%{public}s:%{public}d memset_s failed.", __func__, __LINE__);
            return -1;
        }
        data[TEST_LENGTH - 1] = '\0';
        urb[i].urb->transfer_buffer = (void *)data;
        urb[i].urb->transfer_buffer_length = TEST_LENGTH;
    }

    return HDF_SUCCESS;
}

static int32_t BeginProcess(unsigned char endPoint)
{
    const int32_t transNum = 0;
    if (endPoint == 0) {
        HDF_LOGE("%{public}s:%{public}d parameter error", __func__, __LINE__);
        return -1;
    }

    g_uhe = usb_find_host_endpoint(g_fd, USB_REQUEST_TYPE_BULK, endPoint);
    if (g_uhe == NULL) {
        HDF_LOGE("%{public}s:%{public}d usb_find_host_endpoint error", __func__, __LINE__);
        return -1;
    }
    int32_t ret = BeginProcessHandleFirst();
    if (ret != HDF_SUCCESS) {
        return ret;
    }

    HDF_LOGI("%{public}s:%{public}d test NO SDK endpoint:%{public}u", __func__, __LINE__, endPoint);

    int32_t i;
    for (i = 0; i < TEST_CYCLE; i++) {
        if (urb[i].inUse == 0) {
            urb[i].inUse = 1;
            urb[i].urbNum = transNum;
            g_sendUrb = urb[i].urb;
            ret = usb_setup_endpoint(g_fd, g_uhe, TEST_BYTE_COUNT_UINT);
            if (ret < 0) {
                DPRINTFN(0, "setup failed ret:%d\n", ret);
                return ret;
            }
            ret = usb_submit_urb(g_sendUrb, 0);
            if (ret < 0) {
                HDF_LOGI("%{public}s:%{public}d SubmitBulkRequest: ret:%{public}d", __func__, __LINE__, ret);
                urb[i].inUse = 0;
                continue;
            }
            g_send_count++;
        }
    }

    OsalSemWait(&g_timeSem, TEST_TIME);
    while (!g_speedFlag) {
        OsalSemWait(&g_timeSem, TEST_PRINT_TIME * TEST_PRINT_TIME_UINT);
        SpeedPrint();
    }

    for (i = 0; i < TEST_CYCLE; i++) {
        usb_kill_urb(urb[i].urb);
    }

    return HDF_SUCCESS;
}

static void ShowHelp(const char *name)
{
    printf(">> usage:\n");
    printf(">>      %s [<busNum> <devAddr>]  <ifaceNum> <endpoint> [<printdata>]\n", name);
    printf("\n");
}

static void UsbGetDevInfo(int32_t * const busNum, int32_t * const devNum)
{
    struct UsbGetDevicePara paraData;
    struct usb_device *usbPnpDevice = NULL;
    paraData.type = USB_PNP_DEVICE_VENDOR_PRODUCT_TYPE;
    paraData.vendorId = USB_DEVICE_VENDOR_ID;
    paraData.productId = USB_DEVICE_PRODUCT_ID;
    usbPnpDevice = UsbPnpNotifyGetUsbDevice(paraData);
    if (usbPnpDevice == NULL) {
        HDF_LOGE("%{public}s:%{public}d UsbPnpNotifyGetUsbDevice is NULL!", __func__, __LINE__);
        return;
    }
    *busNum = (int)usbPnpDevice->address;
    *devNum = (int)usbPnpDevice->port_no;
    printf("%s:%d busNum=%d devNum=%d!\n", __func__, __LINE__, *busNum, *devNum);
}

static int32_t UsbSerialOpen(void)
{
    return HDF_SUCCESS;
}
static int32_t UsbSerialClose(void)
{
    if (!g_speedFlag) {
        g_speedFlag = true;
    }
    return HDF_SUCCESS;
}

static int32_t UsbSerialSpeedInit(const struct UsbSpeedTest * const input, int32_t * const ifaceNum)
{
    int32_t ret = HDF_SUCCESS;
    if (input == NULL) {
        HDF_LOGE("%{public}s:%{public}d input is null", __func__, __LINE__);
        return HDF_ERR_INVALID_PARAM;
    }

    g_speedFlag = false;
    g_send_count = 0;
    g_recv_count = 0;
    g_byteTotal = 0;
    g_printData = false;
    g_writeOrRead = TEST_WRITE;
    g_sigCnt = 0;
    g_busNum = DEFAULT_BUSNUM;
    g_devAddr = DEFAULT_DEVADDR;

    UsbGetDevInfo(&g_busNum, &g_devAddr);
    if (input->paramNum == INPUT_COMPARE_PARAMNUM) {
        g_busNum = input->busNum;
        g_devAddr = input->devAddr;
        *ifaceNum = input->ifaceNum;
        g_endNum = input->writeOrRead;
        g_writeOrRead = ((g_endNum >> ENDPOINT_IN_OFFSET) == 0) ? TEST_WRITE : TEST_READ;
        if (g_writeOrRead == TEST_READ) {
            g_printData = input->printData;
        }
    } else if (input->paramNum == INPUT_COMPARE_NUMTWO) {
        g_busNum = input->busNum;
        g_devAddr = input->devAddr;
        *ifaceNum = input->ifaceNum;
        g_endNum = input->writeOrRead;
        g_writeOrRead = ((g_endNum >> ENDPOINT_IN_OFFSET) == 0) ? TEST_WRITE : TEST_READ;
    } else if (input->paramNum == INPUT_COMPARE_NUMONE) {
        *ifaceNum = input->ifaceNum;
        g_endNum = input->writeOrRead;
        g_writeOrRead = ((g_endNum >> ENDPOINT_IN_OFFSET) == 0) ? TEST_WRITE : TEST_READ;
    } else {
        printf("Error: parameter error! \n\n");
        ShowHelp("speedtest");
        return HDF_FAILURE;
    }
    OsalSemInit(&g_sem, 0);
    OsalSemInit(&g_timeSem, 0);

    return ret;
}

static int32_t UsbSerialSpeedThreadCreate(void)
{
    int32_t ret;
    struct OsalThread urbSendProcess;
    struct OsalThreadParam threadCfg;
    ret = memset_s(&threadCfg, sizeof(threadCfg), 0, sizeof(threadCfg));
    if (ret != EOK) {
        HDF_LOGE("%{public}s:%{public}d memset_s failed", __func__, __LINE__);
        return ret;
    }
    threadCfg.name = "urb send process";
    threadCfg.priority = OSAL_THREAD_PRI_DEFAULT;
    threadCfg.stackSize = URB_COMPLETE_PROCESS_STACK_SIZE;

    ret = OsalThreadCreate(&urbSendProcess, (OsalThreadEntry)SendProcess, NULL);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("OsalThreadCreate failed, ret = %{public}d", ret);
        return ret;
    }

    ret = OsalThreadStart(&urbSendProcess, &threadCfg);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("OsalThreadStart failed, ret = %{public}d", ret);
        return ret;
    }

    return ret;
}

static int32_t UsbSerialSpeed(struct HdfSBuf *data)
{
    int32_t ifaceNum = 3;
    int32_t ret;
    struct UsbSpeedTest *input = NULL;
    uint32_t size = 0;
    if (g_acm->busy) {
        HDF_LOGE("%{public}s: %{public}d speed test busy", __func__, __LINE__);
        ret = HDF_ERR_IO;
        goto END;
    } else {
        g_acm->busy = true;
    }

    (void)HdfSbufReadBuffer(data, (const void **)&input, &size);
    if (input == NULL || size != sizeof(struct UsbSpeedTest)) {
        HDF_LOGE("%{public}s: %{public}d sbuf read buffer failed", __func__, __LINE__);
        ret = HDF_ERR_IO;
        goto END;
    }

    ret = UsbSerialSpeedInit(input, &ifaceNum);
    if (ret != HDF_SUCCESS) {
        goto END;
    }

    OpenDevice();

    ret = ClaimInterface(ifaceNum);
    if (ret != HDF_SUCCESS) {
        goto END;
    }

    ret = UsbSerialSpeedThreadCreate();
    if (ret != HDF_SUCCESS) {
        goto END;
    }

    ret = BeginProcess(g_endNum);
    if (ret != HDF_SUCCESS) {
        goto END;
    }

END:
    g_acm->busy = false;
    if (ret != HDF_SUCCESS) {
        printf("please check whether usb drv so is existing or not,like g_acm, ecm,if not,remove it and test again!\n");
    }
    CloseDevice();
    return ret;
}

static int32_t AcmDeviceDispatch(
    struct HdfDeviceIoClient * const client, int32_t cmd, struct HdfSBuf * const data, struct HdfSBuf * const reply)
{
    (void)reply;
    if (client == NULL) {
        HDF_LOGE("%{public}s: client is NULL", __func__);
        return HDF_ERR_INVALID_OBJECT;
    }

    if (client->device == NULL) {
        HDF_LOGE("%{public}s: client->device is NULL", __func__);
        return HDF_ERR_INVALID_OBJECT;
    }

    if (client->device->service == NULL) {
        HDF_LOGE("%{public}s: client->device->service is NULL", __func__);
        return HDF_ERR_INVALID_OBJECT;
    }

    g_acm = (struct AcmDevice *)client->device->service;

    switch (cmd) {
        case USB_SERIAL_OPEN:
            return UsbSerialOpen();
        case USB_SERIAL_CLOSE:
            return UsbSerialClose();
        case USB_SERIAL_SPEED:
            return UsbSerialSpeed(data);
        default:
            return HDF_ERR_NOT_SUPPORT;
    }

    return HDF_SUCCESS;
}

static int32_t AcmDriverBind(struct HdfDeviceObject *device)
{
    if (device == NULL) {
        HDF_LOGE("%{public}s: device is null", __func__);
        return HDF_ERR_INVALID_OBJECT;
    }

    g_acm = (struct AcmDevice *)OsalMemCalloc(sizeof(*g_acm));
    if (g_acm == NULL) {
        HDF_LOGE("%{public}s: Alloc usb g_acm device failed", __func__);
        return HDF_FAILURE;
    }

    g_acm->device = device;
    device->service = &(g_acm->service);
    if (g_acm->device && g_acm->device->service) {
        g_acm->device->service->Dispatch = AcmDeviceDispatch;
    }
    return HDF_SUCCESS;
}

static int32_t AcmDriverInit(struct HdfDeviceObject *device)
{
    (void)device;
    return 0;
}

static void AcmDriverRelease(struct HdfDeviceObject *device)
{
    (void)device;
    return;
}

struct HdfDriverEntry g_usbNoSdkSpeedDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "usb_nosdkspeed",
    .Bind = AcmDriverBind,
    .Init = AcmDriverInit,
    .Release = AcmDriverRelease,
};

HDF_INIT(g_usbNoSdkSpeedDriverEntry);