#include "domain_tcp_client.h"

#include <errno.h>

#ifdef __ANDROID__
    #include <android-base/file.h>
    #include <cutils/sockets.h>
#endif

#ifdef LOG_TAG
    #undef LOG_TAG
#endif
#define LOG_TAG "aiqtool"

extern struct ucred* g_aiqCred;

#ifdef __ANDROID__
    #define ANDROID_RESERVED_SOCKET_PREFIX "/dev/socket/"

// Connects to /dev/socket/<name> and returns the associated fd or returns -1 on error.
// O_CLOEXEC is always set.
static int socket_local_client(const std::string& name, int type)
{
    sockaddr_un addr = {.sun_family = AF_LOCAL};

    std::string path = "/dev/socket/" + name;
    if (path.size() + 1 > sizeof(addr.sun_path)) {
        return -1;
    }
    strlcpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path));

    int fd = socket(AF_LOCAL, type | SOCK_CLOEXEC, 0);
    if (fd == -1) {
        return -1;
    }

    if (connect(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1) {
        close(fd);
        return -1;
    }

    return fd;
}
#endif

DomainTCPClient::DomainTCPClient()
{
    sock = -1;
}

DomainTCPClient::~DomainTCPClient()
{
    Close();
}

void DomainTCPClient::Close()
{
    if (g_aiqCred) {
        delete g_aiqCred;
        g_aiqCred = nullptr;
    }
    if (sock > 0) {
        close(sock);
        sock = -1;
    }
}

bool DomainTCPClient::Setup(string domainPath)
{
#ifdef __ANDROID__
    if (sock > 0) {
        // close(sock);
        return true;
    }
    sock = socket_local_client(android::base::Basename(domainPath), SOCK_STREAM);
    if (sock < 0) {
        LOG_ERROR("Could not create domain socket %s\n", strerror(errno));
        return false;
    } else {
        LOG_DEBUG("Android,Create domain socket success.\n");
    }
#else
    if (sock == -1) {
        sock = socket(AF_UNIX, SOCK_STREAM, 0);
        if (sock == -1) {
            LOG_ERROR("Could not create domain socket\n");
            return false;
        } else {
            LOG_DEBUG("Linux,Create domain socket success.\n");
        }
    }

    server.sun_family = AF_UNIX;
    strcpy(server.sun_path, domainPath.c_str());
    struct timeval timeout = {1, 0}; // 1 sec
    int ret = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));

    if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0) {
        LOG_ERROR("connect domain server failed. Error\n");
        close(sock);
        sock = -1;
        return false;
    }
#endif
    if (g_aiqCred == nullptr) {
        g_aiqCred = new ucred();
    }
    socklen_t len = sizeof(struct ucred);
    if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, (void*)g_aiqCred, &len) == -1) {
        // close(sock);
        // sock = -1;
        LOG_ERROR("getsockopt peer credentials not supported");
    } else {
        LOG_DEBUG("Credentials from SO_PEERCRED: pid=%ld, euid=%ld, egid=%ld\n", (long)g_aiqCred->pid,
                  (long)g_aiqCred->uid, (long)g_aiqCred->gid);
    }
    return true;
}

bool DomainTCPClient::Send(string data)
{
    if (sock != -1) {
        if (send(sock, data.c_str(), strlen(data.c_str()), 0) < 0) {
            LOG_ERROR("Send failed : %s\n", data.c_str());
            Close();
            return false;
        }
    } else {
        return false;
    }
    return true;
}

int DomainTCPClient::Send(char* buff, int size)
{
    int ret = -1;
    if (sock != -1) {
        ret = send(sock, buff, size, 0);
        if (ret < 0 && (errno != EAGAIN && errno != EINTR)) {
            LOG_ERROR("Send buff size %d failed\n", size);
            Close();
            return ret;
        }
    }
    return ret;
}

string DomainTCPClient::Receive(int size)
{
    char buffer[size];
    memset(&buffer[0], 0, sizeof(buffer));
    string reply;
    if (sock < 0) {
        return "\0";
    }
    ssize_t ret = recv(sock, buffer, size, 0);
    if (ret < 0 && (errno != EAGAIN && errno != EINTR)) {
        LOG_ERROR("domain receive 1 failed %s!\n", strerror(errno));
        Close();
        return "\0";
    }
    buffer[size - 1] = '\0';
    reply = buffer;
    return reply;
}

int DomainTCPClient::Receive(char* buff, int size)
{
    ssize_t ret = -1;
    if (sock < 0) {
        return -1;
    }
    memset(buff, 0, size);
    ret = recv(sock, buff, size, 0);
    if (ret < 0 && (errno != EAGAIN && errno != EINTR)) {
        LOG_ERROR("domain receive 2 failed %s!\n", strerror(errno));
        Close();
        return -1;
    }
    return ret;
}