#include "uwsgi.h"

extern struct uwsgi_server uwsgi;


#ifdef UWSGI_UDP

static void cluster_manage_opt(char *key, uint16_t keylen, char *value, uint16_t vallen, void *foobar) {

	add_exported_option( uwsgi_concat2n(key, keylen, "", 0), uwsgi_concat2n(value, vallen, "", 0), 0);

}

void cluster_setup() {

	int rlen;
// get cluster configuration
        if (uwsgi.cluster != NULL) {
                // get multicast socket

                uwsgi.cluster_fd = uwsgi_cluster_join(uwsgi.cluster);

                uwsgi_log("JOINED CLUSTER: %s\n", uwsgi.cluster);

                // ask for cluster options only if bot pre-existent options are set
                if (uwsgi.exported_opts_cnt == 1 && !uwsgi.cluster_nodes) {
                        // now wait max 60 seconds and resend multicast request every 10 seconds
                        for (;;) {
                                uwsgi_log("asking \"%s\" uWSGI cluster for configuration data:\n", uwsgi.cluster);
                                if (uwsgi_send_empty_pkt(uwsgi.cluster_fd, uwsgi.cluster, 99, 0) < 0) {
                                        uwsgi_log("unable to send multicast message to %s\n", uwsgi.cluster);
                                        continue;
                                }
                              waitfd:
                                rlen = uwsgi_waitfd(uwsgi.cluster_fd, 10);
                                if (rlen < 0) {
                                        break;
                                }
                                else if (rlen > 0) {
                                        // receive the packet
                                        char clusterbuf[4096];
                                        if (!uwsgi_hooked_parse_dict_dgram(uwsgi.cluster_fd, clusterbuf, 4096, 99, 1, cluster_manage_opt, NULL)) {
                                                uwsgi_configure();
                                                goto options_parsed;
                                        }
                                        else {
                                                goto waitfd;
                                        }
                                }
                        }
                }
              options_parsed:

                if (!uwsgi.cluster_nodes)
                        uwsgi_cluster_add_me();
        }
}




void uwsgi_cluster_add_node(struct uwsgi_cluster_node *nucn, int type) {

        int i;
        struct uwsgi_cluster_node *ucn;
        char *tcp_port;

#ifdef UWSGI_DEBUG
        uwsgi_log("adding node\n");
#endif

        tcp_port = strchr(nucn->name, ':');
#ifndef UWSGI_ZEROMQ
        if (tcp_port == NULL) {
#else
        char *zmq_dash = strchr(nucn->name, '-');
        if (tcp_port == NULL && zmq_dash == NULL) {
#endif

                fprintf(stdout, "invalid cluster node name %s\n", nucn->name);
                return;
        }

        // first check for already present node
        for (i = 0; i < MAX_CLUSTER_NODES; i++) {
                ucn = &uwsgi.shared->nodes[i];
                if (ucn->name[0] != 0) {
                        if (!strcmp(ucn->name, nucn->name)) {
                                ucn->status = UWSGI_NODE_OK;
                                ucn->last_seen = time(NULL);
                                // update requests
                                ucn->requests = nucn->requests;
                                return;
                        }
                }
        }

        for (i = 0; i < MAX_CLUSTER_NODES; i++) {
                ucn = &uwsgi.shared->nodes[i];

                if (ucn->name[0] == 0) {
                        memcpy(ucn->name, nucn->name, strlen(nucn->name) + 1);
                        memcpy(ucn->nodename, nucn->nodename, strlen(nucn->nodename) + 1);
                        ucn->workers = nucn->workers;
                        ucn->ucn_addr.sin_family = AF_INET;
                        if (tcp_port) {
                                ucn->ucn_addr.sin_port = htons(atoi(tcp_port + 1));
                                tcp_port[0] = 0;
                        }
                        if (nucn->name[0] == 0) {
                                ucn->ucn_addr.sin_addr.s_addr = INADDR_ANY;
                        }
                        else {
#ifdef UWSGI_DEBUG
                                uwsgi_log("%s\n", nucn->name);
#endif
                                ucn->ucn_addr.sin_addr.s_addr = inet_addr(nucn->name);
                        }

                        ucn->type = type;
                        // here memory can be freed, as it is allocated by uwsgi_concat2n
                        if (type != CLUSTER_NODE_DYNAMIC && tcp_port) {
                                tcp_port[0] = ':';
                        }
                        ucn->last_seen = time(NULL);
                        ucn->requests = nucn->requests;
                        uwsgi_log("[uWSGI cluster] added node %s\n", ucn->name);
                        return;
                }
        }

        uwsgi_log("unable to add node %s\n", nucn->name);
}



int uwsgi_cluster_add_me() {

        const char *key1 = "hostname";
        const char *key2 = "address";
        const char *key3 = "workers";
        const char *key4 = "requests";

        char *ptrbuf;
        uint16_t ustrlen;
        char numproc[6];

#ifdef UWSGI_ZEROMQ
        char uuid_zmq_str[37];
        uuid_t uuid_zmq;
        if (!uwsgi.sockets && !uwsgi.zeromq) {
#else
        if (!uwsgi.sockets) {
#endif
                uwsgi_log("you need to specify at least a socket to start a uWSGI cluster\n");
                exit(1);
        }

        snprintf(numproc, 6, "%d", uwsgi.numproc);

        size_t len;

        if (uwsgi.sockets) {
                len = 2 + strlen(key1) + 2 + strlen(uwsgi.hostname) + 2 + strlen(key2) + 2 + strlen(uwsgi.sockets->name) + 2 + strlen(key3) + 2 + strlen(numproc) + 2 + strlen(key4) + 2 + 1;
        }
#ifdef UWSGI_ZEROMQ
        else if (uwsgi.zeromq) {
                uuid_generate(uuid_zmq);
                uuid_unparse(uuid_zmq, uuid_zmq_str);
                len = 2 + strlen(key1) + 2 + strlen(uwsgi.hostname) + 2 + strlen(key2) + 2 + strlen(uuid_zmq_str) + 2 + strlen(key3) + 2 + strlen(numproc) + 2 + strlen(key4) + 2 + 1;
        }
#endif
        else {
                len = 2 + strlen(key1) + 2 + strlen(uwsgi.hostname) + 2 + strlen(key3) + 2 + strlen(numproc) + 2 + strlen(key4) + 2 + 1;
        }
        char *buf = uwsgi_malloc(len);

        ptrbuf = buf;

        ustrlen = strlen(key1);
        *ptrbuf++ = (uint8_t) (ustrlen & 0xff);
        *ptrbuf++ = (uint8_t) ((ustrlen >> 8) & 0xff);
        memcpy(ptrbuf, key1, strlen(key1));
        ptrbuf += strlen(key1);

        ustrlen = strlen(uwsgi.hostname);
        *ptrbuf++ = (uint8_t) (ustrlen & 0xff);
        *ptrbuf++ = (uint8_t) ((ustrlen >> 8) & 0xff);
        memcpy(ptrbuf, uwsgi.hostname, strlen(uwsgi.hostname));
        ptrbuf += strlen(uwsgi.hostname);


        if (uwsgi.sockets && uwsgi.sockets->name) {
                ustrlen = strlen(key2);
                *ptrbuf++ = (uint8_t) (ustrlen & 0xff);
                *ptrbuf++ = (uint8_t) ((ustrlen >> 8) & 0xff);
                memcpy(ptrbuf, key2, strlen(key2));
                ptrbuf += strlen(key2);

                ustrlen = strlen(uwsgi.sockets->name);
                *ptrbuf++ = (uint8_t) (ustrlen & 0xff);
                *ptrbuf++ = (uint8_t) ((ustrlen >> 8) & 0xff);
                memcpy(ptrbuf, uwsgi.sockets->name, strlen(uwsgi.sockets->name));
                ptrbuf += strlen(uwsgi.sockets->name);
        }
#ifdef UWSGI_ZEROMQ
        else if (uwsgi.zeromq) {
                ustrlen = strlen(key2);
                *ptrbuf++ = (uint8_t) (ustrlen & 0xff);
                *ptrbuf++ = (uint8_t) ((ustrlen >> 8) & 0xff);
                memcpy(ptrbuf, key2, strlen(key2));
                ptrbuf += strlen(key2);

                ustrlen = strlen(uuid_zmq_str);
                *ptrbuf++ = (uint8_t) (ustrlen & 0xff);
                *ptrbuf++ = (uint8_t) ((ustrlen >> 8) & 0xff);
                memcpy(ptrbuf, uuid_zmq_str, strlen(uuid_zmq_str));
                ptrbuf += strlen(uuid_zmq_str);
        }
#endif


	ustrlen = strlen(key3);
        *ptrbuf++ = (uint8_t) (ustrlen & 0xff);
        *ptrbuf++ = (uint8_t) ((ustrlen >> 8) & 0xff);
        memcpy(ptrbuf, key3, strlen(key3));
        ptrbuf += strlen(key3);

        ustrlen = strlen(numproc);
        *ptrbuf++ = (uint8_t) (ustrlen & 0xff);
        *ptrbuf++ = (uint8_t) ((ustrlen >> 8) & 0xff);
        memcpy(ptrbuf, numproc, strlen(numproc));
        ptrbuf += strlen(numproc);

        ustrlen = strlen(key4);
        *ptrbuf++ = (uint8_t) (ustrlen & 0xff);
        *ptrbuf++ = (uint8_t) ((ustrlen >> 8) & 0xff);
        memcpy(ptrbuf, key4, strlen(key4));
        ptrbuf += strlen(key4);

        ustrlen = 1;
        *ptrbuf++ = (uint8_t) (ustrlen & 0xff);
        *ptrbuf++ = (uint8_t) ((ustrlen >> 8) & 0xff);
        memcpy(ptrbuf, "0", 1);
        ptrbuf += 1;


        uwsgi_string_sendto(uwsgi.cluster_fd, 95, 0, (struct sockaddr *) &uwsgi.mc_cluster_addr, sizeof(uwsgi.mc_cluster_addr), buf, len);

        free(buf);

#ifdef UWSGI_DEBUG
        uwsgi_log("add_me() successfull\n");
#endif

        return 0;
}


int uwsgi_cluster_join(char *name) {

        int fd;
        char *cp;
        int broadcast = 0;



        if (name[0] == ':') {
                fd = bind_to_udp(name, 0, 1);
                broadcast = 1;
        }
        else {
                fd = bind_to_udp(name, 1, 0);
        }

        if (fd >= 0) {
                cp = strchr(name, ':');
                cp[0] = 0;
                uwsgi.mc_cluster_addr.sin_family = AF_INET;
                if (broadcast) {
                        uwsgi.mc_cluster_addr.sin_addr.s_addr = INADDR_BROADCAST;
                }
                else {
                        uwsgi.mc_cluster_addr.sin_addr.s_addr = inet_addr(name);
                }
                uwsgi.mc_cluster_addr.sin_port = htons(atoi(cp + 1));
                cp[0] = ':';


                // announce my presence to all the nodes
                uwsgi_string_sendto(fd, 73, 0, (struct sockaddr *) &uwsgi.mc_cluster_addr, sizeof(uwsgi.mc_cluster_addr), uwsgi.hostname, strlen(uwsgi.hostname));
        }
        else {
                exit(1);
        }


        return fd;

}


char *uwsgi_cluster_best_node() {

        int i;
        int best_node = -1;
        struct uwsgi_cluster_node *ucn;

        for (i = 0; i < MAX_CLUSTER_NODES; i++) {
                ucn = &uwsgi.shared->nodes[i];
                if (ucn->name[0] != 0 && ucn->status == UWSGI_NODE_OK) {
                        if (best_node == -1) {
                                best_node = i;
                        }
                        else {
                                if (ucn->last_choosen < uwsgi.shared->nodes[best_node].last_choosen) {
                                        best_node = i;
                                }
                        }
                }
        }

        if (best_node == -1) {
                return NULL;
        }

        uwsgi.shared->nodes[best_node].last_choosen = time(NULL);
        return uwsgi.shared->nodes[best_node].name;
}


void manage_cluster_announce(char *key, uint16_t keylen, char *val, uint16_t vallen, void *data) {

        char *tmpstr;
        struct uwsgi_cluster_node *ucn = (struct uwsgi_cluster_node *) data;

#ifdef UWSGI_DEBUG
        uwsgi_log("%.*s = %.*s\n", keylen, key, vallen, val);
#endif

        if (!uwsgi_strncmp("hostname", 8, key, keylen)) {
                strncpy(ucn->nodename, val, UMIN(vallen, 255));
        }

        if (!uwsgi_strncmp("address", 7, key, keylen)) {
                strncpy(ucn->name, val, UMIN(vallen, 100));
        }

        if (!uwsgi_strncmp("workers", 7, key, keylen)) {
                tmpstr = uwsgi_concat2n(val, vallen, "", 0);
                ucn->workers = atoi(tmpstr);
                free(tmpstr);
        }

        if (!uwsgi_strncmp("requests", 8, key, keylen)) {
                tmpstr = uwsgi_concat2n(val, vallen, "", 0);
                ucn->requests = strtoul(tmpstr, NULL, 0);
                free(tmpstr);
        }
}

#endif

