/*
 * Copyright (C) 2017 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 */

#include <config.h>

#include "testutils.h"
#include "virfile.h"
#include "virfilecache.h"


#define VIR_FROM_THIS VIR_FROM_NONE


struct _testFileCacheObj {
    virObject object;
    char *data;
};
typedef struct _testFileCacheObj testFileCacheObj;
typedef testFileCacheObj *testFileCacheObjPtr;


static virClassPtr testFileCacheObjClass;


static void
testFileCacheObjDispose(void *opaque)
{
    testFileCacheObjPtr obj = opaque;
    VIR_FREE(obj->data);
}


static int
testFileCacheObjOnceInit(void)
{
    if (!(testFileCacheObjClass = virClassNew(virClassForObject(),
                                              "testFileCacheObj",
                                              sizeof(testFileCacheObj),
                                              testFileCacheObjDispose)))
        return -1;

    return 0;
}


VIR_ONCE_GLOBAL_INIT(testFileCacheObj)


static testFileCacheObjPtr
testFileCacheObjNew(const char *data)
{
    testFileCacheObjPtr obj;

    if (testFileCacheObjInitialize() < 0)
        return NULL;

    if (!(obj = virObjectNew(testFileCacheObjClass)))
        return NULL;

    if (VIR_STRDUP(obj->data, data) < 0)
        goto error;

    return obj;

 error:
    virObjectUnref(obj);
    return NULL;
}


struct _testFileCachePriv {
    bool dataSaved;
    const char *newData;
    const char *expectData;
};
typedef struct _testFileCachePriv testFileCachePriv;
typedef testFileCachePriv *testFileCachePrivPtr;


static bool
testFileCacheIsValid(void *data,
                     void *priv)
{
    testFileCachePrivPtr testPriv = priv;
    testFileCacheObjPtr obj = data;

    return STREQ(testPriv->expectData, obj->data);
}


static void *
testFileCacheNewData(const char *name ATTRIBUTE_UNUSED,
                     void *priv)
{
    testFileCachePrivPtr testPriv = priv;

    return testFileCacheObjNew(testPriv->newData);
}


static void *
testFileCacheLoadFile(const char *filename,
                      const char *name ATTRIBUTE_UNUSED,
                      void *priv ATTRIBUTE_UNUSED)
{
    testFileCacheObjPtr obj;
    char *data;

    if (virFileReadAll(filename, 20, &data) < 0)
        return NULL;

    obj = testFileCacheObjNew(data);

    VIR_FREE(data);
    return obj;
}


static int
testFileCacheSaveFile(void *data ATTRIBUTE_UNUSED,
                      const char *filename ATTRIBUTE_UNUSED,
                      void *priv)
{
    testFileCachePrivPtr testPriv = priv;

    testPriv->dataSaved = true;

    return 0;
}


virFileCacheHandlers testFileCacheHandlers = {
    .isValid = testFileCacheIsValid,
    .newData = testFileCacheNewData,
    .loadFile = testFileCacheLoadFile,
    .saveFile = testFileCacheSaveFile
};


struct _testFileCacheData {
    virFileCachePtr cache;
    const char *name;
    const char *newData;
    const char *expectData;
    bool expectSave;
};
typedef struct _testFileCacheData testFileCacheData;
typedef testFileCacheData *testFileCacheDataPtr;


static int
testFileCache(const void *opaque)
{
    int ret = -1;
    const testFileCacheData *data = opaque;
    testFileCacheObjPtr obj = NULL;
    testFileCachePrivPtr testPriv = virFileCacheGetPriv(data->cache);

    testPriv->dataSaved = false;
    testPriv->newData = data->newData;
    testPriv->expectData = data->expectData;

    if (!(obj = virFileCacheLookup(data->cache, data->name))) {
        fprintf(stderr, "Getting cached data failed.\n");
        goto cleanup;
    }

    if (!obj->data || STRNEQ(data->expectData, obj->data)) {
        fprintf(stderr, "Expect data '%s', loaded data '%s'.\n",
                data->expectData, NULLSTR(obj->data));
        goto cleanup;
    }

    if (data->expectSave != testPriv->dataSaved) {
        fprintf(stderr, "Expect data to be saved '%s', data saved '%s'.\n",
                data->expectSave ? "yes" : "no",
                testPriv->dataSaved ? "yes" : "no");
        goto cleanup;
    }

    ret = 0;

 cleanup:
    virObjectUnref(obj);
    return ret;
}


static int
mymain(void)
{
    int ret = 0;
    testFileCachePriv testPriv = {0};
    virFileCachePtr cache = NULL;

    if (!(cache = virFileCacheNew(abs_srcdir "/virfilecachedata",
                                  "cache", &testFileCacheHandlers)))
        return EXIT_FAILURE;

    virFileCacheSetPriv(cache, &testPriv);

#define TEST_RUN(name, newData, expectData, expectSave) \
    do { \
        testFileCacheData data = { \
            cache, name, newData, expectData, expectSave \
        }; \
        if (virTestRun(name, testFileCache, &data) < 0) \
            ret = -1; \
    } while (0)

    /* The cache file name is created using:
     * '$ echo -n $TEST_NAME | sha256sum' */
    TEST_RUN("cacheValid", NULL, "aaa\n", false);
    TEST_RUN("cacheInvalid", "bbb\n", "bbb\n", true);
    TEST_RUN("cacheMissing", "ccc\n", "ccc\n", true);

    virObjectUnref(cache);

    return ret != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

VIR_TEST_MAIN_PRELOAD(mymain, abs_builddir "/.libs/virfilecachemock.so")
