/* ============================================================
 *
 * 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-2024 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;
    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];
    char* pBuf;
    static uint64_t pointer = 0;
    int length;
    uint64_t key;
    int i;

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

    if (key)
    {
        *tagpair = pReader->strPairTable[(pointer + 15000 - key) % 15000];
        return 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 = pReader->wayId = pReader->wayNodeId = pReader->relId = pReader->nodeRefId = pReader->wayRefId = pReader->relRefId = 0;
    pReader->lon = pReader->lat = 0;
    pReader->offset = 0;
    pReader->canIterateTags = pReader->canIterateNds = pReader->canIterateRefs = 0;
    return O5MREADER_RET_OK;
}

O5mreaderRet o5mreader_open(O5mreader** ppReader, FILE* f)
{
    uint8_t byte;
    int i;
    *ppReader = (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 = (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] = (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 = (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 = fseek(
                pReader->f,
                (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 (int)((pReader->current - ftell(pReader->f)) + pReader->offset) <= 0;
}

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

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

    ds->version = 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;
    int64_t lon, lat;

    if (o5mreader_readInt(pReader, (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, (uint64_t*)&lon) == O5MREADER_RET_ERR)
    {
        return O5MREADER_ITERATE_RET_ERR;
    }

    pReader->lon += (int32_t)lon;

    if (o5mreader_readInt(pReader, (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, (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;

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

    return ret;
}

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

    if (o5mreader_readInt(pReader, (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;

    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, (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;

    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;

    if (o5mreader_readInt(pReader, (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
