#include "bu/json.h"
#include "bu/staticmembuf.h"
#include "bu/membuf.h"
#include "bu/exceptionparse.h"

#include "bu/sio.h"

#include <stdlib.h>

#define next( txt ) readChar( ps, "Unexpected end of stream while reading " txt "." )

Bu::Json::Json() :
    eType( Null )
{
}

Bu::Json::Json( const Bu::UtfString &sValue ) :
    eType( String ),
    uDat( sValue )
{
}

Bu::Json::Json( const Bu::String &sValue ) :
    eType( String ),
    uDat( sValue )
{
}

Bu::Json::Json( const char *sValue ) :
    eType( String ),
    uDat( sValue )
{
}

Bu::Json::Json( double dValue ) :
    eType( Number ),
    uDat( dValue )
{
}

Bu::Json::Json( bool bValue ) :
    eType( Boolean ),
    uDat( bValue )
{
}

Bu::Json::Json( Type eType ) :
    eType( eType )
{
    switch( eType )
    {
        case Object:
            uDat.pObject = new JsonHash();
            break;

        case Array:
            uDat.pArray = new JsonList();
            break;

        case String:
            uDat.pString = new Bu::UtfString();
            break;

        case Number:
        case Boolean:
        case Null:
        case Invalid:
            uDat.pObject = NULL;
            break;
    }
}

Bu::Json::Json( Bu::Stream &sInput ) :
    eType( Invalid )
{
    parse( sInput );
}

Bu::Json::Json( Bu::Json::ParseState &ps ) :
    eType( Invalid )
{
    parse( ps );
}

Bu::Json::Json( const Json &rSrc ) :
    eType( Invalid )
{
    (*this) = rSrc;
}

Bu::Json::~Json()
{
    reset();
}

Bu::Json::Type Bu::Json::getType() const
{
    return eType;
}

Bu::UtfString Bu::Json::getString() const
{
    if( eType != String )
        throw Bu::ExceptionBase(
            "String requested from non-string json object."
            );

    return *uDat.pString;
}

double Bu::Json::getNumber() const
{
    if( eType != Number )
        throw Bu::ExceptionBase(
            "Number requested from non-number json object."
            );

    return uDat.dNumber;
}

bool Bu::Json::getBoolean() const
{
    if( eType != Boolean )
        throw Bu::ExceptionBase(
            "Boolean requested from non-boolean json object."
            );

    return uDat.bBoolean;
}

bool Bu::Json::isNull() const
{
    return eType == Null;
}

Bu::Json &Bu::Json::operator[]( const Bu::UtfString &sKey ) const
{
    if( eType != Object )
        throw Bu::ExceptionBase(
            "Object entry requested from non-object json object."
            );

    return *uDat.pObject->get( sKey );
}

Bu::Json &Bu::Json::operator[]( int iIndex ) const
{
    if( eType != Array )
        throw Bu::ExceptionBase(
            "Object entry requested from non-array json object."
            );

    return *uDat.pArray->get( iIndex );
}

int Bu::Json::getSize() const
{
    if( eType == Object )
        return uDat.pObject->getSize();
    else if( eType == Array )
        return uDat.pArray->getSize();
    else
        throw Bu::ExceptionBase(
            "Size requseted from json type that doesn't support it."
            );
}

Bu::UtfStringList Bu::Json::getKeys() const
{
    return uDat.pObject->getKeys();
}

Bu::Json::iterator Bu::Json::begin()
{
    return uDat.pArray->begin();
}

Bu::Json::const_iterator Bu::Json::begin() const
{
    return uDat.pArray->begin();
}

Bu::Json::iterator Bu::Json::end()
{
    return uDat.pArray->end();
}

Bu::Json::const_iterator Bu::Json::end() const
{
    return uDat.pArray->end();
}

bool Bu::Json::has( const Bu::String &sKey ) const
{
    return uDat.pObject->has( sKey );
}

Bu::Json &Bu::Json::insert( const Bu::String &sKey, Bu::Json *pObj )
{
    uDat.pObject->insert( sKey, pObj );
    return *this;
}

Bu::Json &Bu::Json::insert( const Bu::String &sKey, const Bu::Json &rObj )
{
    uDat.pObject->insert( sKey, new Bu::Json( rObj ) );
    return *this;
}

Bu::Json &Bu::Json::insert( const Bu::String &sKey, const Bu::String &sValue )
{
    uDat.pObject->insert( sKey, new Json( sValue ) );
    return *this;
}

Bu::Json &Bu::Json::insert( const Bu::String &sKey, const char *sValue )
{
    uDat.pObject->insert( sKey, new Json( sValue ) );
    return *this;
}

Bu::Json &Bu::Json::insert( const Bu::String &sKey, double dValue )
{
    uDat.pObject->insert( sKey, new Json( dValue ) );
    return *this;
}

Bu::Json &Bu::Json::insert( const Bu::String &sKey, bool bValue )
{
    uDat.pObject->insert( sKey, new Json( bValue ) );
    return *this;
}

Bu::Json &Bu::Json::insertObject( const Bu::String &sKey )
{
    Json *pOb = new Json( Object );
    uDat.pObject->insert( sKey, pOb );
    return *pOb;
}

Bu::Json &Bu::Json::insertArray( const Bu::String &sKey )
{
    Json *pAr = new Json( Array );
    uDat.pObject->insert( sKey, pAr );
    return *pAr;
}

Bu::Json &Bu::Json::insertNull( const Bu::String &sKey )
{
    uDat.pObject->insert( sKey, new Json( Null ) );
    return *this;
}

Bu::Json &Bu::Json::append( Bu::Json *pObj )
{
    uDat.pArray->append( pObj );
    return *this;
}

Bu::Json &Bu::Json::append( const Bu::String &sValue )
{
    uDat.pArray->append( new Json( sValue ) );
    return *this;
}

Bu::Json &Bu::Json::append( const char *sValue )
{
    uDat.pArray->append( new Json( sValue ) );
    return *this;
}

Bu::Json &Bu::Json::append( double dValue )
{
    uDat.pArray->append( new Json( dValue ) );
    return *this;
}

Bu::Json &Bu::Json::append( bool bValue )
{
    uDat.pArray->append( new Json( bValue ) );
    return *this;
}

Bu::Json &Bu::Json::appendObject()
{
    if( eType != Array )
        throw Bu::ExceptionBase("Cannot append to non-array Json Types.");
    Json *pOb = new Json( Object );
    uDat.pArray->append( pOb );
    return *pOb;
}

Bu::Json &Bu::Json::appendArray()
{
    Json *pAr = new Json( Array );
    uDat.pArray->append( pAr );
    return *pAr;
}

Bu::Json &Bu::Json::appendNull()
{
    uDat.pArray->append( new Json( Null ) );
    return *this;
}

void Bu::Json::parse( Bu::Stream &sInput )
{
    reset();

    ParseState ps( sInput );
    next("json");

    parse( ps );
}

void Bu::Json::parse( const Bu::String &sInput )
{
    Bu::StaticMemBuf mb( sInput.getStr(), sInput.getSize() );
    parse( mb );
}

void Bu::Json::parse( ParseState &ps )
{
    while( ps.c == ' ' || ps.c == '\t' || ps.c == '\r' || ps.c == '\n' )
    {
        next( "json" );
    }
    if( ps.c == '"' )
    {
        // String
        parseString( ps );
    }
    else if( ps.c == '{' )
    {
        // Object
        parseObject( ps );
    }
    else if( ps.c == '[' )
    {
        // Array
        parseArray( ps );
    }
    else if( ps.c == '-' || (ps.c >= '0' && ps.c <= '9') )
    {
        // Number -- apparently they can't start with a period
        parseNumber( ps );
    }
    else if( ps.c == 't' || ps.c == 'f' || ps.c == 'n' )
    {
        // True / false / null
        parseLiteral( ps );
    }
    else
    {
        ps.error(
            Bu::String("Invalid json: Invalid character: '%1'.").arg( (char)ps.c )
            );
    }
}

void Bu::Json::reset()
{
    switch( eType )
    {
        case Object:
            for( JsonHash::iterator i = uDat.pObject->begin(); i; i++ )
            {
                delete i.getValue();
            }
            delete uDat.pObject;
            break;

        case Array:
            for( JsonList::iterator i = uDat.pArray->begin(); i; i++ )
            {
                delete *i;
            }
            delete uDat.pArray;
            break;

        case String:
            delete uDat.pString;
            break;

        case Invalid:
        case Number:
        case Boolean:
        case Null:
            break;
    }
    uDat.pObject = NULL;
    eType = Invalid;
}

void Bu::Json::write( Bu::Stream &sOutput ) const
{
    switch( eType )
    {
        case Invalid:
            throw Bu::ExceptionBase("Invalid type in json.");
            break;

        case Object:
            {
                sOutput.write("{", 1 );
                bool bFirst = true;
                for( JsonHash::iterator i = uDat.pObject->begin(); i; i++ )
                {
                    if( bFirst == true )
                        bFirst = false;
                    else
                        sOutput.write(",");
                    writeStr( i.getKey(), sOutput );
                    sOutput.write(":", 1 );
                    (*i)->write( sOutput );
                }
                sOutput.write("}", 1 );
            }
            break;

        case Array:
            {
                sOutput.write("[", 1);
                bool bFirst = true;
                for( JsonList::iterator i = uDat.pArray->begin(); i; i++ )
                {
                    if( bFirst == true )
                        bFirst = false;
                    else
                        sOutput.write(",", 1 );
                    (*i)->write( sOutput );
                }
                sOutput.write("]", 1);
            }
            break;

        case String:
            writeStr( *uDat.pString, sOutput );
            break;

        case Number:
            {
                char buf[64];
                if( uDat.dNumber == floor( uDat.dNumber ) )
                {
                    sprintf( buf, "%ld", (int64_t)uDat.dNumber );
                }
                else
                {
                    sprintf( buf, "%f", uDat.dNumber );
                }
                sOutput.write( buf );
            }
            break;

        case Boolean:
            if( uDat.bBoolean )
                sOutput.write("true", 4 );
            else
                sOutput.write("false", 5 );
            break;

        case Null:
            sOutput.write("null", 4 );
            break;
    }
}

void Bu::Json::writeStable( Bu::Stream &sOutput ) const
{
    if( eType == Object )
    {
        sOutput.write("{", 1 );
        bool bFirst = true;
        Bu::List<Bu::UtfString> lKey = uDat.pObject->getKeys();
        lKey.sort();
        for( Bu::List<Bu::UtfString>::iterator i = lKey.begin(); i; i++ )
        {
            if( bFirst == true )
                bFirst = false;
            else
                sOutput.write(",");
            writeStr( *i, sOutput );
            sOutput.write(":", 1 );
            uDat.pObject->get( *i )->write( sOutput );
        }
        sOutput.write("}", 1 );
    }
    else
    {
        write( sOutput );
    }
}

Bu::String Bu::Json::toString() const
{
    Bu::MemBuf mb;
    write( mb );
    return mb.getString();
}

Bu::String Bu::Json::toStringStable() const
{
    Bu::MemBuf mb;
    writeStable( mb );
    return mb.getString();
}

Bu::Json &Bu::Json::operator=( const Bu::Json &rSrc )
{
    reset();

    eType = rSrc.eType;
    switch( eType )
    {
        case Invalid:
        case Null:
            break;

        case String:
            uDat.pString = new Bu::UtfString( *rSrc.uDat.pString );
            break;

        case Number:
            uDat.dNumber = rSrc.uDat.dNumber;
            break;

        case Boolean:
            uDat.bBoolean = rSrc.uDat.bBoolean;
            break;
        
        case Object:
            uDat.pObject = new JsonHash();
            for( JsonHash::const_iterator i = rSrc.uDat.pObject->begin();
                    i; ++i )
            {
                uDat.pObject->insert( i.getKey(), new Json( *i.getValue() ) );
            }
            break;

        case Array:
            uDat.pArray = new JsonList();
            for( JsonList::const_iterator i = rSrc.uDat.pArray->begin();
                    i; ++i )
            {
                uDat.pArray->append( new Json( *(*i) ) );
            }
            break;
    }

    return *this;
}

bool Bu::Json::operator==( const Bu::String &rRhs )
{
    if( eType != String )
        return false;

    return (*uDat.pString) == rRhs;
}

void Bu::Json::parseString( Bu::Json::ParseState &ps, Bu::UtfString &sOut )
{
    skipWs( ps );
    bool bEscape = false;
    for(;;)
    {
        next( "string" );
        if( bEscape )
        {
            switch( ps.c )
            {
                case '"':
                case '\\':
                case '/':
                    sOut += ps.c;
                    break;

                case 'b':
                    sOut += '\b';
                    break;

                case 'f':
                    sOut += '\f';
                    break;

                case 'n':
                    sOut += '\n';
                    break;

                case 'r':
                    sOut += '\r';
                    break;

                case 't':
                    sOut += '\t';
                    break;

                case 'u':
                    {
                        char hex[5];
                        for( int j = 0; j < 4; j++ )
                        {
                            readChar( ps );
                            if( (ps.c >= '0' && ps.c <= '9') || 
                                (ps.c >= 'a' && ps.c <= 'f') ||
                                (ps.c >= 'A' && ps.c <= 'F') )
                            {
                                hex[j] = ps.c;
                            }
                            else
                            {
                                ps.error(
                                    "Invalid json: Invalid \\u<hex> "
                                    "escape sequence."
                                    );
                            }
                        }
                        hex[4] = '\0';
                        sOut += (Bu::UtfChar)strtol( hex, NULL, 16 );
                    }
                    break;

                default:
                    ps.error(
                        Bu::String("Invalid json: Invalid escape sequence: "
                            " '\\%1'.").arg( (char)ps.c )
                        );
                    break;
            }
            bEscape = false;
        }
        else
        {
            if( ps.c == '\\' )
                bEscape = true;
            else if( ps.c == '"' )
            {
                readChar( ps );
                break;
            }
            else
                sOut += ps.c;
        }
    }
}

void Bu::Json::parseString( Bu::Json::ParseState &ps )
{
    eType = String;
    uDat.pString = new Bu::UtfString();
    parseString( ps, *uDat.pString );
}

void Bu::Json::parseObject( Bu::Json::ParseState &ps )
{
    skipWs( ps );
    eType = Object;
    uDat.pObject = new JsonHash();

    next( "object" );
    skipWs( ps );

    // Check to see if it's an empty object.
    if( ps.c == '}' )
    {
        next("object");
        return;
    }

    for(;;)
    {
        skipWs( ps );
        if( ps.c != '"' )
        {
            ps.error(
                Bu::String("Invalid json: expected string as key in object, "
                    "found '%1'.").arg( (char)ps.c )
                );
        }
        Bu::UtfString sKey;
        parseString( ps, sKey );
        skipWs( ps );
        if( ps.c != ':' )
        {
            ps.error(
                Bu::String("Invalid json: expected colon after key in object, "
                    "found '%1'.").arg( (char)ps.c )
                );
        }
        next("object");
        uDat.pObject->insert( sKey, new Json( ps ) );
        skipWs( ps );
        if( ps.c == '}' )
        {
            readChar( ps );
            break;
        }
        else if( ps.c == ',' )
            next( "object" );
        else
            ps.error(
                Bu::String("Invalid json: expected comma or } after value "
                    "in object, found '%1'.").arg( (char)ps.c )
                );
    }
}

void Bu::Json::parseArray( Bu::Json::ParseState &ps )
{
    skipWs( ps );

    eType = Array;
    uDat.pArray = new JsonList();

    next("array");

    // Check to see if it's an empty array.
    if( ps.c == ']' )
    {
        next("array");
        return;
    }

    for(;;)
    {
        uDat.pArray->append( new Json( ps ) );
        skipWs( ps );
        if( ps.c == ']' )
        {
            readChar( ps );
            break;
        }
        else if( ps.c == ',' )
        {
            next("array");
            continue;
        }
        else
        {
            ps.error(
                Bu::String("Invalid json: expected comma or ] after value "
                    "in array, found '%1'.").arg( (char)ps.c )
                );
        }
    }
}

void Bu::Json::parseNumber( Bu::Json::ParseState &ps )
{
    skipWs( ps );

    Bu::String sBuf;
    if( ps.c == '-' )
    {
        sBuf += ps.c;
        next( "number" );
    }
    bool bIntPart = true;
    do
    {
        if( ps.c >= '0' && ps.c <= '9' )
            sBuf += ps.c;
        else if( ps.c == '.' && bIntPart == true )
        {
            bIntPart = false;
            sBuf += ps.c;
        }
        else if( ps.c == ' ' || ps.c == '\t' || ps.c == '\n' || ps.c == '\r' ||
                 ps.c == '}' || ps.c == ']' || ps.c == ',' )
        {
            break;
        }
        else
        {
            ps.error(
                Bu::String("Invalid json: Invalid character in number: '%1'.").
                    arg( (char)ps.c )
                );
        }
    } while( readChar( ps ) );

    eType = Number;
    uDat.dNumber = atof( sBuf.getStr() );
}

void Bu::Json::parseLiteral( Bu::Json::ParseState &ps )
{
    skipWs( ps );

    Bu::String s;
    do
    {
        if( isWs( ps.c ) || ps.c == ',' || ps.c == '}' || ps.c == ']' )
            break;
        else
            s += ps.c;
    } while( readChar( ps ) );

    if( s == "true" )
    {
        eType = Boolean;
        uDat.bBoolean = true;
    }
    else if( s == "false" )
    {
        eType = Boolean;
        uDat.bBoolean = false;
    }
    else if( s == "null" )
    {
        eType = Null;
        uDat.pObject = NULL;
    }
    else
    {
        ps.error(
            Bu::String("Invalid json: Invalid literal token found, '%1'.").
                arg( s )
            );
    }
}

bool Bu::Json::readChar( Bu::Json::ParseState &ps )
{
    if( Bu::UtfString::readPoint( ps.sInput, ps.c ) == 0 && ps.sInput.isEos() )
        return false;

    if( ps.c == '\n' )
    {
        // Increment the line and set iChar to zero.  This makes sense only
        // beacuse we only complain after a charecter has been read, so this
        // will be too large by one unless we start at zero.
        ps.iLine++;
        ps.iChar = 0;
    }
    else 
    {
        ps.iChar++;
    }

    return true;
}

void Bu::Json::readChar( Bu::Json::ParseState &ps, const char *sSection )
{
    if( !readChar( ps ) )
    {
        ps.error( sSection );
    }
}

bool Bu::Json::isWs( Bu::UtfChar c )
{
    return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}

void Bu::Json::skipWs( Bu::Json::ParseState &ps )
{
    while( isWs( ps.c ) )
    {
        next("whitespace");
    }
}

void Bu::Json::writeStr( const Bu::UtfString &sStr, Bu::Stream &sOutput ) const
{
    sOutput.write("\"", 1 );
    for( Bu::UtfString::const_iterator i = sStr.begin(); i; i++ )
    {
        switch( *i )
        {
            case '"':
                sOutput.write("\\\"", 2 );
                break;

            case '\\':
                sOutput.write("\\\\", 2 );
                break;

            case '/':
                sOutput.write("\\/", 2 );
                break;

            case '\b':
                sOutput.write("\\b", 2 );
                break;

            case '\f':
                sOutput.write("\\f", 2 );
                break;

            case '\n':
                sOutput.write("\\n", 2 );
                break;

            case '\r':
                sOutput.write("\\r", 2 );
                break;

            case '\t':
                sOutput.write("\\t", 2 );
                break;

            default:
                if( *i < 32 )
                    sOutput.write(
                        Bu::String("\\u%1").
                        arg( (uint32_t)*i, Bu::Fmt::hex(4).fill('0') ).
                        end().getStr(),
                        6
                        );
                else
                    Bu::UtfString::writePoint( sOutput, *i );
                break;
        }
    }
    sOutput.write("\"", 1 );
}

Bu::Formatter &Bu::operator<<( Bu::Formatter &f, const Bu::Json &j )
{
    f.write( j.toString() );
    return f;
}

void Bu::Json::ParseState::error( const Bu::String &sTxt )
{
    throw Bu::ExceptionParse(
        Bu::String("%1:%2: %3").
            arg( iLine ).arg( iChar ).arg( sTxt ).end().getStr()
        );
}

