/******************************************************************************
 * hiperiso_json.c
 *
 * Copyright (c) 2021, longpanda <admin@hiperiso.net>
 *
 * 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/>.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#if defined(_MSC_VER) || defined(WIN32)
#else
#include <unistd.h>
#include <sys/types.h>
#include <linux/limits.h>
#endif
#include <hiperiso_define.h>
#include <hiperiso_util.h>
#include <hiperiso_json.h>

static void hiso_json_free(HISO_JSON *pstJsonHead)
{
    HISO_JSON *pstNext = NULL;

    while (NULL != pstJsonHead)
    {
        pstNext = pstJsonHead->pstNext;
        if ((pstJsonHead->enDataType < JSON_TYPE_BUTT) && (NULL != pstJsonHead->pstChild))
        {
            hiso_json_free(pstJsonHead->pstChild);
        }

        free(pstJsonHead);
        pstJsonHead = pstNext;
    }

    return;
}

static char *hiso_json_skip(const char *pcData)
{
    while ((NULL != pcData) && ('\0' != *pcData) && (*pcData <= 32))
    {
        pcData++;
    }

    return (char *)pcData;
}

HISO_JSON *hiso_json_find_item
(
    HISO_JSON *pstJson,
    JSON_TYPE  enDataType,
    const char *szKey
)
{
    while (NULL != pstJson)
    {
        if ((enDataType == pstJson->enDataType) && 
            (0 == strcmp(szKey, pstJson->pcName)))
        {
            return pstJson;
        }
        pstJson = pstJson->pstNext;
    }
    
    return NULL;
}

static int hiso_json_parse_number
(
    HISO_JSON *pstJson, 
    const char *pcData,
    const char **ppcEnd
)
{
    unsigned long Value;

    Value = strtoul(pcData, (char **)ppcEnd, 10);
    if (*ppcEnd == pcData)
    {
        vdebug("Failed to parse json number %s.\n", pcData);
        return JSON_FAILED;
    }

    pstJson->enDataType = JSON_TYPE_NUMBER;
    pstJson->unData.lValue = Value;
    
    return JSON_SUCCESS;
}

static int hiso_json_parse_string
(
    char *pcNewStart,
    char *pcRawStart,
    HISO_JSON *pstJson, 
    const char *pcData,
    const char **ppcEnd
)
{
    uint32_t uiLen = 0;
    const char *pcPos = NULL;
    const char *pcTmp = pcData + 1;
    
    *ppcEnd = pcData;

    if ('\"' != *pcData)
    {
        return JSON_FAILED;
    }

    pcPos = strchr(pcTmp, '\"');
    if ((NULL == pcPos) || (pcPos < pcTmp))
    {
        vdebug("Invalid string %s.\n", pcData);
        return JSON_FAILED;
    }

    if (*(pcPos - 1) == '\\')
    {
        for (pcPos++; *pcPos; pcPos++)
        {
            if (*pcPos == '"' && *(pcPos - 1) != '\\')
            {
                break;
            }
        }
        
        if (*pcPos == 0 || pcPos < pcTmp)
        {
            vdebug("Invalid quotes string %s.", pcData);
            return JSON_FAILED;
        }
    }

    *ppcEnd = pcPos + 1;
    uiLen = (uint32_t)(unsigned long)(pcPos - pcTmp);    
    
    pstJson->enDataType = JSON_TYPE_STRING;
    pstJson->unData.pcStrVal = pcNewStart + (pcTmp - pcRawStart);
    pstJson->unData.pcStrVal[uiLen] = '\0';
    
    return JSON_SUCCESS;
}

static int hiso_json_parse_array
(
    char *pcNewStart,
    char *pcRawStart,
    HISO_JSON *pstJson, 
    const char *pcData,
    const char **ppcEnd
)
{
    int Ret = JSON_SUCCESS;
    HISO_JSON *pstJsonChild = NULL;
    HISO_JSON *pstJsonItem = NULL;
    const char *pcTmp = pcData + 1;

    *ppcEnd = pcData;
    pstJson->enDataType = JSON_TYPE_ARRAY;

    if ('[' != *pcData)
    {
        return JSON_FAILED;
    }

    pcTmp = hiso_json_skip(pcTmp);

    if (']' == *pcTmp)
    {
        *ppcEnd = pcTmp + 1;
        return JSON_SUCCESS;
    }

    JSON_NEW_ITEM(pstJson->pstChild, JSON_FAILED);

    Ret = hiso_json_parse_value(pcNewStart, pcRawStart, pstJson->pstChild, pcTmp, ppcEnd);
    if (JSON_SUCCESS != Ret)
    {
        vdebug("Failed to parse array child.\n");
        return JSON_FAILED;
    }

    pstJsonChild = pstJson->pstChild;
    pcTmp = hiso_json_skip(*ppcEnd);
    while ((NULL != pcTmp) && (',' == *pcTmp))
    {
        JSON_NEW_ITEM(pstJsonItem, JSON_FAILED);
        pstJsonChild->pstNext = pstJsonItem;
        pstJsonItem->pstPrev = pstJsonChild;
        pstJsonChild = pstJsonItem;

        Ret = hiso_json_parse_value(pcNewStart, pcRawStart, pstJsonChild, hiso_json_skip(pcTmp + 1), ppcEnd);
        if (JSON_SUCCESS != Ret)
        {
            vdebug("Failed to parse array child.\n");
            return JSON_FAILED;
        }
        pcTmp = hiso_json_skip(*ppcEnd);
    }

    if ((NULL != pcTmp) && (']' == *pcTmp))
    {
        *ppcEnd = pcTmp + 1;
        return JSON_SUCCESS;
    }
    else
    {
        *ppcEnd = pcTmp;
        return JSON_FAILED;
    }
}

static int hiso_json_parse_object
(
    char *pcNewStart,
    char *pcRawStart,
    HISO_JSON *pstJson, 
    const char *pcData,
    const char **ppcEnd
)
{
    int Ret = JSON_SUCCESS;
    HISO_JSON *pstJsonChild = NULL;
    HISO_JSON *pstJsonItem = NULL;
    const char *pcTmp = pcData + 1;

    *ppcEnd = pcData;
    pstJson->enDataType = JSON_TYPE_OBJECT;

    if ('{' != *pcData)
    {
        return JSON_FAILED;
    }

    pcTmp = hiso_json_skip(pcTmp);
    if ('}' == *pcTmp)
    {
        *ppcEnd = pcTmp + 1;
        return JSON_SUCCESS;
    }

    JSON_NEW_ITEM(pstJson->pstChild, JSON_FAILED);

    Ret = hiso_json_parse_string(pcNewStart, pcRawStart, pstJson->pstChild, pcTmp, ppcEnd);
    if (JSON_SUCCESS != Ret)
    {
        vdebug("Failed to parse array child.\n");
        return JSON_FAILED;
    }

    pstJsonChild = pstJson->pstChild;
    pstJsonChild->pcName = pstJsonChild->unData.pcStrVal;
    pstJsonChild->unData.pcStrVal = NULL;

    pcTmp = hiso_json_skip(*ppcEnd);
    if ((NULL == pcTmp) || (':' != *pcTmp))
    {
        *ppcEnd = pcTmp;
        return JSON_FAILED;
    }

    Ret = hiso_json_parse_value(pcNewStart, pcRawStart, pstJsonChild, hiso_json_skip(pcTmp + 1), ppcEnd);
    if (JSON_SUCCESS != Ret)
    {
        vdebug("Failed to parse array child.\n");
        return JSON_FAILED;
    }

    pcTmp = hiso_json_skip(*ppcEnd);
    while ((NULL != pcTmp) && (',' == *pcTmp))
    {
        JSON_NEW_ITEM(pstJsonItem, JSON_FAILED);
        pstJsonChild->pstNext = pstJsonItem;
        pstJsonItem->pstPrev = pstJsonChild;
        pstJsonChild = pstJsonItem;

        Ret = hiso_json_parse_string(pcNewStart, pcRawStart, pstJsonChild, hiso_json_skip(pcTmp + 1), ppcEnd);
        if (JSON_SUCCESS != Ret)
        {
            vdebug("Failed to parse array child.\n");
            return JSON_FAILED;
        }

        pcTmp = hiso_json_skip(*ppcEnd);
        pstJsonChild->pcName = pstJsonChild->unData.pcStrVal;
        pstJsonChild->unData.pcStrVal = NULL;
        if ((NULL == pcTmp) || (':' != *pcTmp))
        {
            *ppcEnd = pcTmp;
            return JSON_FAILED;
        }

        Ret = hiso_json_parse_value(pcNewStart, pcRawStart, pstJsonChild, hiso_json_skip(pcTmp + 1), ppcEnd);
        if (JSON_SUCCESS != Ret)
        {
            vdebug("Failed to parse array child.\n");
            return JSON_FAILED;
        }

        pcTmp = hiso_json_skip(*ppcEnd);
    }

    if ((NULL != pcTmp) && ('}' == *pcTmp))
    {
        *ppcEnd = pcTmp + 1;
        return JSON_SUCCESS;
    }
    else
    {
        *ppcEnd = pcTmp;
        return JSON_FAILED;
    }
}

int hiso_json_parse_value
(
    char *pcNewStart,
    char *pcRawStart,
    HISO_JSON *pstJson, 
    const char *pcData,
    const char **ppcEnd
)
{
    pcData = hiso_json_skip(pcData);
    
    switch (*pcData)
    {
        case 'n':
        {
            if (0 == strncmp(pcData, "null", 4))
            {
                pstJson->enDataType = JSON_TYPE_NULL;
                *ppcEnd = pcData + 4;
                return JSON_SUCCESS;
            }
            break;
        }
        case 'f':
        {
            if (0 == strncmp(pcData, "false", 5))
            {
                pstJson->enDataType = JSON_TYPE_BOOL;
                pstJson->unData.lValue = 0;
                *ppcEnd = pcData + 5;
                return JSON_SUCCESS;
            }
            break;
        }
        case 't':
        {
            if (0 == strncmp(pcData, "true", 4))
            {
                pstJson->enDataType = JSON_TYPE_BOOL;
                pstJson->unData.lValue = 1;
                *ppcEnd = pcData + 4;
                return JSON_SUCCESS;
            }
            break;
        }
        case '\"':
        {
            return hiso_json_parse_string(pcNewStart, pcRawStart, pstJson, pcData, ppcEnd);
        }
        case '[':
        {
            return hiso_json_parse_array(pcNewStart, pcRawStart, pstJson, pcData, ppcEnd);
        }
        case '{':
        {
            return hiso_json_parse_object(pcNewStart, pcRawStart, pstJson, pcData, ppcEnd);
        }
        case '-':
        {
            return hiso_json_parse_number(pstJson, pcData, ppcEnd);
        }
        default :
        {
            if (*pcData >= '0' && *pcData <= '9')
            {
                return hiso_json_parse_number(pstJson, pcData, ppcEnd);
            }
        }
    }

    *ppcEnd = pcData;
    vdebug("Invalid json data %u.\n", (uint8_t)(*pcData));
    return JSON_FAILED;
}

HISO_JSON * hiso_json_create(void)
{
    HISO_JSON *pstJson = NULL;

    pstJson = (HISO_JSON *)zalloc(sizeof(HISO_JSON));
    if (NULL == pstJson)
    {
        return NULL;
    }
    
    return pstJson;
}

int hiso_json_parse(HISO_JSON *pstJson, const char *szJsonData)
{
    uint32_t uiMemSize = 0;
    int Ret = JSON_SUCCESS;
    char *pcNewBuf = NULL;
    const char *pcEnd = NULL;

    uiMemSize = (uint32_t)strlen(szJsonData) + 1;
    pcNewBuf = (char *)malloc(uiMemSize);
    if (NULL == pcNewBuf)
    {
        vdebug("Failed to alloc new buf.\n");
        return JSON_FAILED;
    }
    memcpy(pcNewBuf, szJsonData, uiMemSize);
    pcNewBuf[uiMemSize - 1] = 0;

    Ret = hiso_json_parse_value(pcNewBuf, (char *)szJsonData, pstJson, szJsonData, &pcEnd);
    if (JSON_SUCCESS != Ret)
    {
        vdebug("Failed to parse json data start=%p, end=%p.\n", szJsonData, pcEnd);
        return JSON_FAILED;
    }

    return JSON_SUCCESS;
}

int hiso_json_parse_ex(HISO_JSON *pstJson, const char *szJsonData, int szLen)
{
    uint32_t uiMemSize = 0;
    int Ret = JSON_SUCCESS;
    char *pcNewBuf = NULL;
    const char *pcEnd = NULL;

    uiMemSize = (uint32_t)szLen;
    pcNewBuf = (char *)malloc(uiMemSize + 1);
    if (NULL == pcNewBuf)
    {
        vdebug("Failed to alloc new buf.\n");
        return JSON_FAILED;
    }
    memcpy(pcNewBuf, szJsonData, szLen);
    pcNewBuf[uiMemSize] = 0;

    Ret = hiso_json_parse_value(pcNewBuf, (char *)szJsonData, pstJson, szJsonData, &pcEnd);
    if (JSON_SUCCESS != Ret)
    {
        vdebug("Failed to parse json data start=%p, end=%p\n", szJsonData, pcEnd);
        return JSON_FAILED;
    }

    return JSON_SUCCESS;
}

int hiso_json_scan_parse
(
    const HISO_JSON    *pstJson,
    uint32_t            uiParseNum,
    HISO_JSON_PARSE_S  *pstJsonParse
)
{   
    uint32_t i = 0;
    const HISO_JSON *pstJsonCur = NULL;
    HISO_JSON_PARSE_S *pstCurParse = NULL;

    for (pstJsonCur = pstJson; NULL != pstJsonCur; pstJsonCur = pstJsonCur->pstNext)
    {
        if ((JSON_TYPE_OBJECT == pstJsonCur->enDataType) ||
            (JSON_TYPE_ARRAY == pstJsonCur->enDataType))
        {
            continue;
        }

        for (i = 0, pstCurParse = NULL; i < uiParseNum; i++)
        {
            if (0 == strcmp(pstJsonParse[i].pcKey, pstJsonCur->pcName))
            {   
                pstCurParse = pstJsonParse + i;
                break;
            }
        }

        if (NULL == pstCurParse)
        {
            continue;
        }
    
        switch (pstJsonCur->enDataType)
        {
            case JSON_TYPE_NUMBER:
            {
                if (sizeof(uint32_t) == pstCurParse->uiBufSize)
                {
                    *(uint32_t *)(pstCurParse->pDataBuf) = (uint32_t)pstJsonCur->unData.lValue;
                }
                else if (sizeof(uint16_t) == pstCurParse->uiBufSize)
                {
                    *(uint16_t *)(pstCurParse->pDataBuf) = (uint16_t)pstJsonCur->unData.lValue;
                }
                else if (sizeof(uint8_t) == pstCurParse->uiBufSize)
                {
                    *(uint8_t *)(pstCurParse->pDataBuf) = (uint8_t)pstJsonCur->unData.lValue;
                }
                else if ((pstCurParse->uiBufSize > sizeof(uint64_t)))
                {
                    scnprintf((char *)pstCurParse->pDataBuf, pstCurParse->uiBufSize, "%llu", 
                        (unsigned long long)(pstJsonCur->unData.lValue));
                }
                else
                {
                    vdebug("Invalid number data buf size %u.\n", pstCurParse->uiBufSize);
                }
                break;
            }
            case JSON_TYPE_STRING:
            {
                scnprintf((char *)pstCurParse->pDataBuf, pstCurParse->uiBufSize, "%s", pstJsonCur->unData.pcStrVal);
                break;
            }
            case JSON_TYPE_BOOL:
            {
                *(uint8_t *)(pstCurParse->pDataBuf) = (pstJsonCur->unData.lValue) > 0 ? 1 : 0;
                break;
            }
            default :
            {
                break;
            }
        }
    }

    return JSON_SUCCESS;
}

int hiso_json_scan_array
(
     HISO_JSON *pstJson, 
     const char *szKey, 
     HISO_JSON **ppstArrayItem
)
{
    HISO_JSON *pstJsonItem = NULL;
    
    pstJsonItem = hiso_json_find_item(pstJson, JSON_TYPE_ARRAY, szKey);
    if (NULL == pstJsonItem)
    {
        vdebug("Key %s is not found in json data.\n", szKey);
        return JSON_NOT_FOUND;
    }

    *ppstArrayItem = pstJsonItem;

    return JSON_SUCCESS;
}

int hiso_json_scan_array_ex
(
     HISO_JSON *pstJson, 
     const char *szKey, 
     HISO_JSON **ppstArrayItem
)
{
    HISO_JSON *pstJsonItem = NULL;
    
    pstJsonItem = hiso_json_find_item(pstJson, JSON_TYPE_ARRAY, szKey);
    if (NULL == pstJsonItem)
    {
        vdebug("Key %s is not found in json data.\n", szKey);
        return JSON_NOT_FOUND;
    }
    
    *ppstArrayItem = pstJsonItem->pstChild;

    return JSON_SUCCESS;
}

int hiso_json_scan_object
(
     HISO_JSON *pstJson, 
     const char *szKey, 
     HISO_JSON **ppstObjectItem
)
{
    HISO_JSON *pstJsonItem = NULL;

    pstJsonItem = hiso_json_find_item(pstJson, JSON_TYPE_OBJECT, szKey);
    if (NULL == pstJsonItem)
    {
        vdebug("Key %s is not found in json data.\n", szKey);
        return JSON_NOT_FOUND;
    }

    *ppstObjectItem = pstJsonItem;

    return JSON_SUCCESS;
}

int hiso_json_get_int
(
    HISO_JSON *pstJson, 
    const char *szKey, 
    int *piValue
)
{
    HISO_JSON *pstJsonItem = NULL;
    
    pstJsonItem = hiso_json_find_item(pstJson, JSON_TYPE_NUMBER, szKey);
    if (NULL == pstJsonItem)
    {
        //vdebug("Key %s is not found in json data.\n", szKey);
        return JSON_NOT_FOUND;
    }

    *piValue = (int)pstJsonItem->unData.lValue;

    return JSON_SUCCESS;
}

int hiso_json_get_uint
(
    HISO_JSON *pstJson, 
    const char *szKey, 
    uint32_t *puiValue
)
{
    HISO_JSON *pstJsonItem = NULL;
    
    pstJsonItem = hiso_json_find_item(pstJson, JSON_TYPE_NUMBER, szKey);
    if (NULL == pstJsonItem)
    {
        vdebug("Key %s is not found in json data.\n", szKey);
        return JSON_NOT_FOUND;
    }

    *puiValue = (uint32_t)pstJsonItem->unData.lValue;

    return JSON_SUCCESS;
}

int hiso_json_get_uint64
(
    HISO_JSON *pstJson, 
    const char *szKey, 
    uint64_t *pui64Value
)
{
    HISO_JSON *pstJsonItem = NULL;
    
    pstJsonItem = hiso_json_find_item(pstJson, JSON_TYPE_NUMBER, szKey);
    if (NULL == pstJsonItem)
    {
        vdebug("Key %s is not found in json data.\n", szKey);
        return JSON_NOT_FOUND;
    }

    *pui64Value = (uint64_t)pstJsonItem->unData.lValue;

    return JSON_SUCCESS;
}

int hiso_json_get_bool
(
    HISO_JSON *pstJson,
    const char *szKey, 
    uint8_t *pbValue
)
{
    HISO_JSON *pstJsonItem = NULL;
    
    pstJsonItem = hiso_json_find_item(pstJson, JSON_TYPE_BOOL, szKey);
    if (NULL == pstJsonItem)
    {
        vdebug("Key %s is not found in json data.\n", szKey);
        return JSON_NOT_FOUND;
    }

    *pbValue = pstJsonItem->unData.lValue > 0 ? 1 : 0;

    return JSON_SUCCESS;
}

int hiso_json_get_string
(
     HISO_JSON *pstJson, 
     const char *szKey, 
     uint32_t  uiBufLen,
     char *pcBuf
)
{
    HISO_JSON *pstJsonItem = NULL;
    
    pstJsonItem = hiso_json_find_item(pstJson, JSON_TYPE_STRING, szKey);
    if (NULL == pstJsonItem)
    {
        //vdebug("Key %s is not found in json data.\n", szKey);
        return JSON_NOT_FOUND;
    }

    scnprintf(pcBuf, uiBufLen, "%s", pstJsonItem->unData.pcStrVal);

    return JSON_SUCCESS;
}

const char * hiso_json_get_string_ex(HISO_JSON *pstJson,  const char *szKey)
{
    HISO_JSON *pstJsonItem = NULL;

    if ((NULL == pstJson) || (NULL == szKey))
    {
        return NULL;
    }

    pstJsonItem = hiso_json_find_item(pstJson, JSON_TYPE_STRING, szKey);
    if (NULL == pstJsonItem)
    {
        //vdebug("Key %s is not found in json data.\n", szKey);
        return NULL;
    }

    return pstJsonItem->unData.pcStrVal;
}

int hiso_json_destroy(HISO_JSON *pstJson)
{
    if (NULL == pstJson)
    {   
        return JSON_SUCCESS;
    }

    if (NULL != pstJson->pstChild)
    {
        hiso_json_free(pstJson->pstChild);
    }

    if (NULL != pstJson->pstNext)
    {
        hiso_json_free(pstJson->pstNext);
    }

    free(pstJson);
    
    return JSON_SUCCESS;
}

int hiso_json_escape_string(char *buf, int buflen, const char *str, int newline)
{
    char last = 0;
    int count = 0;

    *buf++ = '"';
    count++;

    while (*str)
    {
        if (*str == '"' && last != '\\')
        {
            *buf = '\\';
            count++;
            buf++;
        }
    
        *buf = *str;
        count++;
        buf++;

        last = *str;
        str++;
    }

    *buf++ = '"';
    count++;
    
    *buf++ = ',';
    count++;

    if (newline)
    {
        *buf++ = '\n';
        count++;        
    }

    return count;
}
