/* mod_rivet_cache.c -- The mod_rivet cache */
/*
    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 <apr_strings.h>
#include <mpm_common.h>

#include "mod_rivet.h"

/* Function prototypes are defined with EXTERN. Since we are in the same DLL,
 * no need to keep this extern... */
#ifdef EXTERN
#   undef EXTERN
#   define EXTERN DLLEXPORT
#endif /* EXTERN */
#include "mod_rivet_cache.h"

extern mod_rivet_globals* module_globals;


/*
 * -- RivetCache_DefaultSize
 *
 * Basic determination of a default size for the Rivet cache
 *
 */

int RivetCache_DefaultSize (void)
{
    if (ap_max_requests_per_child != 0) {
        return  (ap_max_requests_per_child / 5);
    } else {
        return 50;
    }
}

/*
 * -- RivetCache_Create
 *
 * Creates a per interpreter script cach
 *
 * Arguments:
 *     apr_pool_t *p - APR memory pool pointer,
 *     rivet_thread_interp* interp_obj - interpreter object
 *
 *
 * Results:
 *     None
 *
 * Side Effects:
 *
 */

void RivetCache_Create (apr_pool_t *p, rivet_thread_interp* interp_obj)
{
    interp_obj->objCacheList =
                apr_pcalloc(p,(signed)((interp_obj->cache_size)*sizeof(char *)));
    interp_obj->objCache    =
                apr_pcalloc(p,sizeof(Tcl_HashTable));

    Tcl_InitHashTable(interp_obj->objCache,TCL_STRING_KEYS);
}

/*
 * -- RivetCache_Cleanup
 *
 * Cache clean-up. This function is called when a user configuration
 * is changed thus invalidating the whole cache. A better solution is
 * still to be found though
 *
 * Arguments:
 *     rivet_thread_interp* interp_obj - interpreter object
 *
 * Results:
 *     None
 *
 * Side Effects:
 *
 *      the cache associated to the thread interpreter is emptied
 */

void RivetCache_Cleanup (rivet_thread_private* private,rivet_thread_interp* rivet_interp)
{
    int ct;
    Tcl_HashEntry *delEntry;

    /* Clean out the list. */
    ct = rivet_interp->cache_free;
    while (ct < rivet_interp->cache_size) {
        /* Free the corresponding hash entry. */
        delEntry = Tcl_FindHashEntry(rivet_interp->objCache,
                                     rivet_interp->objCacheList[ct]);

        if (delEntry != NULL) {
            Tcl_DecrRefCount((Tcl_Obj *)Tcl_GetHashValue(delEntry));
            Tcl_DeleteHashEntry(delEntry);
            rivet_interp->objCacheList[ct] = NULL;
        }

        ct++;
    }
    apr_pool_destroy(rivet_interp->pool);

    /* let's recreate the cache list */

    if (apr_pool_create(&rivet_interp->pool, private->pool) != APR_SUCCESS)
    {
        ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL, module_globals->server,
                     MODNAME ": could not recreate cache private pool. Cache disabled");
        rivet_interp->cache_free = rivet_interp->cache_size = 0;
    }
    else
    {
        rivet_interp->objCacheList = apr_pcalloc (rivet_interp->pool,
                                                (signed)(rivet_interp->cache_size*sizeof(char *)));
        rivet_interp->cache_free = rivet_interp->cache_size;
    }

}

/*
 * -- Rivet_MakeCacheKey
 *
 * Arguments:
 *      apr_pool_t*         pool
 *      char*               filename
 *      time_t              ctime      - file creation time
 *      time_t              mtime      - file last modification
 *      unsigned int        user_conf  - user configuration flag
 *      int                 toplevel   - toplevel template
 */

char* RivetCache_MakeKey (apr_pool_t*   pool,
                          char*         filename,
                          time_t        ctime,
                          time_t        mtime,
                          unsigned int  user_conf,
                          int           toplevel)
{
    return (char*) apr_psprintf (pool, "%s%lx%lx%d-%d", filename,
                                 mtime, ctime, toplevel, user_conf);
}

/*
 * -- RivetCache_EntryLookup
 *
 * Cache entry lookup. A hash table lookup key is created and an entry
 * searched in the cache. If an entry is not found the function returns NULL
 *
 * Arguments:
 *      rivet_thread_interp* rivet_interp - interpreter object
 *      char*                hashKey      - key to the cache entry
 *
 * Results:
 *      Tcl_HashEntry*       entry object - NULL if the entry for hashKey is not
 *                                          existing
 *
 * Side Effects:
 *
 */

Tcl_HashEntry* RivetCache_EntryLookup (rivet_thread_interp* rivet_interp,char* hashKey)
{
    return Tcl_FindHashEntry(rivet_interp->objCache, hashKey);
}

/*
 * -- RivetCache_CreateEntry
 *
 * Cache entry lookup. A hash table lookup key is created and an entry
 * searched in the cache. If an entry is not found the function returns NULL
 *
 * Arguments:
 *      rivet_thread_interp* rivet_interp - interpreter object
 *      char*                hashKey      - key to the cache entry
 *      int*                 isNew        - pointer to an integer. If the
 *                                          entry create didn't exists isNew
 *                                          is set to 1
 *
 * Results:
 *      Tcl_HashEntry*       entry object
 *
 * Side Effects:
 *
 */

Tcl_HashEntry* RivetCache_CreateEntry (rivet_thread_interp* rivet_interp,char* hashKey,int* isNew)
{
    return Tcl_CreateHashEntry(rivet_interp->objCache, hashKey, isNew);
}

/*
 * -- RivetCache_FetchScript
 *
 * Cache entry lookiup. A hash table lookup key is created and an entry
 * searched in the cache. If an entry is not found the function returns NULL
 *
 * Arguments:
 *      Tcl_HashEntry*      entry
 *
 * Results:
 *      Tcl_Obj*            entry_object
 *
 * Side Effects:
 *
 */
Tcl_Obj* RivetCache_FetchScript (Tcl_HashEntry* entry)
{
    return (Tcl_Obj *)Tcl_GetHashValue(entry);
}

/* -- RivetCache_StoreScript
 *
 */

int RivetCache_StoreScript(rivet_thread_interp* rivet_interp, Tcl_HashEntry* entry, Tcl_Obj* script)
{
    if (rivet_interp->cache_size) {

        if (rivet_interp->cache_free) {
            char* hashKey = (char *) Tcl_GetHashKey (rivet_interp->objCache,entry);

            /* Tcl_SetHashValue is a macro that simply stuffs the value pointer in an array
             * We need to incr the reference count of outbuf because we want it to outlive
             * this function and be kept as long as the cache is preserved
             */

            Tcl_IncrRefCount (script);
            Tcl_SetHashValue (entry,(ClientData)script);

            rivet_interp->objCacheList[--rivet_interp->cache_free] =
                (char*) apr_pcalloc (rivet_interp->pool,(strlen(hashKey)+1)*sizeof(char));
            strcpy(rivet_interp->objCacheList[rivet_interp->cache_free], hashKey);

            return 0;

        } else {
            /* cache full */

            return 1;
        }

    }
    return 0;
}

/*
 * -- RivetCache_DeleteEntry
 *
 * Cache entry delete. Removes an existing entry from a table. The memory
 * associated with the entry itself will be freed, but the client is
 * responsible for any cleanup associated with the entry's value, such as
 * freeing a structure that it points to.
 *
 * Arguments:
 *      Tcl_HashEntry*       entry object
 *
 * Results:
 *
 * Side Effects:
 *
 */

void RivetCache_DeleteEntry (Tcl_HashEntry *entry)
{
    Tcl_DeleteHashEntry (entry);
}
