/*
 * (C) Copyright 1996- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include <cstring>
#include <iostream>
#include <vector>

#include "eckit/filesystem/PathName.h"
#include "eckit/io/Buffer.h"
#include "eckit/io/DataHandle.h"
#include "eckit/testing/Test.h"
#include "fdb5/database/Key.h"

#include "fdb5/api/fdb_c.h"
#include "fdb5/fdb5_config.h"

using namespace eckit::testing;
using namespace eckit;


namespace fdb {
namespace test {

//----------------------------------------------------------------------------------------------------------------------
int fdb_request_add1(fdb_request_t* req, const char* param, const char* value) {
    return fdb_request_add(req, param, &value, 1);
}

void key_compare(const std::vector<fdb5::Key>& keys, fdb_listiterator_t* it, bool checkLevel = true) {
    const char* k;
    const char* v;
    size_t l;
    int err;

    fdb_split_key_t* sk = nullptr;
    fdb_new_splitkey(&sk);
    err = fdb_listiterator_splitkey(it, sk);
    EXPECT(err == FDB_SUCCESS);

    size_t level = 0;
    for (const auto& key : keys) {
        for (const auto& k1 : key) {
            int err = fdb_splitkey_next_metadata(sk, &k, &v, checkLevel ? &l : nullptr);
            std::cerr << "k=" << k << " v=" << v << " l=" << l << std::endl;
            EXPECT(err == FDB_SUCCESS);
            EXPECT(k1.first == k);
            EXPECT(k1.second == v);
            if (checkLevel) {
                EXPECT(level == l);
            }
        }
        level++;
    }
    err = fdb_splitkey_next_metadata(sk, &k, &v, &l);
    EXPECT(err == FDB_ITERATION_COMPLETE);

    err = fdb_delete_splitkey(sk);
}

CASE("fdb_c - archive & list") {
    const int depth = 3;
    size_t length;
    DataHandle* dh;

    fdb_handle_t* fdb;
    fdb_new_handle(&fdb);

    fdb_key_t* key;
    fdb_new_key(&key);
    fdb_key_add(key, "domain", "g");
    fdb_key_add(key, "stream", "oper");
    fdb_key_add(key, "levtype", "pl");
    fdb_key_add(key, "levelist", "300");
    fdb_key_add(key, "date", "20191110");
    fdb_key_add(key, "time", "0000");
    fdb_key_add(key, "step", "0");
    fdb_key_add(key, "param", "138");
    fdb_key_add(key, "class", "rd");
    fdb_key_add(key, "type", "an");
    fdb_key_add(key, "expver", "xxxx");

    eckit::PathName grib1("x138-300.grib");
    length = grib1.size();
    eckit::Buffer buf1(length);
    dh = grib1.fileHandle();
    dh->openForRead();
    dh->read(buf1, length);
    dh->close();
    delete dh;

    EXPECT(FDB_SUCCESS == fdb_archive(fdb, key, buf1, length));
    EXPECT(FDB_SUCCESS == fdb_flush(fdb));

    fdb_request_t* request;
    fdb_new_request(&request);
    fdb_request_add1(request, "domain", "g");
    fdb_request_add1(request, "stream", "oper");
    fdb_request_add1(request, "levtype", "pl");
    fdb_request_add1(request, "levelist", "300");
    fdb_request_add1(request, "date", "20191110");
    fdb_request_add1(request, "time", "0000");
    fdb_request_add1(request, "step", "0");
    fdb_request_add1(request, "param", "138");
    fdb_request_add1(request, "class", "rd");
    fdb_request_add1(request, "type", "an");
    fdb_request_add1(request, "expver", "xxxx");

    fdb_listiterator_t* it;
    fdb_list(fdb, request, &it, true, depth);
    int err = fdb_listiterator_next(it);
    ASSERT(err == FDB_SUCCESS);

    const char* uri;
    size_t off, attr_len;

    fdb_listiterator_attrs(it, &uri, &off, &attr_len);
    EXPECT(attr_len == 3280398);

    std::vector<fdb5::Key> k1test{
        {{"class", "rd"},
         {"expver", "xxxx"},
         {"stream", "oper"},
         {"date", "20191110"},
         {"time", "0000"},
         {"domain", "g"}},
        {{"type", "an"}, {"levtype", "pl"}},
        {{"step", "0"}, {"levelist", "300"}, {"param", "138"}},
    };
    //    std::vector<fdb5::Key> k1test{fdb5::Key{"class=rd,expver=xxxx,stream=oper,date=20191110,time=0000,domain=g"},
    //                                  fdb5::Key{"type=an,levtype=pl"},fdb5::Key{"step=0,levelist=300,param=138"}};
    key_compare(k1test, it);

    err = fdb_listiterator_next(it);
    ASSERT(err == FDB_ITERATION_COMPLETE);
    fdb_delete_listiterator(it);


    fdb_request_add1(request, "param", "139");
    fdb_list(fdb, request, &it, true, depth);
    err = fdb_listiterator_next(it);
    ASSERT(err == FDB_ITERATION_COMPLETE);
    fdb_delete_listiterator(it);

    fdb_key_add(key, "levelist", "400");

    eckit::PathName grib2("x138-400.grib");
    length = grib2.size();
    eckit::Buffer buf2(length);
    dh = grib2.fileHandle();
    dh->openForRead();
    dh->read(buf2, length);
    dh->close();
    delete dh;

    EXPECT(FDB_SUCCESS == fdb_archive(fdb, key, buf2, length));
    EXPECT(FDB_SUCCESS == fdb_flush(fdb));

    fdb_request_add1(request, "levelist", "400");
    fdb_list(fdb, request, &it, true, depth);
    err = fdb_listiterator_next(it);
    ASSERT(err == FDB_ITERATION_COMPLETE);
    fdb_delete_listiterator(it);


    fdb_request_add1(request, "param", "138");
    fdb_list(fdb, request, &it, true, depth);
    err = fdb_listiterator_next(it);
    ASSERT(err == FDB_SUCCESS);

    fdb_listiterator_attrs(it, &uri, &off, &attr_len);
    EXPECT(attr_len == 3280398);

    std::vector<fdb5::Key> k2test{
        {{"class", "rd"},
         {"expver", "xxxx"},
         {"stream", "oper"},
         {"date", "20191110"},
         {"time", "0000"},
         {"domain", "g"}},
        {{"type", "an"}, {"levtype", "pl"}},
        {{"step", "0"}, {"levelist", "400"}, {"param", "138"}},
    };
    //    std::vector<fdb5::Key> k2test{fdb5::Key{"class=rd,expver=xxxx,stream=oper,date=20191110,time=0000,domain=g"},
    //                                  fdb5::Key{"type=an,levtype=pl"},fdb5::Key{"step=0,levelist=400,param=138"}};
    key_compare(k2test, it, false);
    key_compare(k2test, it);

    err = fdb_listiterator_next(it);
    ASSERT(err == FDB_ITERATION_COMPLETE);
    fdb_delete_listiterator(it);

    const char* values[] = {"400", "300"};
    fdb_request_add(request, "levelist", values, 2);
    fdb_list(fdb, request, &it, true, depth);
    err = fdb_listiterator_next(it);
    ASSERT(err == FDB_SUCCESS);

    fdb_listiterator_attrs(it, &uri, &off, &attr_len);
    EXPECT(attr_len == 3280398);

    key_compare(k2test, it);

    err = fdb_listiterator_next(it);
    ASSERT(err == FDB_SUCCESS);

    fdb_listiterator_attrs(it, &uri, &off, &length);
    EXPECT(length == 3280398);

    key_compare(k1test, it);

    err = fdb_listiterator_next(it);
    ASSERT(err == FDB_ITERATION_COMPLETE);
    fdb_delete_listiterator(it);

    fdb_key_add(key, "expver", "xxxy");

    eckit::PathName grib3("y138-400.grib");
    length = grib3.size();
    eckit::Buffer buf3(length);
    dh = grib3.fileHandle();
    dh->openForRead();
    dh->read(buf3, length);
    dh->close();
    delete dh;

    fdb_delete_request(request);
    fdb_delete_key(key);
    fdb_delete_handle(fdb);
}

#if fdb5_HAVE_GRIB
CASE("fdb_c - multiple archive & list") {
    const int depth = 3;
    size_t length1, length2, length3;
    DataHandle* dh;

    fdb_handle_t* fdb;
    fdb_new_handle(&fdb);

    std::vector<fdb5::Key> k1{
        {{"class", "rd"},
         {"expver", "xxxx"},
         {"stream", "oper"},
         {"date", "20191110"},
         {"time", "0000"},
         {"domain", "g"}},
        {{"type", "an"}, {"levtype", "pl"}},
        {{"step", "0"}, {"levelist", "300"}, {"param", "138"}},
    };
    std::vector<fdb5::Key> k2{
        {{"class", "rd"},
         {"expver", "xxxx"},
         {"stream", "oper"},
         {"date", "20191110"},
         {"time", "0000"},
         {"domain", "g"}},
        {{"type", "an"}, {"levtype", "pl"}},
        {{"step", "0"}, {"levelist", "400"}, {"param", "138"}},
    };

    eckit::PathName grib1("x138-300.grib");
    length1 = grib1.size();
    eckit::PathName grib2("x138-400.grib");
    length2 = grib2.size();
    eckit::PathName grib3("y138-400.grib");
    length3 = grib3.size();

    eckit::Buffer buf(length1 + length2 + length3);
    dh = grib1.fileHandle();
    dh->openForRead();
    dh->read(buf, length1);
    dh->close();
    delete dh;

    dh = grib2.fileHandle();
    dh->openForRead();
    dh->read(buf + length1, length2);
    dh->close();
    delete dh;

    fdb_request_t* req;
    fdb_new_request(&req);
    fdb_request_add1(req, "domain", "g");
    fdb_request_add1(req, "stream", "oper");
    fdb_request_add1(req, "levtype", "pl");
    const char* levels[] = {"400", "300"};
    fdb_request_add(req, "levelist", levels, 2);
    fdb_request_add1(req, "date", "20191110");
    fdb_request_add1(req, "time", "0000");
    fdb_request_add1(req, "step", "0");
    fdb_request_add1(req, "param", "138");
    fdb_request_add1(req, "class", "rd");
    fdb_request_add1(req, "type", "an");
    fdb_request_add1(req, "expver", "xxxx");

    EXPECT(FDB_ERROR_GENERAL_EXCEPTION == fdb_archive_multiple(fdb, req, buf, length1));
    EXPECT(FDB_SUCCESS == fdb_flush(fdb));

    EXPECT(FDB_SUCCESS == fdb_archive_multiple(fdb, req, buf, length1 + length2));
    EXPECT(FDB_SUCCESS == fdb_flush(fdb));

    fdb_request_add1(req, "levelist", "300");

    EXPECT(FDB_ERROR_GENERAL_EXCEPTION == fdb_archive_multiple(fdb, req, buf, length1 + length2));
    EXPECT(FDB_SUCCESS == fdb_flush(fdb));

    fdb_request_add(req, "levelist", levels, 2);

    EXPECT(FDB_SUCCESS == fdb_archive_multiple(fdb, req, buf, length1 + length2));
    EXPECT(FDB_SUCCESS == fdb_flush(fdb));

    dh = grib3.fileHandle();
    dh->openForRead();
    dh->read(buf + length1 + length2, length3);
    dh->close();
    delete dh;

    const char* expvers[] = {"xxxx", "xxxy"};
    fdb_request_add(req, "expver", expvers, 2);

    const char* levels3[] = {"300", "400", "500"};
    fdb_request_add(req, "levelist", levels3, 3);

    EXPECT(FDB_ERROR_GENERAL_EXCEPTION == fdb_archive_multiple(fdb, req, buf, length1 + length2 + length3));
    EXPECT(FDB_SUCCESS == fdb_flush(fdb));

    EXPECT(FDB_SUCCESS == fdb_archive_multiple(fdb, nullptr, buf, length1 + length2 + length3));
    EXPECT(FDB_SUCCESS == fdb_flush(fdb));
    fdb_delete_request(req);

    fdb_request_t* request;
    fdb_new_request(&request);
    fdb_request_add1(request, "domain", "g");
    fdb_request_add1(request, "stream", "oper");
    fdb_request_add1(request, "levtype", "pl");
    fdb_request_add1(request, "levelist", "300");
    fdb_request_add1(request, "date", "20191110");
    fdb_request_add1(request, "time", "0000");
    fdb_request_add1(request, "step", "0");
    fdb_request_add1(request, "param", "138");
    fdb_request_add1(request, "class", "rd");
    fdb_request_add1(request, "type", "an");
    fdb_request_add1(request, "expver", "xxxx");

    fdb_listiterator_t* it;
    fdb_list(fdb, request, &it, true, depth);
    int err = fdb_listiterator_next(it);
    ASSERT(err == FDB_SUCCESS);

    key_compare(k1, it);

    err = fdb_listiterator_next(it);
    ASSERT(err == FDB_ITERATION_COMPLETE);
    fdb_delete_listiterator(it);

    fdb_request_add1(request, "step", "1");
    fdb_list(fdb, request, &it, true, depth);
    err = fdb_listiterator_next(it);
    ASSERT(err == FDB_ITERATION_COMPLETE);
    fdb_delete_listiterator(it);

    fdb_request_add1(request, "step", "0");
    const char* values[] = {"400", "300"};
    fdb_request_add(request, "levelist", values, 2);
    fdb_list(fdb, request, &it, true, depth);
    err = fdb_listiterator_next(it);
    ASSERT(err == FDB_SUCCESS);

    key_compare(k1, it);

    err = fdb_listiterator_next(it);
    ASSERT(err == FDB_SUCCESS);

    key_compare(k2, it);

    err = fdb_listiterator_next(it);
    ASSERT(err == FDB_ITERATION_COMPLETE);
    fdb_delete_listiterator(it);

    fdb_delete_request(request);
    fdb_delete_handle(fdb);
}

CASE("fdb_c - list depth=1,2,3") {
    fdb_handle_t* fdb = nullptr;
    fdb_new_handle(&fdb);

    std::vector<fdb5::Key> key300d1{
        {{"class", "rd"},
         {"expver", "xxxx"},
         {"stream", "oper"},
         {"date", "20191110"},
         {"time", "0000"},
         {"domain", "g"}},
    };

    std::vector<fdb5::Key> key300d2{
        {{"class", "rd"},
         {"expver", "xxxx"},
         {"stream", "oper"},
         {"date", "20191110"},
         {"time", "0000"},
         {"domain", "g"}},
        {{"type", "an"}, {"levtype", "pl"}},
    };

    std::vector<fdb5::Key> key300d3{
        {{"class", "rd"},
         {"expver", "xxxx"},
         {"stream", "oper"},
         {"date", "20191110"},
         {"time", "0000"},
         {"domain", "g"}},
        {{"type", "an"}, {"levtype", "pl"}},
        {{"step", "0"}, {"levelist", "300"}, {"param", "138"}},
    };

    fdb_request_t* request = nullptr;
    fdb_new_request(&request);
    fdb_request_add1(request, "domain", "g");
    fdb_request_add1(request, "stream", "oper");
    fdb_request_add1(request, "levtype", "pl");
    fdb_request_add1(request, "levelist", "300");
    fdb_request_add1(request, "date", "20191110");
    fdb_request_add1(request, "time", "0000");
    fdb_request_add1(request, "step", "0");
    fdb_request_add1(request, "param", "138");
    fdb_request_add1(request, "class", "rd");
    fdb_request_add1(request, "type", "an");
    fdb_request_add1(request, "expver", "xxxx");

    {  // depth=1
        fdb_listiterator_t* iter = nullptr;
        fdb_list(fdb, request, &iter, true, 1);
        int err = fdb_listiterator_next(iter);
        ASSERT(err == FDB_SUCCESS);

        key_compare(key300d1, iter);

        err = fdb_listiterator_next(iter);
        ASSERT(err == FDB_ITERATION_COMPLETE);
        fdb_delete_listiterator(iter);
    }

    {  // depth=2
        fdb_listiterator_t* iter = nullptr;
        fdb_list(fdb, request, &iter, true, 2);
        int err = fdb_listiterator_next(iter);
        ASSERT(err == FDB_SUCCESS);

        key_compare(key300d2, iter);

        err = fdb_listiterator_next(iter);
        ASSERT(err == FDB_ITERATION_COMPLETE);
        fdb_delete_listiterator(iter);
    }

    {  // depth=3
        fdb_listiterator_t* iter = nullptr;
        fdb_list(fdb, request, &iter, true, 3);
        int err = fdb_listiterator_next(iter);
        ASSERT(err == FDB_SUCCESS);

        key_compare(key300d3, iter);

        err = fdb_listiterator_next(iter);
        ASSERT(err == FDB_ITERATION_COMPLETE);
        fdb_delete_listiterator(iter);
    }

    fdb_delete_request(request);
    fdb_delete_handle(fdb);
}
#endif

CASE("fdb_c - retrieve bad request") {

    fdb_handle_t* fdb;
    fdb_new_handle(&fdb);
    fdb_request_t* request;
    fdb_new_request(&request);
    fdb_request_add1(request, "domain", "g");
    fdb_request_add1(request, "stream", "oper");
    fdb_request_add1(request, "levtype", "pl");
    fdb_request_add1(request, "levelist", "300");
    fdb_request_add1(request, "date", "20191110");
    fdb_request_add1(request, "time", "0000");
    fdb_request_add1(request, "step", "0");
    fdb_request_add1(request, "param", "138/139");
    fdb_request_add1(request, "class", "rd");
    fdb_request_add1(request, "type", "an");
    fdb_request_add1(request, "expver", "xxxx");

    char buf[1000];
    char grib[4];
    long read = 0;
    long size;
    fdb_datareader_t* dr;
    fdb_new_datareader(&dr);
    //  thrown by deduplication (now deactivted)
    //    EXPECT(fdb_retrieve(fdb, request, dr) == FDB_ERROR_GENERAL_EXCEPTION);
    fdb_delete_datareader(dr);
    fdb_delete_request(request);
    fdb_delete_handle(fdb);
}


CASE("fdb_c - retrieve") {

    fdb_handle_t* fdb;
    fdb_new_handle(&fdb);
    fdb_request_t* request;
    fdb_new_request(&request);
    fdb_request_add1(request, "domain", "g");
    fdb_request_add1(request, "stream", "oper");
    fdb_request_add1(request, "levtype", "pl");
    fdb_request_add1(request, "levelist", "300");
    fdb_request_add1(request, "date", "20191110");
    fdb_request_add1(request, "time", "0000");
    fdb_request_add1(request, "step", "0");
    fdb_request_add1(request, "param", "138");
    fdb_request_add1(request, "class", "rd");
    fdb_request_add1(request, "type", "an");
    fdb_request_add1(request, "expver", "xxxx");

    char buf[1000];
    char grib[4];
    long read = 0;
    long size;
    fdb_datareader_t* dr;
    fdb_new_datareader(&dr);
    EXPECT(fdb_retrieve(fdb, request, dr) == FDB_SUCCESS);
    fdb_datareader_open(dr, &size);
    EXPECT_NOT_EQUAL(0, size);
    fdb_datareader_read(dr, grib, 4, &read);
    EXPECT_EQUAL(4, read);
    EXPECT_EQUAL(0, strncmp(grib, "GRIB", 4));
    fdb_datareader_tell(dr, &read);
    EXPECT_EQUAL(4, read);
    fdb_datareader_seek(dr, 3);
    fdb_datareader_tell(dr, &read);
    EXPECT_EQUAL(3, read);
    fdb_datareader_skip(dr, 3);
    fdb_datareader_tell(dr, &read);
    EXPECT_EQUAL(6, read);
    fdb_datareader_read(dr, buf, 1000, &read);
    EXPECT_EQUAL(1000, read);
    fdb_datareader_tell(dr, &read);
    EXPECT_EQUAL(1006, read);
    fdb_delete_datareader(dr);

    long size2;
    const char* values[] = {"400", "300"};
    fdb_request_add(request, "levelist", values, 2);
    fdb_new_datareader(&dr);
    EXPECT(fdb_retrieve(fdb, request, dr) == FDB_SUCCESS);
    fdb_datareader_open(dr, &size2);
    EXPECT_EQUAL(2 * size, size2);
    fdb_datareader_seek(dr, size);
    fdb_datareader_read(dr, grib, 4, &read);
    EXPECT_EQUAL(4, read);
    EXPECT_EQUAL(0, strncmp(grib, "GRIB", 4));
    fdb_datareader_tell(dr, &read);
    EXPECT_EQUAL(4 + size, read);
    fdb_datareader_seek(dr, size2 - 4);
    fdb_datareader_read(dr, grib, 6, &read);
    EXPECT_EQUAL(4, read);
    fdb_delete_datareader(dr);
    fdb_delete_request(request);
    fdb_delete_handle(fdb);
}

CASE("fdb_c - expand") {

    fdb_handle_t* fdb;
    fdb_new_handle(&fdb);
    fdb_request_t* request;
    fdb_new_request(&request);
    fdb_request_add1(request, "domain", "g");
    fdb_request_add1(request, "stream", "oper");
    fdb_request_add1(request, "levtype", "pl");
    fdb_request_add1(request, "levelist", "300");
    const char* dates[] = {"20191110", "to", "20191111"};
    fdb_request_add(request, "date", dates, 3);
    fdb_request_add1(request, "time", "0000");
    fdb_request_add1(request, "step", "0");
    fdb_request_add1(request, "param", "138");
    fdb_request_add1(request, "class", "rd");
    fdb_request_add1(request, "type", "an");
    fdb_request_add1(request, "expver", "xxxx");

    char buf[1000];
    char grib[4];
    long read = 0;
    long size;
    fdb_datareader_t* dr;
    fdb_new_datareader(&dr);
    EXPECT_EQUAL(fdb_retrieve(fdb, request, dr), FDB_ERROR_GENERAL_EXCEPTION);

    EXPECT_EQUAL(fdb_expand_request(request), FDB_SUCCESS);

    size_t numValues;
    char** values;

    fdb_request_get(request, "date", &values, &numValues);
    EXPECT_EQUAL(numValues, 2);
    EXPECT_EQUAL(0, strncmp(values[0], "20191110", 8));
    EXPECT_EQUAL(0, strncmp(values[1], "20191111", 8));
    delete[] values[0];
    delete[] values[1];
    delete[] values;

    EXPECT(fdb_retrieve(fdb, request, dr) == FDB_SUCCESS);
    fdb_datareader_open(dr, &size);
    EXPECT_NOT_EQUAL(0, size);
    fdb_datareader_read(dr, grib, 4, &read);
    EXPECT_EQUAL(4, read);
    EXPECT_EQUAL(0, strncmp(grib, "GRIB", 4));
    fdb_datareader_tell(dr, &read);
    EXPECT_EQUAL(4, read);
    fdb_datareader_seek(dr, 3);
    fdb_datareader_tell(dr, &read);
    EXPECT_EQUAL(3, read);
    fdb_datareader_skip(dr, 3);
    fdb_datareader_tell(dr, &read);
    EXPECT_EQUAL(6, read);
    fdb_datareader_read(dr, buf, 1000, &read);
    EXPECT_EQUAL(1000, read);
    fdb_datareader_tell(dr, &read);
    EXPECT_EQUAL(1006, read);
    fdb_delete_datareader(dr);

    fdb_request_add1(request, "date", "20191110/to/20191115/by/2");

    fdb_request_get(request, "date", &values, &numValues);
    EXPECT_EQUAL(numValues, 5);
    EXPECT_EQUAL(0, strncmp(values[0], "20191110", 8));
    EXPECT_EQUAL(0, strncmp(values[1], "to", 2));
    EXPECT_EQUAL(0, strncmp(values[2], "20191115", 8));
    EXPECT_EQUAL(0, strncmp(values[3], "by", 2));
    EXPECT_EQUAL(0, strncmp(values[4], "2", 1));
    for (size_t i = 0; i < numValues; i++) {
        delete[] values[i];
    }
    delete[] values;

    EXPECT(fdb_expand_request(request) == FDB_SUCCESS);

    fdb_request_get(request, "date", &values, &numValues);
    EXPECT_EQUAL(numValues, 3);
    EXPECT_EQUAL(0, strncmp(values[0], "20191110", 8));
    EXPECT_EQUAL(0, strncmp(values[1], "20191112", 8));
    EXPECT_EQUAL(0, strncmp(values[2], "20191114", 8));
    for (size_t i = 0; i < numValues; i++) {
        delete[] values[i];
    }
    delete[] values;

    fdb_delete_request(request);
    fdb_delete_handle(fdb);
}


//----------------------------------------------------------------------------------------------------------------------

}  // namespace test
}  // namespace fdb

int main(int argc, char** argv) {
    fdb_initialise();

    return run_tests(argc, argv);
}
