/*
 * Copyright (c) 2021 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_sdkapi_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/time.h>
#include <unistd.h>

#include "hdf_base.h"
#include "hdf_log.h"
#include "hdf_usb_pnp_manage.h"
#include "implementation/global_implementation.h"
#include "osal_mem.h"
#include "osal_time.h"
#include "securec.h"
#include "usb_ddk_interface.h"
#include "usb_pnp_notify.h"

#define HDF_LOG_TAG USB_HOST_ACM

static bool g_speedFlag = false;
static uint64_t g_recv_count = 0;
static uint64_t g_send_count = 0;
static uint64_t g_byteTotal = 0;
static bool g_writeOrRead = TEST_WRITE;
static bool g_printData = false;
static struct AcmDevice *g_acm = NULL;
static struct OsalSem timeSem;
static uint32_t sigCnt = 0;

static void AcmTestBulkCallback(struct UsbRequest *req);
static int32_t SerialBegin(struct AcmDevice * const acm);

static int32_t AcmDbAlloc(struct AcmDevice *acm)
{
    int32_t i, dbn;
    struct AcmDb *db = NULL;
    dbn = 0;
    i = 0;
    while (true) {
        if (TEST_CYCLE <= dbn) {
            return -1;
        }
        db = &acm->db[dbn];
        if (!db->use) {
            db->use = 1;
            db->len = 0;
            return dbn;
        }
        dbn = (dbn + 1) % TEST_CYCLE;
        if (++i >= TEST_CYCLE) {
            return -1;
        }
    }
}

static int32_t AcmDbIsAvail(const struct AcmDevice *acm)
{
    int32_t i, n;
    n = TEST_CYCLE;
    for (i = 0; i < TEST_CYCLE; i++) {
        n -= acm->db[i].use;
    }
    return n;
}

static UsbInterfaceHandle *InterfaceIdToHandle(const struct AcmDevice *acm, uint8_t id)
{
    UsbInterfaceHandle *devHandle = NULL;

    if (id == 0xFF) {
        devHandle = acm->ctrDevHandle;
    } else {
        for (int32_t i = 0; i < acm->interfaceCnt; i++) {
            if (acm->iface[i]->info.interfaceIndex == id) {
                devHandle = acm->devHandle[i];
                break;
            }
        }
    }
    return devHandle;
}

static int32_t AcmStartDb(struct AcmDevice *acm, struct AcmDb * const db, struct UsbPipeInfo *pipe)
{
    (void)acm;
    (void)pipe;
    int32_t rc;
    rc = UsbSubmitRequestAsync(db->request);
    if (rc < 0) {
        HDF_LOGE("UsbSubmitRequestAsync failed, ret=%{public}d ", rc);
        db->use = 0;
    }
    return rc;
}

static int32_t AcmDataBufAlloc(struct AcmDevice *acm)
{
    int32_t i;
    struct AcmDb *db = NULL;
    if (acm == NULL) {
        return 0;
    }
    for (db = &acm->db[0], i = 0; i < TEST_CYCLE; i++, db++) {
        db->buf = OsalMemCalloc(acm->dataSize);
        if (!db->buf) {
            while (i != 0) {
                --i;
                --db;
                OsalMemFree(db->buf);
                db->buf = NULL;
            }
            return -HDF_ERR_MALLOC_FAIL;
        } else {
            memset_s(db->buf, acm->dataSize, 'a', acm->dataSize);
            db->instance = acm;
        }
    }
    return 0;
}

static int32_t AcmDataBufFree(const struct AcmDevice *acm)
{
    int32_t i;
    struct AcmDb *db;
    for (db = (struct AcmDb *)&acm->db[0], i = 0; i < TEST_CYCLE; i++, db++) {
        OsalMemFree(db->buf);
        db->use = 0;
    }
    return 0;
}

static void AcmTestBulkCallback(struct UsbRequest *req)
{
    if (req == NULL) {
        printf("req is null\r\n");
        return;
    }
    int32_t status = req->compInfo.status;
    struct AcmDb *db = (struct AcmDb *)req->compInfo.userData;
    if (status == 0) {
        if (g_byteTotal == 0) {
            OsalSemPost(&timeSem);
        }
        g_recv_count++;
        g_byteTotal += req->compInfo.actualLength;
    } else {
        printf("error status=%d\r\n", status);
    }

    if (g_printData) {
        for (unsigned int i = 0; i < req->compInfo.actualLength; i++) {
            printf("%c", req->compInfo.buffer[i]);
        }
        fflush(stdout);
    } else if (g_recv_count % TEST_RECV_COUNT == 0) {
        printf("#");
        fflush(stdout);
    }
    if (db == NULL) {
        return;
    }
    db->use = 0;
    if (!g_speedFlag) {
        if (SerialBegin(db->instance) != HDF_SUCCESS) {
            HDF_LOGW("%{public}s:%{public}d SerialBegin error!", __func__, __LINE__);
        }
        g_send_count++;
    }
}

static int32_t SerialBegin(struct AcmDevice * const acm)
{
    int32_t ret;
    struct AcmDb *db = NULL;
    int32_t dbn;
    if (AcmDbIsAvail(acm) != 0) {
        dbn = AcmDbAlloc(acm);
    } else {
        HDF_LOGE("no buf");
        return 0;
    }
    if (dbn < 0) {
        HDF_LOGE("AcmDbAlloc failed");
        return HDF_FAILURE;
    }
    db = &acm->db[dbn];
    db->len = (int)acm->dataSize;
    ret = AcmStartDb(acm, db, NULL);
    return ret;
}

static struct UsbInterface *GetUsbInterfaceById(const struct AcmDevice *acm, uint8_t interfaceIndex)
{
    return UsbClaimInterface(NULL, acm->busNum, acm->devAddr, interfaceIndex);
}

static struct UsbPipeInfo *EnumePipe(
    const struct AcmDevice *acm, uint8_t interfaceIndex, UsbPipeType pipeType, UsbPipeDirection pipeDirection)
{
    struct UsbInterfaceInfo *info = NULL;
    UsbInterfaceHandle *interfaceHandle = NULL;
    if (USB_PIPE_TYPE_CONTROL == pipeType) {
        info = &acm->ctrIface->info;
        interfaceHandle = acm->ctrDevHandle;
    } else {
        info = &acm->iface[interfaceIndex]->info;
        interfaceHandle = InterfaceIdToHandle(acm, info->interfaceIndex);
    }

    for (uint8_t i = 0; i <= info->pipeNum; i++) {
        struct UsbPipeInfo p;
        int32_t ret = UsbGetPipeInfo(interfaceHandle, info->curAltSetting, i, &p);
        if (ret < 0) {
            continue;
        }
        if ((p.pipeDirection == pipeDirection) && (p.pipeType == pipeType)) {
            struct UsbPipeInfo *pi = OsalMemCalloc(sizeof(*pi));
            if (pi == NULL) {
                HDF_LOGE("%{public}s: Alloc pipe failed", __func__);
                return NULL;
            }
            p.interfaceId = info->interfaceIndex;
            *pi = p;
            return pi;
        }
    }
    return NULL;
}

static struct UsbPipeInfo *GetPipe(const struct AcmDevice *acm, UsbPipeType pipeType, UsbPipeDirection pipeDirection)
{
    uint8_t i;
    if (acm == NULL) {
        HDF_LOGE("%{public}s: invalid parmas", __func__);
        return NULL;
    }
    for (i = 0; i < acm->interfaceCnt; i++) {
        struct UsbPipeInfo *p = NULL;
        if (!acm->iface[i]) {
            continue;
        }
        p = EnumePipe(acm, i, pipeType, pipeDirection);
        if (p == NULL) {
            continue;
        }
        return p;
    }
    return NULL;
}

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

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

static void ShowHelp(const char *name)
{
    printf(">> usage:\n");
    printf(">>      %s [<busNum> <devAddr>]  <ifaceNum> <w>/<r> [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 *input, int32_t *ifaceNum)
{
    int32_t busNum = 1;
    int32_t devAddr = 2;
    if (input == NULL) {
        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;
    sigCnt = 0;

    UsbGetDevInfo(&busNum, &devAddr);
    if (input->paramNum == TEST_SIX_TYPE) {
        busNum = input->busNum;
        devAddr = input->devAddr;
        *ifaceNum = input->ifaceNum;
        g_writeOrRead = input->writeOrRead;
        if (g_writeOrRead == TEST_READ) {
            g_printData = input->printData;
        }
    } else if (input->paramNum == TEST_FIVE_TYPE) {
        busNum = input->busNum;
        devAddr = input->devAddr;
        *ifaceNum = input->ifaceNum;
        g_writeOrRead = input->writeOrRead;
    } else if (input->paramNum == TEST_THREE_TYPE) {
        *ifaceNum = input->ifaceNum;
        g_writeOrRead = input->writeOrRead;
    } else {
        printf("Error: parameter error! \n\n");
        ShowHelp("speedtest");
        return HDF_FAILURE;
    }

    g_acm->busNum = (uint8_t)busNum;
    g_acm->devAddr = (uint8_t)devAddr;
    g_acm->interfaceCnt = 1;
    g_acm->interfaceIndex[0] = *ifaceNum;
    OsalSemInit(&timeSem, 0);

    return HDF_SUCCESS;
}

static int32_t UsbSpeedDdkInit(void)
{
    uint8_t i;
    int32_t ret = UsbInitHostSdk(NULL);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%{public}s:%{public}d UsbInitHostSdk failed", __func__, __LINE__);
        return HDF_ERR_IO;
    }

    for (i = 0; i < g_acm->interfaceCnt; i++) {
        g_acm->iface[i] = GetUsbInterfaceById((const struct AcmDevice *)g_acm, g_acm->interfaceIndex[i]);
    }

    for (i = 0; i < g_acm->interfaceCnt; i++) {
        if (g_acm->iface[i]) {
            g_acm->devHandle[i] = UsbOpenInterface(g_acm->iface[i]);
            if (g_acm->devHandle[i] == NULL) {
                HDF_LOGI("%{public}s: UsbOpenInterface null", __func__);
            }
        } else {
            HDF_LOGE("%{public}s:%{public}d g_acm->iface[%{public}hhu] is null.", __func__, __LINE__, i);
            return HDF_FAILURE;
        }
    }
    if (g_writeOrRead == TEST_WRITE) {
        g_acm->dataPipe = GetPipe(g_acm, USB_PIPE_TYPE_BULK, USB_PIPE_DIRECTION_OUT);
    } else {
        g_acm->dataPipe = GetPipe(g_acm, USB_PIPE_TYPE_BULK, USB_PIPE_DIRECTION_IN);
    }
    if (g_acm->dataPipe == NULL) {
        HDF_LOGE("%{public}s:%{public}d dataPipe is null", __func__, __LINE__);
        return HDF_FAILURE;
    }

    g_acm->dataSize = TEST_LENGTH;
    if (AcmDataBufAlloc(g_acm) < 0) {
        HDF_LOGE("%{public}s:%{public}d AcmDataBufAlloc failed", __func__, __LINE__);
        return HDF_FAILURE;
    }

    return ret;
}

static int32_t UsbSpeedRequestHandle(void)
{
    for (int32_t i = 0; i < TEST_CYCLE; i++) {
        struct AcmDb *snd = &(g_acm->db[i]);
        snd->request = UsbAllocRequest(InterfaceIdToHandle(g_acm, g_acm->dataPipe->interfaceId), 0, g_acm->dataSize);
        if (snd->request == NULL) {
            printf("%s:%d snd request fail", __func__, __LINE__);
        }
        g_acm->transmitting++;
        struct UsbRequestParams parmas = {};
        parmas.interfaceId = g_acm->dataPipe->interfaceId;
        parmas.pipeAddress = g_acm->dataPipe->pipeAddress;
        parmas.pipeId = g_acm->dataPipe->pipeId;
        parmas.callback = AcmTestBulkCallback;
        parmas.requestType = USB_REQUEST_PARAMS_DATA_TYPE;
        parmas.timeout = USB_CTRL_SET_TIMEOUT;
        parmas.dataReq.numIsoPackets = 0;
        parmas.userData = (void *)snd;
        parmas.dataReq.length = (int)g_acm->dataSize;
        parmas.dataReq.buffer = snd->buf;
        parmas.dataReq.directon = (((uint8_t)g_acm->dataPipe->pipeDirection) >> USB_PIPE_DIR_OFFSET) & 0x1;
        snd->dbNum = g_acm->transmitting;
        int32_t rc = UsbFillRequest(snd->request, InterfaceIdToHandle(g_acm, g_acm->dataPipe->interfaceId), &parmas);
        if (rc != HDF_SUCCESS) {
            printf("%s:UsbFillRequest failed,ret = %d \n", __func__, rc);
            return rc;
        }
    }

    return HDF_SUCCESS;
}

static void UsbSpeedDdkExit(void)
{
    uint8_t i;

    for (i = 0; i < TEST_CYCLE; i++) {
        struct AcmDb *snd = &(g_acm->db[i]);
        UsbCancelRequest(snd->request);
        UsbFreeRequest(snd->request);
    }
    AcmDataBufFree(g_acm);
    for (i = 0; i < g_acm->interfaceCnt; i++) {
        UsbCloseInterface(g_acm->devHandle[i], false);
        UsbReleaseInterface(g_acm->iface[i]);
    }
    UsbExitHostSdk(NULL);
    OsalSemDestroy(&timeSem);
    g_acm->busy = false;
}

static int32_t UsbSerialSpeed(struct HdfSBuf *data)
{
    int32_t ifaceNum = 3;
    int32_t ret = HDF_SUCCESS;
    struct UsbSpeedTest *input = NULL;
    uint32_t size = 0;
    int32_t i;
    if (g_acm->busy) {
        printf("%s: speed test busy\n", __func__);
        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))) {
        printf("%s: sbuf read buffer failed", __func__);
        ret = HDF_ERR_IO;
        goto END;
    }

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

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

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

    printf("test SDK API [%s]\n", g_writeOrRead ? "write" : "read");

    for (i = 0; i < TEST_CYCLE; i++) {
        if (SerialBegin(g_acm) != HDF_SUCCESS) {
            printf("SerialBegin error!\n");
        }
        g_send_count++;
    }

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

    UsbSpeedDdkExit();

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

static int32_t AcmDeviceDispatch(
    struct HdfDeviceIoClient * const client, int32_t cmd, struct HdfSBuf *data, struct HdfSBuf *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 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_usbSdkApiSpeedDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "usb_sdkapispeed",
    .Bind = AcmDriverBind,
    .Init = AcmDriverInit,
    .Release = AcmDriverRelease,
};

HDF_INIT(g_usbSdkApiSpeedDriverEntry);