/*
 * 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 "nstackx_event.h"
#include "nstackx_log.h"
#include "nstackx_error.h"
#include "nstackx_util.h"
#include "nstackx_epoll.h"
#include "securec.h"

#define TAG "nStackXEvent"

typedef struct {
    EventHandle handle;
    void *arg;
} EventInfo;

EventNode *SearchEventNode(const List *eventNodeChain, EpollDesc epollfd);

void CloseNodePipe(const EventNode *node)
{
    CloseDesc(node->pipeFd[PIPE_OUT]);
    CloseDesc(node->pipeFd[PIPE_IN]);
}

static void EventProcessHandle(void *arg)
{
    int32_t ret;
    EventInfo event = {0};
    EpollTask *task = arg;
    EventNode *node = container_of(task, EventNode, task);

#ifdef NSTACKX_WITH_LITEOS_M
    ret = (int32_t)recvfrom(node->pipeFd[PIPE_OUT], &event, sizeof(event), 0, NULL, NULL);
#else
    ret = (int32_t)read(node->pipeFd[PIPE_OUT], &event, sizeof(event));
#endif
    if (ret != (int32_t)sizeof(event)) {
        LOGE(TAG, "failed to read from pipe: %d", GetErrno());
        return;
    }

    if (event.handle != NULL) {
        event.handle(event.arg);
    }
}

int32_t PostEvent(const List *eventNodeChain, EpollDesc epollfd, EventHandle handle, void *arg)
{
    int32_t ret;
    EventNode *node = NULL;
    EventInfo event = {
        .handle = handle,
        .arg = arg,
    };

    if (eventNodeChain == NULL || handle == NULL) {
        return NSTACKX_EINVAL;
    }

    node = SearchEventNode(eventNodeChain, epollfd);
    if (node == NULL) {
        LOGE(TAG, "Cannot find event node for %d", epollfd->recvFd);
        return NSTACKX_EFAILED;
    }
#ifdef NSTACKX_WITH_LITEOS_M
    ret = (int32_t)sendto(node->pipeFd[PIPE_IN], &event, sizeof(event), 0, NULL, 0);
#else
    ret = (int32_t)write(node->pipeFd[PIPE_IN], &event, sizeof(event));
#endif
    if (ret != (int32_t)sizeof(event)) {
        LOGE(TAG, "failed to write to pipe: %d", errno);
        return NSTACKX_EFAILED;
    }

    return NSTACKX_EOK;
}

void ClearEvent(const List *eventNodeChain, EpollDesc epollfd)
{
    EventNode *node = NULL;
    EventInfo event = {0};
    int32_t eventLen = (int32_t)sizeof(event);
    if (eventNodeChain == NULL) {
        LOGE(TAG, "eventNodeChain is null");
        return;
    }

    node = SearchEventNode(eventNodeChain, epollfd);
    if (node == NULL) {
        return;
    }

    int32_t ret = eventLen;
    while (ret == eventLen) {
#ifdef NSTACKX_WITH_LITEOS_M
        ret = (int32_t)recvfrom(node->pipeFd[PIPE_OUT], &event, sizeof(event), 0, NULL, NULL);
#else
        ret = (int32_t)read(node->pipeFd[PIPE_OUT], &event, sizeof(event));
#endif
        if (ret != eventLen) {
            break;
        }

        if (event.handle != NULL) {
            event.handle(event.arg);
        }
    }
}

static int32_t CreateNonBlockPipe(EventNode *node)
{
    struct EpollDescStr fds;

    if (CreateEpollFdPair(&fds) != NSTACKX_EOK) {
        LOGE(TAG, "CreateEpollFdPair failed");
        return NSTACKX_EFAILED;
    }

    node->pipeFd[PIPE_OUT] = fds.recvFd;
    node->pipeFd[PIPE_IN] = fds.sendFd;

    return NSTACKX_EOK;
}

int32_t EventModuleInit(List *eventNodeChain, EpollDesc epollfd)
{
    List *pos = NULL;
    EventNode *node = NULL;
    if (eventNodeChain == NULL) {
        LOGE(TAG, "eventNodeChain is null");
        return NSTACKX_EINVAL;
    }
    LIST_FOR_EACH(pos, eventNodeChain) {
        node = (EventNode *)pos;
        if (IsEpollDescEqual(node->epollfd, epollfd)) {
            return NSTACKX_EOK;
        }
    }

    node = calloc(1, sizeof(EventNode));
    if (node == NULL) {
        LOGE(TAG, "calloc failed");
        return NSTACKX_ENOMEM;
    }

    if (CreateNonBlockPipe(node) != NSTACKX_EOK) {
        LOGE(TAG, "create pipe failed");
        goto L_ERR_FAILED;
    }

    node->task.taskfd = node->pipeFd[PIPE_OUT];
    node->task.epollfd = epollfd;
    node->task.readHandle = EventProcessHandle;

    node->epollfd = epollfd;
    if (RegisterEpollTask(&node->task, EPOLLIN) != NSTACKX_EOK) {
        LOGE(TAG, "RegisterEpollTask failed");
        CloseNodePipe(node);
        goto L_ERR_FAILED;
    }

    ListInsertTail(eventNodeChain, &(node->list));
    return NSTACKX_EOK;
L_ERR_FAILED:
    free(node);
    return NSTACKX_EFAILED;
}

void DeleteEventNode(EventNode *node)
{
    ListRemoveNode(&node->list);
    if (DeRegisterEpollTask(&node->task) != NSTACKX_EOK) {
        LOGE(TAG, "DeRegisterEpollTask failed");
    }
    CloseNodePipe(node);
    free(node);
}