/*
 * tpllib
 *
 * C library of functions for text template processing.
 * Copyright (C) 2003-2007 Niels Wojciech Tadeusz Andersen <haj@zhat.dk>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef WIN32
#include <io.h>                 /* write() */
#else
#include <unistd.h>             /* write() */
#endif

#include <stdlib.h> 		/* exit() */
#include <stdio.h> 		/* fopen(), sprintf() ... */
#include <stdarg.h>		/* va_start(), va_end() */
#include <string.h> 		/* memset(), memcmp() ... */

/* Define ZTS when building a PHP extension */
/* Define WIN32 when compiling under MS Windows */

#ifdef WIN32
# define snprintf _snprintf
# define vsnprintf _vsnprintf
#endif

#include "lib_tpl.h"

/* Length defines for markup elements */
#define DELIM_LEN_LEFT (sizeof(DELIMITER_LEFT) - 1)
#define DELIM_LEN_RIGHT (sizeof(DELIMITER_RIGHT) - 1)
#define SEC_HEAD_LEN (sizeof(SECTIONTAG_HEAD) - 1)
#define SEC_TAIL_LEN (sizeof(SECTIONTAG_TAIL) - 1)


#define M_MIN(a, b) (((a) < (b)) ? (a) : (b))
#define M_MAX(a, b) (((a) > (b)) ? (a) : (b))

#define M_NODE_LEN(n) (((n)->val) ? (n)->len : (n)->fval->len)
#define M_NODE_TEXT(n) (((n)->val) ? (n)->val : (n)->fval->val)

#define M_INT_TO_STRING(buffer, buffer_len, val, signed_v) \
    do \
    { \
        char rev[5 * sizeof(int) / 2]; \
        int i = 0, digits = 0; \
        if ((signed_v) && (val) < 0) \
        { \
            buffer[i] = '-'; \
            i++; \
        } \
        do \
        { \
            rev[digits++] = '0' + abs((val) % 10); \
            (val) /= 10; \
        } \
        while ((val) != 0); \
        for (; digits > 0 && i < (buffer_len); i++) \
            buffer[i] = rev[--digits]; \
        buffer[i] = 0; \
        (buffer_len) = i; \
    } while (0)


/* Binary safe string search */
static const char *tpl_strstr(register const char *haystack,
                              ssize_t haystack_len,
                              const char *needle,
                              int needle_len)
{
    ssize_t i;
    const char *p_toofar = haystack + haystack_len - needle_len + 1;

    if (p_toofar <= haystack)
        return NULL;

    do
    {
        if (*haystack == *needle)
        {
            for (i = 1; i != needle_len && haystack[i] == needle[i]; i++);

            if (i == needle_len)
                return haystack;
        }
        
        haystack++;
    }
    while (haystack != p_toofar);

    return NULL;
}


static tpl_node_t* create_node(int len)
{
    tpl_node_t* node = (tpl_node_t*)acl_mymalloc(len+1+sizeof(tpl_node_t));
    node->len = len;
    node->val = (char*)(node + 1);
    node->next = NULL;
    return node;
}


static void destroy_node(tpl_node_t* node)
{
    acl_myfree(node);
}


static tpl_fcell_t* create_fcell(const char *key, int key_len, int data_len)
{
    tpl_fcell_t* field = (tpl_fcell_t*)acl_mymalloc(key_len + 1
                                                + sizeof(tpl_fcell_t));
    
    field->key = (char *)(field + 1);

    (void)memcpy(field->key, key, key_len);
    field->key[key_len] = 0;
    field->val = (char *)acl_mymalloc(M_MAX(data_len, INITIAL_FIELD_LEN) + 1);
    field->len = 0;
    field->val[0] = 0;
    field->next = NULL;
    return field;
}


static void destroy_fcell(tpl_fcell_t* field)
{
    acl_myfree(field->val);
    acl_myfree(field);
}


static tpl_tcell_t* create_tcell(const char *key, int key_len)
{
    tpl_tcell_t* section = (tpl_tcell_t*)acl_mymalloc(key_len + 1
                                                  + sizeof(tpl_tcell_t)
                                                  + sizeof(tpl_t));
    section->tpl = (tpl_t *)(section + 1);
    tpl_init(section->tpl);

    section->key = (char *)(section->tpl + 1);
    (void)memcpy(section->key, key, key_len);

    section->key[key_len] = 0;
    section->next = NULL;
    section->_next = NULL;
    return section;
}


static void destroy_tcell(tpl_tcell_t* section)
{
    tpl_release(section->tpl);
    acl_myfree(section);
}


/* Hash function using multipy by 31 */
static unsigned int tpl_hash(const char *key, int key_len)
{
    const char *p_key_end = key + key_len;
    register unsigned int h = *key++;

    while (key != p_key_end && *key != 0)
    {
        unsigned int g;
        h = (h << 5) + *key++;
        if ((g = h & 0xf0000000) != 0)
            h = (h ^ (g >> 24) ^ g);
    }
    return h % HASH_TABLE_SIZE;
}
 
static tpl_fcell_t* tpl_produce_field(tpl_t *tpl,
                                      const char* key,
                                      int key_len,
                                      int data_len,
                                      int may_create)
{
    tpl_fcell_t **pfield = &tpl->fields[tpl_hash(key, key_len)];

    while (*pfield != NULL)
    {
        if (memcmp((*pfield)->key, key, key_len) == 0)
        {
            if (data_len > INITIAL_FIELD_LEN && data_len > (*pfield)->len)
                (*pfield)->val = (char*) acl_myrealloc((*pfield)->val, data_len);

            return *pfield;
        }

        pfield = &(*pfield)->next;
    }

    if (*pfield == NULL && may_create != 0)
    {
        *pfield = create_fcell(key, key_len, data_len);
        return *pfield;
    }

    return NULL;
}

#define tpl_get_field(tpl, key) \
    tpl_produce_field((tpl), (key), strlen((key)), 0, 0)

#define tpl_new_field(tpl, key, key_len) \
    tpl_produce_field((tpl), (key), (key_len), 0, 1)

#define tpl_cpy_field(field, tpl, key, key_len, data, data_len) \
    do \
    { \
        (field) = tpl_produce_field((tpl), (key), (key_len), (data_len), 1); \
        if ((field)->len == 0 && (data_len) != 0) \
        { \
            (void)memcpy((field)->val, (data), (data_len));\
            (field)->val[(data_len)] = 0; \
            (field)->len = (data_len); \
        } \
    } while (0)


static tpl_tcell_t* tpl_produce_section(tpl_t *tpl,
                                        const char* key,
                                        int key_len,
                                        int must_create)
{
    tpl_tcell_t **psection = &tpl->sections[tpl_hash(key, key_len)];

    while (*psection != NULL)
    {
        if (memcmp((*psection)->key, key, key_len) == 0)
            return (must_create) ? NULL : *psection;

        psection = &(*psection)->_next;
    }
    if (*psection == NULL && must_create)
    {
        *psection = create_tcell(key, key_len);
        (*psection)->tpl->parent = tpl;
        return *psection;
    }

    return NULL;
}

#define tpl_get_section(tpl, key) \
    tpl_produce_section(tpl, key, strlen(key), 0)

#define tpl_make_section(tpl, key, key_len) \
    tpl_produce_section(tpl, key, key_len, 1)


/* Big, ugly, horrible, quite possibly buggy...
   live with it or correct it and let me know */
static int tpl_construct(tpl_t *tpl, const char *p_last, const char *p_end)
{
    const char *p_begin = p_last, *p_curr, *p_next;
    tpl_node_t **tail = &tpl->head;
    tpl_tcell_t **last_section = &tpl->first;

    /* While a field delimiter can be found in what is left */
    while ((p_curr = tpl_strstr(p_last, p_end - p_last, 
                                DELIMITER_LEFT, DELIM_LEN_LEFT)) != NULL)
    {
        /* Advance to beginning of field/section name */
        p_curr += DELIM_LEN_LEFT;

        /* Find end delimiter of identifier or fail with syntax error */
        p_next = tpl_strstr(p_curr, p_end - p_curr, DELIMITER_RIGHT, DELIM_LEN_RIGHT);

        if (p_next == NULL)
        {
            *last_section = NULL;
            *tail = NULL;
            return TPL_SYNTAX_ERROR;
        }

        /* Section */
        if (p_curr + 1 >= p_begin + SEC_HEAD_LEN
             && p_end - p_next >= (int) SEC_TAIL_LEN
             && memcmp(SECTIONTAG_HEAD, p_curr - SEC_HEAD_LEN, 
                        SEC_HEAD_LEN) == 0
             && memcmp(SECTIONTAG_TAIL, p_next, SEC_TAIL_LEN) == 0)
        {
            tpl_tcell_t *section;

            if ((p_curr - SEC_HEAD_LEN - p_last) != 0)
            {
                int val_len = p_curr - SEC_HEAD_LEN - p_last;
                *tail = create_node(val_len);
                (*tail)->val[val_len] = 0;
                (void)memcpy((*tail)->val, p_last, val_len);
                tail = &(*tail)->next;
            }
        
            /* Create and chain in entry for section */
            section = tpl_make_section(tpl, p_curr, p_next - p_curr);

            if (section != NULL)
            {
                int code;
                const char *beginning, *ending;

                *last_section = section;
                last_section = &section->next;
                section->preceding = tail;

                /* Advance past the section tag */
                p_last = p_next + SEC_TAIL_LEN;
                beginning = p_next + SEC_TAIL_LEN;

                /* Find next occurrence of this section tag */
                ending = tpl_strstr(beginning, 
                                    p_end - beginning,
                                    p_curr - SEC_HEAD_LEN,
                                    beginning - p_curr + SEC_HEAD_LEN);

                p_last = ending + (SEC_HEAD_LEN + p_last - p_curr);
        
                /* If found and found before p_end */
                if (ending != NULL && p_last <= p_end)
                {
                    /* Construct recursively */
                    code = tpl_construct(section->tpl, beginning, ending);
                }
                else
                    code = TPL_SYNTAX_ERROR;
            
                if (code != TPL_OK)
                {
                    tpl_release(section->tpl);
                    tpl_init(section->tpl);
                    *last_section = NULL;
                    *tail = NULL;
                    return TPL_SYNTAX_ERROR;
                }
            }
            else
            {
                *last_section = NULL;
                *tail = NULL;
                return TPL_SYNTAX_ERROR;
            }
        }
        /* Field */
        else
        {
            if ((p_curr - DELIM_LEN_LEFT - p_last) != 0)
            {
                int val_len = p_curr - DELIM_LEN_LEFT - p_last;
                *tail = create_node(val_len);
                (*tail)->val[val_len] = 0;
                (void)memcpy((*tail)->val, p_last, val_len);
                tail = &(*tail)->next;
            }

            p_last = p_next + DELIM_LEN_RIGHT;

            /* Create node and set fval to new field cell */
            *tail = create_node(0);
            (*tail)->val = NULL;
            (*tail)->fval = tpl_new_field(tpl, p_curr, p_next - p_curr);
            tail = &(*tail)->next;
        }
    }

    /* Store rest of the text */
    if (p_last < p_end)
    {
        int val_len = p_end - p_last;
        *tail = create_node(val_len);
        (void)memcpy((*tail)->val, p_last, val_len);
        (*tail)->val[val_len] = 0;
        tail = &(*tail)->next;
    }
    *tail = NULL;
    *last_section = NULL;

    return TPL_OK;
}

void tpl_init(tpl_t *tpl)
{
    /* Set everything to NULL */
    (void)memset(tpl, 0, sizeof(tpl_t));
    
    tpl->tpl = tpl;
    tpl->added_tail = &tpl->added_head;
}

void tpl_release(tpl_t *tpl)
{
    register int i;

    /* Free nodes */
    while (tpl->head != NULL)
    {
        tpl_node_t *deleted = tpl->head;
        tpl->head = tpl->head->next;
        destroy_node(deleted);    
    }

    /* Free any added content */
    while (tpl->added_head != NULL)
    {
        tpl_node_t *deleted = tpl->added_head;
        tpl->added_head = tpl->added_head->next;
        destroy_node(deleted);
    }
    /* Free field cells */
    for (i = 0; i < HASH_TABLE_SIZE; i++)
    {
        tpl_fcell_t *fc = tpl->fields[i];

        while (fc != NULL)
        {
            tpl_fcell_t *deleted = fc;
            fc = fc->next;
            destroy_fcell(deleted);
        }
    }
    /* Free sections including added content */
    while (tpl->first != NULL)
    {
        tpl_tcell_t *deleted = tpl->first;
        tpl->first = tpl->first->next;

        /* This will call tpl_release() recursively */
        destroy_tcell(deleted);
    }
}

tpl_t* tpl_alloc(void)
{
    tpl_t *tpl = (tpl_t*)acl_mymalloc(sizeof(tpl_t));
    tpl_init(tpl);
    return tpl;
}

void tpl_free(tpl_t *tpl)
{
    tpl_release(tpl);
    acl_myfree(tpl);
}

/* Load template file */
int tpl_load(tpl_t *tpl, const char *filename)
{
    char *buffer;
    int len;
    FILE *fp = fopen(filename, "rb");
    
    if (fp == NULL)
        return TPL_OPEN_ERROR;

    /* Find length of file content */
    (void)fseek(fp, 0, SEEK_END);
    len = ftell(fp);
    (void) rewind(fp);
    
    /* Allocate buffer for data + 0 byte */
    buffer = (char*)acl_mymalloc(len+1);
    
    if (fread(buffer, 1, len, fp) < (unsigned)len)
    {
        (void)fclose(fp);
        acl_myfree(buffer);
        return TPL_READ_ERROR;
    }
    (void)fclose(fp);
    buffer[len] = 0;
    
    /* Use data in buffer to construct template */
    len = tpl_construct(tpl, buffer, buffer + len);
    if (len != TPL_OK)
    {
        tpl_release(tpl);
        tpl_init(tpl);
    }
    acl_myfree(buffer);
    return len;
}

/* Load from a string */
int tpl_from_string(tpl_t *tpl, const char *buffer, int len)
{
    return tpl_construct(tpl, buffer, buffer + len);
}

/* Recursively clear added content */
void tpl_reset(tpl_t* tpl)
{
    int i = HASH_TABLE_SIZE;
    tpl_tcell_t* section;
    
    /* Clear fields */
    while (i != 0)
    {
        tpl_fcell_t* field = tpl->fields[--i];
        while (field != NULL)
        {
            field->len = 0;
            *field->val = 0;
            field = field->next;
        }
    }
    /* Clear sections */
    for (section = tpl->first; section != NULL; section = section->next)
    {
        /* Clear added content */
        while (section->tpl->added_head != NULL)
        {
            tpl_node_t* n = section->tpl->added_head;
            section->tpl->added_head = section->tpl->added_head->next;
            destroy_node(n);
        }
        section->tpl->added_tail = &section->tpl->added_head;
    
        /* Clear this section */
        tpl_reset(section->tpl);
    }
}


/* Set up the selected section pointer to correspond to 
   that of the copied template */
static void tpl_adjust_selection(tpl_t* tpl,
                                 const tpl_t* srctpl,
                                 tpl_tcell_t* sec,
                                 const tpl_tcell_t* srcsec)
{
    const tpl_tcell_t *sec1 = srcsec;
    tpl_tcell_t *sec2 = sec;

    if (srctpl->tpl != srctpl)
    {
        for (; tpl->tpl == tpl && sec1 != NULL;
               sec1 = sec1->next, sec2 = sec2->next)
        {
            if (srctpl->tpl == sec1->tpl)
                tpl->tpl = sec2->tpl;
            else
            {
                tpl_adjust_selection(tpl,
                                     srctpl, 
                                     sec2->tpl->first,
                                     sec1->tpl->first);
            }
        }
    }
}


/* Recursive copy */
void tpl_copy(tpl_t* tpl, const tpl_t* srctpl)
{
    tpl_node_t **tail = &tpl->head;
    tpl_node_t *curr_node = srctpl->head;
    tpl_tcell_t **last_section = &tpl->first;
    tpl_tcell_t *curr_section = srctpl->first;

    for (; curr_section != NULL; curr_section = curr_section->next)
    {
        /* Copy text and fields until the current section's position */
        while (curr_node != *curr_section->preceding)
        {
            /* Text */
            if (curr_node->val != NULL)
            {
                *tail = create_node(curr_node->len);
                (void)memcpy((*tail)->val, curr_node->val, curr_node->len);
                (*tail)->val[curr_node->len] = 0;
            }
            /* Field */
            else
            {
                /* Create node and set fval to field cell */
                *tail = create_node(0);
                (*tail)->val = NULL;

                tpl_cpy_field((*tail)->fval, 
                              tpl,
                              curr_node->fval->key,
                              strlen(curr_node->fval->key),
                              curr_node->fval->val,
                              curr_node->fval->len);

            }

            tail = &(*tail)->next;
            curr_node = curr_node->next;
        }

        /* Create emtpy section entry for current section */
        *last_section = tpl_make_section(tpl,
                                         curr_section->key,
                                         strlen(curr_section->key));

        (*last_section)->preceding = tail;


        /* Consolidate added content into a single node in newly created
           section entry so that only one memory allocation is needed */
        if (curr_section->tpl->added_head != NULL)
        {
            tpl_node_t *some_node = curr_section->tpl->added_head;
            char *buffer;
            int len = 0;

            /* Get length of added content */
            for (; some_node!=NULL; some_node=some_node->next)
                len += some_node->len;

            /* Create node for added content */
            (*last_section)->tpl->added_head = create_node(len);
            (*last_section)->tpl->added_head->next = NULL;
            (*last_section)->tpl->added_tail = 
                &(*last_section)->tpl->added_head->next;

            /* Copy added content into new node */

            buffer = (*last_section)->tpl->added_head->val;

            for (some_node = curr_section->tpl->added_head;
                 some_node != NULL;
                 some_node = some_node->next)
            {
                (void)memcpy(buffer, some_node->val, some_node->len);
                buffer += some_node->len;
            }
            *buffer = 0;
        }


        /* Recursively copy section */
        tpl_copy((*last_section)->tpl, curr_section->tpl);

        last_section = &(*last_section)->next;
    }

    /* Copy the rest */
    while (curr_node != NULL)
    {
        /* Text */
        if (curr_node->val != NULL)
        {
            *tail = create_node(curr_node->len);
            (void)memcpy((*tail)->val, curr_node->val, curr_node->len + 1);
        }
        /* Field */
        else
        {
            *tail = create_node(0);
            (*tail)->val = NULL;

            tpl_cpy_field((*tail)->fval, 
                          tpl,
                          curr_node->fval->key,
                          strlen(curr_node->fval->key),
                          curr_node->fval->val,
                          curr_node->fval->len);
        }

        tail = &(*tail)->next;
        curr_node = curr_node->next;
    }

    *tail = NULL;
    *last_section = NULL;
    tpl->tpl = tpl;

    tpl_adjust_selection(tpl, srctpl, tpl->first, srctpl->first);
}


void tpl_set_field(tpl_t* tpl, const char* key, const char* val, int len)
{
    tpl_fcell_t *field = tpl_get_field(tpl->tpl, key);
    if (field != NULL)
    {
        if (len > INITIAL_FIELD_LEN && len > field->len)
            field->val = (char*) acl_myrealloc(field->val, len + 1);

        field->len = len;
        (void)memcpy(field->val, val, len);
        field->val[len] = 0;
    }
}

void tpl_set_field_fmt(tpl_t* tpl, const char* key, const char* fmt, ...)
{
    tpl_fcell_t *field = tpl_get_field(tpl->tpl, key);

    if (field != NULL)
    {
        int n;

        if (field->len < INITIAL_FIELD_LEN)
            field->len = INITIAL_FIELD_LEN;

        while (1)
        {
            va_list ap;

            va_start(ap, fmt);
            n = vsnprintf(field->val, field->len + 1, fmt, ap);
            va_end(ap);

            if (n > -1 && n <= field->len)
                break;

            field->len *= 2;
            field->val = (char*) acl_myrealloc(field->val, field->len + 1);
        }
        field->len = n;
    }
}

void tpl_set_field_int(tpl_t* tpl, const char* key, int val)
{
    tpl_fcell_t *field = tpl_get_field(tpl->tpl, key);

    if (field != NULL)
    {
        if (field->len < INITIAL_FIELD_LEN)
            field->len = INITIAL_FIELD_LEN;

        M_INT_TO_STRING(field->val, field->len, val, 1);
    } 
}

void tpl_set_field_uint(tpl_t* tpl, const char* key, unsigned int val)
{
    tpl_fcell_t *field = tpl_get_field(tpl->tpl, key);

    if (field != NULL)
    {
	int  v = (int) val;

        if (field->len < INITIAL_FIELD_LEN)
            field->len = INITIAL_FIELD_LEN;

        M_INT_TO_STRING(field->val, field->len, v, 0);
    } 
}

void tpl_set_field_double(tpl_t* tpl, const char* key, double val)
{
    tpl_fcell_t *field = tpl_get_field(tpl->tpl, key);

    if (field != NULL)
    {
        if (field->len < INITIAL_FIELD_LEN)
            field->len = INITIAL_FIELD_LEN;

        field->len = snprintf(field->val, field->len, "%.2f", val);
    }
}

int tpl_set_field_from_file(tpl_t* tpl, const char* key, const char* filename)
{
    tpl_fcell_t *field = tpl_get_field(tpl->tpl, key);

    if (field != NULL)
    {
        int len;
        FILE *fp = fopen(filename, "rb");

        if (fp == NULL)
            return TPL_OPEN_ERROR;

        /* Find length of file content */
        (void)fseek(fp, 0, SEEK_END);
        len = ftell(fp);
        (void)rewind(fp);

        if (len > INITIAL_FIELD_LEN && len > field->len)
            field->val = (char*) acl_myrealloc(field->val, len + 1);

        if (fread(field->val, 1, len, fp) < (unsigned)len)
        {
            (void)fclose(fp);
            return TPL_READ_ERROR;
        }
        (void)fclose(fp); 
    
        field->len = len;
        field->val[len] = 0;
    }
    return TPL_OK;
}

void tpl_set_field_global(tpl_t* tpl, const char* key, const char* val, int len)
{
    tpl_tcell_t *section = tpl->first;
    tpl_set_field(tpl, key, val, len);
    while (section != NULL)
    {
        /* Recursively set in all sections */
        tpl_set_field_global(section->tpl, key, val, len);
        section = section->next;
    }
}

void tpl_set_field_fmt_global(tpl_t* tpl, const char* key, const char* fmt, ...)
{
    int n;
    int len = 256;
    char *buf = (char*)acl_mymalloc(len);

    while (1)
    {
        va_list ap;

        va_start(ap, fmt);
        n = vsnprintf(buf, len, fmt, ap);
        va_end(ap);

        if (n > -1 && n < len)
            break;

        len *= 2;
        buf = (char*) acl_myrealloc(buf, len);
    }
    tpl_set_field_global(tpl, key, buf, n);
    acl_myfree(buf);
}

void tpl_set_field_int_global(tpl_t* tpl, const char* key, int val)
{
    tpl_tcell_t *section = tpl->first;
    tpl_set_field_int(tpl, key, val);
    while (section != NULL)
    {
        /* Recursively set in all sections */
        tpl_set_field_int_global(section->tpl, key, val);
        section = section->next;
    }
}

void tpl_set_field_uint_global(tpl_t* tpl, const char* key, unsigned int val)
{
    tpl_tcell_t *section = tpl->first;
    tpl_set_field_uint(tpl, key, val);
    while (section != NULL)
    {
        /* Recursively set in all sections */
        tpl_set_field_uint_global(section->tpl, key, val);
        section = section->next;
    }
}

void tpl_set_field_double_global(tpl_t* tpl, const char* key, double val)
{
    tpl_tcell_t *section = tpl->first;
    tpl_set_field_double(tpl, key, val);
    while (section != NULL)
    {
        /* Recursively set in all sections */
        tpl_set_field_double_global(section->tpl, key, val);
        section = section->next;
    }
}

int tpl_select_section(tpl_t *tpl, const char* key)
{
    tpl_tcell_t *section = tpl_get_section(tpl->tpl, key);
    if (section != NULL)
    {
        tpl->tpl->tpl = section->tpl;
        tpl->tpl = tpl->tpl->tpl;
        return TPL_OK;
    }
    return TPL_SECTION_NOT_FOUND_ERROR;
}

int tpl_deselect_section(tpl_t *tpl)
{
    if (tpl->tpl != tpl)
    {
        tpl->tpl = tpl->tpl->parent;
        tpl->tpl->tpl = tpl->tpl;
        return TPL_OK;
    }
    return TPL_NO_SECTION_SELECTED_ERROR;
}


/* Set or replace added content in section with supplied value */
static int tpl_set_section_ex(tpl_t* tpl,
                              const char* key,
                              const char* val,
                              int len,
                              tpl_node_t *existing_node)
{
    tpl_tcell_t *section = tpl_get_section(tpl->tpl, key);

    if (section != NULL)
    {
        if (section->tpl->first != NULL
            || section->tpl->head == NULL
            || section->tpl->head->next != NULL
            || section->tpl->head->len != len)
        {
            /* Destroy the contents of this section */
            tpl_release(section->tpl);
            tpl_init(section->tpl);

            section->tpl->parent = tpl->tpl;
            section->tpl->head = create_node(len);
        }

        (void)memcpy(section->tpl->head->val, val, len);
        section->tpl->head->val[len] = 0;

        if (existing_node != NULL)
        {
             existing_node->next = section->tpl->added_head;
             section->tpl->added_head = existing_node;
        }
        else
        {
            if (section->tpl->added_head != NULL
                && section->tpl->added_head->len != len)
            {
                tpl_node_t *node = create_node(len);
                node->next = section->tpl->added_head;
                section->tpl->added_head = node;
            }
            else if (section->tpl->added_head == NULL)
                section->tpl->added_head = create_node(len);

            (void)memcpy(section->tpl->added_head->val, val, len);
            section->tpl->added_head->val[len] = 0;
        }

        while (section->tpl->added_head->next != NULL)
        {
            tpl_node_t *deleted = section->tpl->added_head->next;
            section->tpl->added_head->next = deleted->next;
            destroy_node(deleted);
        }

        section->tpl->added_tail = &section->tpl->added_head->next;

        return TPL_OK;
    }

    return TPL_SECTION_NOT_FOUND_ERROR;
}

int tpl_set_section(tpl_t* tpl, const char* key, const char* val, int len)
{
    return tpl_set_section_ex(tpl, key, val, len, NULL);
}

int tpl_set_section_from_file(tpl_t* tpl, const char* key, const char* filename)
{
    int len, status;
    tpl_node_t *node;
    FILE *fp = fopen(filename, "rb");

    if (fp == NULL)
        return TPL_OPEN_ERROR;

    /* Find length of file content */
    (void)fseek(fp, 0, SEEK_END);
    len = ftell(fp);
    (void)rewind(fp);

    node = create_node(len);

    if (fread(node->val, 1, len, fp) < (unsigned)len)
        status = TPL_READ_ERROR;
    else
        status = tpl_set_section_ex(tpl, key, node->val, len, node);

    (void)fclose(fp);

    if (status != TPL_OK)
        destroy_node(node);

    return status;
}


int tpl_append_section(tpl_t* tpl)
{
    if (tpl->tpl != tpl)
    {
        tpl_tcell_t *curr_sect = tpl->tpl->first;
        tpl_node_t *curr_node = tpl->tpl->head;
        tpl_node_t *some_node;
        int len;
        char *buffer;

        for (; curr_sect != NULL; curr_sect = curr_sect->next)
        {
            if (curr_sect->tpl->added_head != NULL)
            {

                for (len = 0, some_node = curr_node; 
                     some_node != *curr_sect->preceding;
                     some_node = some_node->next)
                {
                    len += M_NODE_LEN(some_node);
                }

                *tpl->tpl->added_tail = create_node(len);
                buffer = (*tpl->tpl->added_tail)->val;
                tpl->tpl->added_tail = &(*tpl->tpl->added_tail)->next;

                for (; 
                     curr_node != *curr_sect->preceding;
                     curr_node = curr_node->next)
                {
                    if (curr_node->val)
                    {
                        (void)memcpy(buffer, curr_node->val, curr_node->len);
                        buffer += curr_node->len;
                    }
                    else
                    {
                        (void)memcpy(buffer, 
                                     curr_node->fval->val,
                                     curr_node->fval->len);
                        buffer += curr_node->fval->len;
                    }
                }

                *buffer = 0;

                /* Cut 'n paste added content chain from section */
                *tpl->tpl->added_tail = curr_sect->tpl->added_head;
                tpl->tpl->added_tail = curr_sect->tpl->added_tail;
                curr_sect->tpl->added_tail = &curr_sect->tpl->added_head;
                curr_sect->tpl->added_head = NULL;
            }
        }

        for (len = 0, some_node = curr_node;
             some_node != NULL;
             some_node = some_node->next)
        {
            len += M_NODE_LEN(some_node);
        }


        *tpl->tpl->added_tail = create_node(len);
        buffer = (*tpl->tpl->added_tail)->val;
        tpl->tpl->added_tail = &(*tpl->tpl->added_tail)->next;

        for (; curr_node != NULL; curr_node = curr_node->next)
        {
            if (curr_node->val)
            {
                (void)memcpy(buffer, curr_node->val, curr_node->len);
                buffer += curr_node->len;
            }
            else
            {
                (void)memcpy(buffer,
                             curr_node->fval->val,
                             curr_node->fval->len);

                buffer += curr_node->fval->len;
            }
        }
        *buffer = 0;
        *tpl->tpl->added_tail = NULL;

        return TPL_OK;
    }
    return TPL_NO_SECTION_SELECTED_ERROR;
}


int tpl_length(const tpl_t* tpl)
{
    tpl_node_t *n = tpl->head;
    tpl_tcell_t *s = tpl->first;
    int len;

    for (len = 0; n != NULL; n = n->next)
        len += (n->val) ? n->len : n->fval->len;

    for (; s != NULL; s = s->next)
    {
        for (n = s->tpl->added_head; n != NULL; n = n->next)
            len += n->len;
    }

    return len;
}


int tpl_section_length(const tpl_t* tpl)
{
    return tpl_length(tpl->tpl);
}

void tpl_get_content(const tpl_t* tpl, char* buffer)
{
    tpl_node_t *n = tpl->head;
    tpl_tcell_t *s = tpl->first;
    
    for (; s != NULL; s = s->next)
    {
        tpl_node_t *an = s->tpl->added_head;

        /* Copy part until beginning of section */
        for (; n != *s->preceding; n = n->next)
        {
            if (n->val)
            {
                (void)memcpy(buffer, n->val, n->len);
                buffer += n->len;
            }
            else
            {
                (void)memcpy(buffer, n->fval->val, n->fval->len);
                buffer += n->fval->len;
            }
        }

        /* Copy content appended in section */
        for (; an != NULL; an = an->next)
        {
            (void)memcpy(buffer, an->val, an->len);
            buffer += an->len;
        }
    }

    /* Copy the rest */
    for (; n != NULL; n = n->next)
    {
        if (n->val)
        {
            (void)memcpy(buffer, n->val, n->len);
            buffer += n->len;
        }
        else
        {
            (void)memcpy(buffer, n->fval->val, n->fval->len);
            buffer += n->fval->len;
        }
    }
    *buffer = 0;
}

void tpl_get_section_content(const tpl_t* tpl, char* buffer)
{
    tpl_get_content(tpl->tpl, buffer);
}

int tpl_save_as(const tpl_t* tpl, const char* filename)
{
    FILE *fp = fopen(filename, "wb");

    if (fp != NULL)
    {
        int len = tpl_length(tpl);
	int n;
        char *content = (char *)acl_mymalloc(len + 1);

        tpl_get_content(tpl, content);

        n = fwrite(content, len, 1, fp);
	if (n != 1)
        {
            (void)fclose(fp);
            acl_myfree(content);
            return TPL_WRITE_ERROR;
        }
        (void)fclose(fp);

        acl_myfree(content);
        return TPL_OK;
    }
    return TPL_OPEN_ERROR;
}


int tpl_write(const tpl_t* tpl, int fd)
{  
    int len = tpl_length(tpl);
    char *content = (char *)acl_mymalloc(len + 1);

    tpl_get_content(tpl, content);
    len = write(fd, content, len);
    acl_myfree(content);

    if (len < 0)
        return TPL_WRITE_ERROR;

    return TPL_OK;
}


int tpl_http_write(const tpl_t* tpl, int fd)
{  
    /* Assumption: the content length can be max a 10 digit number */
    int length_digits = 10;

    int header_len = sizeof(TPL_NPH_HTTP_HEADER_START)
                   + sizeof(TPL_NPH_HTTP_HEADER_END)
                   + length_digits;

    int content_len = tpl_length(tpl);
    int tmp_len = content_len;
    
    /* Long enough for both the HTTP header and content */
    char *response = (char*)acl_mymalloc(content_len + header_len + 1);

    char *str_p = response;

    (void)memcpy(str_p, 
                TPL_NPH_HTTP_HEADER_START,
                sizeof(TPL_NPH_HTTP_HEADER_START) - 1);

    str_p += sizeof(TPL_NPH_HTTP_HEADER_START) - 1;
    
    M_INT_TO_STRING(str_p, length_digits, tmp_len, 0);
    str_p += length_digits;

    (void)memcpy(str_p,
                 TPL_NPH_HTTP_HEADER_END,
                 sizeof(TPL_NPH_HTTP_HEADER_END) - 1);

    str_p += sizeof(TPL_NPH_HTTP_HEADER_END) - 1;

    tpl_get_content(tpl, str_p);
    content_len = write(fd, response, content_len + (str_p - response));
    acl_myfree(response);

    if (content_len < 0)
        return TPL_WRITE_ERROR;

    return TPL_OK;
}

void tpl_out(tpl_t *tpl, ACL_VSTREAM *out)
{
	char *buf;
	int   n;

	if (tpl == NULL)
		return;
	if (out == NULL)
		out = ACL_VSTREAM_OUT;

	n = tpl_length(tpl);
	if (n <= 0)
		return;
	buf = (char*) acl_mymalloc(n + 1);
	tpl_get_content(tpl, buf);
	acl_vstream_writen(out, buf, n);
	acl_myfree(buf);
}
