/**
 * @file raw_tcp.cc
 *
 * @brief this is the main file impelents raw tcp transfer
 *
 * @ingroup evcamera
 *
 * @author Bruce Lu
 * Contact: lzbgt@icloud.com
 *
 */

#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <fcntl.h>
#include "raw_tcp.h"

using namespace std;

extern shared_ptr<spdlog::logger> rlogger;

int _raw_connect(std::string host, std::string port, int *socket_, int recv_timeout, int send_timeout)
{
    int rv = -1;
    struct addrinfo hints {};
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET;       // IPv4
    hints.ai_socktype = SOCK_STREAM; // TCP
    hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value
    hints.ai_protocol = 0;

    rlogger->info("raw connect: {}:{}", host, port);

    addrinfo *addrinfo_result;
    rv = ::getaddrinfo(host.c_str(), port.c_str(), &hints, &addrinfo_result);
    if (rv != 0) {
        rlogger->error("raw_connect ::getaddrinfo failed: {}", gai_strerror(rv));
        return rv;
    }

    int last_errno = 0;
    for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) {
        *socket_ = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (*socket_ == -1) {
            last_errno = errno;
            continue;
        }

        struct timeval timeout;
        if(recv_timeout != 0) {
            timeout.tv_sec = recv_timeout;
            timeout.tv_usec = 0;
            if (setsockopt (*socket_, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) < 0) {
                rlogger->warn("raw_connect setsockopt SO_RCVTIMEO failed");
                continue;
            }
        }

        if(send_timeout != 0) {
            timeout.tv_sec = send_timeout;
            if (setsockopt (*socket_, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)) < 0) {
                rlogger->warn("raw_connect setsockopt SO_SNDTIMEO failed");
                continue;
            }
        }

        rv = ::connect(*socket_, rp->ai_addr, rp->ai_addrlen);
        // auto addr = (struct sockaddr_in *)rp->ai_addr;
        // auto hostIp = string(inet_ntoa((struct in_addr)addr->sin_addr));
        if (rv == 0) {
            // auto addr = (struct sockaddr_in *)rp->ai_addr;
            // //auto hostIp = string(inet_ntoa((struct in_addr)addr->sin_addr));
            // rv = fcntl(*socket_, F_SETFL, fcntl(*socket_, F_GETFL, 0) | O_NONBLOCK);
            // if (rv == -1){
            //     rlogger->error("error calling fcntl");
            // }
            rlogger->info("raw_connect success to {}:{}", host, port);
            break;
        }
        else {
            last_errno = errno;
            ::close(*socket_);
            *socket_ = -1;
            rv = -1;
            rlogger->warn("raw_connect failed to {}:{}", host, port);
        }
    }

    ::freeaddrinfo(addrinfo_result);
    return rv;
}


int raw_connect(std::string host, std::string port, int *socket_)
{
    return _raw_connect(host, port, socket_, 0, 0);
}
int raw_connect_nonblock(std::string host, std::string port, int *socket_, int recv_timeout, int send_timeout)
{
    return _raw_connect(host, port, socket_, recv_timeout, send_timeout);
}


string raw_send(int s, const void *buf, size_t len)
{
    string rc;
    ssize_t sent = 0;
    ssize_t ret = 0;
    int retries = 0;
    while(len > 0) {
        ret = ::send(s, (char*)buf + sent, len, MSG_NOSIGNAL);
        if(ret < 0) {
            break;
        }
        len -= ret;
        sent += ret;
    }
    if(len > 0 || ret < 0) {
        rc = fmt::format("failed to send: {}", strerror(errno));
    }
    return rc;
}