/* ajson.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2017-2023 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "ajson.hh"
#include <fstream>
#include <sstream>
#include "aguix/lowlevelfunc.h"
#include "aguix/util.h"

using namespace AJSON;

JSONType::~JSONType()
{
}

static void output_indentation( std::ostream &out, int indent_level )
{
	for ( int i = 0; i < indent_level; i++ ) {
		out << "  ";
	}
}

static std::string escape_string( const std::string &str )
{
    std::string escaped;

    int res = AGUIXUtils::escape_characters( str, escaped, AGUIXUtils::ESCAPE_JSON );

    if ( res != 0 ) return "";

	return escaped;
}

static std::string unescape_string( const std::string &str )
{
    std::string unescaped;

    int res = AGUIXUtils::unescape_characters( str, unescaped, AGUIXUtils::ESCAPE_JSON );

    if ( res != 0 ) return "";

	return unescaped;
}

int JSONObject::dump( std::ostream &out, int indent_level )
{
	int res = 0;

	out << "{" << std::endl;

	size_t pos = 0;
	for ( auto &k : m_elements ) {
		output_indentation( out, indent_level + 1 );

		out << "\"" << escape_string( k.first ) << "\": ";

        if ( k.second ) {
            res |= k.second->dump( out, indent_level + 1 );
        } else {
            out << "null";
        }

		if ( pos + 1 < m_elements.size() ) {
			out << ",";
		}

		out << std::endl;

		pos++;
	}

	output_indentation( out, indent_level );

	out << "}";

	return res;
}

std::shared_ptr< JSONType > JSONObject::get( const std::string &key )
{
	if ( m_elements.count( key ) == 0 ) return nullptr;

	return m_elements[key];
}

std::shared_ptr< JSONType > JSONObject::set( const std::string &key, std::shared_ptr< JSONType > value )
{
	m_elements[key] = value;

	return value;
}

std::list< std::string > JSONObject::keys() const
{
    std::list< std::string > res;

    for ( auto &e : m_elements ) {
        res.push_back( e.first );
    }

    return res;
}

int JSONArray::dump( std::ostream &out, int indent_level )
{
	int res = 0;

	out << "[" << std::endl;

	for ( size_t pos = 0; pos < m_elements.size(); pos++ ) {
		output_indentation( out, indent_level + 1 );

        if ( m_elements[pos] ) {
            res |= m_elements[pos]->dump( out, indent_level + 1 );
        } else {
            out << "null";
        }

		if ( pos + 1 < m_elements.size() ) {
			out << ",";
		}

		out << std::endl;
	}

	output_indentation( out, indent_level );

	out << "]";

	return res;
}

size_t JSONArray::size() const
{
	return m_elements.size();
}

std::shared_ptr< JSONType > JSONArray::get( size_t index )
{
	if ( index >= m_elements.size() ) return nullptr;

	return m_elements.at( index );
}

int JSONArray::append( std::shared_ptr< JSONType > value )
{
	m_elements.push_back( value );

	return 0;
}

JSONString::JSONString( const std::string &value ) : m_value( value )
{
}

int JSONString::dump( std::ostream &out, int indent_level )
{
	out << "\"" << escape_string( m_value ) << "\"";

	return 0;
}

std::string JSONString::get_value() const
{
	return m_value;
}

JSONNumber::JSONNumber( const std::string &value )
{
    errno = 0;
    m_value = strtol( value.c_str(), NULL, 10 );

    if ( errno != 0 ) {
        m_value = 0;
    }
}

JSONNumber::JSONNumber( long int value ) : m_value( value )
{
}

int JSONNumber::dump( std::ostream &out, int indent_level )
{
	out << m_value;

	return 0;
}

long int JSONNumber::get_value() const
{
	return m_value;
}

JSONBoolean::JSONBoolean( bool value ) : m_value( value )
{
}

int JSONBoolean::dump( std::ostream &out, int indent_level )
{
    if ( m_value ) {
        out << "true";
    } else {
        out << "false";
    }

	return 0;
}

bool JSONBoolean::get_value() const
{
	return m_value;
}

std::shared_ptr< JSONType > AJSON::load( const std::string &filename )
{
    std::ifstream ifile( filename.c_str() );

    if ( ! ifile.is_open() ) return nullptr;

    std::stringstream buffer;

    buffer << ifile.rdbuf();

    JSONParser p;

    auto res = p.parse( buffer.str() );

    return res;
}

int AJSON::dump( std::shared_ptr< JSONType > value, const std::string &filename )
{
	std::ofstream ostr( filename );

    int res = 0;

    if ( value ) {
        res = value->dump( ostr, 0 );

        ostr << std::endl;
    }
	
	ostr.close();

	return res;
}

std::shared_ptr<JSONObject> AJSON::make_object()
{
	return std::shared_ptr<JSONObject>( new JSONObject() );
}

std::shared_ptr<JSONArray> AJSON::make_array()
{
	return std::shared_ptr<JSONArray>( new JSONArray() );
}

std::shared_ptr<JSONString> AJSON::make_string( const std::string &value )
{
	return std::shared_ptr<JSONString>( new JSONString( value ) );
}

std::shared_ptr<JSONNumber> AJSON::make_number( long int value )
{
	return std::shared_ptr<JSONNumber>( new JSONNumber( value ) );
}

std::shared_ptr<JSONNumber> AJSON::make_number( const std::string &value )
{
	return std::shared_ptr<JSONNumber>( new JSONNumber( value ) );
}

std::shared_ptr<JSONBoolean> AJSON::make_boolean( bool value )
{
	return std::shared_ptr<JSONBoolean>( new JSONBoolean( value ) );
}

std::shared_ptr<JSONString> AJSON::as_string( std::shared_ptr< JSONType > o )
{
    return std::dynamic_pointer_cast< JSONString >( o );
}

std::shared_ptr<JSONObject> AJSON::as_object( std::shared_ptr< JSONType > o )
{
    return std::dynamic_pointer_cast< JSONObject >( o );
}

std::shared_ptr<JSONArray> AJSON::as_array( std::shared_ptr< JSONType > o )
{
    return std::dynamic_pointer_cast< JSONArray >( o );
}

std::shared_ptr<JSONNumber> AJSON::as_number( std::shared_ptr< JSONType > o )
{
    return std::dynamic_pointer_cast< JSONNumber >( o );
}

std::shared_ptr<JSONBoolean> AJSON::as_boolean( std::shared_ptr< JSONType > o )
{
    return std::dynamic_pointer_cast< JSONBoolean >( o );
}

std::shared_ptr< JSONType > JSONParser::parse( const std::string &data )
{
    m_tokens.clear();

    if ( ajson_scanner( data,
                        m_tokens ) != 0 ) {
        fprintf( stderr, "processing json data failed\n" );
        return nullptr;
    }

    return value();
}

ajson_token_t JSONParser::get_current_token() const
{
    if ( m_tokens.empty() ) return std::make_pair( JSON_INVALID, "" );

    return m_tokens.front();
}

void JSONParser::advance_token()
{
    if ( m_tokens.empty() ) abort();

    m_tokens.pop_front();
}

bool JSONParser::has_next_token() const
{
    return ! m_tokens.empty();
}

std::shared_ptr< JSONType > JSONParser::value()
{
    if ( ! has_next_token() ) return nullptr;

    auto token = get_current_token();

    switch ( token.first ) {
        case JSON_NULL:
            // not supported
            break;
        case JSON_FALSE: {
            auto e = make_boolean( false );

            advance_token();

            return e;
        }
        case JSON_TRUE: {
            auto e = make_boolean( true );

            advance_token();

            return e;
        }
        case JSON_STRING: {
            auto e = make_string( unescape_string( token.second ) );

            advance_token();

            return e;
        }
        case JSON_NUMBER: {
            auto e = make_number( token.second );

            advance_token();

            return e;
        }
        case JSON_BEGIN_OBJECT:
            advance_token();
            return object();
        case JSON_BEGIN_ARRAY:
            advance_token();
            return array();
        default:
            break;
    }

    advance_token();

    return nullptr;
}

std::shared_ptr< JSONType > JSONParser::object()
{
    auto o = make_object();

    for (;;) {
        if ( ! has_next_token() ) return nullptr;

        auto token = get_current_token();

        if ( token.first == JSON_END_OBJECT ) {
            advance_token();
            break;
        }

        if ( token.first != JSON_STRING ) {
            return nullptr;
        }

        auto name = unescape_string( token.second );

        advance_token();

        if ( ! has_next_token() ) return nullptr;
                
        token = get_current_token();

        if ( token.first != JSON_NAME_SEPARATOR ) return nullptr;

        advance_token();

        if ( ! has_next_token() ) return nullptr;
                
        auto m = value();

        if ( ! m ) return nullptr;

        o->set( name, m );

        if ( ! has_next_token() ) return nullptr;
                
        token = get_current_token();

        if ( token.first == JSON_VALUE_SEPARATOR ) {
            advance_token();
        }
    }

    return o;
}

std::shared_ptr< JSONType > JSONParser::array()
{
    auto a = make_array();

    for (;;) {
        if ( ! has_next_token() ) return nullptr;

        auto token = get_current_token();

        if ( token.first == JSON_END_ARRAY ) {
            advance_token();
            break;
        }

        auto m = value();

        if ( ! m ) return nullptr;

        a->append( m );

        if ( ! has_next_token() ) return nullptr;
                
        token = get_current_token();

        if ( token.first == JSON_VALUE_SEPARATOR ) {
            advance_token();
        }
    }

    return a;
}
