// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html


#include "precomp.hpp"
#include "persistence.hpp"

/****************************************************************************************\
*                                       JSON Parser                                      *
\****************************************************************************************/

static char*
icvJSONSkipSpaces( CvFileStorage* fs, char* ptr )
{
    bool is_eof = false;
    bool is_completed = false;

    while ( is_eof == false && is_completed == false )
    {
        switch ( *ptr )
        {
        /* comment */
        case '/' : {
            ptr++;
            if ( *ptr == '\0' )
            {
                ptr = icvGets( fs, fs->buffer_start, static_cast<int>(fs->buffer_end - fs->buffer_start) );
                if ( !ptr ) { is_eof = true; break; }
            }

            if ( *ptr == '/' )
            {
                while ( *ptr != '\n' && *ptr != '\r' )
                {
                    if ( *ptr == '\0' )
                    {
                        ptr = icvGets( fs, fs->buffer_start, static_cast<int>(fs->buffer_end - fs->buffer_start) );
                        if ( !ptr ) { is_eof = true; break; }
                    }
                    else
                    {
                        ptr++;
                    }
                }
            }
            else if ( *ptr == '*' )
            {
                ptr++;
                for (;;)
                {
                    if ( *ptr == '\0' )
                    {
                        ptr = icvGets( fs, fs->buffer_start, static_cast<int>(fs->buffer_end - fs->buffer_start) );
                        if ( !ptr ) { is_eof = true; break; }
                    }
                    else if ( *ptr == '*' )
                    {
                        ptr++;
                        if ( *ptr == '\0' )
                        {
                            ptr = icvGets( fs, fs->buffer_start, static_cast<int>(fs->buffer_end - fs->buffer_start) );
                            if ( !ptr ) { is_eof = true; break; }
                        }
                        if ( *ptr == '/' )
                        {
                            ptr++;
                            break;
                        }
                    }
                    else
                    {
                        ptr++;
                    }
                }
            }
            else
            {
                CV_PARSE_ERROR( "Not supported escape character" );
            }
        } break;
        /* whitespace */
        case '\t':
        case ' ' : {
            ptr++;
        } break;
        /* newline || end mark */
        case '\0':
        case '\n':
        case '\r': {
            ptr = icvGets( fs, fs->buffer_start, static_cast<int>(fs->buffer_end - fs->buffer_start) );
            if ( !ptr ) { is_eof = true; break; }
        } break;
        /* other character */
        default: {
            if ( !cv_isprint(*ptr) )
                CV_PARSE_ERROR( "Invalid character in the stream" );
            is_completed = true;
        } break;
        }
    }

    if ( is_eof )
    {
        ptr = fs->buffer_start;
        *ptr = '\0';
        fs->dummy_eof = 1;
    }
    else if ( !is_completed )
    {
        /* should not be executed */
        ptr = 0;
        fs->dummy_eof = 1;
        CV_PARSE_ERROR( "Abort at parse time" );
    }
    return ptr;
}


static char* icvJSONParseKey( CvFileStorage* fs, char* ptr, CvFileNode* map, CvFileNode** value_placeholder )
{
    if( *ptr != '"' )
        CV_PARSE_ERROR( "Key must start with \'\"\'" );

    char * beg = ptr + 1;
    char * end = beg;

    do {
        ++ptr;
        CV_PERSISTENCE_CHECK_END_OF_BUFFER_BUG();
    } while( cv_isprint(*ptr) && *ptr != '"' );

    if( *ptr != '"' )
        CV_PARSE_ERROR( "Key must end with \'\"\'" );

    end = ptr;
    ptr++;
    ptr = icvJSONSkipSpaces( fs, ptr );
    if ( ptr == 0 || fs->dummy_eof )
        return 0;

    if( *ptr != ':' )
        CV_PARSE_ERROR( "Missing \':\' between key and value" );

    /* [beg, end) */
    if( end <= beg )
        CV_PARSE_ERROR( "Key is empty" );

    if ( end - beg == 7u && memcmp(beg, "type_id", 7u) == 0 )
    {
        *value_placeholder = 0;
    }
    else
    {
        CvStringHashNode* str_hash_node = cvGetHashedKey( fs, beg, static_cast<int>(end - beg), 1 );
        *value_placeholder = cvGetFileNode( fs, map, str_hash_node, 1 );
    }

    ptr++;
    return ptr;
}

static char* icvJSONParseValue( CvFileStorage* fs, char* ptr, CvFileNode* node )
{
    ptr = icvJSONSkipSpaces( fs, ptr );
    if ( ptr == 0 || fs->dummy_eof )
        CV_PARSE_ERROR( "Unexpected End-Of-File" );

    memset( node, 0, sizeof(*node) );

    if ( *ptr == '"' )
    {   /* must be string or Base64 string */
        ptr++;
        char * beg = ptr;
        size_t len = 0u;
        for ( ; (cv_isalnum(*ptr) || *ptr == '$' ) && len <= 9u; ptr++ )
            len++;

        if ( len >= 8u && memcmp( beg, "$base64$", 8u ) == 0 )
        {   /**************** Base64 string ****************/
            ptr = beg += 8;

            std::string base64_buffer;
            base64_buffer.reserve( PARSER_BASE64_BUFFER_SIZE );

            bool is_matching = false;
            while ( !is_matching )
            {
                switch ( *ptr )
                {
                case '\0':
                {
                    base64_buffer.append( beg, ptr );

                    ptr = icvGets( fs, fs->buffer_start, static_cast<int>(fs->buffer_end - fs->buffer_start) );
                    if ( !ptr )
                        CV_PARSE_ERROR( "'\"' - right-quote of string is missing" );

                    beg = ptr;
                    break;
                }
                case '\"':
                {
                    base64_buffer.append( beg, ptr );
                    beg = ptr;
                    is_matching = true;
                    break;
                }
                case '\n':
                case '\r':
                {
                    CV_PARSE_ERROR( "'\"' - right-quote of string is missing" );
                    break;
                }
                default:
                {
                    ptr++;
                    break;
                }
                }
            }

            if ( *ptr != '\"' )
                CV_PARSE_ERROR( "'\"' - right-quote of string is missing" );
            else
                ptr++;

            if ( base64_buffer.size() >= base64::ENCODED_HEADER_SIZE )
            {
                const char * base64_beg = base64_buffer.data();
                const char * base64_end = base64_beg + base64_buffer.size();

                /* get dt from header */
                std::string dt;
                {
                    std::vector<char> header(base64::HEADER_SIZE + 1, ' ');
                    base64::base64_decode(base64_beg, header.data(), 0U, base64::ENCODED_HEADER_SIZE);
                    if ( !base64::read_base64_header(header, dt) || dt.empty() )
                        CV_PARSE_ERROR("Invalid `dt` in Base64 header");
                }

                /* set base64_beg to beginning of base64 data */
                base64_beg = &base64_buffer.at( base64::ENCODED_HEADER_SIZE );

                if ( base64_buffer.size() > base64::ENCODED_HEADER_SIZE )
                {
                    if ( !base64::base64_valid( base64_beg, 0U, base64_end - base64_beg ) )
                        CV_PARSE_ERROR( "Invalid Base64 data." );

                    /* buffer for decoded data(exclude header) */
                    std::vector<uchar> binary_buffer( base64::base64_decode_buffer_size(base64_end - base64_beg) );
                    int total_byte_size = static_cast<int>(
                        base64::base64_decode_buffer_size( base64_end - base64_beg, base64_beg, false )
                        );
                    {
                        base64::Base64ContextParser parser(binary_buffer.data(), binary_buffer.size() );
                        const uchar * binary_beg = reinterpret_cast<const uchar *>( base64_beg );
                        const uchar * binary_end = binary_beg + (base64_end - base64_beg);
                        parser.read( binary_beg, binary_end );
                        parser.flush();
                    }

                    /* save as CvSeq */
                    int elem_size = ::icvCalcStructSize(dt.c_str(), 0);
                    if (total_byte_size % elem_size != 0)
                        CV_PARSE_ERROR("Byte size not match elememt size");
                    int elem_cnt = total_byte_size / elem_size;

                    /* after icvFSCreateCollection, node->tag == struct_flags */
                    icvFSCreateCollection(fs, CV_NODE_FLOW | CV_NODE_SEQ, node);
                    base64::make_seq(binary_buffer.data(), elem_cnt, dt.c_str(), *node->data.seq);
                }
                else
                {
                    /* empty */
                    icvFSCreateCollection(fs, CV_NODE_FLOW | CV_NODE_SEQ, node);
                }
            }
            else if ( base64_buffer.empty() )
            {
                /* empty */
                icvFSCreateCollection(fs, CV_NODE_FLOW | CV_NODE_SEQ, node);
            }
            else
            {
                CV_PARSE_ERROR("Unrecognized Base64 header");
            }
        }
        else
        {   /**************** normal string ****************/
            std::string string_buffer;
            string_buffer.reserve( PARSER_BASE64_BUFFER_SIZE );

            ptr = beg;
            bool is_matching = false;
            while ( !is_matching )
            {
                switch ( *ptr )
                {
                case '\\':
                {
                    string_buffer.append( beg, ptr );
                    ptr++;
                    switch ( *ptr )
                    {
                    case '\\':
                    case '\"':
                    case '\'': { string_buffer.append( 1u, *ptr ); break; }
                    case 'n' : { string_buffer.append( 1u, '\n' ); break; }
                    case 'r' : { string_buffer.append( 1u, '\r' ); break; }
                    case 't' : { string_buffer.append( 1u, '\t' ); break; }
                    case 'b' : { string_buffer.append( 1u, '\b' ); break; }
                    case 'f' : { string_buffer.append( 1u, '\f' ); break; }
                    case 'u' : { CV_PARSE_ERROR( "'\\uXXXX' currently not supported" ); }
                    default  : { CV_PARSE_ERROR( "Invalid escape character" ); }
                        break;
                    }
                    ptr++;
                    beg = ptr;
                    break;
                }
                case '\0':
                {
                    string_buffer.append( beg, ptr );

                    ptr = icvGets( fs, fs->buffer_start, static_cast<int>(fs->buffer_end - fs->buffer_start) );
                    if ( !ptr )
                        CV_PARSE_ERROR( "'\"' - right-quote of string is missing" );

                    beg = ptr;
                    break;
                }
                case '\"':
                {
                    string_buffer.append( beg, ptr );
                    beg = ptr;
                    is_matching = true;
                    break;
                }
                case '\n':
                case '\r':
                {
                    CV_PARSE_ERROR( "'\"' - right-quote of string is missing" );
                    break;
                }
                default:
                {
                    ptr++;
                    break;
                }
                }
            }

            if ( *ptr != '\"' )
                CV_PARSE_ERROR( "'\"' - right-quote of string is missing" );
            else
                ptr++;

            node->data.str = cvMemStorageAllocString
            (
                fs->memstorage,
                string_buffer.c_str(),
                static_cast<int>(string_buffer.size())
            );
            node->tag = CV_NODE_STRING;
        }
    }
    else if ( cv_isdigit(*ptr) || *ptr == '-' || *ptr == '+' || *ptr == '.' )
    {    /**************** number ****************/
        char * beg = ptr;
        if ( *ptr == '+' || *ptr == '-' )
        {
            ptr++;
            CV_PERSISTENCE_CHECK_END_OF_BUFFER_BUG();
        }
        while( cv_isdigit(*ptr) )
        {
            ptr++;
            CV_PERSISTENCE_CHECK_END_OF_BUFFER_BUG();
        }
        if (*ptr == '.' || *ptr == 'e')
        {
            node->data.f = icv_strtod( fs, beg, &ptr );
            CV_PERSISTENCE_CHECK_END_OF_BUFFER_BUG();
            node->tag = CV_NODE_REAL;
        }
        else
        {
            node->data.i = static_cast<int>(strtol( beg, &ptr, 0 ));
            CV_PERSISTENCE_CHECK_END_OF_BUFFER_BUG();
            node->tag = CV_NODE_INT;
        }

        if ( beg >= ptr )
            CV_PARSE_ERROR( "Invalid numeric value (inconsistent explicit type specification?)" );
    }
    else
    {    /**************** other data ****************/
        const char * beg = ptr;
        size_t len = 0u;
        for ( ; cv_isalpha(*ptr) && len <= 6u; )
        {
            len++;
            ptr++;
            CV_PERSISTENCE_CHECK_END_OF_BUFFER_BUG();
        }

        if ( len >= 4u && memcmp( beg, "null", 4u ) == 0 )
        {
            CV_PARSE_ERROR( "Value 'null' is not supported by this parser" );
        }
        else if ( len >= 4u && memcmp( beg, "true", 4u ) == 0 )
        {
            node->data.i = 1;
            node->tag = CV_NODE_INT;
        }
        else if ( len >= 5u && memcmp( beg, "false", 5u ) == 0 )
        {
            node->data.i = 0;
            node->tag = CV_NODE_INT;
        }
        else
        {
            CV_PARSE_ERROR( "Unrecognized value" );
        }
    }

    return ptr;
}

static char* icvJSONParseSeq( CvFileStorage* fs, char* ptr, CvFileNode* node );
static char* icvJSONParseMap( CvFileStorage* fs, char* ptr, CvFileNode* node );

static char* icvJSONParseSeq( CvFileStorage* fs, char* ptr, CvFileNode* node )
{
    if (!ptr)
        CV_PARSE_ERROR( "ptr is NULL" );

    if ( *ptr != '[' )
        CV_PARSE_ERROR( "'[' - left-brace of seq is missing" );
    else
        ptr++;

    memset( node, 0, sizeof(*node) );
    icvFSCreateCollection( fs, CV_NODE_SEQ, node );

    for (;;)
    {
        ptr = icvJSONSkipSpaces( fs, ptr );
        if ( ptr == 0 || fs->dummy_eof )
            break;

        if ( *ptr != ']' )
        {
            CvFileNode* child = (CvFileNode*)cvSeqPush( node->data.seq, 0 );

            if ( *ptr == '[' )
                ptr = icvJSONParseSeq( fs, ptr, child );
            else if ( *ptr == '{' )
                ptr = icvJSONParseMap( fs, ptr, child );
            else
                ptr = icvJSONParseValue( fs, ptr, child );
        }

        ptr = icvJSONSkipSpaces( fs, ptr );
        if ( ptr == 0 || fs->dummy_eof )
            break;

        if ( *ptr == ',' )
            ptr++;
        else if ( *ptr == ']' )
            break;
        else
            CV_PARSE_ERROR( "Unexpected character" );
    }

    if (!ptr)
        CV_PARSE_ERROR("ptr is NULL");

    if ( *ptr != ']' )
        CV_PARSE_ERROR( "']' - right-brace of seq is missing" );
    else
        ptr++;

    return ptr;
}

static char* icvJSONParseMap( CvFileStorage* fs, char* ptr, CvFileNode* node )
{
    if (!ptr)
        CV_PARSE_ERROR("ptr is NULL");

    if ( *ptr != '{' )
        CV_PARSE_ERROR( "'{' - left-brace of map is missing" );
    else
        ptr++;

    memset( node, 0, sizeof(*node) );
    icvFSCreateCollection( fs, CV_NODE_MAP, node );

    for ( ;; )
    {
        ptr = icvJSONSkipSpaces( fs, ptr );
        if ( ptr == 0 || fs->dummy_eof )
            break;

        if ( *ptr == '"' )
        {
            CvFileNode* child = 0;
            ptr = icvJSONParseKey( fs, ptr, node, &child );
            if ( ptr == 0 || fs->dummy_eof )
                break;
            ptr = icvJSONSkipSpaces( fs, ptr );
            if ( ptr == 0 || fs->dummy_eof )
                break;

            if ( child == 0 )
            {   /* type_id */
                CvFileNode tmp;
                ptr = icvJSONParseValue( fs, ptr, &tmp );
                if ( CV_NODE_IS_STRING(tmp.tag) )
                {
                    node->info = cvFindType( tmp.data.str.ptr );
                    if ( node->info )
                        node->tag |= CV_NODE_USER;
                    // delete tmp.data.str
                }
                else
                {
                    CV_PARSE_ERROR( "\"type_id\" should be of type string" );
                }
            }
            else
            {   /* normal */
                if ( *ptr == '[' )
                    ptr = icvJSONParseSeq( fs, ptr, child );
                else if ( *ptr == '{' )
                    ptr = icvJSONParseMap( fs, ptr, child );
                else
                    ptr = icvJSONParseValue( fs, ptr, child );
                child->tag |= CV_NODE_NAMED;
            }
        }

        ptr = icvJSONSkipSpaces( fs, ptr );
        if ( ptr == 0 || fs->dummy_eof )
            break;

        if ( *ptr == ',' )
            ptr++;
        else if ( *ptr == '}' )
            break;
        else
            CV_PARSE_ERROR( "Unexpected character" );
    }

    if (!ptr)
        CV_PARSE_ERROR("ptr is NULL");

    if ( *ptr != '}' )
        CV_PARSE_ERROR( "'}' - right-brace of map is missing" );
    else
        ptr++;

    return ptr;
}


void icvJSONParse( CvFileStorage* fs )
{
    char* ptr = fs->buffer_start;
    ptr = icvJSONSkipSpaces( fs, ptr );
    if ( ptr == 0 || fs->dummy_eof )
        return;

    if ( *ptr == '{' )
    {
        CvFileNode* root_node = (CvFileNode*)cvSeqPush( fs->roots, 0 );
        ptr = icvJSONParseMap( fs, ptr, root_node );
    }
    else if ( *ptr == '[' )
    {
        CvFileNode* root_node = (CvFileNode*)cvSeqPush( fs->roots, 0 );
        ptr = icvJSONParseSeq( fs, ptr, root_node );
    }
    else
    {
        CV_PARSE_ERROR( "left-brace of top level is missing" );
    }

    if ( fs->dummy_eof != 0 )
        CV_PARSE_ERROR( "Unexpected End-Of-File" );
}


/****************************************************************************************\
*                                       JSON Emitter                                     *
\****************************************************************************************/

void icvJSONWrite( CvFileStorage* fs, const char* key, const char* data )
{
    /* check write_struct */

    check_if_write_struct_is_delayed( fs );
    if ( fs->state_of_writing_base64 == base64::fs::Uncertain )
    {
        switch_to_Base64_state( fs, base64::fs::NotUse );
    }
    else if ( fs->state_of_writing_base64 == base64::fs::InUse )
    {
        CV_Error( CV_StsError, "At present, output Base64 data only." );
    }

    /* check parameters */

    size_t key_len = 0u;
    if( key && *key == '\0' )
        key = 0;
    if ( key )
    {
        key_len = strlen(key);
        if ( key_len == 0u )
            CV_Error( CV_StsBadArg, "The key is an empty" );
        else if ( static_cast<int>(key_len) > CV_FS_MAX_LEN )
            CV_Error( CV_StsBadArg, "The key is too long" );
    }

    size_t data_len = 0u;
    if ( data )
        data_len = strlen(data);

    int struct_flags = fs->struct_flags;
    if( CV_NODE_IS_COLLECTION(struct_flags) )
    {
        if ( (CV_NODE_IS_MAP(struct_flags) ^ (key != 0)) )
            CV_Error( CV_StsBadArg, "An attempt to add element without a key to a map, "
                                    "or add element with key to sequence" );
    } else {
        fs->is_first = 0;
        struct_flags = CV_NODE_EMPTY | (key ? CV_NODE_MAP : CV_NODE_SEQ);
    }

    /* start to write */

    char* ptr = 0;

    if( CV_NODE_IS_FLOW(struct_flags) )
    {
        int new_offset;
        ptr = fs->buffer;
        if( !CV_NODE_IS_EMPTY(struct_flags) )
            *ptr++ = ',';
        new_offset = static_cast<int>(ptr - fs->buffer_start + key_len + data_len);
        if( new_offset > fs->wrap_margin && new_offset - fs->struct_indent > 10 )
        {
            fs->buffer = ptr;
            ptr = icvFSFlush(fs);
        }
        else
            *ptr++ = ' ';
    }
    else
    {
        if ( !CV_NODE_IS_EMPTY(struct_flags) )
        {
            ptr = fs->buffer;
            *ptr++ = ',';
            *ptr++ = '\n';
            *ptr++ = '\0';
            ::icvPuts( fs, fs->buffer_start );
            ptr = fs->buffer = fs->buffer_start;
        }
        ptr = icvFSFlush(fs);
    }

    if( key )
    {
        if( !cv_isalpha(key[0]) && key[0] != '_' )
            CV_Error( CV_StsBadArg, "Key must start with a letter or _" );

        ptr = icvFSResizeWriteBuffer( fs, ptr, static_cast<int>(key_len) );
        *ptr++ = '\"';

        for( size_t i = 0u; i < key_len; i++ )
        {
            char c = key[i];

            ptr[i] = c;
            if( !cv_isalnum(c) && c != '-' && c != '_' && c != ' ' )
                CV_Error( CV_StsBadArg, "Key names may only contain alphanumeric characters [a-zA-Z0-9], '-', '_' and ' '" );
        }

        ptr += key_len;
        *ptr++ = '\"';
        *ptr++ = ':';
        *ptr++ = ' ';
    }

    if( data )
    {
        ptr = icvFSResizeWriteBuffer( fs, ptr, static_cast<int>(data_len) );
        memcpy( ptr, data, data_len );
        ptr += data_len;
    }

    fs->buffer = ptr;
    fs->struct_flags = struct_flags & ~CV_NODE_EMPTY;
}


void icvJSONStartWriteStruct( CvFileStorage* fs, const char* key, int struct_flags, const char* type_name)
{
    int parent_flags;
    char data[CV_FS_MAX_LEN + 1024];

    struct_flags = (struct_flags & (CV_NODE_TYPE_MASK|CV_NODE_FLOW)) | CV_NODE_EMPTY;
    if( !CV_NODE_IS_COLLECTION(struct_flags))
        CV_Error( CV_StsBadArg,
        "Some collection type - CV_NODE_SEQ or CV_NODE_MAP, must be specified" );

    if ( type_name && *type_name == '\0' )
        type_name = 0;

    bool has_type_id = false;
    bool is_real_collection = true;
    if (type_name && memcmp(type_name, "binary", 6) == 0)
    {
        struct_flags = CV_NODE_STR;
        data[0] = '\0';
        is_real_collection = false;
    }
    else if( type_name )
    {
        has_type_id = true;
    }

    if ( is_real_collection )
    {
        char c = CV_NODE_IS_MAP(struct_flags) ? '{' : '[';
        data[0] = c;
        data[1] = '\0';
    }

    icvJSONWrite( fs, key, data );

    parent_flags = fs->struct_flags;
    cvSeqPush( fs->write_stack, &parent_flags );
    fs->struct_flags = struct_flags;
    fs->struct_indent += 4;

    if ( has_type_id )
        fs->write_string( fs, "type_id", type_name, 1 );
}


void icvJSONEndWriteStruct( CvFileStorage* fs )
{
    if( fs->write_stack->total == 0 )
        CV_Error( CV_StsError, "EndWriteStruct w/o matching StartWriteStruct" );

    int parent_flags = 0;
    int struct_flags = fs->struct_flags;
    cvSeqPop( fs->write_stack, &parent_flags );
    fs->struct_indent -= 4;
    fs->struct_flags = parent_flags & ~CV_NODE_EMPTY;
    assert( fs->struct_indent >= 0 );

    if ( CV_NODE_IS_COLLECTION(struct_flags) )
    {
        if ( !CV_NODE_IS_FLOW(struct_flags) )
        {
            if ( fs->buffer <= fs->buffer_start + fs->space )
            {
                /* some bad code for base64_writer... */
                *fs->buffer++ = '\n';
                *fs->buffer++ = '\0';
                icvPuts( fs, fs->buffer_start );
                fs->buffer = fs->buffer_start;
            }
            icvFSFlush(fs);
        }

        char* ptr = fs->buffer;
        if( ptr > fs->buffer_start + fs->struct_indent && !CV_NODE_IS_EMPTY(struct_flags) )
            *ptr++ = ' ';
        *ptr++ = CV_NODE_IS_MAP(struct_flags) ? '}' : ']';
        fs->buffer = ptr;
    }
}


void icvJSONStartNextStream( CvFileStorage* fs )
{
    if( !fs->is_first )
    {
        while( fs->write_stack->total > 0 )
            icvJSONEndWriteStruct(fs);

        fs->struct_indent = 4;
        icvFSFlush(fs);
        fs->buffer = fs->buffer_start;
    }
}


void icvJSONWriteInt( CvFileStorage* fs, const char* key, int value )
{
    char buf[128];
    icvJSONWrite( fs, key, icv_itoa( value, buf, 10 ));
}


void icvJSONWriteReal( CvFileStorage* fs, const char* key, double value )
{
    char buf[128];
    size_t len = strlen( icvDoubleToString( buf, value ) );
    if( len > 0 && buf[len-1] == '.' )
    {
        // append zero if string ends with decimal place to match JSON standard
        buf[len] = '0';
        buf[len+1] = '\0';
    }
    icvJSONWrite( fs, key, buf );
}


void icvJSONWriteString( CvFileStorage* fs, const char* key, const char* str, int quote)
{
    char buf[CV_FS_MAX_LEN*4+16];
    char* data = (char*)str;
    int i, len;

    if( !str )
        CV_Error( CV_StsNullPtr, "Null string pointer" );

    len = (int)strlen(str);
    if( len > CV_FS_MAX_LEN )
        CV_Error( CV_StsBadArg, "The written string is too long" );

    if( quote || len == 0 || str[0] != str[len-1] || (str[0] != '\"' && str[0] != '\'') )
    {
        int need_quote = 1;
        data = buf;
        *data++ = '\"';
        for( i = 0; i < len; i++ )
        {
            char c = str[i];

            switch ( c )
            {
            case '\\':
            case '\"':
            case '\'': { *data++ = '\\'; *data++ = c;   break; }
            case '\n': { *data++ = '\\'; *data++ = 'n'; break; }
            case '\r': { *data++ = '\\'; *data++ = 'r'; break; }
            case '\t': { *data++ = '\\'; *data++ = 't'; break; }
            case '\b': { *data++ = '\\'; *data++ = 'b'; break; }
            case '\f': { *data++ = '\\'; *data++ = 'f'; break; }
            default  : { *data++ = c; }
                break;
            }
        }

        *data++ = '\"';
        *data++ = '\0';
        data = buf + !need_quote;
    }

    icvJSONWrite( fs, key, data );
}


void icvJSONWriteComment( CvFileStorage* fs, const char* comment, int eol_comment )
{
    if( !comment )
        CV_Error( CV_StsNullPtr, "Null comment" );

    int         len = static_cast<int>(strlen(comment));
    char*       ptr = fs->buffer;
    const char* eol = strchr(comment, '\n');
    bool  multiline = eol != 0;

    if( !eol_comment || multiline || fs->buffer_end - ptr < len || ptr == fs->buffer_start )
        ptr = icvFSFlush( fs );
    else
        *ptr++ = ' ';

    while( comment )
    {
        *ptr++ = '/';
        *ptr++ = '/';
        *ptr++ = ' ';
        if( eol )
        {
            ptr = icvFSResizeWriteBuffer( fs, ptr, (int)(eol - comment) + 1 );
            memcpy( ptr, comment, eol - comment + 1 );
            fs->buffer = ptr + (eol - comment);
            comment = eol + 1;
            eol = strchr( comment, '\n' );
        }
        else
        {
            len = (int)strlen(comment);
            ptr = icvFSResizeWriteBuffer( fs, ptr, len );
            memcpy( ptr, comment, len );
            fs->buffer = ptr + len;
            comment = 0;
        }
        ptr = icvFSFlush( fs );
    }
}
