/* ============================================================
 *
 * This file is a part of digiKam project
 * https://www.digikam.org
 *
 * Date        : 2023-05-15
 * Description : geolocation engine based on Marble.
 *               (c) 2007-2022 Marble Team
 *               https://invent.kde.org/education/marble/-/raw/master/data/credits_authors.html
 *
 * SPDX-FileCopyrightText: 2023-2026 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * ============================================================ */

#include "o5mreader.h"

// Qt includes

#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <mutex>

#define STR_PAIR_TABLE_SIZE  15000
#define STR_PAIR_STRING_SIZE 256

namespace Marble
{

std::mutex mtx;

O5mreaderRet o5mreader_readUInt(O5mreader* pReader, uint64_t* ret)
{
    uint8_t b = 0;
    uint8_t i = 0;
    *ret      = 0LL;

    do
    {
        if (fread(&b, 1, 1, pReader->f) == 0)
        {
            o5mreader_setError(pReader,
                               O5MREADER_ERR_CODE_UNEXPECTED_END_OF_FILE,
                               NULL);

            return O5MREADER_RET_ERR;
        }

        *ret |= (long long)(b & 0x7f) << (i++ * 7);
    }
    while (b & 0x80);

    o5mreader_setNoError(pReader);

    return O5MREADER_RET_OK;
}

O5mreaderRet o5mreader_readInt(O5mreader* pReader, uint64_t* ret)
{
    if (o5mreader_readUInt(pReader, ret) == O5MREADER_RET_ERR)
    {
        return O5MREADER_RET_ERR;
    }

    *ret = (*ret & 1)
           ? -(int64_t)(*ret >> 1) - 1
           :  (int64_t)(*ret >> 1);

    return O5MREADER_RET_OK;
}

O5mreaderRet o5mreader_readStrPair(O5mreader* pReader, char** tagpair, int single)
{
    static char     buffer[1024] = { 0 };
    char*           pBuf         = NULL;
    static uint64_t pointer      = 0;
    int             length       = 0;
    uint64_t        key          = 0;
    int             i            = 0;

    if (o5mreader_readUInt(pReader, &key) == O5MREADER_RET_ERR)
    {
        return O5MREADER_RET_ERR;
    }

    if (key)
    {
        *tagpair = pReader->strPairTable[(pointer + 15000 - key) % 15000];

        return (O5mreaderRet)key;
    }
    else
    {
        pBuf = buffer;

        for (i = 0; i < (single ? 1 : 2); i++)
        {
            do
            {
                if (fread(pBuf, 1, 1, pReader->f) == 0)
                {
                    o5mreader_setError(pReader,
                                       O5MREADER_ERR_CODE_UNEXPECTED_END_OF_FILE,
                                       NULL);

                    return O5MREADER_RET_ERR;
                }
            }
            while (*(pBuf++));
        }

        length = strlen(buffer) + (single ? 1 : strlen(buffer + strlen(buffer) + 1) + 2);

        if (length <= 252)
        {
            *tagpair = pReader->strPairTable[(pointer + 15000) % 15000];
            memcpy(pReader->strPairTable[((pointer++) + 15000) % 15000], buffer, length);
        }
        else
        {
            *tagpair = buffer;
        }

    }

    return O5MREADER_RET_OK;
}

O5mreaderRet o5mreader_reset(O5mreader* pReader)
{
    pReader->nodeId         = 0;
    pReader->wayId          = 0;
    pReader->wayNodeId      = 0;
    pReader->relId          = 0;
    pReader->nodeRefId      = 0;
    pReader->wayRefId       = 0;
    pReader->relRefId       = 0;
    pReader->lon            = 0;
    pReader->lat            = 0;
    pReader->offset         = 0;
    pReader->canIterateTags = 0;
    pReader->canIterateNds  = 0;
    pReader->canIterateRefs = 0;

    return O5MREADER_RET_OK;
}

O5mreaderRet o5mreader_open(O5mreader** ppReader, FILE* f)
{
    uint8_t byte = 0;
    int i        = 0;
    *ppReader    = reinterpret_cast<O5mreader*>(malloc(sizeof(O5mreader)));

    if (!(*ppReader))
    {
        return O5MREADER_RET_ERR;
    }

    (*ppReader)->errMsg       = NULL;
    (*ppReader)->f            = f;
    (*ppReader)->strPairTable = NULL;

    if (fread(&byte, 1, 1, (*ppReader)->f) == 0)
    {
        o5mreader_setError(*ppReader,
                           O5MREADER_ERR_CODE_UNEXPECTED_END_OF_FILE,
                           NULL);

        return O5MREADER_RET_ERR;
    }

    if (byte != O5MREADER_DS_RESET)
    {
        o5mreader_setError(*ppReader,
                           O5MREADER_ERR_CODE_FILE_HAS_WRONG_START,
                           NULL);

        return O5MREADER_RET_ERR;
    }

    o5mreader_reset(*ppReader);

    (*ppReader)->strPairTable = reinterpret_cast<char**>(malloc(STR_PAIR_TABLE_SIZE * sizeof(char*)));

    if ((*ppReader)->strPairTable == 0)
    {
        o5mreader_setError(*ppReader,
                           O5MREADER_ERR_CODE_MEMORY_ERROR,
                           NULL);

        return O5MREADER_RET_ERR;
    }

    for (i = 0; i < STR_PAIR_TABLE_SIZE; ++i)
    {
        (*ppReader)->strPairTable[i] = reinterpret_cast<char*>(malloc(sizeof(char) * STR_PAIR_STRING_SIZE));

        if ((*ppReader)->strPairTable[i] == 0)
        {
            o5mreader_setError(*ppReader,
                               O5MREADER_ERR_CODE_MEMORY_ERROR,
                               NULL);

            return O5MREADER_RET_ERR;
        }
    }

    o5mreader_setNoError(*ppReader);

    return O5MREADER_RET_OK;
}

void o5mreader_close(O5mreader* pReader)
{
    int i;

    if (pReader)
    {
        if (pReader->strPairTable)
        {
            for (i = 0; i < STR_PAIR_TABLE_SIZE; ++i)
            {
                if (pReader->strPairTable[i])
                {
                    free(pReader->strPairTable[i]);
                }
            }

            free(pReader->strPairTable);
        }

        o5mreader_setNoError(pReader);
        free(pReader);
    }
}

const char* o5mreader_strerror(int errCode)
{
    switch (errCode)
    {
        case O5MREADER_ERR_CODE_FILE_HAS_WRONG_START:
        {
            return "'0xFF' isn't first byte of file.";
        }

        case O5MREADER_ERR_CODE_MEMORY_ERROR:
        {
            return "Memory error.";
        }

        case O5MREADER_ERR_CODE_UNEXPECTED_END_OF_FILE:
        {
            return "Unexpected end of file.";
        }

        case O5MREADER_ERR_CODE_CAN_NOT_ITERATE_TAGS_HERE:
        {
            return "Tags iteration is not allowed here.";
        }

        case O5MREADER_ERR_CODE_CAN_NOT_ITERATE_NDS_HERE:
        {
            return "Nodes iteration is not allowed here.";
        }

        case O5MREADER_ERR_CODE_CAN_NOT_ITERATE_REFS_HERE:
        {
            return "References iteration is not allowed here.";
        }

        default:
        {
            return "Unknown error code";
        }
    }
}

void o5mreader_setError(O5mreader* pReader, int code, const char* message)
{
    pReader->errCode = code;

    if (pReader->errMsg)
    {
        free(pReader->errMsg);
    }

    if (message)
    {
        pReader->errMsg = reinterpret_cast<char*>(malloc(strlen(message) + 1));
        strcpy(pReader->errMsg, message);
    }
}

void o5mreader_setNoError(O5mreader* pReader)
{
    pReader->errCode = O5MREADER_ERR_CODE_OK;

    if (pReader->errMsg)
    {
        free(pReader->errMsg);
    }

    pReader->errMsg = NULL;
}

O5mreaderIterateRet o5mreader_iterateDataSet(O5mreader* pReader, O5mreaderDataset* ds)
{
    for (;;)
    {
        if (pReader->offset)
        {
            if (o5mreader_skipTags(pReader) == O5MREADER_ITERATE_RET_ERR)
            {
                return O5MREADER_ITERATE_RET_ERR;
            }

            int ret = (int)fseek(
                                 pReader->f,
                                 (long)((pReader->current - ftell(pReader->f)) + pReader->offset),
                                 SEEK_CUR
                                );

            (void)ret;

            pReader->offset = 0;
        }

        if (fread(&(ds->type), 1, 1, pReader->f) == 0)
        {
            o5mreader_setError(pReader,
                               O5MREADER_ERR_CODE_UNEXPECTED_END_OF_FILE,
                               NULL);

            return O5MREADER_ITERATE_RET_ERR;
        }

        if (O5MREADER_DS_END == ds->type)
        {
            return O5MREADER_ITERATE_RET_DONE;
        }

        if      (O5MREADER_DS_RESET == ds->type)
        {
            o5mreader_reset(pReader);
        }
        else if (0xf0 == ds->type)
        {
        }
        else
        {
            if (o5mreader_readUInt(pReader, &pReader->offset) == O5MREADER_RET_ERR)
            {
                return O5MREADER_ITERATE_RET_ERR;
            }

            pReader->current = ftell(pReader->f);

            switch (ds->type)
            {
                case O5MREADER_DS_NODE:
                {
                    return o5mreader_readNode(pReader, ds);
                }

                case O5MREADER_DS_WAY:
                {
                    return o5mreader_readWay(pReader, ds);
                }

                case O5MREADER_DS_REL:
                {
                    return o5mreader_readRel(pReader, ds);
                }

/*
                case O5MREADER_DS_BBOX:
                case O5MREADER_DS_TSTAMP:
                case O5MREADER_DS_HEADER:
                case O5MREADER_DS_SYNC:
                case O5MREADER_DS_JUMP:
                default:
                {
                    break;
                }
*/
            }
        }
    }
}

int o5mreader_thereAreNoMoreData(O5mreader* pReader)
{
    return (pReader->current + pReader->offset) <= static_cast<uint64_t>(ftell(pReader->f));
}

O5mreaderIterateRet o5mreader_readVersion(O5mreader* pReader, O5mreaderDataset* ds)
{
    uint64_t tmp = 0;

    if (o5mreader_readUInt(pReader, &tmp) == O5MREADER_ITERATE_RET_ERR)
    {
        return O5MREADER_ITERATE_RET_ERR;
    }

    ds->version = (uint32_t)tmp;

    if (tmp)
    {
        if (o5mreader_readUInt(pReader, &tmp) == O5MREADER_ITERATE_RET_ERR)
        {
            return O5MREADER_ITERATE_RET_ERR;
        }

        if (o5mreader_readInt(pReader, &tmp) == O5MREADER_ITERATE_RET_ERR)
        {
            return O5MREADER_ITERATE_RET_ERR;
        }

        if (o5mreader_thereAreNoMoreData(pReader))
        {
            return O5MREADER_ITERATE_RET_DONE;
        }

        mtx.lock();
        O5mreaderRet strPair = o5mreader_readStrPair(pReader, &pReader->tagPair, 0);
        mtx.unlock();

        if (strPair == O5MREADER_ITERATE_RET_ERR)
        {
            return O5MREADER_ITERATE_RET_ERR;
        }
    }

    if (o5mreader_thereAreNoMoreData(pReader))
    {
        return O5MREADER_ITERATE_RET_DONE;
    }

    return O5MREADER_ITERATE_RET_NEXT;
}

O5mreaderIterateRet o5mreader_iterateTags(O5mreader* pReader, char** pKey, char** pVal)
{
    if (pReader->canIterateRefs)
    {
        if (o5mreader_skipRefs(pReader) == O5MREADER_ITERATE_RET_ERR)
        {
            return O5MREADER_ITERATE_RET_ERR;
        }
    }

    if (pReader->canIterateNds)
    {
        if (o5mreader_skipNds(pReader) == O5MREADER_ITERATE_RET_ERR)
        {
            return O5MREADER_ITERATE_RET_ERR;
        }
    }

    if (!pReader->canIterateTags)
    {
        o5mreader_setError(pReader,
                           O5MREADER_ERR_CODE_CAN_NOT_ITERATE_TAGS_HERE,
                           NULL
                          );
        return O5MREADER_ITERATE_RET_ERR;
    }

    if (o5mreader_thereAreNoMoreData(pReader))
    {
        pReader->canIterateTags = 0;
        return O5MREADER_ITERATE_RET_DONE;
    }

    mtx.lock();
    O5mreaderRet strPair = o5mreader_readStrPair(pReader, &pReader->tagPair, 0);
    mtx.unlock();

    if (strPair == O5MREADER_RET_ERR)
    {
        return O5MREADER_ITERATE_RET_ERR;
    }

    if (pKey)
    {
        *pKey = pReader->tagPair;
    }

    if (pVal)
    {
        *pVal = pReader->tagPair + strlen(pReader->tagPair) + 1;
    }

    return O5MREADER_ITERATE_RET_NEXT;
}

O5mreaderIterateRet o5mreader_skipTags(O5mreader* pReader)
{
    int ret = O5MREADER_ITERATE_RET_DONE;

    if (pReader->canIterateTags)
    {
        while (O5MREADER_ITERATE_RET_NEXT == (ret = o5mreader_iterateTags(pReader, NULL, NULL)));
    }

    return ret;
}

O5mreaderIterateRet o5mreader_readNode(O5mreader* pReader, O5mreaderDataset* ds)
{
    int64_t nodeId  = 0;
    int64_t lon     = 0;
    int64_t lat     = 0;

    if (o5mreader_readInt(pReader, reinterpret_cast<uint64_t*>(&nodeId)) == O5MREADER_RET_ERR)
    {
        return O5MREADER_ITERATE_RET_ERR;
    }

    pReader->canIterateRefs = 0;
    pReader->canIterateNds  = 0;
    pReader->canIterateTags = 1;

    pReader->nodeId        += nodeId;
    ds->id                  = pReader->nodeId;

    if (o5mreader_readVersion(pReader, ds) == O5MREADER_ITERATE_RET_DONE)
    {
        ds->isEmpty = 1;

        return O5MREADER_ITERATE_RET_NEXT;
    }
    else
    {
        ds->isEmpty = 0;
    }

    if (o5mreader_thereAreNoMoreData(pReader))
    {
        return O5MREADER_ITERATE_RET_NEXT;
    }

    if (o5mreader_readInt(pReader, reinterpret_cast<uint64_t*>(&lon)) == O5MREADER_RET_ERR)
    {
        return O5MREADER_ITERATE_RET_ERR;
    }

    pReader->lon += (int32_t)lon;

    if (o5mreader_readInt(pReader, reinterpret_cast<uint64_t*>(&lat)) == O5MREADER_RET_ERR)
    {
        return O5MREADER_ITERATE_RET_ERR;
    }

    pReader->lat += (int32_t)lat;

    ds->lon = pReader->lon;
    ds->lat = pReader->lat;

    return O5MREADER_ITERATE_RET_NEXT;
}

O5mreaderIterateRet o5mreader_iterateNds(O5mreader* pReader, uint64_t* nodeId)
{
    int64_t wayNodeId;

    if (!pReader->canIterateNds)
    {
        o5mreader_setError(pReader,
                           O5MREADER_ERR_CODE_CAN_NOT_ITERATE_NDS_HERE,
                           NULL);

        return O5MREADER_ITERATE_RET_ERR;
    }

    if (ftell(pReader->f) >= long(pReader->offsetNd))
    {
        pReader->canIterateNds = 0;
        pReader->canIterateTags = 1;
        pReader->canIterateRefs = 0;

        return O5MREADER_ITERATE_RET_DONE;
    }

    if (o5mreader_readInt(pReader, reinterpret_cast<uint64_t*>(&wayNodeId)) == O5MREADER_RET_ERR)
    {
        return O5MREADER_ITERATE_RET_ERR;
    }

    pReader->wayNodeId += wayNodeId;

    if (nodeId)
    {
        *nodeId = pReader->wayNodeId;
    }

    return O5MREADER_ITERATE_RET_NEXT;
}

O5mreaderIterateRet o5mreader_skipNds(O5mreader* pReader)
{
    uint8_t ret = 0;

    while (pReader->canIterateNds &&
           O5MREADER_ITERATE_RET_NEXT == (ret = o5mreader_iterateNds(pReader, NULL)));

    return ret;
}

O5mreaderIterateRet o5mreader_readWay(O5mreader* pReader, O5mreaderDataset* ds)
{
    int64_t wayId = 0;

    if (o5mreader_readInt(pReader, reinterpret_cast<uint64_t*>(&wayId)) == O5MREADER_RET_ERR)
    {
        return O5MREADER_ITERATE_RET_ERR;
    }

    pReader->wayId += wayId;
    ds->id          = pReader->wayId;

    if (o5mreader_readVersion(pReader, ds) == O5MREADER_ITERATE_RET_DONE)
    {
        ds->isEmpty = 1;

        return O5MREADER_ITERATE_RET_NEXT;
    }

    else
    {
        ds->isEmpty = 0;
    }

    if (o5mreader_readUInt(pReader, &pReader->offsetNd) == O5MREADER_RET_ERR)
    {
        return O5MREADER_ITERATE_RET_ERR;
    }

    pReader->offsetNd      += ftell(pReader->f);
    pReader->canIterateRefs = 0;
    pReader->canIterateNds  = 1;
    pReader->canIterateTags = 0;

    return O5MREADER_ITERATE_RET_NEXT;
}

O5mreaderIterateRet o5mreader_iterateRefs(O5mreader* pReader, uint64_t* refId, uint8_t* type, char** pRole)
{
    int64_t relRefId = 0;

    if (!pReader->canIterateRefs)
    {
        o5mreader_setError(pReader,
                           O5MREADER_ERR_CODE_CAN_NOT_ITERATE_REFS_HERE,
                           NULL);

        return O5MREADER_ITERATE_RET_ERR;
    }

    if (ftell(pReader->f) >= long(pReader->offsetRf))
    {
        pReader->canIterateNds  = 0;
        pReader->canIterateTags = 1;
        pReader->canIterateRefs = 0;

        return O5MREADER_ITERATE_RET_DONE;
    }

    if (o5mreader_readInt(pReader, reinterpret_cast<uint64_t*>(&relRefId)) == O5MREADER_RET_ERR)
    {
        return O5MREADER_ITERATE_RET_ERR;
    }

    //fread(_,1,1,pReader->f);

    mtx.lock();
    O5mreaderRet strPair = o5mreader_readStrPair(pReader, &pReader->tagPair, 1);
    mtx.unlock();

    if (strPair == O5MREADER_RET_ERR)
    {
        return O5MREADER_ITERATE_RET_ERR;
    }

    switch (pReader->tagPair[0])
    {
        case '0':
        {
            if (type)
            {
                *type = O5MREADER_DS_NODE;
            }

            pReader->nodeRefId += relRefId;

            if (refId)
            {
                *refId = pReader->nodeRefId;
            }

            break;
        }

        case '1':
        {
            if (type)
            {
                *type = O5MREADER_DS_WAY;
            }

            pReader->wayRefId += relRefId;

            if (refId)
            {
                *refId = pReader->wayRefId;
            }

            break;
        }

        case '2':
        {
            if (type)
            {
                *type = O5MREADER_DS_REL;
            }

            pReader->relRefId += relRefId;

            if (refId)
            {
                *refId = pReader->relRefId;
            }

            break;
        }
    }

    if (pRole)
    {
        *pRole = pReader->tagPair + 1;
    }

    return O5MREADER_ITERATE_RET_NEXT;
}

O5mreaderIterateRet o5mreader_skipRefs(O5mreader* pReader)
{
    uint8_t ret = 0;

    while (
           pReader->canIterateRefs &&
           (O5MREADER_ITERATE_RET_NEXT == (ret = o5mreader_iterateRefs(pReader, NULL, NULL, NULL)))
          );

    return ret;
}

O5mreaderIterateRet o5mreader_readRel(O5mreader* pReader, O5mreaderDataset* ds)
{
    int64_t relId = 0;

    if (o5mreader_readInt(pReader, reinterpret_cast<uint64_t*>(&relId)) == O5MREADER_RET_ERR)
    {
        return O5MREADER_ITERATE_RET_ERR;
    }

    pReader->relId += relId;
    ds->id          = pReader->relId;

    if (o5mreader_readVersion(pReader, ds) == O5MREADER_ITERATE_RET_DONE)
    {
        ds->isEmpty = 1;

        return O5MREADER_ITERATE_RET_NEXT;
    }
    else
    {
        ds->isEmpty = 0;
    }

    o5mreader_readUInt(pReader, &pReader->offsetRf);
    pReader->offsetRf       += ftell(pReader->f);

    pReader->canIterateRefs = 1;
    pReader->canIterateNds  = 0;
    pReader->canIterateTags = 0;

    return O5MREADER_ITERATE_RET_NEXT;
}

} // namespace Marble
