#include <errno.h>
#include <fcntl.h>
#include <glob.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <caca.h>

#include "neercs.h"

char * build_socket_path(char *socket_dir, char *session_name, int client)
{
    char *path, *dir;
    path = (char *)malloc(PATH_MAX+1);
    dir = socket_dir;
    if(!dir)
        dir = getenv("NEERCSDIR");
    if(!dir)
        dir = getenv("TMPDIR");
    if(!dir)
        dir = "/tmp";
    if(path)
        snprintf(path, PATH_MAX+1, "%s/neercs.%s%s.sock", dir, session_name, client?"-clt":"");
    return path;
}

int create_client_socket(struct screen_list* screen_list)
{
    int sock;
    struct sockaddr_un myaddr;

    sock = socket(AF_UNIX, SOCK_DGRAM, 0);

    if(sock < 0)
    {
        perror("create_client_socket:socket");
        return errno;
    }

    memset(&myaddr, 0, sizeof(struct sockaddr_un));

    myaddr.sun_family = AF_UNIX;
    strncpy(myaddr.sun_path, screen_list->c_socket_path, sizeof(myaddr.sun_path) - 1);

    unlink(screen_list->c_socket_path);

    if(bind(sock, (struct sockaddr *)&myaddr, sizeof(struct sockaddr_un)) < 0)
    {
        free(screen_list->c_socket_path);
        screen_list->c_socket_path = NULL;
        close(sock);
        perror("create_client_socket:bind");
        return errno;
    }
    fcntl(sock, F_SETFL, O_NONBLOCK);

    debug("Client listening on %s (%d)", screen_list->c_socket_path, sock);

    screen_list->c_socket = sock;

    return 0;
}

int create_server_socket(struct screen_list* screen_list)
{
    int sock;
    struct sockaddr_un myaddr;

    sock = socket(AF_UNIX, SOCK_DGRAM, 0);

    if(sock < 0)
    {
        perror("create_server_socket:socket");
        return errno;
    }

    memset(&myaddr, 0, sizeof(struct sockaddr_un));

    myaddr.sun_family = AF_UNIX;
    strncpy(myaddr.sun_path, screen_list->s_socket_path, sizeof(myaddr.sun_path) - 1);

    unlink(screen_list->c_socket_path);

    if(bind(sock, (struct sockaddr *)&myaddr, sizeof(struct sockaddr_un)) < 0)
    {
        free(screen_list->s_socket_path);
        screen_list->s_socket_path = NULL;
        close(sock);
        perror("create_socket:bind");
        return errno;
    }
    fcntl(sock, F_SETFL, O_NONBLOCK);

    debug("Client listening on %s (%d)", screen_list->s_socket_path, sock);

    screen_list->s_socket = sock;

    return 0;
}

char ** list_sockets(char *socket_dir, char *session_name)
{
    char *pattern, *dir;
    glob_t globbuf;

    globbuf.gl_pathv = NULL;

    pattern = (char *)malloc(PATH_MAX+1);

    dir = socket_dir;
    if(!dir)
        dir = getenv("NEERCSDIR");
    if(!dir)
        dir = getenv("TMPDIR");
    if(!dir)
        dir = "/tmp";

    if(!pattern)
        return globbuf.gl_pathv;

    if(session_name && strlen(session_name)+strlen(dir)+13<PATH_MAX)
        sprintf(pattern, "%s/neercs.%s.sock", dir, session_name);
    else
        snprintf(pattern, PATH_MAX, "%s/neercs.*.sock", dir);
    pattern[PATH_MAX] = '\0';

    debug("Looking for sockets in the form %s", pattern);

    glob(pattern, 0, NULL, &globbuf);

    free(pattern);

    return globbuf.gl_pathv;
}

char * connect_server(struct screen_list* screen_list)
{
    int sock;
    struct sockaddr_un addr;
    char *p, *s;

    debug("Connecting to %s", screen_list->s_socket_path);

    /* Open the socket */
    if ((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
        perror("connect_server:socket");
        return NULL;
    }

    memset(&addr,0,sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path,screen_list->s_socket_path);
    if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        fprintf(stderr, "Failed to connect to %s: %s\n", screen_list->s_socket_path, strerror(errno));
        return NULL;
    }
    fcntl(sock, F_SETFL, O_NONBLOCK);

    screen_list->s_socket = sock;

    p = strrchr(screen_list->s_socket_path, '/');
    p+=8; /* skip neercs. */
    s = strdup(p);
    p = strrchr(s, '.');
    *p = '\0'; /* drop .sock */
    p = strdup(s);
    free(s);
    return p;
}

int connect_client(struct screen_list* screen_list)
{
    int sock;
    struct sockaddr_un addr;

    debug("Connecting to %s", screen_list->c_socket_path);

    /* Open the socket */
    if ((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
        perror("connect_client:socket");
        return 1;
    }

    memset(&addr,0,sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path,screen_list->c_socket_path);
    if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        fprintf(stderr, "Failed to connect to %s: %s\n", screen_list->c_socket_path, strerror(errno));
        return 1;
    }
    fcntl(sock, F_SETFL, O_NONBLOCK);

    screen_list->c_socket = sock;

    return 0;
}

int request_attach(struct screen_list* screen_list)
{
    char buf[32];
    int bytes;

    bytes = snprintf(buf, sizeof(buf)-1, "ATTACH %10d%c%10d",
                     cucul_get_canvas_width(screen_list->cv),
                     ' ',
                     cucul_get_canvas_height(screen_list->cv));
    buf[bytes] = '\0';
    debug("Requesting attach: %s", buf);
    return write(screen_list->s_socket, buf, strlen(buf)) <= 0;
}

int send_event(caca_event_t ev, struct screen_list* screen_list)
{
    enum caca_event_type t;

    t = caca_get_event_type(&ev);

    if(t & CACA_EVENT_KEY_PRESS)
    {
        char buf[16];
        int bytes;
        bytes = snprintf(buf, sizeof(buf)-1, "KEY %d", caca_get_event_key_ch(&ev));
        buf[bytes] = '\0';
        debug("Sending key press to server: %s", buf);
        return write(screen_list->s_socket, buf, strlen(buf)) <= 0;
    }
    else if(t & CACA_EVENT_RESIZE)
    {
        char buf[32];
        int bytes;
        bytes = snprintf(buf, sizeof(buf)-1, "RESIZE %10d%c%10d",
                         caca_get_event_resize_width(&ev),
                         ' ',
                         caca_get_event_resize_height(&ev));
        buf[bytes] = '\0';
        debug("Sending resize to server: %s", buf);
        return write(screen_list->s_socket, buf, strlen(buf)) <= 0;
    }
    else if(t & CACA_EVENT_QUIT)
        return write(screen_list->s_socket, "QUIT", strlen("QUIT")) <= 0;

    return 0;
}

