/* 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 "mod_perl.h"

void modperl_global_request_cfg_set(request_rec *r)
{
    MP_dDCFG;
    MP_dRCFG;

    /* only if PerlOptions +GlobalRequest and not done already */
    if (MpDirGLOBAL_REQUEST(dcfg) && !MpReqSET_GLOBAL_REQUEST(rcfg)) {
        modperl_global_request_set(r);
    }
}

void modperl_global_request_set(request_rec *r)
{
    MP_dRCFG;
    request_rec *old_r = NULL;

    /* reset old value, important for subrequests */
    (void)modperl_tls_get_request_rec(&old_r);
    modperl_tls_reset_cleanup_request_rec(r->pool, old_r);

    modperl_tls_set_request_rec(r);

    /* so 'PerlOptions +GlobalRequest' doesnt wipe us out */
    MpReqSET_GLOBAL_REQUEST_On(rcfg);
}

/* get/set */
request_rec *modperl_global_request(pTHX_ SV *svr)
{
    request_rec *cur = NULL;
    apr_status_t status = modperl_tls_get_request_rec(&cur);
    if (status != APR_SUCCESS) {
        /* an internal problem */
        Perl_croak(aTHX_ "failed to retrieve the request object");
    }

    if (GIMME_V != G_VOID && !cur) {
        /* wrong configuration */
        Perl_croak(aTHX_ "Global $r object is not available. Set:\n"
                   "\tPerlOptions +GlobalRequest\nin httpd.conf");
    }

    if (svr) {
        modperl_global_request_obj_set(aTHX_ svr);
    }

    return cur;
}

void modperl_global_request_obj_set(pTHX_ SV *svr)
{
    /* XXX: support sublassing */
    request_rec *r = modperl_sv2request_rec(aTHX_ svr);
    modperl_global_request_set(r);
}

#if MP_THREADED
static apr_status_t modperl_global_cleanup(void *data)
{
    modperl_global_t *global = (modperl_global_t *)data;

    MP_TRACE_g(MP_FUNC, "destroy lock for %s", global->name);
    MUTEX_DESTROY(&global->glock);

    return APR_SUCCESS;
}
#endif

void modperl_global_init(modperl_global_t *global, apr_pool_t *p,
                         void *data, const char *name)
{
    Zero(global, 1, modperl_global_t);

    global->data = data;
    global->name = name;

#if MP_THREADED
    MUTEX_INIT(&global->glock);

    apr_pool_cleanup_register(p, (void *)global,
                              modperl_global_cleanup,
                              apr_pool_cleanup_null);
#endif

    MP_TRACE_g(MP_FUNC, "init %s", name);
}

void modperl_global_lock(modperl_global_t *global)
{
#if MP_THREADED
    MP_TRACE_g(MP_FUNC, "locking %s", global->name);
    MUTEX_LOCK(&global->glock);
#endif
}

void modperl_global_unlock(modperl_global_t *global)
{
#if MP_THREADED
    MP_TRACE_g(MP_FUNC, "unlocking %s", global->name);
    MUTEX_UNLOCK(&global->glock);
#endif
}

void *modperl_global_get(modperl_global_t *global)
{
    return global->data;
}

void modperl_global_set(modperl_global_t *global, void *data)
{
    global->data = data;
}

/* hopefully there wont be many of these */

#define MP_GLOBAL_IMPL(gname, type)                      \
                                                         \
static modperl_global_t MP_global_##gname;               \
                                                         \
void modperl_global_init_##gname(apr_pool_t *p,          \
                                 type gname)             \
{                                                        \
    modperl_global_init(&MP_global_##gname, p,           \
                        (void *)gname, #gname);          \
}                                                        \
                                                         \
void modperl_global_lock_##gname(void)                   \
{                                                        \
    modperl_global_lock(&MP_global_##gname);             \
}                                                        \
                                                         \
void modperl_global_unlock_##gname(void)                 \
{                                                        \
    modperl_global_unlock(&MP_global_##gname);           \
}                                                        \
                                                         \
type modperl_global_get_##gname(void)                    \
{                                                        \
    return (type )                                       \
       modperl_global_get(&MP_global_##gname);           \
}                                                        \
                                                         \
void modperl_global_set_##gname(void *data)              \
{                                                        \
    modperl_global_set(&MP_global_##gname, data);        \
}                                                        \

MP_GLOBAL_IMPL(pconf, apr_pool_t *)
MP_GLOBAL_IMPL(server_rec, server_rec *)





/*** anon handlers code ***/

static modperl_global_t MP_global_anon_cnt;

void modperl_global_anon_cnt_init(apr_pool_t *p)
{
    int *data = (int *)apr_pcalloc(p, sizeof(int));
    *data = 0;
    modperl_global_init(&MP_global_anon_cnt, p, (void *)data, "anon_cnt");
}

int modperl_global_anon_cnt_next(void)
{
    int next;
    /* XXX: inline lock/unlock? */
    modperl_global_lock(&MP_global_anon_cnt);

    next = ++*(int *)(MP_global_anon_cnt.data);

    modperl_global_unlock(&MP_global_anon_cnt);

    return next;
}



/*** TLS ***/

#if MP_THREADED
static apr_status_t modperl_tls_cleanup(void *data)
{
    return apr_threadkey_private_delete((apr_threadkey_t *)data);
}
#endif

apr_status_t modperl_tls_create(apr_pool_t *p, modperl_tls_t **key)
{
#if MP_THREADED
    apr_status_t status = apr_threadkey_private_create(key, NULL, p);
    apr_pool_cleanup_register(p, (void *)*key,
                              modperl_tls_cleanup,
                              apr_pool_cleanup_null);
    return status;
#else
    *key = apr_pcalloc(p, sizeof(**key));
    return APR_SUCCESS;
#endif
}

apr_status_t modperl_tls_get(modperl_tls_t *key, void **data)
{
#if MP_THREADED
    if (!key) {
        *data = NULL;
        return APR_SUCCESS;
    }
    return apr_threadkey_private_get(data, key);
#else
    *data = modperl_global_get((modperl_global_t *)key);
    return APR_SUCCESS;
#endif
}

apr_status_t modperl_tls_set(modperl_tls_t *key, void *data)
{
#if MP_THREADED
    return apr_threadkey_private_set(data, key);
#else
    modperl_global_set((modperl_global_t *)key, data);
    return APR_SUCCESS;
#endif
}

typedef struct {
    modperl_tls_t *key;
    void *data;
} modperl_tls_cleanup_data_t;

static apr_status_t modperl_tls_reset(void *data)
{
    modperl_tls_cleanup_data_t *cdata =
        (modperl_tls_cleanup_data_t *)data;
    return modperl_tls_set(cdata->key, cdata->data);
}

void modperl_tls_reset_cleanup(apr_pool_t *p, modperl_tls_t *key,
                               void *data)
{
    modperl_tls_cleanup_data_t *cdata =
        (modperl_tls_cleanup_data_t *)apr_palloc(p, sizeof(*cdata));

    cdata->key = key;
    cdata->data = data;

    apr_pool_cleanup_register(p, (void *)cdata,
                              modperl_tls_reset,
                              apr_pool_cleanup_null);
}

/* hopefully there wont be many of these either */

#define MP_TLS_IMPL(gname, type)                         \
                                                         \
static modperl_tls_t *MP_tls_##gname;                    \
                                                         \
apr_status_t                                             \
modperl_tls_create_##gname(apr_pool_t *p)                \
{                                                        \
    return modperl_tls_create(p, &MP_tls_##gname);       \
}                                                        \
                                                         \
apr_status_t modperl_tls_get_##gname(type *data)         \
{                                                        \
    void *ptr;                                           \
    apr_status_t status =                                \
        modperl_tls_get(MP_tls_##gname, &ptr);           \
    *data = (type )ptr;                                  \
    return status;                                       \
}                                                        \
                                                         \
apr_status_t modperl_tls_set_##gname(void *data)         \
{                                                        \
    return modperl_tls_set(MP_tls_##gname, data);        \
}                                                        \
                                                         \
void modperl_tls_reset_cleanup_##gname(apr_pool_t *p,    \
                                       type data)        \
{                                                        \
    modperl_tls_reset_cleanup(p, MP_tls_##gname,         \
                              (void *)data);             \
}

MP_TLS_IMPL(request_rec, request_rec *)
