/* bot/context.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <glib.h>
#include <strophe.h>
#include <openssl/sha.h> /* For SHA1 */
#include <unistd.h>
#include <pthread.h>
#include <assert.h> /* debug */
#include "context.h"

/* static lifetime but only accessible here and through get and set functions below */
static struct BotXmppCtx *g_bctx = NULL;
pthread_mutex_t g_ctxLock = PTHREAD_MUTEX_INITIALIZER;

BotXmppCtx * const bot_get_ctx()
{
    return g_bctx;
}

void bot_set_ctx(BotXmppCtx * const bctx)
{
    pthread_mutex_lock(&bctx->lock);
    if(g_bctx) {
        fprintf(stderr, "Dev error: cannot set XMPP ctx twice in the same program run.\n");
    } else {
        g_bctx = bctx;
    }
    pthread_mutex_unlock(&bctx->lock);
}

void bot_free_user_data(struct BotUserInfo user, GString *filename, bool freeSegment)
{
    g_string_free(filename, freeSegment);
    g_string_free(user.jid, freeSegment);
    g_string_free(user.pass, freeSegment);
}

/* Implement https://xmpp.org/extensions/xep-0084.html
 * TODO need to understand the format for data stanza encoding (bytes? base64?):
 * - base64 encoded data is sent in the <data> node
 * - SHA1 hash is computed from the original PNG file (not the base64 encoded)
*/
char * bot_xmpp_set_avatar_pubsub(struct BotXmppCtx * const bctx, bool setData)
{
    gchar *avatar;
    gsize length;
    gchar *png;
    unsigned char hash[SHA_DIGEST_LENGTH]; /* == 20 */
    char xHash[SHA_DIGEST_LENGTH*2+1]; // always use two characters for hex encoding one byte
    xmpp_stanza_t *iq, *pubsub, *publish, *item, *data, *dt, *metadata, *info;
    int i;

    /* 1. Create IQ 'set' stanza */
    iq = xmpp_stanza_new(bctx->ctx);
    xmpp_stanza_set_name(iq, "iq");
    xmpp_stanza_set_type(iq, "set");
    if(setData) {
        xmpp_stanza_set_id(iq, "publish1");
    } else {
        xmpp_stanza_set_id(iq, "publish2");
    }
        

    /* Create <pubsub> protocol element*/
    pubsub = xmpp_stanza_new(bctx->ctx);
    xmpp_stanza_set_name(pubsub, "pubsub");
    xmpp_stanza_set_ns(pubsub, "http://jabber.org/protocol/pubsub");
    xmpp_stanza_add_child(iq, pubsub);

    /* Create <publish> element */
    publish = xmpp_stanza_new(bctx->ctx);
    xmpp_stanza_set_name(publish, "publish");
    if(setData) {
        xmpp_stanza_set_attribute(publish, "node", "urn:xmpp:avatar:data");
    } else {
        xmpp_stanza_set_attribute(publish, "node", "urn:xmpp:avatar:metadata");
    }
    xmpp_stanza_add_child(pubsub, publish);

    /* Add <item> where id should be the sha1sum of the image */
    item = xmpp_stanza_new(bctx->ctx);
    xmpp_stanza_set_name(item, "item"); // setting the name before the attributes makes the stanza a 'tag' type
    /* return the avatar ID computed as SHA1 of the actual png file. TODO avoid doing this twice (one for data, one for metadata)! */
    memset(hash, 0, SHA_DIGEST_LENGTH);
    memset(xHash, 0, 2*SHA_DIGEST_LENGTH+1);
    if(g_file_get_contents(bctx->avatarFile->str, &png, &length, NULL)) {
        SHA1((const unsigned char*)png, length, hash);
        for(i=0; i<SHA_DIGEST_LENGTH; i++) {
            sprintf(&xHash[i*2], "%02x", hash[i]);
        }
        xmpp_stanza_set_id(item, xHash);
    }
    xmpp_stanza_add_child(publish, item);

    if(setData) {
        /* Add <data> with base64 data */
        data = xmpp_stanza_new(bctx->ctx);
        assert(xmpp_stanza_set_name(data, "data") == 0);
        assert(xmpp_stanza_set_ns(data, "urn:xmpp:avatar:data") == 0);
        assert(xmpp_stanza_add_child(item, data) == 0);

        /* requires an hidden stanza */
        dt = xmpp_stanza_new(bctx->ctx);
        /* get contents of file */
        if(g_file_get_contents(bctx->avatarBase64->str, &avatar, &length, NULL)) {
            assert(xmpp_stanza_set_text(dt, avatar) == 0);
        } else {
            fprintf(stderr, "Unable to read BASE64-encoded avatar.\n");
        }
        assert(xmpp_stanza_add_child(data, dt) == 0);
    } else {
        struct stat fileInfo;
        char * bytes;
        int enough = 0;

        /* Add <metadata> with info about png file */
        metadata = xmpp_stanza_new(bctx->ctx);
        xmpp_stanza_set_name(metadata, "metadata");
        xmpp_stanza_set_ns(metadata, "urn:xmpp:avatar:metadata");
        xmpp_stanza_add_child(item, metadata);

        /* Add <info> stanza */
        stat(bctx->avatarFile->str, &fileInfo);
        enough = (int)((ceil(log10(fileInfo.st_size))+1)*sizeof(char));
        bytes = malloc(enough);
        memset(bytes, 0, enough);
        sprintf(bytes, "%ld", fileInfo.st_size);

        info = xmpp_stanza_new(bctx->ctx);
        xmpp_stanza_set_name(info, "info");

        xmpp_stanza_set_id(info, xHash);
        xmpp_stanza_set_attribute(info, "type", "image/png");
        xmpp_stanza_set_attribute(info, "bytes", bytes);
        xmpp_stanza_add_child(metadata, info);
    }

    /* 6. Send stanza and release it + all children */
    xmpp_send(bctx->conn, iq);
    xmpp_stanza_release(iq);

    return strdup(xHash);
}


/* implement vcard-based avatars. This doesn't work ATM. TODO
 * https://xmpp.org/extensions/xep-0153.html
 * returns the SHA1 hash of the actual PNG file encoded as hexadecimal
 */
char * bot_xmpp_set_avatar(struct BotXmppCtx * const bctx)
{
    gchar *avatar;
    gsize length;
    gchar *png;
    unsigned char hash[SHA_DIGEST_LENGTH]; /* == 20 */
    char xHash[SHA_DIGEST_LENGTH*2+1]; // always use two characters for hex encoding one byte
    xmpp_stanza_t *iq, *vcard, *photo, *type, *binval;
    int i = 0;

    /* 1. Create IQ 'set' stanza */
    iq = xmpp_stanza_new(bctx->ctx);
    xmpp_stanza_set_name(iq, "iq");
    xmpp_stanza_set_type(iq, "set");
    xmpp_stanza_set_id(iq, "vc1");

    /* Create <vCard xmlns='vcard-temp' */
    vcard = xmpp_stanza_new(bctx->ctx);
    xmpp_stanza_set_name(vcard, "vCard");
    xmpp_stanza_set_ns(vcard, "vcard-temp");
    xmpp_stanza_add_child(iq, vcard);

    /* Create <PHOTO> element */
    photo = xmpp_stanza_new(bctx->ctx);
    xmpp_stanza_set_name(photo, "PHOTO");
    xmpp_stanza_add_child(vcard, photo);

    /* Add <TYPE> (e.g., image/png) */
    type = xmpp_stanza_new(bctx->ctx);
    // gotcha: calling set_text AFTER set_name will fail with XMPP_EINVOP (invalid operation)
    xmpp_stanza_set_text(type, "image/png");
    xmpp_stanza_set_name(type, "TYPE");
    xmpp_stanza_add_child(photo, type);

    /* Add <BINVAL> with base64 data */
    binval = xmpp_stanza_new(bctx->ctx);
    if(g_file_get_contents(bctx->avatarBase64->str, &avatar, &length, NULL)) {
        xmpp_stanza_set_text(binval, avatar);
    } else {
        fprintf(stderr, "Unable to read BASE64-encoded avatar.\n");
    }
    xmpp_stanza_set_name(binval, "BINVAL");
    xmpp_stanza_add_child(photo, binval);

    /* 6. Send stanza and release it + all children */
    xmpp_send(bctx->conn, iq);
    xmpp_stanza_release(iq);

    /* return the avatar ID computed as SHA1 of the actual png file */
    memset(hash, 0, SHA_DIGEST_LENGTH);
    memset(xHash, 0, 2*SHA_DIGEST_LENGTH+1);
    if(g_file_get_contents(bctx->avatarFile->str, &png, &length, NULL)) {
        SHA1((const unsigned char*)png, length, hash);
        for(i=0; i<SHA_DIGEST_LENGTH; i++) {
            // we use a const string for convenience, can index it
            xHash[i*2]   = "0123456789ABCDEF"[hash[i] >> 4]; 
            xHash[i*2+1] = "0123456789ABCDEF"[hash[i] & 0x0F];
        }
        return strdup(xHash);
    }

    return NULL;
}

int bot_xmpp_startup(const struct BotUserInfo user, GString * const avatarFile, GString * const avatarBase64)
{
    xmpp_log_t* newLog;
    xmpp_ctx_t* newCtx;
    xmpp_conn_t* newCon;
    int connectStatus;
    struct BotXmppCtx *bctx = malloc(sizeof *bctx);

    /* init library */
    xmpp_initialize();

    /* create a context */
    newLog = xmpp_get_default_logger(XMPP_LEVEL_DEBUG); /* pass NULL instead to silence output */
    newCtx = xmpp_ctx_new(NULL, newLog);

    if (!(newLog && newCtx)) {
        fprintf(stderr, "Unable to startup. Context creation failed.");
        return 1;
    }

    /* create a connection */
    newCon = xmpp_conn_new(newCtx);

    if (!newCon) {
        fprintf(stderr, "Unable to startup. Connection failed.");
        return 1;
    }

    /* setup authentication information */
    xmpp_conn_set_jid(newCon, user.jid->str);
    xmpp_conn_set_pass(newCon, user.pass->str);

    /* no need to lock here as this is the only thread */
    pthread_mutex_init (&bctx->lock, NULL);
    bctx->log = newLog;
    bctx->ctx = newCtx;
    bctx->conn = newCon;
    bctx->avatarBase64 = avatarBase64;
    bctx->avatarFile = avatarFile;

    /* initiate connection */
    connectStatus = xmpp_connect_client(newCon, NULL, 0, conn_handler, newCtx);
    if(connectStatus != 0) {
        /* connecting went wrong */
        return connectStatus;
    }

    /* store ctx as global */
    bot_set_ctx(bctx);
    
    return 0;
}

void bot_xmpp_shutdown()
{
    struct BotXmppCtx *bctx = bot_get_ctx();

    /* enter the event loop - 
       our connect handler will trigger an exit */
    xmpp_run(bctx->ctx);

    /* release our connection and context */
    xmpp_conn_release(bctx->conn);
    xmpp_ctx_free(bctx->ctx);

    /* final shutdown of the library */
    xmpp_shutdown();
}

