/* vcard.c
 * strophe XMPP client library -- vCard example
 *
 * Copyright (C) 2016 Dmitry Podgorny <pasis.ua@gmail.com>
 *
 *  This software is provided AS-IS with no warranty, either express
 *  or implied.
 *
 *  This program is dual licensed under the MIT and GPLv3 licenses.
 */

#include <assert.h>
#include <libgen.h> /* basename */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <strophe.h>

typedef struct {
    xmpp_ctx_t *ctx;
    const char *recipient;
    const char *img_path;
} vcard_t;

typedef void (*vcard_cb_t)(vcard_t *, xmpp_stanza_t *);

#define REQ_TIMEOUT 5000
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

static void vcard_photo(vcard_t *vc, xmpp_stanza_t *stanza)
{
    xmpp_stanza_t *tmp;
    char *s;
    char *tok;
    char *saveptr = NULL;
    char *copy;
    unsigned char *img;
    size_t img_size;
    size_t written;
    FILE *fd;

    tmp = xmpp_stanza_get_child_by_name(stanza, "TYPE");
    assert(tmp != NULL);
    s = xmpp_stanza_get_text(tmp);
    assert(s != NULL);
    printf("PHOTO: %s, saving to file %s\n", s, vc->img_path);
    xmpp_free(vc->ctx, s);

    tmp = xmpp_stanza_get_child_by_name(stanza, "BINVAL");
    assert(tmp != NULL);
    s = xmpp_stanza_get_text(tmp);
    assert(s != NULL);

    /* remove \n and \r */
    copy = (char *)malloc(strlen(s) + 1);
    assert(copy != NULL);
    copy[0] = '\0';
    tok = strtok_r(s, "\n\r", &saveptr);
    while (tok != NULL) {
        strcat(copy, tok);
        tok = strtok_r(NULL, "\n\r", &saveptr);
    }

    xmpp_base64_decode_bin(vc->ctx, copy, strlen(copy), &img, &img_size);
    assert(img != NULL);

    fd = fopen(vc->img_path, "w");
    assert(fd != NULL);
    written = fwrite(img, 1, img_size, fd);
    if (written < img_size)
        printf("Saving photo failed\n");
    fclose(fd);

    free(copy);
    xmpp_free(vc->ctx, s);
    xmpp_free(vc->ctx, img);
}

static void
vcard_print_string(vcard_t *vc, xmpp_stanza_t *stanza, const char *info)
{
    char *s = xmpp_stanza_get_text(stanza);

    assert(s != NULL);
    printf("%s: %s\n", info, s);
    xmpp_free(vc->ctx, s);
}

static void vcard_bday(vcard_t *vc, xmpp_stanza_t *stanza)
{
    vcard_print_string(vc, stanza, "Birthday");
}

static void vcard_desc(vcard_t *vc, xmpp_stanza_t *stanza)
{
    vcard_print_string(vc, stanza, "Description");
}

static void vcard_email(vcard_t *vc, xmpp_stanza_t *stanza)
{
    xmpp_stanza_t *userid = xmpp_stanza_get_child_by_name(stanza, "USERID");

    if (userid != NULL)
        vcard_print_string(vc, userid, "E-mail");
}

static void vcard_fn(vcard_t *vc, xmpp_stanza_t *stanza)
{
    vcard_print_string(vc, stanza, "Full name");
}

static void vcard_name(vcard_t *vc, xmpp_stanza_t *stanza)
{
    xmpp_stanza_t *name = xmpp_stanza_get_child_by_name(stanza, "GIVEN");
    xmpp_stanza_t *family = xmpp_stanza_get_child_by_name(stanza, "FAMILY");

    if (name != NULL)
        vcard_print_string(vc, name, "Given name");
    if (family != NULL)
        vcard_print_string(vc, family, "Family name");
}

static void vcard_nick(vcard_t *vc, xmpp_stanza_t *stanza)
{
    vcard_print_string(vc, stanza, "Nickname");
}

static void vcard_url(vcard_t *vc, xmpp_stanza_t *stanza)
{
    vcard_print_string(vc, stanza, "URL");
}

static vcard_cb_t vcard_cb_get(xmpp_stanza_t *stanza)
{
    vcard_cb_t cb = NULL;
    const char *tag;
    size_t i;

    static struct {
        const char *tag;
        vcard_cb_t cb;
    } vcard_tbl[] = {
        {"PHOTO", vcard_photo},   {"BDAY", vcard_bday}, {"DESC", vcard_desc},
        {"EMAIL", vcard_email},   {"FN", vcard_fn},     {"N", vcard_name},
        {"NICKNAME", vcard_nick}, {"URL", vcard_url},
    };

    tag = xmpp_stanza_get_name(stanza);
    if (tag == NULL)
        goto exit;

    for (i = 0; i < ARRAY_SIZE(vcard_tbl); ++i) {
        if (strcmp(tag, vcard_tbl[i].tag) == 0) {
            cb = vcard_tbl[i].cb;
            break;
        }
    }

exit:
    return cb;
}

static int timedout(xmpp_conn_t *const conn, void *const userdata)
{
    (void)userdata;

    fprintf(stderr, "Timeout reached.\n");
    xmpp_disconnect(conn);

    return 0;
}

static int recv_vcard(xmpp_conn_t *const conn,
                      xmpp_stanza_t *const stanza,
                      void *const userdata)
{
    vcard_t *vc = userdata;
    vcard_cb_t cb;
    xmpp_stanza_t *child;
    char *s;
    size_t s_size;
    int rc;

    printf("Received vCard.\n\n");

    child = xmpp_stanza_get_child_by_name(stanza, "error");
    if (child != NULL) {
        rc = xmpp_stanza_to_text(child, &s, &s_size);
        assert(rc == XMPP_EOK);
        printf("Error returned: %s.\n", s);
        xmpp_free(vc->ctx, s);
        goto exit;
    }

    child = xmpp_stanza_get_child_by_name(stanza, "vCard");
    assert(child != NULL);
    child = xmpp_stanza_get_children(child);
    while (child != NULL) {
        cb = vcard_cb_get(child);
        if (cb != NULL)
            cb(vc, child);
        child = xmpp_stanza_get_next(child);
    }

exit:
    xmpp_disconnect(conn);

    return 0;
}

static void send_vcard_req(xmpp_conn_t *conn, const char *to, const char *id)
{
    printf("Requesting vCard from %s.\n", to);
    xmpp_send_raw_string(conn,
                         "<iq from='%s' to='%s' type='get' id='%s'>"
                         "<vCard xmlns='vcard-temp'/></iq>",
                         xmpp_conn_get_bound_jid(conn), to, id);
}

static void conn_handler(xmpp_conn_t *const conn,
                         const xmpp_conn_event_t status,
                         const int error,
                         xmpp_stream_error_t *const stream_error,
                         void *const userdata)
{
    vcard_t *vc = userdata;

    if (status == XMPP_CONN_CONNECT) {
        send_vcard_req(conn, vc->recipient, "vc1");
        xmpp_id_handler_add(conn, recv_vcard, "vc1", vc);
        xmpp_timed_handler_add(conn, timedout, REQ_TIMEOUT, NULL);
    } else {
        if (error != 0)
            fprintf(stderr, "Disconnected with error=%d.\n", error);
        if (stream_error != NULL)
            fprintf(stderr, "Stream error type=%d text=%s.\n",
                    stream_error->type, stream_error->text);
        xmpp_stop(vc->ctx);
    }
}

int main(int argc, char **argv)
{
    xmpp_log_t *log;
    xmpp_ctx_t *ctx;
    xmpp_conn_t *conn;
    const char *jid;
    const char *pass;
    char *prog;
    vcard_t vcard;

    if (argc < 4 || argc > 5) {
        prog = argc > 0 ? strdup(argv[0]) : NULL;
        printf("Usage: %s <login-jid> <password> <recipient-jid> "
               "[image-file]\n\n",
               prog == NULL ? "vcard" : basename(prog));
        printf("If vCard contains a photo it will be stored to "
               "image-file. If you don't provide the image-file "
               "default filename will be generated.\n");
        free(prog);
        return 1;
    }

    jid = argv[1];
    pass = argv[2];
    vcard.recipient = argv[3];
    vcard.img_path = argc > 4 ? argv[4] : "vcard.jpg";

    xmpp_initialize();
    log = xmpp_get_default_logger(XMPP_LEVEL_INFO);
    ctx = xmpp_ctx_new(NULL, log);
    conn = xmpp_conn_new(ctx);
    xmpp_conn_set_jid(conn, jid);
    xmpp_conn_set_pass(conn, pass);
    vcard.ctx = ctx;
    xmpp_connect_client(conn, NULL, 0, conn_handler, &vcard);
    xmpp_run(ctx);
    xmpp_conn_release(conn);
    xmpp_ctx_free(ctx);
    xmpp_shutdown();

    return 0;
}
