/*
(c) Thales group, 2012

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation;
    version 2 of the License.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
Contact: http://eodev.sourceforge.net

Authors:
    Benjamin Bouvier <benjamin.bouvier@gmail.com>
*/
# include <string>

# include "Parser.h"

# include "Array.h"
# include "Object.h"
# include "String.h"

// in debug mode only
// # define DEBUG(x) std::cout << x << std::endl;
# define DEBUG(x)

using namespace eoserial;

namespace eoserial
{

/**
 * @brief Parses a string contained between double quotes.
 *
 * Strings can contain escaped double quotes.
 * @param str The string we're parsing.
 * @param pos The index of current position in parsed string.
 * This index will be updated so as to allow the parser to
 * continue.
 */
static std::string parseString(const std::string& str, size_t & pos)
{
    // example : "hello"
    // example 2 : "\"world\""
    // for hello:
    // firstQuote == 0, secondQuote == 6
    // sub string should be from firstQuote+1 to secondQuote-1
    // so its size should be (secondQuote-1 -(firstQuote+1) + 1)
    std::string value;
    size_t firstQuote = str.find( '"', pos );
    size_t secondQuote;

    /* instead of just seeking the second quote, we need to ensure
    // that there is no escaped quote before this one.
    // actually this is harder than that. Using backslashes
    // to escape double quotes mean that backslashes have to be
    // escaped to.
    // example : "text\\" to symbolize : text\
    // example : "text\\\" to symbolize : text\"
    // In fact, we should find if number of backslashes is odd; in this case,
    // the double quotes are escaped and we should find the next one.
    */
    int backslashesCount;
    do {
        ++pos;
        secondQuote = str.find( '"', pos );
        size_t i = secondQuote - 1;

        // Find the backslashes
        backslashesCount = 0;
        while ( str[ i ] == '\\' )
        {
            --i;
            ++backslashesCount;
        }
        pos = secondQuote;
    } while( backslashesCount % 2 == 1 );

    value = str.substr( firstQuote+1, secondQuote-firstQuote-1 );
    pos = secondQuote + 1;
    return value;
}

/**
 * @brief Moves the given index pos to the next character which is
 * neither a coma, a space nor a new line.
 *
 * @param str The string in which we want to ignores those characters.
 * @param pos The index of current position in parsed string.
 */
static void ignoreChars(const std::string& str, size_t & pos)
{
    // ignore white spaces and comas
    for (char current = str[ pos ];
        current == ',' || current == ' ' || current == '\n';
        current = str[ ++pos ]);
}

String* Parser::parseJsonString(const std::string & str, size_t & pos)
{
    return new String( parseString( str, pos ) );
}

Object* Parser::parse(const std::string & str)
{
    size_t initial(0); // we begin at position 0
    return static_cast<Object*>( parseRight(str, initial) );
}

Entity* Parser::parseRight(const std::string & str, size_t & pos)
{
    Entity* value = 0;

    if ( str[ pos ] == '{' )
    {
        // next one is an object
        DEBUG("We read an object.")
        Object* obj = new Object;
        pos += 1;
        while( pos < str.size() && str[ pos ] != '}' )
        {
            parseLeft( str, pos, obj );
            ignoreChars( str, pos );
        }
        DEBUG("We just finished to read an object ! ")
        pos += 1; // we're on the }, go to the next char
        value = obj;
    }
    else if ( str[ pos ] == '"' )
    {
        // next one is a string
        DEBUG("We read a string")
        value = parseJsonString( str, pos );
    }
    else if ( str[ pos ] == '[' )
    {
        // next one is an array
        DEBUG("We read an array")
        Array* array = new Array;
        pos += 1;
        while( pos < str.size() && str[ pos ] != ']' )
        {
            Entity* child = parseRight( str, pos );
            if ( child )
                array->push_back( child );
        }
        DEBUG("We've finished to read our array.")
        pos += 1; // we're on the ], go to the next char
        value = array;
    }
    ignoreChars( str, pos );
    return value;
}

void Parser::parseLeft(const std::string & str, size_t & pos, Object* eoserial)
{
    std::string key = parseString(str, pos);
    ++pos; // the colon
    DEBUG("We've read the key ")
    (*eoserial)[ key ] = parseRight( str, pos ); 
}

} // namespace eoserial

