/*
 * copyright: 2013-2015
 * name : Francis Banyikwa
 * email: mhogomchungu@gmail.com
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <libsecret-1/libsecret/secret.h>

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

/*
 * The libsecret API we use stores information in key-value pairs.
 *
 * The API requires a key to give the key's associated value.
 *
 * How can we tell how many and what keys libsecret is holding for us?
 *
 * The API does not give a list of keys it is holding for us and we solve this
 * problem using two schemas.
 *
 * Schema "keyValues" holds user provided keys and their associated user provided values.
 *
 * Schema "keyID" holds unique numbers generated by the library and these numbers are associated
 * with user provided keys.
 *
 * We also store an entry in libsecret that keeps track of the number of entries
 * we have.
 *
 * In "keyID" schema,we get a list of all user provided keys we are managing by looping over these
 * unique numbers looking for their corresponding values.These values are user provided keys.
 *
 * Once we have all the keys,we use them in "keyValue" schema to get their corresponding values and
 * then we return all keys and their values in key-value pairs to the library user.
 *
 */

#define BUFFER_SIZE 32

static char   *_get_string_value_0(const SecretSchema *s, const char *key)
{
    return secret_password_lookup_sync(s, NULL, NULL, "string", key, NULL);
}

static int _get_string_value(const SecretSchema *s, const char *key)
{
    char *c = _get_string_value_0(s, key);
    int e;

    if (c == NULL)
    {
        return -1;
    }
    else
    {
        e = atoi(c);
        free(c);
        return e;
    }
}

static char *_get_integer_value_0(const SecretSchema *s, int key)
{
    return secret_password_lookup_sync(s, NULL, NULL, "integer", key, NULL);
}

static int _get_integer_value(const SecretSchema *s, int key)
{
    char *c = _get_integer_value_0(s, key);
    int e;

    if (c == NULL)
    {
        return -1;
    }
    else
    {
        e = atoi(c);
        free(c);
        return e;
    }
}

static int _set_integer_value(const SecretSchema *s, const char *name, const char *key, int value)
{
    return secret_password_store_sync(s, "default", name, key, NULL, NULL, "integer", value, NULL);
}

static int _set_string_value(const SecretSchema *s, const char *name, const char *key, const char *value)
{
    return secret_password_store_sync(s, "default", name, key, NULL, NULL, "string", value, NULL);
}

static int _number_of_entries_in_the_wallet(const SecretSchema *s)
{
    int e = _get_string_value(s, "lxqt_wallet_size");

    if (e == -1)
    {
        return 0;
    }
    else
    {
        return e;
    }
}

static int _clear_integer_value(const SecretSchema *s, int k)
{
    return secret_password_clear_sync(s, NULL, NULL, "integer", k, NULL);
}

static int _clear_string_value(const SecretSchema *s, const char *k)
{
    return secret_password_clear_sync(s, NULL, NULL, "string", k, NULL);
}

/*
 * This function is used to check if a wallet is open or not and force prompt to open it if it is not open.
 *
 * write to the wallet and the operation will succeed if the wallet is open.
 * If the wallet is not open,the operation will block while a user is prompted for a key to unlock it.
 * If the user fail to unlock it,the operation will fail.
 */
int lxqt_libsecret_wallet_is_open(const void *s)
{
    const SecretSchema *e = s;
    return _set_string_value(e, e->name, "lxqt_wallet_open", "lxqt_wallet_open");
}

char *lxqt_libsecret_get_value(const char *key, const void *s)
{
    return _get_string_value_0(s, key);
}

void *lxqt_libsecret_create_schema(const char *schemaName, const char *type)
{
    SecretSchema *s = malloc(sizeof(SecretSchema));

    memset(s, '\0', sizeof(SecretSchema));

    s->name  = schemaName;
    s->flags = SECRET_SCHEMA_NONE;

    s->attributes[0].name = type;

    if (strcmp(type, "string") == 0)
    {
        s->attributes[0].type = SECRET_SCHEMA_ATTRIBUTE_STRING;
    }
    else
    {
        s->attributes[0].type = SECRET_SCHEMA_ATTRIBUTE_INTEGER;
    }

    s->attributes[1].name = "NULL";
    s->attributes[1].type = 0;

    return s;
}

static gboolean _password_store_sync(const char *key,
                                     const char *value,
                                     const void *kv,
                                     const void *id,
                                     int ws)
{
    const SecretSchema *keyValues = kv;
    const SecretSchema *keyID     = id;

    int i = 0;
    int k;

    char wallet_size[ BUFFER_SIZE ];

    const char *walletLabel = keyValues->name;

    snprintf(wallet_size, BUFFER_SIZE, "%d", ws);

    _set_string_value(keyValues, walletLabel, wallet_size, "lxqt_wallet_size");

    while (i < ws)
    {
        k = _get_integer_value(keyID, i);

        if (k == -1)
        {
            if (_set_integer_value(keyID, walletLabel, key, i))
            {
                if (_set_string_value(keyValues, walletLabel, value, key))
                {
                    return TRUE;
                }
                else
                {
                    _clear_integer_value(keyID, i);
                    return FALSE;
                }
            }
            else
            {
                return FALSE;
            }
        }
        else
        {
            i++;
        }
    }

    return FALSE;
}

gboolean lxqt_libsecret_password_store_sync(const char *key,
        const char *value,
        const void *keyValues,
        const void *keyID)
{
    int j;

    if (lxqt_libsecret_wallet_is_open(keyValues))
    {
        j = _get_string_value(keyValues, "lxqt_wallet_size");

        if (j == -1)
        {
            return _password_store_sync(key, value, keyValues, keyID, 1);
        }
        else
        {
            return _password_store_sync(key, value, keyValues, keyID, j + 1);
        }
    }
    else
    {
        return FALSE;
    }
}

static gboolean _exceeded_limit(int k)
{
    /*
     * We dont expect to manage 10000 entries and getting this far most likely means we are
     * stuck in an endless loop
     */
    return k == 10000;
}

gboolean lxqt_libsecret_clear_sync(const char *key, const void *kv, const void *id)
{
    const SecretSchema *keyValues = kv;
    const SecretSchema *keyID     = id;

    int i = 0;
    int k = 0;
    int e;

    char *c;
    char wallet_size[ BUFFER_SIZE ];
    const char *walletLabel = keyValues->name;

    int j = _number_of_entries_in_the_wallet(keyValues);

    if (lxqt_libsecret_wallet_is_open(keyValues))
    {
        while (i <= j)
        {
            c = _get_integer_value_0(keyID, k);

            if (c != NULL)
            {
                e = strcmp(c, key);

                free(c);

                if (e == 0)
                {
                    _clear_integer_value(keyID, k);

                    e = _get_string_value(keyValues, "lxqt_wallet_size");
                    snprintf(wallet_size, BUFFER_SIZE, "%d", e - 1);

                    _set_string_value(keyValues, walletLabel, wallet_size, "lxqt_wallet_size");

                    _clear_string_value(keyValues, key);

                    return TRUE;
                }
                else
                {
                    i++;
                    k++;
                }
            }
            else
            {
                k++;

                if (_exceeded_limit(k))
                {

                    break;
                }
            }
        }
    }

    return FALSE;
}

char **lxqt_secret_get_all_keys(const void *kv, const void *id, size_t *count)
{
    const SecretSchema *keyValues = kv;
    const SecretSchema *keyID     = id;

    int k = 0;
    int i = 0;
    int j;

    char **c = NULL;
    char *e  = NULL;

    *count = 0;

    if (lxqt_libsecret_wallet_is_open(keyValues))
    {
        j = _number_of_entries_in_the_wallet(keyValues);
        c = malloc(sizeof(char *) * j);

        if (c != NULL)
        {
            while (i < j)
            {
                e = _get_integer_value_0(keyID, k);

                if (e != NULL)
                {
                    *(c + i) = e;
                    *count += 1;
                    i++;
                    k++;
                }
                else
                {
                    k++;

                    if (_exceeded_limit(k))
                    {

                        break;
                    }
                }
            }
        }
    }

    return c;
}

int lxqt_libsecret_wallet_size(const void *s)
{
    return _number_of_entries_in_the_wallet(s);
}
