/*
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/*
 * JSON Writer - Conditional compilation for HTTP/1.1 vs HTTP/2 modes
 *
 * HTTP/1.1 mode (no WITH_NGHTTP2): Full AXIOM-to-JSON conversion
 * HTTP/2 mode (WITH_NGHTTP2): Stub implementation - JSON handled by JsonRpcMessageReceiver
 */

#ifdef WITH_NGHTTP2
/* ============================================================================
 * HTTP/2 JSON Mode - Stub Implementation (AXIOM-free)
 * ============================================================================
 * In HTTP/2 JSON mode, JSON processing is handled directly by the
 * JsonRpcMessageReceiver without AXIOM intermediation.
 */

#include <json-c/json.h>
#include <string.h>
#include "axis2_json_writer.h"

struct axis2_json_writer
{
    json_object* json_obj;
};

AXIS2_EXTERN axis2_json_writer_t* AXIS2_CALL
axis2_json_writer_create(const axutil_env_t* env)
{
    axis2_json_writer_t* writer = (axis2_json_writer_t*)AXIS2_MALLOC(env->allocator,
                                                                     sizeof(struct axis2_json_writer));
    if (writer) {
        writer->json_obj = json_object_new_object();
    }
    return writer;
}

AXIS2_EXTERN void AXIS2_CALL
axis2_json_writer_free(
        axis2_json_writer_t* writer,
        const axutil_env_t* env)
{
    if (writer) {
        if (writer->json_obj) {
            json_object_put(writer->json_obj);
        }
        AXIS2_FREE(env->allocator, writer);
    }
}

AXIS2_EXTERN void AXIS2_CALL
axis2_json_writer_write(
        axis2_json_writer_t* writer,
        const axiom_node_t* node,
        const axutil_env_t* env)
{
    /* Stub - HTTP/2 JSON processing done by JsonRpcMessageReceiver */
    (void)writer;
    (void)node;
    (void)env;
}

AXIS2_EXTERN const axis2_char_t* AXIS2_CALL
axis2_json_writer_get_json_string(
        axis2_json_writer_t* writer,
        const axutil_env_t* env,
        int* json_string_length)
{
    (void)env;
    if (writer && writer->json_obj) {
        const char* json_str = json_object_to_json_string(writer->json_obj);
        if (json_string_length) {
            *json_string_length = (int)strlen(json_str);
        }
        return json_str;
    }
    if (json_string_length) {
        *json_string_length = 2;
    }
    return "{}";
}

#else /* !WITH_NGHTTP2 */
/* ============================================================================
 * HTTP/1.1 SOAP Mode - Full AXIOM-to-JSON Implementation
 * ============================================================================
 * Traditional mode with full AXIOM node to JSON conversion support.
 */

#include <axiom_node.h>
#include <axiom_element.h>
#include <axiom_attribute.h>
#include <json-c/json.h>
#include "axis2_json_writer.h"

#define AXIS2_JSON_XSI_URI "http://www.w3.org/2001/XMLSchema-instance"

struct axis2_json_writer
{
    json_object* json_obj;
};

axis2_bool_t axis2_json_element_is_nil(axiom_element_t* om_element, const axutil_env_t* env)
{
    axiom_attribute_t* attr = NULL;
    axutil_hash_index_t* index;
    axutil_hash_t* attr_hash = axiom_element_get_all_attributes(om_element, env);
    axis2_bool_t result = AXIS2_FALSE;

    if (!attr_hash)
        return AXIS2_FALSE;

    for (index = axutil_hash_first(attr_hash, env);
         index; index = axutil_hash_next(env, index))
    {
        axutil_hash_this(index, NULL, NULL, (void**)&attr);
        if (attr && !strcmp(axiom_attribute_get_localname(attr, env), "nil"))
        {
            /* found some "nil" attribute, check it namespace */
            axutil_qname_t* qname =
                    axiom_attribute_get_qname(attr, env);
            if (qname && !strcmp(axutil_qname_get_uri(qname, env), AXIS2_JSON_XSI_URI))
            {
                axis2_char_t* attr_value =
                        axiom_attribute_get_value(attr, env);
                result = (!strcmp(attr_value, "true") || !strcmp(attr_value, "1")) ?
                            AXIS2_TRUE : AXIS2_FALSE;
                /* Found what we're looking for - use axutil_hash_index_free to cleanup */
                axutil_hash_index_free(index, env);
                return result;
            }
        }
    }

    return result;
}

void axis2_json_write_node(json_object* parent, axiom_node_t* om_node, const axutil_env_t* env)
{
    axiom_element_t* elem;
    axiom_node_t* child_node;
    const axis2_char_t* local_name;
    json_object* obj;
    json_object* array = NULL;

    if (!om_node || axiom_node_get_node_type(om_node, env) != AXIOM_ELEMENT)
        return;

    elem = (axiom_element_t*)axiom_node_get_data_element(om_node, env);
    local_name = axiom_element_get_localname(elem, env);

    child_node = axiom_node_get_first_element(om_node, env);

    /* find existing object */
    if (json_object_object_get_ex(parent, local_name, &obj))
    {
        /* check that object is array? */
        if (!json_object_is_type(obj, json_type_array))
        {
            /* convert to array */
            obj = json_object_get(obj);
            array = json_object_new_array();
            json_object_array_add(array, obj);
            json_object_object_del(parent, local_name);
            json_object_object_add(parent, local_name, array);
        }
        else
            array = obj;
    }

    if (!child_node)
    {
        /* this is a leaf node */
        json_object* json_value = NULL;

        /* check for nillable */
        if (!axis2_json_element_is_nil(elem, env))
        {
            const axis2_char_t* value =
                    axiom_element_get_text(elem, env, om_node);
            json_value = json_object_new_string(value ? value : "");
        }

        if (array)
            json_object_array_add(array, json_value);
        else
            json_object_object_add(parent, local_name, json_value);
        return;
    }

    /* iterate through children elements */
    obj = json_object_new_object();
    if (array)
        json_object_array_add(array, obj);
    else
        json_object_object_add(parent, local_name, obj);

    for (; child_node; child_node = axiom_node_get_next_sibling(child_node, env))
        if (axiom_node_get_node_type(child_node, env) == AXIOM_ELEMENT)
            axis2_json_write_node(obj, child_node, env);
}


AXIS2_EXTERN axis2_json_writer_t* AXIS2_CALL
axis2_json_writer_create(
        const axutil_env_t* env)
{
    axis2_json_writer_t* writer = (axis2_json_writer_t*)AXIS2_MALLOC(env->allocator,
                                                                     sizeof(struct axis2_json_writer));
    if (writer)
        writer->json_obj = NULL;

    return writer;
}


AXIS2_EXTERN void AXIS2_CALL
axis2_json_writer_free(
        axis2_json_writer_t* writer,
        const axutil_env_t* env)
{
    if (writer) {
        if (writer->json_obj) {
            json_object_put(writer->json_obj);
        }
        AXIS2_FREE(env->allocator, writer);
    }
}


AXIS2_EXTERN void AXIS2_CALL
axis2_json_writer_write(
        axis2_json_writer_t* writer,
        const axiom_node_t* node,
        const axutil_env_t* env)
{
    /* free existing object */
    if (writer->json_obj)
        json_object_put(writer->json_obj);

    writer->json_obj = json_object_new_object();
    axis2_json_write_node(writer->json_obj, (axiom_node_t*)node, env);
}


AXIS2_EXTERN const axis2_char_t* AXIS2_CALL
axis2_json_writer_get_json_string(
        axis2_json_writer_t* writer,
        const axutil_env_t* env,
        int* json_string_length)
{
    (void)env;
    const axis2_char_t* result =
            (const axis2_char_t*)json_object_to_json_string_ext(writer->json_obj,
                                                               JSON_C_TO_STRING_PLAIN);
    if (json_string_length)
        *json_string_length = strlen(result);

    return result;
}

#endif /* WITH_NGHTTP2 */
