#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
extern int errno;
#include <string.h>
#include <dirent.h>

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <sys/un.h>
#include <stdlib.h>
#include <poll.h>

#include "fileops.h"

/* Slurm portability */
#define SLURM_SUCCESS 0
#define SLURM_ERROR 1
/* TODO: fix */
#define FILENAME_PREFIX "pmix_addr"

typedef struct {
    int jobid, stepid;
} local_records_t;

int my_jobid = -1;
int my_stepid = -1;
int sfd = -1, lockfd = -1;

char linkname[FILENAME_MAX];
char usockname[FILENAME_MAX], lockname[FILENAME_MAX];

int prepare_srv_socket(char *path);
void establish_listen_sock(int jobid, int stepid);

int pid_from_usockname(char *us_name, int jobid);
int run_discovery(int jobid, int stepid, int *is_leader);

int main(int argc, char **argv)
{
    int is_leader;
    if (argc < 3) {
        fprintf(stderr,"Not enough arguments\n");
        exit(0);
    }
    my_jobid = atoi(argv[1]);
    my_stepid = atoi(argv[2]);
    snprintf(linkname, FILENAME_MAX, "%s.%d", FILENAME_PREFIX, my_jobid);
    snprintf(usockname, FILENAME_MAX, "%s.%d", linkname, my_stepid);
    snprintf(lockname, FILENAME_MAX, "%s.lock",usockname);

    if (0 > (lockfd = pmix_create_locked(lockname))) {
        fprintf(stderr,"Can't create lock file %s\n", lockname);
        exit(0);
    }

    sfd = prepare_srv_socket(usockname);

    int i = 0;
    while(1) {
        int fd = run_discovery(my_jobid, my_stepid, &is_leader);
        if (is_leader) {
            fprintf("Iteration %d. I am the leader\n", i);
            service_requests(fd);
        } else {
            monitor_leader(fd);
            printf("server closed connection. Repeat resolution\n");
        }
        i++;
    }
}

int run_discovery(int jobid, int my_stepid, int *is_leader)
{
    char lname[FILENAME_MAX], fname[FILENAME_MAX], fname1[FILENAME_MAX];

    *is_leader = 0;
    if (!pmix_leader_is_alive(linkname)) {
        pmix_remove_leader_symlink(linkname);
        if (!symlink(usockname, linkname)) {
            *is_leader = 1;
            return sfd;
        }
    }
    return connect_to_server(linkname);
}


int prepare_srv_socket(char *path)
{
    static struct sockaddr_un sa;
    int ret = 0;

    /* Make sure that socket file doesn't exists */
    if (0 == access(path, F_OK)) {
        /* remove old file */
        if (0 != unlink(path)) {
            /*PMIXP_ERROR_STD*/
            printf("Cannot delete outdated socket fine: %s",
                    path);
            return SLURM_ERROR;
        }
    }

    if (strlen(path) >= sizeof(sa.sun_path)) {
        /*PMIXP_ERROR_STD*/
        printf("UNIX socket path is too long: %lu, max %lu",
                (unsigned long)strlen(path),
                (unsigned long)sizeof(sa.sun_path)-1);
        return SLURM_ERROR;
    }

    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd < 0) {
        /*PMIXP_ERROR_STD*/
        printf("Cannot create UNIX socket");
        return SLURM_ERROR;
    }

    memset(&sa, 0, sizeof(sa));
    sa.sun_family = AF_UNIX;
    strcpy(sa.sun_path, path);
    if (ret = bind(fd, (struct sockaddr*)&sa, SUN_LEN(&sa))) {
        /*PMIXP_ERROR_STD*/
        printf("Cannot bind() UNIX socket %s", path);
        goto err_fd;
    }

    if ((ret = listen(fd, 64))) {
        /*PMIXP_ERROR_STD*/
        printf("Cannot listen(%d, 64) UNIX socket %s", fd, path);
        goto err_bind;
    }
    return fd;

err_bind:
    unlink(path);
err_fd:
    close(fd);
    return ret;
}

int connect_to_server(char *path)
{
    static struct sockaddr_un sa;
    memset(&sa, 0, sizeof(sa));
    sa.sun_family = AF_UNIX;
    strcpy(sa.sun_path, path);

    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd < 0) {
        fprintf(stderr,"Cannot create UNIX socket");
        return -1;
    }

    if (connect(fd, &sa, SUN_LEN(&sa))) {
        close(fd);
        return -1;
    }
    local_records_t rec;
    rec.jobid = my_jobid;
    rec.stepid = my_stepid;
    int ret = write(fd, &rec, sizeof(rec));
    if (sizeof(rec) != ret) {
        close(fd);
        return -1;
    }
    return fd;
}

void service_requests(int fd)
{
    while(1) {
        int cfd;
        if (0 < (cfd = accept(fd, NULL, 0))) {
            local_records_t rec;
            int ret = read(cfd, &rec, sizeof(rec));
            if (ret != sizeof(rec)) {
                fprintf("%s:%d: read mismatch: %d vs %d\n",
                        __FILE__, __LINE__, ret, sizeof(rec));
                exit(0);
            }
            fprintf(stderr,"New client connected: jobid=%d, stepid=%d, fd = %d\n",
                   rec.jobid, rec.stepid, fd);
        }
    }
}

void monitor_leader(int fd)
{
    struct pollfd fds;
    fds.fd = fd;
    fds.events = 0;

    /* Drop shutdown before the check */

    int rc = poll(&fds, 1, -1);
    if (rc < 0) {
        fprintf(stderr,"Get poll error %d: %s", errno, strerror(errno));
        exit(1);
    }
    if (fds.revents != POLLHUP) {
        fprintf(stderr,"revents = %x", fds.revents);
    }
}
