/* cache.c
 * Copyright (c) 2011, Peter Ohler
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *  - Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 *  - Neither the name of Peter Ohler nor the names of its contributors may be
 *    used to endorse or promote products derived from this software without
 *    specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdarg.h>

#include "cache.h"

struct _Cache {
    /* The key is a length byte followed by the key as a string. If the key is longer than 254 characters then the
       length is 255. The key can be for a premature value and in that case the length byte is greater than the length
       of the key. */
    char                *key;
    VALUE               value;
    struct _Cache       *slots[16];
};

static void     slot_print(Cache cache, unsigned int depth);

static char* form_key(const char *s) {
    size_t	len = strlen(s);
    char	*d = ALLOC_N(char, len + 2);

    *d = (255 <= len) ? 255 : len;
    memcpy(d + 1, s, len + 1);

    return d;
}

void
ox_cache_new(Cache *cache) {
    *cache = ALLOC(struct _Cache);
    (*cache)->key = 0;
    (*cache)->value = Qundef;
    memset((*cache)->slots, 0, sizeof((*cache)->slots));
}

VALUE
ox_cache_get(Cache cache, const char *key, VALUE **slot, char **keyp) {
    unsigned char       *k = (unsigned char*)key;
    Cache               *cp;

    for (; '\0' != *k; k++) {
        cp = cache->slots + (unsigned int)(*k >> 4); /* upper 4 bits */
        if (0 == *cp) {
            ox_cache_new(cp);
        }
        cache = *cp;
        cp = cache->slots + (unsigned int)(*k & 0x0F); /* lower 4 bits */
        if (0 == *cp) { /* nothing on this tree so set key and value as a premature key/value pair */
            ox_cache_new(cp);
            cache = *cp;
            cache->key = form_key(key);
            break;
	} else {
	    int	depth = (int)(k - (unsigned char*)key + 1);

	    cache = *cp;
	    
	    if ('\0' == *(k + 1)) { /* exact match */
		if (0 == cache->key) { /* nothing in this spot so take it */
		    cache->key = form_key(key);
		    break;
		} else if ((depth == *cache->key || 255 < depth) && 0 == strcmp(key, cache->key + 1)) { /* match */
		    break;
		} else { /* have to move the current premature key/value deeper */
		    unsigned char	*ck = (unsigned char*)(cache->key + depth + 1);
                    Cache		orig = *cp;
		    
                    cp = (*cp)->slots + (*ck >> 4);
                    ox_cache_new(cp);
                    cp = (*cp)->slots + (*ck & 0x0F);
                    ox_cache_new(cp);
		    (*cp)->key = cache->key;
		    (*cp)->value = cache->value;
		    orig->key = form_key(key);
		    orig->value = Qundef;
		}
	    } else { /* not exact match but on the path */
		if (0 != cache->key) { /* there is a key/value here already */
		    if (depth == *cache->key || (255 <= depth && 0 == strncmp(cache->key, key, depth) && '\0' == cache->key[depth])) { /* key belongs here */
			continue;
		    } else {
			unsigned char	*ck = (unsigned char*)(cache->key + depth + 1);
			Cache		orig = *cp;
		    
			cp = (*cp)->slots + (*ck >> 4);
			ox_cache_new(cp);
			cp = (*cp)->slots + (*ck & 0x0F);
			ox_cache_new(cp);
			(*cp)->key = cache->key;
			(*cp)->value = cache->value;
			orig->key = 0;
			orig->value = Qundef;
		    }
		}
	    }
        }
    }
    *slot = &cache->value;
    if (0 != keyp) {
	if (0 == cache->key) {
	    printf("*** Error: failed to set the key for %s\n", key);
	    *keyp = 0;
	} else {
	    *keyp = cache->key + 1;
	}
    }
    return cache->value;
}

void
ox_cache_print(Cache cache) {
    /*printf("-------------------------------------------\n");*/
    slot_print(cache, 0);
}

static void
slot_print(Cache c, unsigned int depth) {
    char                indent[256];
    Cache               *cp;
    unsigned int        i;

    if (sizeof(indent) - 1 < depth) {
        depth = ((int)sizeof(indent) - 1);
    }
    memset(indent, ' ', depth);
    indent[depth] = '\0';
    for (i = 0, cp = c->slots; i < 16; i++, cp++) {
        if (0 == *cp) {
            /*printf("%s%02u:\n", indent, i);*/
        } else {
            if (0 == (*cp)->key && Qundef == (*cp)->value) {
                printf("%s%02u:\n", indent, i);
            } else {
                const char      *vs;
                const char      *clas;

                if (Qundef == (*cp)->value) {
                    vs = "undefined";
                    clas = "";
                } else {
                    VALUE       rs = rb_funcall2((*cp)->value, rb_intern("to_s"), 0, 0);

                    vs = StringValuePtr(rs);
                    clas = rb_class2name(rb_obj_class((*cp)->value));
                }
                printf("%s%02u: %s = %s (%s)\n", indent, i, (*cp)->key, vs, clas);
            }
            slot_print(*cp, depth + 2);
        }
    }
}
