
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

#include <axis2_listener_manager.h>
#include <axis2_const.h>
#include <axutil_hash.h>
#include <axis2_transport_receiver.h>

/**
 * keep information about the listener for a given transport
 */
typedef struct axis2_transport_listener_state
{
    int waiting_calls;

    axis2_transport_receiver_t *listener;

} axis2_transport_listener_state_t;

struct axis2_listener_manager
{

    /** hash map of listeners */
    axis2_transport_listener_state_t *listener_map[AXIS2_TRANSPORT_ENUM_MAX];

    /** configuration context */
    axis2_conf_ctx_t *conf_ctx;
};

typedef struct axis2_listener_manager_worker_func_args
{
    const axutil_env_t *env;
    axis2_listener_manager_t *listner_manager;
    axis2_transport_receiver_t *listener;
} axis2_listener_manager_worker_func_args_t;

void *AXIS2_THREAD_FUNC axis2_listener_manager_worker_func(
    axutil_thread_t * thd,
    void *data);

AXIS2_EXTERN axis2_listener_manager_t *AXIS2_CALL
axis2_listener_manager_create(
    const axutil_env_t * env)
{
    axis2_listener_manager_t *listener_manager = NULL;
    int i = 0;

    AXIS2_ENV_CHECK(env, NULL);

    listener_manager =
        AXIS2_MALLOC(env->allocator, sizeof(axis2_listener_manager_t));

    if (!listener_manager)
    {
        AXIS2_ERROR_SET(env->error, AXIS2_ERROR_NO_MEMORY, AXIS2_FAILURE);
        AXIS2_LOG_ERROR(env->log, AXIS2_LOG_SI, "No memory. Cannot create listener manager.");
        return NULL;
    }

    listener_manager->conf_ctx = NULL;

    for (i = 0; i < AXIS2_TRANSPORT_ENUM_MAX; i++)
    {
        listener_manager->listener_map[i] = NULL;
    }

    return listener_manager;
}

AXIS2_EXTERN axis2_status_t AXIS2_CALL
axis2_listener_manager_make_sure_started(
    axis2_listener_manager_t * listener_manager,
    const axutil_env_t * env,
    const AXIS2_TRANSPORT_ENUMS transport,
    axis2_conf_ctx_t * conf_ctx)
{
    axis2_transport_listener_state_t *tl_state = NULL;

    AXIS2_PARAM_CHECK(env->error, conf_ctx, AXIS2_FAILURE);

    if (listener_manager->conf_ctx)
    {
        if (conf_ctx != listener_manager->conf_ctx)
        {
            AXIS2_ERROR_SET(env->error,
                            AXIS2_ERROR_CLIENT_SIDE_SUPPORT_ONLY_ONE_CONF_CTX,
                            AXIS2_FAILURE);
            AXIS2_LOG_ERROR(env->log, AXIS2_LOG_SI, "Only one configuration context is supported at client side.");
            return AXIS2_FAILURE;
        }
    }
    else
    {
        listener_manager->conf_ctx = conf_ctx;
    }

    tl_state = listener_manager->listener_map[transport];

    if (!tl_state)
    {
        /*means this transport not yet started, start the transport */
        axis2_transport_in_desc_t *transport_in = NULL;
        axis2_conf_t *conf = NULL;
        axis2_transport_receiver_t *listener = NULL;

        conf = axis2_conf_ctx_get_conf(conf_ctx, env);
        if (conf)
        {
            transport_in = axis2_conf_get_transport_in(conf, env, transport);
            if (transport_in)
            {
                listener = axis2_transport_in_desc_get_recv(transport_in, env);
                if (listener)
                {
                    axutil_thread_t *worker_thread = NULL;
                    axis2_listener_manager_worker_func_args_t *arg_list = NULL;
                    arg_list = AXIS2_MALLOC(env->allocator,
                                            sizeof
                                            (axis2_listener_manager_worker_func_args_t));
                    if (!arg_list)
                    {
                        AXIS2_ERROR_SET(env->error, AXIS2_ERROR_NO_MEMORY, AXIS2_FAILURE);
                        AXIS2_LOG_ERROR(env->log, AXIS2_LOG_SI, "No memory. Cannot create listener manager worker function arguments.");
                        return AXIS2_FAILURE;
                    }
                    arg_list->env = env;
                    arg_list->listner_manager = listener_manager;
                    arg_list->listener = listener;
#ifdef AXIS2_SVR_MULTI_THREADED
                    if (env->thread_pool)
                    {
                        worker_thread =
                            axutil_thread_pool_get_thread(env->thread_pool,
                                                          axis2_listener_manager_worker_func,
                                                          (void *) arg_list);
                        if (!worker_thread)
                        {
                            AXIS2_LOG_ERROR(env->log, AXIS2_LOG_SI,
                                            "Thread creation failed"
                                            "Invoke non blocking failed");
                        }
                        else
                        {
                            axutil_thread_pool_thread_detach(env->thread_pool,
                                                             worker_thread);
                        }
                    }
                    else
                    {
                        AXIS2_LOG_ERROR(env->log, AXIS2_LOG_SI,
                                        "Thread pool not set in environment."
                                        " Cannot invoke call non blocking");
                    }
#else
                    AXIS2_LOG_ERROR(env->log, AXIS2_LOG_SI,
                                    "Threading not enabled."
                                    " Cannot start separate listener");
                    return AXIS2_FAILURE;
#endif

                    tl_state = AXIS2_MALLOC(env->allocator,
                                            sizeof
                                            (axis2_transport_listener_state_t));

                    if (!tl_state)
                    {
                        AXIS2_ERROR_SET(env->error, AXIS2_ERROR_NO_MEMORY,
                                        AXIS2_FAILURE);
                        AXIS2_LOG_ERROR(env->log, AXIS2_LOG_SI, "No memory. Cannot create transport listener state.");
                    }
                    else
                    {
                        tl_state->listener = listener;
                        tl_state->waiting_calls = 0;
                        listener_manager->listener_map[transport] = tl_state;
                    }
                }
            }
        }
    }

    if (tl_state)
    {
        tl_state->waiting_calls++;
        return AXIS2_SUCCESS;
    }
    else
        return AXIS2_FAILURE;
}

AXIS2_EXTERN axis2_status_t AXIS2_CALL
axis2_listener_manager_stop(
    axis2_listener_manager_t * listener_manager,
    const axutil_env_t * env,
    const AXIS2_TRANSPORT_ENUMS transport)
{
    axis2_transport_listener_state_t *tl_state = NULL;
    axis2_status_t status = AXIS2_FAILURE;

    tl_state = listener_manager->listener_map[transport];

    if (tl_state)
    {
        tl_state->waiting_calls--;
        if (tl_state->waiting_calls == 0)
        {
            status = axis2_transport_receiver_stop(tl_state->listener, env);
            if (status == AXIS2_SUCCESS)
                listener_manager->listener_map[transport] = NULL;
        }
    }

    return status;
}

AXIS2_EXTERN axis2_endpoint_ref_t *AXIS2_CALL
axis2_listener_manager_get_reply_to_epr(
    const axis2_listener_manager_t * listener_manager,
    const axutil_env_t * env,
    const axis2_char_t * svc_name,
    const AXIS2_TRANSPORT_ENUMS transport)
{
    axis2_transport_listener_state_t *tl_state = NULL;

    tl_state = listener_manager->listener_map[transport];
    if (tl_state)
    {
        return axis2_transport_receiver_get_reply_to_epr(tl_state->listener,
                                                         env, svc_name);
    }
    return NULL;
}

AXIS2_EXTERN void AXIS2_CALL
axis2_listener_manager_free(
    axis2_listener_manager_t * listener_manager,
    const axutil_env_t * env)
{
    int i = 0;

    for (i = 0; i < AXIS2_TRANSPORT_ENUM_MAX; i++)
    {
        if (listener_manager->listener_map[i])
            AXIS2_FREE(env->allocator, listener_manager->listener_map[i]);
    }

    AXIS2_FREE(env->allocator, listener_manager);
}

AXIS2_EXTERN axis2_conf_ctx_t *AXIS2_CALL
axis2_listener_manager_get_conf_ctx(
    const axis2_listener_manager_t * listener_manager,
    const axutil_env_t * env)
{
    return listener_manager->conf_ctx;
}

void *AXIS2_THREAD_FUNC
axis2_listener_manager_worker_func(
    axutil_thread_t * thd,
    void *data)
{
    axis2_listener_manager_worker_func_args_t *args_list = NULL;
    const axutil_env_t *th_env = NULL;

    args_list = (axis2_listener_manager_worker_func_args_t *) data;
    if (!args_list)
        return NULL;

    th_env = axutil_init_thread_env(args_list->env);
    /* Start the protocol server. For examlle if protocol is http axis2_http_server_start function
     * of the axis2_http_receiver is called.
     */
    if (args_list->listener)
    {
        axis2_transport_receiver_start(args_list->listener, th_env);
    }
    return NULL;
}
