/*
 +----------------------------------------------------------------------+
 | Swoole                                                               |
 +----------------------------------------------------------------------+
 | This source file is subject to version 2.0 of the Apache license,    |
 | that is bundled with this package in the file LICENSE, and is        |
 | available through the world-wide-web at the following url:           |
 | http://www.apache.org/licenses/LICENSE-2.0.html                      |
 | If you did not receive a copy of the Apache2.0 license and are unable|
 | to obtain it through the world-wide-web, please send a note to       |
 | license@swoole.com so we can mail you a copy immediately.            |
 +----------------------------------------------------------------------+
 | Author: Tianfeng Han  <mikan.tenny@gmail.com>                        |
 +----------------------------------------------------------------------+
 */

#include "server.h"

#include <signal.h>

typedef struct _swFactoryProcess
{
    swPipe *pipes;
    swPipeBuffer *send_buffer;
} swFactoryProcess;

typedef int (*send_func_t)(swServer *, swPipeBuffer *, size_t, void *);

static int swFactoryProcess_start(swFactory *factory);
static int swFactoryProcess_notify(swFactory *factory, swDataHead *event);
static int swFactoryProcess_dispatch(swFactory *factory, swSendData *data);
static int swFactoryProcess_finish(swFactory *factory, swSendData *data);
static int swFactoryProcess_shutdown(swFactory *factory);
static int swFactoryProcess_end(swFactory *factory, int fd);
static void swFactoryProcess_free(swFactory *factory);

static int process_send_packet(swServer *serv, swPipeBuffer *buf, swSendData *resp, send_func_t _send, void* private_data);
static int process_sendto_worker(swServer *serv, swPipeBuffer *buf, size_t n, void *private_data);
static int process_sendto_reactor(swServer *serv, swPipeBuffer *buf, size_t n, void *private_data);

int swFactoryProcess_create(swFactory *factory, uint32_t worker_num)
{
    swFactoryProcess *object = (swFactoryProcess *) SwooleG.memory_pool->alloc(SwooleG.memory_pool, sizeof(swFactoryProcess));
    if (object == NULL)
    {
        swWarn("[Master] malloc[object] failed");
        return SW_ERR;
    }

    factory->object = object;
    factory->dispatch = swFactoryProcess_dispatch;
    factory->finish = swFactoryProcess_finish;
    factory->start = swFactoryProcess_start;
    factory->notify = swFactoryProcess_notify;
    factory->shutdown = swFactoryProcess_shutdown;
    factory->end = swFactoryProcess_end;
    factory->free = swFactoryProcess_free;

    return SW_OK;
}

static int swFactoryProcess_shutdown(swFactory *factory)
{
    int status;
    swServer *serv = (swServer *) factory->ptr;

    if (swoole_kill(serv->gs->manager_pid, SIGTERM) < 0)
    {
        swSysWarn("swKill(%d) failed", serv->gs->manager_pid);
    }

    if (swoole_waitpid(serv->gs->manager_pid, &status, 0) < 0)
    {
        swSysWarn("waitpid(%d) failed", serv->gs->manager_pid);
    }

    return SW_OK;
}

static void swFactoryProcess_free(swFactory *factory)
{
    swServer *serv = (swServer *) factory->ptr;
    swFactoryProcess *object = (swFactoryProcess *) serv->factory.object;

    uint32_t i;

    for (i = 0; i < serv->reactor_num; i++)
    {
        sw_free(serv->pipe_buffers[i]);
    }
    sw_free(serv->pipe_buffers);

    if (serv->stream_socket)
    {
        unlink(serv->stream_socket);
        close(serv->stream_fd);
        sw_free(serv->stream_socket);
    }

    for (i = 0; i < serv->worker_num; i++)
    {
        object->pipes[i].close(&object->pipes[i]);
    }
}

static int swFactoryProcess_start(swFactory *factory)
{
    uint32_t i;
    swServer *serv = (swServer *) factory->ptr;

    if (serv->dispatch_mode == SW_DISPATCH_STREAM)
    {
        serv->stream_socket = swoole_string_format(64, "/tmp/swoole.%d.sock", serv->gs->master_pid);
        if (serv->stream_socket == NULL)
        {
            return SW_ERR;
        }
        int _reuse_port = SwooleG.reuse_port;
        SwooleG.reuse_port = 0;
        serv->stream_fd = swSocket_create_server(SW_SOCK_UNIX_STREAM, serv->stream_socket, 0, 2048);
        if (serv->stream_fd < 0)
        {
            return SW_ERR;
        }
        swoole_fcntl_set_option(serv->stream_fd, 1, 1);
        SwooleG.reuse_port = _reuse_port;
    }

    for (i = 0; i < serv->worker_num; i++)
    {
        if (swServer_worker_create(serv, swServer_get_worker(serv, i)) < 0)
        {
            return SW_ERR;
        }
    }

    serv->reactor_pipe_num = serv->worker_num / serv->reactor_num;

    swFactoryProcess *object = (swFactoryProcess *) serv->factory.object;

    object->pipes = (swPipe *) sw_calloc(serv->worker_num, sizeof(swPipe));
    if (object->pipes == NULL)
    {
        swSysError("malloc[pipes] failed");
        return SW_ERR;
    }

    for (i = 0; i < serv->worker_num; i++)
    {
        if (swPipeUnsock_create(&object->pipes[i], 1, SOCK_DGRAM) < 0)
        {
            sw_free(object->pipes);
            object->pipes = NULL;
            return SW_ERR;
        }
        serv->workers[i].pipe_master = object->pipes[i].getFd(&object->pipes[i], SW_PIPE_MASTER);
        serv->workers[i].pipe_worker = object->pipes[i].getFd(&object->pipes[i], SW_PIPE_WORKER);

        int kernel_buffer_size = SW_UNIXSOCK_MAX_BUF_SIZE;
        setsockopt(serv->workers[i].pipe_master, SOL_SOCKET, SO_SNDBUF, &kernel_buffer_size, sizeof(kernel_buffer_size));
        setsockopt(serv->workers[i].pipe_worker, SOL_SOCKET, SO_SNDBUF, &kernel_buffer_size, sizeof(kernel_buffer_size));

        serv->workers[i].pipe_object = &object->pipes[i];
        swServer_store_pipe_fd(serv, serv->workers[i].pipe_object);
    }

#ifdef HAVE_KQUEUE
    serv->ipc_max_size = SW_IPC_MAX_SIZE;
#else
    int bufsize;
    socklen_t _len = sizeof(bufsize);
    /**
     * Get the maximum ipc[unix socket with dgram] transmission length
     */
    if (getsockopt(serv->workers[0].pipe_master, SOL_SOCKET, SO_SNDBUF, &bufsize, &_len) != 0)
    {
        bufsize = SW_IPC_MAX_SIZE;
    }
    // - dgram header [32 byte]
    serv->ipc_max_size = bufsize - 32;
#endif
    /**
     * alloc memory
     */
    serv->pipe_buffers = (swPipeBuffer **) sw_calloc(serv->reactor_num, sizeof(swPipeBuffer *));
    if (serv->pipe_buffers == NULL)
    {
        swSysError("malloc[buffers] failed");
        return SW_ERR;
    }
    for (i = 0; i < serv->reactor_num; i++)
    {
        serv->pipe_buffers[i] = (swPipeBuffer *) sw_malloc(serv->ipc_max_size);
        if (serv->pipe_buffers[i] == NULL)
        {
            swSysError("malloc[sndbuf][%d] failed", i);
            return SW_ERR;
        }
        bzero(serv->pipe_buffers[i], sizeof(swDataHead));
    }
    object->send_buffer = (swPipeBuffer *) sw_malloc(serv->ipc_max_size);
    if (object->send_buffer == NULL)
    {
        swSysError("malloc[send_buffer] failed");
        return SW_ERR;
    }
    bzero(object->send_buffer, sizeof(swDataHead));

    /**
     * The manager process must be started first, otherwise it will have a thread fork
     */
    if (swManager_start(serv) < 0)
    {
        swWarn("swFactoryProcess_manager_start failed");
        return SW_ERR;
    }
    factory->finish = swFactory_finish;
    return SW_OK;
}

/**
 * [ReactorThread] notify info to worker process
 */
static int swFactoryProcess_notify(swFactory *factory, swDataHead *ev)
{
    swSendData task;
    task.info = *ev;
    task.data = NULL;
    return swFactoryProcess_dispatch(factory, &task);
}

static int process_sendto_worker(swServer *serv, swPipeBuffer *buf, size_t n, void *private_data)
{
    return swReactorThread_send2worker(serv, (swWorker *) private_data, buf, n);
}

static int process_sendto_reactor(swServer *serv, swPipeBuffer *buf, size_t n, void *private_data)
{
    return swWorker_send2reactor(serv, (swEventData *) buf, sizeof(buf->info) + buf->info.len,
            ((swConnection *) private_data)->session_id);
}

/**
 * [ReactorThread] dispatch request to worker
 */
static int swFactoryProcess_dispatch(swFactory *factory, swSendData *task)
{
    swServer *serv = (swServer *) factory->ptr;
    int fd = task->info.fd;

    int target_worker_id = swServer_worker_schedule(serv, fd, task);
    if (target_worker_id < 0)
    {
        switch (target_worker_id)
        {
        case SW_DISPATCH_RESULT_DISCARD_PACKET:
            return SW_ERR;
        case SW_DISPATCH_RESULT_CLOSE_CONNECTION:
            // TODO: close connection
            return SW_ERR;
        default:
            swWarn("invalid target worker id[%d]", target_worker_id);
            return SW_ERR;
        }
    }

    if (swEventData_is_stream(task->info.type))
    {
        swConnection *conn = swServer_connection_get(serv, fd);
        if (conn == NULL || conn->active == 0)
        {
            swWarn("dispatch[type=%d] failed, connection#%d is not active", task->info.type, fd);
            return SW_ERR;
        }
        //server active close, discard data.
        if (conn->closed)
        {
            //Connection has been clsoed by server
            if (!(task->info.type == SW_SERVER_EVENT_CLOSE && conn->close_force))
            {
                return SW_OK;
            }
        }
        //converted fd to session_id
        task->info.fd = conn->session_id;
        task->info.server_fd = conn->server_fd;
    }

    swWorker *worker = swServer_get_worker(serv, target_worker_id);

    //without data
    if (task->data == NULL)
    {
        task->info.flags = 0;
        return swReactorThread_send2worker(serv, worker, &task->info, sizeof(task->info));
    }

    if (task->info.type == SW_SERVER_EVENT_SEND_DATA)
    {
        worker->dispatch_count++;
    }

    /**
     * Multi-Threads
     */
    swPipeBuffer *buf = serv->pipe_buffers[SwooleTG.id];
    buf->info = task->info;

    return process_send_packet(serv, buf, task, process_sendto_worker, worker);
}

static int process_send_packet(swServer *serv, swPipeBuffer *buf, swSendData *resp, send_func_t _send, void* private_data)
{
    const char* data = resp->data;
    uint32_t send_n = resp->info.len;
    off_t offset = 0;

    uint32_t max_length = serv->ipc_max_size - sizeof(buf->info);

    if (send_n <= max_length)
    {
        buf->info.flags = 0;
        buf->info.len = send_n;
        memcpy(buf->data, data, send_n);

        int retval = _send(serv, buf, sizeof(buf->info) + send_n, private_data);
#ifdef __linux__
        if (retval < 0 && errno == ENOBUFS)
        {
            max_length = SW_IPC_BUFFER_SIZE;
            goto _ipc_use_chunk;
        }
#endif
        return retval;
    }

#ifdef __linux__
    _ipc_use_chunk:
#endif
    buf->info.flags = SW_EVENT_DATA_CHUNK;

    while (send_n > 0)
    {
        if (send_n > max_length)
        {
            buf->info.len = max_length;
        }
        else
        {
            buf->info.flags |= SW_EVENT_DATA_END;
            buf->info.len = send_n;
        }

        memcpy(buf->data, data + offset, buf->info.len);

        swTrace("finish, type=%d|len=%d", buf->info.type, buf->info.len);

        if (_send(serv, buf, sizeof(buf->info) + buf->info.len, private_data) < 0)
        {
#ifdef __linux__
            if (errno == ENOBUFS && max_length > SW_BUFFER_SIZE_STD)
            {
                max_length = SW_IPC_BUFFER_SIZE;
                continue;
            }
#endif
            return SW_ERR;
        }

        send_n -= buf->info.len;
        offset += buf->info.len;
    }

    return SW_OK;
}

static bool inline process_is_supported_send_yield(swServer *serv, swConnection *conn)
{
    if (!swServer_dispatch_mode_is_mod(serv))
    {
        return false;
    }
    else
    {
        return swServer_worker_schedule(serv, conn->fd, nullptr) == (int) SwooleWG.id;
    }
}

/**
 * [Worker] send to client, proxy by reactor
 */
static int swFactoryProcess_finish(swFactory *factory, swSendData *resp)
{
    swServer *serv = (swServer *) factory->ptr;
    swFactoryProcess *object = (swFactoryProcess *) serv->factory.object;

    /**
     * More than the output buffer
     */
    if (resp->info.len > serv->buffer_output_size)
    {
        swoole_error_log(
            SW_LOG_WARNING, SW_ERROR_DATA_LENGTH_TOO_LARGE,
            "The length of data [%u] exceeds the output buffer size[%u], "
            "please use the sendfile, chunked transfer mode or adjust the buffer_output_size",
            resp->info.len, serv->buffer_output_size
        );
        return SW_ERR;
    }

    int session_id = resp->info.fd;
    swConnection *conn;
    if (resp->info.type != SW_SERVER_EVENT_CLOSE)
    {
        conn = swServer_connection_verify(serv, session_id);
    }
    else
    {
        conn = swServer_connection_verify_no_ssl(serv, session_id);
    }
    if (!conn)
    {
        swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_NOT_EXIST, "connection[fd=%d] does not exists", session_id);
        return SW_ERR;
    }
    else if ((conn->closed || conn->peer_closed) && resp->info.type != SW_SERVER_EVENT_CLOSE)
    {
        swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_CLOSED,
                "send %d byte failed, because connection[fd=%d] is closed", resp->info.len, session_id);
        return SW_ERR;
    }
    else if (conn->overflow)
    {
        if (serv->send_yield && process_is_supported_send_yield(serv, conn))
        {
            SwooleG.error = SW_ERROR_OUTPUT_SEND_YIELD;
        }
        else
        {
            swoole_error_log(SW_LOG_WARNING, SW_ERROR_OUTPUT_BUFFER_OVERFLOW,
                    "send failed, connection[fd=%d] output buffer has been overflowed", session_id);
        }
        return SW_ERR;
    }

    /**
     * stream
     */
    if (serv->last_stream_fd > 0)
    {
        int _len = resp->info.len;
        int _header = htonl(_len + sizeof(resp->info));
        if (SwooleTG.reactor->write(SwooleTG.reactor, serv->last_stream_fd, (char*) &_header, sizeof(_header)) < 0)
        {
            return SW_ERR;
        }
        if (SwooleTG.reactor->write(SwooleTG.reactor, serv->last_stream_fd, &resp->info, sizeof(resp->info)) < 0)
        {
            return SW_ERR;
        }
        if (SwooleTG.reactor->write(SwooleTG.reactor, serv->last_stream_fd, resp->data, _len) < 0)
        {
            return SW_ERR;
        }
        return SW_OK;
    }

    swPipeBuffer *buf = object->send_buffer;

    buf->info.fd = session_id;
    buf->info.type = resp->info.type;
    buf->info.reactor_id = conn->reactor_id;
    buf->info.server_fd = SwooleWG.id;

    swTrace("worker_id=%d, type=%d",SwooleWG.id, buf->info.type);

    return process_send_packet(serv, buf, resp, process_sendto_reactor, conn);
}

static int swFactoryProcess_end(swFactory *factory, int fd)
{
    swServer *serv = (swServer *) factory->ptr;
    swSendData _send = {};
    swDataHead info = {};

    _send.info.fd = fd;
    _send.info.len = 0;
    _send.info.type = SW_SERVER_EVENT_CLOSE;

    swConnection *conn = swWorker_get_connection(serv, fd);
    if (conn == NULL || conn->active == 0)
    {
        SwooleG.error = SW_ERROR_SESSION_NOT_EXIST;
        return SW_ERR;
    }
    else if (conn->close_force)
    {
        goto _do_close;
    }
    else if (conn->closing)
    {
        swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_CLOSING, "The connection[%d] is closing", fd);
        return SW_ERR;
    }
    else if (conn->closed)
    {
        return SW_ERR;
    }
    else
    {
        _do_close:
        conn->closing = 1;
        if (serv->onClose != NULL)
        {
            info.fd = fd;
            if (conn->close_actively)
            {
                info.reactor_id = -1;
            }
            else
            {
                info.reactor_id = conn->reactor_id;
            }
            info.server_fd = conn->server_fd;
            serv->onClose(serv, &info);
        }
        conn->closing = 0;
        conn->closed = 1;
        conn->close_errno = 0;
        return factory->finish(factory, &_send);
    }
}
