/*****************************************************************************
*    Open LiteSpeed is an open source HTTP server.                           *
*    Copyright (C) 2013 - 2022  LiteSpeed Technologies, Inc.                 *
*                                                                            *
*    This program is free software: you can redistribute it and/or modify    *
*    it under the terms of the GNU General Public License as published by    *
*    the Free Software Foundation, either version 3 of the License, or       *
*    (at your option) any later version.                                     *
*                                                                            *
*    This program 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 General Public License for more details.                            *
*                                                                            *
*    You should have received a copy of the GNU General Public License       *
*    along with this program. If not, see http://www.gnu.org/licenses/.      *
*****************************************************************************/
#ifdef RUN_TEST

#include <lsdef.h>
#include <util/radixtree.h>

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

#include "unittest-cpp/UnitTest++.h"

// #define RADIXTREE_DEBUG
// #define RADIXTREE_PRINTTREE
#define RT_DOREGTEST
#define RT_DOWCTEST

#define RTTEST_NOCONTEXT    (1 << 0)
#define RTTEST_GLOBALPOOL   (1 << 1)
#define RTTEST_MERGE        (1 << 2)

const char *pInputs[] =
{
    "/home/one/user/dir",
    "/home/two/user/dir",
    "/home/one/client/",
    "/home/one/client/dir",
    "/home/one",
    "/home/one/client1",
    "/home/one/client2",
    "/home/one/client3",
    "/home/one/client4",
    "/home/one/client5",
    "/home/one/client6",
    "/home/one/client7",
    "/home/one/client8",
    "/home/one/client9",
    "/home/one/client10",
    "/home/one/client11",
    "/home/one/client12",
    "/home/one/client13",
    "/home/one/client14",
    "/home/one/client15",
    "/home/one/client16",
    "/home/one/client17",
    "/home/one/client18",
    "/home/one/client19",
    "/not/starting/with/slash",
    "/not",
    "/home/one/two/three/four/five/six/seven/eight/nine/ten/"
};
const int iInputLen = 27;

const char *pWCTest[] =
{
    "/home/user*",
    "/home/test*",
    "/home/newbegin*",
    "/home/end*",
    "/home/*diff",
    "/home/in*middle",
    "/home/username/",
    "/home/testing",
    "/home/test/wherewillthisgo",
    "/home/testing/where/this/will/go",
    "/home/*",
    "/home/*/has/children",
    "/home/*/has/multi",
    "/home/*/should/these/be/found/",
    "/home/*/multi/layer*",
    "/home/*/to/be.sure",
    "/home/one/*"
};
const int iWCLen = 17;
// How got to this value:
// 1 - /home/* added obj to /home/test
// 9 - /home/*/to/be.sure added 9 objects, and was not deleted at the end.
// - 4 - When erasing, erased the original objects as well.
const int iMergeIncr = 1 + 9 - 4;

const char *pWCFind[] =
{
    "/home/usermatch/",
    "/home/testmatch/",
    "/home/newbeginning",
    "/home/ending",
    "/home/blah/to/be.sure",
    "/home/updating",
    "/home/one/match"
};
const int iWCFindLen = 7;

static int test_for_each(void *pObj, const char *pKey, int iKeyLen)
{
#ifdef RADIXTREE_DEBUG
    printf("%.*s, %p\n", iKeyLen, pKey, pObj);
#endif
    return 0;
}


static int test_for_each2(void *pObj, void *pUData, const char *pKey,
                          int iKeyLen)
{
#ifdef RADIXTREE_DEBUG
    int *p = (int *)pUData;
    printf("%.*s, %p %d\n", iKeyLen, pKey, pObj, ++(*p));
#endif
    return 0;
}


void doTest(RadixTree *pTree, char **pDynamicInputs, int count)
{
    int i;
    RadixTree tree2;
#ifdef RADIXTREE_PRINTTREE
    pTree->printTree();
#endif
    for (i = 0; i < count; ++i)
    {
#ifdef RADIXTREE_DEBUG
        printf("Testing add %s\n", pDynamicInputs[i]);
#endif
        CHECK(pTree->insert(pDynamicInputs[i], strlen(pDynamicInputs[i]),
                            pTree) != NULL);
#ifdef RADIXTREE_PRINTTREE
        pTree->printTree();
#endif
    }

    for (i = 0; i < count; i += 2)
    {
#ifdef RADIXTREE_DEBUG
        printf("Testing update %s\n", pDynamicInputs[i]);
#endif
        CHECK(pTree->update(pDynamicInputs[i], strlen(pDynamicInputs[i]),
                            &tree2) == pTree);
#ifdef RADIXTREE_PRINTTREE
        pTree->printTree();
#endif
    }

    for (i = 0; i < count; i += 2)
    {
#ifdef RADIXTREE_DEBUG
        printf("Testing find %s\n", pDynamicInputs[i]);
#endif
        CHECK(pTree->find(pDynamicInputs[i], strlen(pDynamicInputs[i])) == &tree2);
#ifdef RADIXTREE_PRINTTREE
        pTree->printTree();
#endif
    }

    for (i = 1; i < count; i += 2)
    {
#ifdef RADIXTREE_DEBUG
        printf("Testing find %s\n", pDynamicInputs[i]);
#endif
        CHECK(pTree->find(pDynamicInputs[i], strlen(pDynamicInputs[i])) == pTree);
    }

    const char *pBegin = pDynamicInputs[26];
    const char *pNext = (const char *)memchr(pBegin + 1, '/',
                        strlen(pBegin) - 1);
    void *pOut, *data = pTree->bestMatch(pDynamicInputs[4],
                                         strlen(pDynamicInputs[4]));
    CHECK((pOut = pTree->bestMatch(pBegin, pNext - pBegin)) == NULL);
#ifdef RADIXTREE_DEBUG
    printf("Should be null: %p\n", pOut);
#endif
    if (pTree->getNoContext() == 0)
    {
        while (pNext && (pNext = (const char *)memchr(pNext + 1, '/',
                                             strlen(pNext) - 1)) != NULL)
        {
            CHECK((pOut = pTree->bestMatch(pBegin, pNext - pBegin)) == data);
#ifdef RADIXTREE_DEBUG
            printf("Should match: %p %p\n", pOut, data);
#endif
        }
    }
    else
    {
        if (pNext)
            pNext = (const char *)memchr(pNext + 1, '/', strlen(pNext) - 1);
        if (pNext)
        {
            CHECK((pOut = pTree->bestMatch(pBegin, pNext - pBegin)) == data);
            while ((pNext = (const char *)memchr(pNext + 1, '/',
                                                 strlen(pNext) - 2)) != NULL)
            {
                CHECK((pOut = pTree->bestMatch(pBegin, pNext - pBegin)) == NULL);
#ifdef RADIXTREE_DEBUG
                printf("Should be NULL: %p\n", pOut);
#endif
            }
            CHECK((pOut = pTree->bestMatch(pBegin, strlen(pBegin))) == &tree2);
        }
    }

    CHECK(pTree->for_each(test_for_each) == count);
    int p = 0;
    CHECK(pTree->for_each2(test_for_each2, &p) == count);
}

void doWCTest(RadixTree *pTree)
{
    int i, count = iWCLen + iInputLen;
    RadixTree tree2;
    RadixTree tree3;
#ifdef RADIXTREE_PRINTTREE
    pTree->printTree();
#endif
    for (i = 0; i < iWCLen; ++i)
    {
#ifdef RADIXTREE_DEBUG
        printf("Testing add %s\n", pWCTest[i]);
#endif
        CHECK(pTree->insert(pWCTest[i], strlen(pWCTest[i]), pTree) != NULL);
#ifdef RADIXTREE_PRINTTREE
        pTree->printTree();
#endif
    }

    for (i = 0; i < iInputLen; ++i)
    {
#ifdef RADIXTREE_DEBUG
        printf("Testing add %s\n", pInputs[i]);
#endif
        CHECK(pTree->insert(pInputs[i], strlen(pInputs[i]), pTree) != NULL);
#ifdef RADIXTREE_PRINTTREE
        pTree->printTree();
#endif
    }

    for (i = 0; i < iInputLen; i += 2)
    {
#ifdef RADIXTREE_DEBUG
        printf("Testing update %s\n", pInputs[i]);
#endif
        CHECK(pTree->update(pInputs[i], strlen(pInputs[i]), &tree2) == pTree);
#ifdef RADIXTREE_PRINTTREE
        pTree->printTree();
#endif
    }

    for (i = 0; i < iInputLen; i += 2)
    {
#ifdef RADIXTREE_DEBUG
        printf("Testing find %s\n", pInputs[i]);
#endif
        CHECK(pTree->find(pInputs[i], strlen(pInputs[i])) == &tree2);
#ifdef RADIXTREE_PRINTTREE
        pTree->printTree();
#endif
    }

    for (i = 1; i < iInputLen; i += 2)
    {
#ifdef RADIXTREE_DEBUG
        printf("Testing find %s\n", pInputs[i]);
#endif
        CHECK(pTree->find(pInputs[i], strlen(pInputs[i])) == pTree);
    }

    for (i = 0; i < iWCFindLen; ++i)
    {
#ifdef RADIXTREE_DEBUG
        printf("Testing find %s\n", pWCFind[i]);
#endif
        CHECK(pTree->find(pWCFind[i], strlen(pWCFind[i])) == pTree);
    }

    for (i = 0; i < iWCFindLen; ++i)
    {
#ifdef RADIXTREE_DEBUG
        printf("Testing update %s\n", pWCFind[i]);
#endif
        CHECK(pTree->update(pWCFind[i], strlen(pWCFind[i]), &tree2) == NULL);
    }

    for (i = 0; i < iWCLen; ++i)
    {
#ifdef RADIXTREE_DEBUG
        printf("Testing update %s\n", pWCTest[i]);
#endif
        CHECK(pTree->update(pWCTest[i], strlen(pWCTest[i]), &tree3) == pTree);
#ifdef RADIXTREE_PRINTTREE
        pTree->printTree();
#endif
    }

    for (i = 0; i < iWCFindLen - 1; ++i)
    {
#ifdef RADIXTREE_DEBUG
        printf("Testing bestMatch %s\n", pWCFind[i]);
#endif
        CHECK(pTree->bestMatch(pWCFind[i], strlen(pWCFind[i])) == &tree3);
    }

    for (i = 0; i < iInputLen; i += 2)
    {
#ifdef RADIXTREE_DEBUG
        printf("Testing bestMatch %s\n", pInputs[i]);
#endif
        CHECK(pTree->bestMatch(pInputs[i], strlen(pInputs[i])) == &tree2);
#ifdef RADIXTREE_PRINTTREE
        pTree->printTree();
#endif
    }

    const char *pFindFail = "/home/undiff/has/no/children";
#ifdef RADIXTREE_DEBUG
    printf("Testing find %s\n", pFindFail);
#endif
    if (pTree->getNoContext())
        CHECK(pTree->find(pFindFail, strlen(pFindFail)) != NULL);
    else
        CHECK(pTree->find(pFindFail, strlen(pFindFail)) == NULL);

    if (pTree->getUseMerge())
    {
        const char *pEraseFail[] =
        {
            "/home/testing/has/children",
            "/home/testing/has/multi",
            "/home/*diff/multi/layer*",
            "/home/username/should/these/be/found"
        };
        const int iEraseFailCnt = 4;
        for (i = 0; i < iEraseFailCnt; ++i)
        {
#ifdef RADIXTREE_DEBUG
            printf("Testing find and erase %s\n", pEraseFail[i]);
#endif
            CHECK(pTree->find(pEraseFail[i], strlen(pEraseFail[i])) != NULL);
            CHECK(pTree->erase(pEraseFail[i], strlen(pEraseFail[i])) == NULL);
        }
#ifdef RADIXTREE_DEBUG
        printf("Testing erase %s\n", pWCTest[11]);
#endif
        CHECK(pTree->erase(pWCTest[11], strlen(pWCTest[11])) != NULL);
#ifdef RADIXTREE_DEBUG
        printf("Testing erase %s\n", pWCTest[12]);
#endif
        CHECK(pTree->erase(pWCTest[12], strlen(pWCTest[12])) != NULL);
#ifdef RADIXTREE_DEBUG
        printf("Testing erase %s\n", pWCTest[13]);
#endif
        CHECK(pTree->erase(pWCTest[13], strlen(pWCTest[13])) != NULL);
#ifdef RADIXTREE_DEBUG
        printf("Testing erase %s\n", pWCTest[14]);
#endif
        CHECK(pTree->erase(pWCTest[14], strlen(pWCTest[14])) != NULL);

        for (i = 0; i < iEraseFailCnt; ++i)
        {
#ifdef RADIXTREE_DEBUG
            printf("Testing find %s\n", pEraseFail[i]);
#endif
            CHECK(pTree->find(pEraseFail[i], strlen(pEraseFail[i])) == NULL);
        }
        if (pTree->getNoContext() != 0)
            count -= iEraseFailCnt;
        else
            count += iMergeIncr;
    }

    CHECK(pTree->for_each(test_for_each) == count);
    int p = 0;
    CHECK(pTree->for_each2(test_for_each2, &p) == count);
}

void setupTest(char **pDynamicInputs, int count)
{
    int i, iNumFlags = 1 << 2;
    RadixTree *pContTree, *pPtrTree;
#ifdef RT_DOREGTEST
    for (i = 0; i < iNumFlags; ++i)
    {
        // NULL pool -> tree uses (and frees) the global pool; otherwise the
        // caller owns the xpool and must delete it after the tree.
        int useGlobal = (i & RTTEST_GLOBALPOOL) != 0;
        ls_xpool_t *pContPool = useGlobal ? NULL : ls_xpool_new();
        ls_xpool_t *pPtrPool = useGlobal ? NULL : ls_xpool_new();
        pContTree = new RadixTree(pContPool, RTMODE_CONTIGUOUS);
        pPtrTree = new RadixTree(pPtrPool, RTMODE_POINTER);
        if ((i & RTTEST_NOCONTEXT) != 0)
        {
            pContTree->setNoContext();
            pPtrTree->setNoContext();
        }
        else
        {
            pContTree->setRootLabel("/", 1);
            pPtrTree->setRootLabel("/", 1);
        }

        printf("CONTIGUOUS TEST: NoContext: %d, GlobalPool: %d\n",
               pContTree->getNoContext() != 0, useGlobal);
        doTest(pContTree, pDynamicInputs, count);
        printf("END CONTIGUOUS TEST\n");

        printf("POINTER TEST: NoContext: %d, GlobalPool: %d\n",
               pPtrTree->getNoContext() != 0, useGlobal);
        doTest(pPtrTree, pDynamicInputs, count);
        printf("END POINTER TEST\n");

        delete pContTree;
        delete pPtrTree;
        if (pContPool != NULL)
            ls_xpool_delete(pContPool);
        if (pPtrPool != NULL)
            ls_xpool_delete(pPtrPool);
    }
#endif
#ifdef RT_DOWCTEST
    iNumFlags = 1 << 3;
    for (i = 0; i < iNumFlags; ++i)
    {
        // NULL pool -> tree uses (and frees) the global pool; otherwise the
        // caller owns the xpool and must delete it after the tree.
        int useGlobal = (i & RTTEST_GLOBALPOOL) != 0;
        ls_xpool_t *pContPool = useGlobal ? NULL : ls_xpool_new();
        ls_xpool_t *pPtrPool = useGlobal ? NULL : ls_xpool_new();
        pContTree = new RadixTree(pContPool, RTMODE_CONTIGUOUS);
        pPtrTree = new RadixTree(pPtrPool, RTMODE_POINTER);
        pContTree->setUseWildCard();
        pPtrTree->setUseWildCard();
        if ((i & RTTEST_NOCONTEXT) != 0)
        {
            pContTree->setNoContext();
            pPtrTree->setNoContext();
        }
        else
        {
            pContTree->setRootLabel("/", 1);
            pPtrTree->setRootLabel("/", 1);
        }
        if ((i & RTTEST_MERGE) != 0)
        {
            pContTree->setUseMerge();
            pPtrTree->setUseMerge();
        }

        printf("CONTIGUOUS TEST: NoContext: %d, GlobalPool: %d, Merge: %d\n",
               pContTree->getNoContext() != 0, useGlobal,
               pContTree->getUseMerge());
        doWCTest(pContTree);
        printf("END CONTIGUOUS TEST\n");

        printf("POINTER TEST: NoContext: %d, GlobalPool: %d, Merge: %d\n",
               pPtrTree->getNoContext() != 0, useGlobal,
               pPtrTree->getUseMerge());
        doWCTest(pPtrTree);
        printf("END POINTER TEST\n");
        delete pContTree;
        delete pPtrTree;
        if (pContPool != NULL)
            ls_xpool_delete(pContPool);
        if (pPtrPool != NULL)
            ls_xpool_delete(pPtrPool);
    }
#endif
}

TEST(radixtreetest)
{
    printf("Start Radix Tree Test!\n");
    int i, count = iInputLen, iDynamicCount = iInputLen;
    char **pDynamicInputs = (char **)malloc(sizeof(char *)*iDynamicCount);
    for (i = 0; i < iDynamicCount; ++i)
        pDynamicInputs[i] = strdup(pInputs[i]);

    setupTest(pDynamicInputs, count);

    for (i = 0; i < iDynamicCount; ++i)
        free(pDynamicInputs[i]);
    free(pDynamicInputs);
    printf("End Radix Tree Test!\n");
}


#endif

