// Written by retoor@molodetz.nl

// The code is an implementation for managing network sockets. It provides functionality to create, connect, and manage a server or client
// sockets, handle multiple connections, read and write data, and handle events such as connecting, receiving data, and closing a
// connection.

// External import summary:
// This code uses external libraries such as rmalloc.h and rio.h for memory allocation and I/O operations.

// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#ifndef NSOCK_H
#define NSOCK_H
#include <unistd.h>
#include "rmalloc.h"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#include "rio.h"

int *nsock_socks = NULL;
int *nsock_readable = NULL;
void **nsock_data = NULL;
int nsock_server_fd = 0;
int nsock_max_socket_fd = 0;

typedef enum nsock_type_t { NSOCK_NONE = 0, NSOCK_SERVER, NSOCK_CLIENT, NSOCK_UPSTREAM } nsock_type_t;

typedef struct nsock_it {
    int fd;
    int *upstreams;
    bool connected;
    bool downstream;
    unsigned int upstream_count;
    nsock_type_t type;
} nsock_t;

nsock_t **nsocks = NULL;
int nsocks_count = 0;

void (*nsock_on_connect)(int fd) = NULL;
void (*nsock_on_data)(int fd) = NULL;
void (*nsock_on_close)(int fd) = NULL;
void nsock_on_before_data(int fd);

nsock_t *nsock_get(int fd) {
    if (nsock_socks[fd] == 0) {
        return NULL;
    }
    if (fd >= nsocks_count || nsocks[fd] == NULL) {
        if (fd >= nsocks_count) {
            nsocks_count = fd + 1;
            nsocks = realloc(nsocks, sizeof(nsock_t *) * sizeof(nsock_t) * nsocks_count);
            nsocks[fd] = calloc(1, sizeof(nsock_t));
        }
        nsocks[fd]->upstreams = NULL;
        nsocks[fd]->fd = fd;
        nsocks[fd]->connected = false;
        nsocks[fd]->downstream = false;
        nsocks[fd]->upstream_count = 0;
        nsocks[fd]->type = NSOCK_CLIENT;
        return nsocks[fd];
    }
    return nsocks[fd];
}

void nsock_close(int fd) {
    if (nsock_on_close)
        nsock_on_close(fd);
    nsock_t *sock = nsock_get(fd);
    if (sock && sock->connected) {
        sock->connected = false;
        for (unsigned int i = 0; i < sock->upstream_count; i++) {
            nsock_t *upstream = nsock_get(sock->upstreams[i]);
            if (upstream->connected)
                nsock_close(sock->upstreams[i]);
            sock->upstreams[i] = 0;
        }
        if (sock->upstream_count) {
            free(sock->upstreams);
        }
        sock->upstream_count = 0;
    }
    nsock_socks[fd] = 0;
    close(fd);
}

nsock_t *nsock_create(int fd, nsock_type_t type) {
    if (fd <= 0)
        return NULL;
    nsock_socks[fd] = fd;
    nsock_t *sock = nsock_get(fd);
    sock->connected = true;
    sock->downstream = false;
    sock->type = type;
    return sock;
}

int *nsock_init(int socket_count) {
    if (nsock_socks) {
        return nsock_socks;
    }
    nsock_socks = calloc(1, sizeof(int) * sizeof(int *) * socket_count + 1);
    if (nsock_data) {
        free(nsock_data);
        nsock_data = NULL;
    }
    nsock_data = malloc(sizeof(void **) * socket_count + 1);
    nsock_socks[socket_count] = -1;
    return nsock_socks;
}

void nsock_free() {
    if (nsock_socks)
        free(nsock_socks);
    if (nsock_readable)
        free(nsock_readable);
    nsock_server_fd = 0;
    nsock_max_socket_fd = 0;
    if (nsock_data) {
        exit(1);
    }
}

void nsock_add_upstream(int source, int target, bool downstream) {
    if (!nsock_socks[target])
        return;
    if (!nsock_socks[source])
        return;
    nsock_t *sock = nsock_get(source);
    nsock_t *sock_target = nsock_get(target);
    sock_target->type = NSOCK_UPSTREAM;
    sock->upstreams = realloc(sock->upstreams, sizeof(int) * (sock->upstream_count + 1));
    sock->downstream = downstream;
    sock->upstreams[sock->upstream_count] = target;
    sock->upstream_count++;
}

void *nsock_get_data(int socket) { return nsock_data[socket]; }

void nsock_set_data(int socket, void *data) { nsock_data[socket] = data; }

int nsock_connect(const char *host, unsigned int port) {
    char port_str[10] = {0};
    sprintf(port_str, "%d", port);
    int status;
    int socket_fd = 0;
    struct addrinfo hints;
    struct addrinfo *res;
    struct addrinfo *p;
    if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        return false;
    }
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    if ((status = getaddrinfo(host, port_str, &hints, &res)) != 0) {
        return 0;
    }
    for (p = res; p != NULL; p = p->ai_next) {
        if ((socket_fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            continue;
        }
        if (connect(socket_fd, p->ai_addr, p->ai_addrlen) == -1) {
            close(socket_fd);
            continue;
        }
        break;
    }
    if (p == NULL) {
        freeaddrinfo(res);
        return 0;
    }
    freeaddrinfo(res);
    if (socket_fd) {
        if (nsock_socks == NULL) {
            nsock_init(2048);
        }
        nsock_socks[socket_fd] = socket_fd;
        nsock_t *sock = nsock_create(socket_fd, NSOCK_CLIENT);
        sock->connected = true;
    }
    return socket_fd;
}

void nsock_listen(int port) {
    int server_fd;
    struct sockaddr_in address;
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("Socket failed");
        exit(EXIT_FAILURE);
    }
    int opt = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(port);
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("Bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    if (listen(server_fd, 8096) < 0) {
        perror("Listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    nsock_server_fd = server_fd;
}

int *nsock_select(suseconds_t timeout) {
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = timeout;
    int server_fd = nsock_server_fd;
    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(server_fd, &rfds);
    int *socks = nsock_socks;
    fd_set efds;
    FD_ZERO(&efds);
    nsock_max_socket_fd = server_fd;
    for (int i = 0; socks[i] != -1; i++) {
        if (i == server_fd)
            continue;
        if (!socks[i])
            continue;
        if (socks[i] > nsock_max_socket_fd) {
            nsock_max_socket_fd = socks[i];
        }
        FD_SET(socks[i], &rfds);
        FD_SET(socks[i], &efds);
    }
    int activity = select(nsock_max_socket_fd + 1, &rfds, NULL, &efds, timeout == 0 ? NULL : &tv);
    if ((activity < 0) && (errno != EINTR)) {
        perror("Select error\n");
        return NULL;
    } else if (activity == 0) {
        return NULL;
    }
    if (FD_ISSET(server_fd, &rfds)) {
        struct sockaddr_in address;
        int addrlen = sizeof(address);
        address.sin_family = AF_INET;
        address.sin_addr.s_addr = INADDR_ANY;
        int new_socket = 0;
        if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {
            perror("Accept failed");
        } else {
            nsock_socks[new_socket] = new_socket;
            nsock_create(new_socket, NSOCK_CLIENT);
            if (nsock_on_connect)
                nsock_on_connect(new_socket);
            if (new_socket > nsock_max_socket_fd)
                nsock_max_socket_fd = new_socket;
        }
    }
    if (nsock_readable) {
        free(nsock_readable);
    }
    nsock_readable = calloc(1, sizeof(int *) + sizeof(int) * (nsock_max_socket_fd + 2));
    nsock_readable[nsock_max_socket_fd + 1] = -1;
    nsock_readable[0] = 0;
    int readable_count = 0;
    for (int i = 0; i < nsock_max_socket_fd + 1; i++) {
        nsock_t *sock = nsock_get(i);
        if (!sock)
            continue;
        if (FD_ISSET(i, &efds)) {
            nsock_close(nsock_socks[i]);
            nsock_socks[i] = 0;
            nsock_readable[i] = 0;
        } else if (FD_ISSET(i, &rfds) && i != server_fd) {
            nsock_readable[i] = i;
            readable_count++;
            nsock_on_before_data(i);
        } else {
            nsock_readable[i] = 0;
            sock->connected = false;
        }
    }
    return nsock_readable;
}

unsigned char *nsock_read(int fd, int length) {
    if (!nsock_socks[fd])
        return NULL;
    unsigned char *buffer = malloc(length + 1);
    int bytes_read = read(fd, buffer, length);
    if (bytes_read <= 0) {
        nsock_close(fd);
        return NULL;
    }
    buffer[bytes_read] = 0;
    return buffer;
}

unsigned char *nsock_read_all(int fd, int length) {
    if (!nsock_socks[fd])
        return NULL;
    unsigned char *buffer = malloc(length + 1);
    int bytes_read = 0;
    while (bytes_read < length) {
        int bytes_chunk = read(fd, buffer + bytes_read, length - bytes_read);
        if (bytes_chunk <= 0) {
            nsock_close(fd);
            return NULL;
        }
        bytes_read += bytes_chunk;
    }
    buffer[bytes_read] = 0;
    return buffer;
}

int nsock_write_all(int fd, unsigned char *data, int length) {
    if (!nsock_socks[fd])
        return 0;
    int bytes_written = 0;
    while (bytes_written < length) {
        int bytes_chunk = write(fd, data + bytes_written, length - bytes_written);
        if (bytes_chunk <= 0) {
            nsock_close(fd);
            return 0;
        }
        bytes_written += bytes_chunk;
    }
    return bytes_written;
}

int nsock_execute_upstream(int source, size_t buffer_size) {
    int result = 0;
    nsock_t *sock = nsock_get(source);
    unsigned char data[buffer_size];
    memset(data, 0, buffer_size);
    int bytes_read = read(source, data, buffer_size);
    if (bytes_read <= 0) {
        nsock_close(source);
        return 0;
    }
    bool downstreamed = false;
    for (unsigned int i = 0; i < sock->upstream_count; i++) {
        if (!nsock_socks[sock->upstreams[i]])
            continue;
        int bytes_sent = nsock_write_all(sock->upstreams[i], data, bytes_read);
        if (bytes_sent <= 0) {
            nsock_close(sock->upstreams[i]);
            continue;
        }
        if (sock->downstream && !downstreamed) {
            downstreamed = true;
            unsigned char data[4096];
            memset(data, 0, 4096);
            int bytes_read = read(sock->upstreams[i], data, 4096);
            if (bytes_read <= 0) {
                nsock_close(source);
                return 0;
            }
            int bytes_sent = nsock_write_all(sock->fd, data, bytes_read);
            if (bytes_sent <= 0) {
                nsock_close(sock->upstreams[i]);
                return 0;
            }
        }
        result++;
    }
    return result;
}

void nsock_on_before_data(int fd) {
    if (!nsock_socks[fd])
        return;
    nsock_t *sock = nsock_get(fd);
    if (sock->upstream_count) {
        int upstreamed_to_count = nsock_execute_upstream(fd, 4096);
        if (!upstreamed_to_count) {
            nsock_close(fd);
        }
        return;
    } else if (sock->type == NSOCK_UPSTREAM) {
        while (rfd_wait(sock->fd, 0)) {
            unsigned char *data = nsock_read(fd, 4096);
            (void)data;
        }
    }
    if (nsock_on_data)
        nsock_on_data(fd);
}

void nsock(int port, void (*on_connect)(int fd), void (*on_data)(int fd), void (*on_close)(int fd)) {
    nsock_init(2048);
    nsock_listen(port);
    nsock_on_connect = on_connect;
    nsock_on_data = on_data;
    nsock_on_close = on_close;
    int serve_in_terminal = nsock_on_connect == NULL && nsock_on_data == NULL && nsock_on_close == NULL;
    while (1) {
        int *readable = nsock_select(0);
        if (!serve_in_terminal)
            continue;
        if (!readable)
            continue;
        for (int i = 0; readable[i] != -1; i++) {
            if (!readable[i])
                continue;
            char buffer[1024] = {0};
            int bytes_read = read(readable[i], buffer, 1);
            buffer[bytes_read] = 0;
            if (bytes_read <= 0) {
                nsock_close(readable[i]);
                continue;
            }
            if (write(readable[i], buffer, bytes_read) <= 0) {
                nsock_close(readable[i]);
                continue;
            }
        }
    }
}
#endif