/*
 * Copyright (c) 1994, 2021 Alex Leigh, All Rights Reserved
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF ALEX LEIGH The copyright
 * notice above does not evidence any actual or intended publication of such
 * source code.
 *
 * Disclosure, copying, distribution, or transmission of this code without
 * express permission is prohibited.
 */

#include <mecha.h>
#include <stdio.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>

static int sock_write(struct mSnet_con *con, const void *buf, size_t len, size_t *wrote_len);
static void sock_close(struct mSnet_con *con);
static int sock_read(struct mSnet_con *con, void *buf, size_t len, size_t *read_len);

static struct mSnet_pt pt = {sock_close, sock_write, sock_read};
static struct addrinfo hints;

int mFnet_sock_init() {
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags |= AI_CANONNAME;
    return MECHA_OK;
}

void sock_close(struct mSnet_con *con) {
    int ret = close(con->ud.ud_int);
    if (ret != 0) {
        mFlog_msg(MECHA_LOG_WARN, "close() failed on socket.");
    }
}

/**
 * Attempt a new socket-based connection to the given URL.
 *
 * If the connection is successful, MECHA_OK is returned, otherwise, MECHA_ERR is returned.
 */
int mFnet_sock_connect(struct mSnet_con *con, const struct mSurl *url) {
    struct addrinfo *addrs, *addr;
    int sd;
    int myret = MECHA_OK;
    char portbuf[8];

    if (snprintf(portbuf, 8, "%d", url->port) < 0) {
        return MECHA_ERR;
    };

    int ret = getaddrinfo(url->host, portbuf, &hints, &addrs);

    if (ret != 0) {
        perror("getaddrinfo():");
        mFlog_msg(MECHA_LOG_ERROR, "getaddrinfo() failed.");
        return MECHA_ERR;
    }

    addr = addrs;

    sd = socket(PF_INET, SOCK_STREAM, 0);
    if (sd == -1) {
        myret = MECHA_ERR;
        goto done;
    }

    while (addr) {
        mFlog_msg(MECHA_LOG_DIAG, "Found address: 0x%x", addr);

        switch (addr->ai_family) {
            case AF_INET:
                ret = connect(sd, addr->ai_addr, addr->ai_addrlen);
                if (ret == 0) {
                    con->ud.ud_int = sd;
                    mFnet_pt_set(con, &pt, 0);
                    goto done;
                }
        }

        addr = addr->ai_next;
    }

    myret = MECHA_ERR;

    done:

    freeaddrinfo(addrs);
    return myret;
}

static int sock_write(struct mSnet_con *con, const void *buf, size_t len, size_t *wrote_len) {
    ssize_t ret = write(con->ud.ud_int, buf, len);
    if (ret < 0) return MECHA_ERR;
    *wrote_len = ret;
    return MECHA_OK;
}

/**
 * Attempt to read up to len bytes into buf from the given connection.
 */
static int sock_read(struct mSnet_con *con, void *buf, const size_t len, size_t *read_len) {
    ssize_t rc = read(con->ud.ud_int, buf, len);
    if (rc < 0) return MECHA_ERR;

    *read_len = rc;

    return MECHA_OK;
}