/*
 * AWS IoT Common V1.0.0
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * @file aws_iot_doc_parser.c
 * @brief Implements the functions in aws_iot_doc_parser.h
 */

/* The config header is always included first. */
#include "iot_config.h"

/* Standard includes. */
#include <string.h>

/* JSON utilities include. */
#include "aws_iot_doc_parser.h"

#define IS_QUOTE( str, idx ) \
    ( ( str )[ ( idx ) ] == '"' && ( ( idx ) == 0 || ( str )[ ( idx ) - 1 ] != '\\' ) )
#define IS_WHITESPACE( str, idx ) \
    ( ( str )[ ( idx ) ] == ' ' || ( str )[ ( idx ) ] == '\n' || ( str )[ ( idx ) ] == '\r' || ( str )[ ( idx ) ] == '\t' )

/*-----------------------------------------------------------*/

bool AwsIotDocParser_FindValue( const char * pAwsIotJsonDocument,
                                size_t awsIotJsonDocumentLength,
                                const char * pAwsIotJsonKey,
                                size_t awsIotJsonKeyLength,
                                const char ** pAwsIotJsonValue,
                                size_t * pAwsIotJsonValueLength )
{
    size_t i = 0;
    size_t jsonValueLength = 0;
    char openCharacter = '\0', closeCharacter = '\0';
    int nestingLevel = 0;
    bool isWithinQuotes = false;

    /* Validate all the arguments.*/
    if( ( pAwsIotJsonDocument == NULL ) || ( pAwsIotJsonKey == NULL ) ||
        ( awsIotJsonDocumentLength == 0 ) || ( awsIotJsonKeyLength == 0 ) )
    {
        return false;
    }

    /* Ensure the JSON document is long enough to contain the key/value pair. At
     * the very least, a JSON key/value pair must contain the key and the 6
     * characters {":""} */
    if( awsIotJsonDocumentLength < awsIotJsonKeyLength + 6 )
    {
        return false;
    }

    /* Search the characters in the JSON document for the key. The end of the JSON
     * document does not have to be searched once too few characters remain to hold a
     * value. */
    while( i < awsIotJsonDocumentLength - awsIotJsonKeyLength - 3 )
    {
        /* If the first character in the key is found and there's an unescaped double
         * quote after the key length, do a string compare for the key. */
        if( ( IS_QUOTE( pAwsIotJsonDocument, i ) ) &&
            ( IS_QUOTE( pAwsIotJsonDocument, i + 1 + awsIotJsonKeyLength ) ) &&
            ( pAwsIotJsonDocument[ i + 1 ] == pAwsIotJsonKey[ 0 ] ) &&
            ( strncmp( pAwsIotJsonDocument + i + 1,
                       pAwsIotJsonKey,
                       awsIotJsonKeyLength ) == 0 ) )
        {
            /* Key found; this is a potential match. */

            /* Skip the characters in the JSON key and closing double quote. */
            /* While loop guarantees that i < awsIotJsonDocumentLength - 1 */
            i += awsIotJsonKeyLength + 2;

            /* Skip all whitespace characters between the closing " and the : */
            while( IS_WHITESPACE( pAwsIotJsonDocument, i ) )
            {
                i++;

                /* If the end of the document is reached, this isn't a match. */
                if( i >= awsIotJsonDocumentLength )
                {
                    return false;
                }
            }

            /* The character immediately following a key (and any whitespace) must be a :
             * If it's another character, then this string is a JSON value; skip it. */
            if( pAwsIotJsonDocument[ i ] != ':' )
            {
                continue;
            }
            else
            {
                /* Skip the : */
                i++;
            }

            /* If the end of the document is reached, this isn't a match. */
            if( i >= awsIotJsonDocumentLength )
            {
                return false;
            }

            /* Skip all whitespace characters between : and the first character in the value. */
            while( IS_WHITESPACE( pAwsIotJsonDocument, i ) )
            {
                i++;

                /* If the end of the document is reached, this isn't a match. */
                if( i >= awsIotJsonDocumentLength )
                {
                    return false;
                }
            }

            /* Value found. Set the output parameter. */
            if( pAwsIotJsonValue != NULL )
            {
                *pAwsIotJsonValue = pAwsIotJsonDocument + i;
            }

            /* Calculate the value's length. */
            switch( pAwsIotJsonDocument[ i ] )
            {
                /* Calculate length of a JSON string. */
                case '\"':
                    /* Include the length of the opening and closing double quotes. */
                    jsonValueLength = 2;

                    /* Skip the opening double quote. */
                    i++;

                    /* If the end of the document is reached, this isn't a match. */
                    if( i >= awsIotJsonDocumentLength )
                    {
                        return false;
                    }

                    /* Add the length of all characters in the JSON string. */
                    while( pAwsIotJsonDocument[ i ] != '\"' )
                    {
                        /* Ignore escaped double quotes. */
                        if( ( pAwsIotJsonDocument[ i ] == '\\' ) &&
                            ( i + 1 < awsIotJsonDocumentLength ) &&
                            ( pAwsIotJsonDocument[ i + 1 ] == '\"' ) )
                        {
                            /* Skip the characters \" */
                            i += 2;
                            jsonValueLength += 2;
                        }
                        else
                        {
                            /* Add the length of a single character. */
                            i++;
                            jsonValueLength++;
                        }

                        /* If the end of the document is reached, this isn't a match. */
                        if( i >= awsIotJsonDocumentLength )
                        {
                            return false;
                        }
                    }

                    break;

                /* Set the matching opening and closing characters of a JSON object or array.
                 * The length calculation is performed below. */
                case '{':
                    openCharacter = '{';
                    closeCharacter = '}';
                    break;

                case '[':
                    openCharacter = '[';
                    closeCharacter = ']';
                    break;

                /* Calculate the length of a JSON primitive. */
                default:

                    /* Skip the characters in the JSON value. The JSON value ends with a , or } */
                    while( pAwsIotJsonDocument[ i ] != ',' &&
                           pAwsIotJsonDocument[ i ] != '}' )
                    {
                        /* Any whitespace before a , or } means the JSON document is invalid. */
                        if( IS_WHITESPACE( pAwsIotJsonDocument, i ) )
                        {
                            return false;
                        }

                        i++;
                        jsonValueLength++;

                        /* If the end of the document is reached, this isn't a match. */
                        if( i >= awsIotJsonDocumentLength )
                        {
                            return false;
                        }
                    }

                    break;
            }

            /* Calculate the length of a JSON object or array. */
            if( ( openCharacter != '\0' ) && ( closeCharacter != '\0' ) )
            {
                /* Include the length of the opening and closing characters. */
                jsonValueLength = 2;

                /* Skip the opening character. */
                i++;

                /* If the end of the document is reached, this isn't a match. */
                if( i >= awsIotJsonDocumentLength )
                {
                    return false;
                }

                /* Add the length of all characters in the JSON object or array. This
                 * includes the length of nested objects. */
                while( pAwsIotJsonDocument[ i ] != closeCharacter ||
                       ( pAwsIotJsonDocument[ i ] == closeCharacter && ( nestingLevel != 0 || isWithinQuotes ) ) )
                {
                    /* Check if its a quote so as to avoid considering the
                     * nested levels for opening and closing characters within
                     * quotes.
                     */
                    if( IS_QUOTE( pAwsIotJsonDocument, i ) )
                    {
                        /*Toggle the flag*/
                        isWithinQuotes = !isWithinQuotes;
                    }

                    /* Calculate the nesting levels only if not in quotes. */
                    if( !isWithinQuotes )
                    {
                        /* An opening character starts a nested object. */
                        if( pAwsIotJsonDocument[ i ] == openCharacter )
                        {
                            nestingLevel++;
                        }
                        /* A closing character ends a nested object. */
                        else if( pAwsIotJsonDocument[ i ] == closeCharacter )
                        {
                            nestingLevel--;
                        }
                    }

                    i++;
                    jsonValueLength++;

                    /* If the end of the document is reached, this isn't a match. */
                    if( i >= awsIotJsonDocumentLength )
                    {
                        return false;
                    }
                }
            }

            /* JSON value length calculated; set the output parameter. */
            if( pAwsIotJsonValueLength != NULL )
            {
                *pAwsIotJsonValueLength = jsonValueLength;
            }

            return true;
        }

        i++;
    }

    return false;
}

/*-----------------------------------------------------------*/
