/* ====================================================================
 * Copyright 1999 Web Juice, LLC. All rights reserved.
 *
 * parser.c
 *
 * The parsing bits of the template library.
 *
 * ==================================================================== */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include <template.h>


int  parser(context_p ctx, int looping, char *input, char **output);
void parse_tag(context_p ctx, char *tag, char **tagname,
               int *argc, char ***argv);
void parse_arg(context_p ctx, char *inarg, int size, char **outarg);
void append_output(char **output, char *append, int append_length,
                   int *current_size);


/* ====================================================================
 * NAME:          parser
 *
 * DESCRIPTION:   parses the given input using the current context, and
 *                outputs to a new string pointed to by the output
 *                parameter.
 *
 * RETURN VALUES: Returns 0 if there's a problem, 1 if the parsing was
 *                successful.  The real output goes into the output
 *                string (which the caller is responsible for freeing!)
 *
 * BUGS:          There may be ways of removing some string copying from
 *                this function.  I wasn't able to think of any good ones
 *                that wouldn't make it even more hideous than it already
 *                is.
 * ==================================================================== */
int
parser(context_p ctx, int looping, char *input, char **output)
{
    context_p current = ctx;
    int  output_size  = 1024;
    int i;
    int strip = string_truth(context_get_value(ctx, "INTERNAL_strip"));

    *output = (char *)calloc(1, output_size);
    if (*output == NULL)
    {
        return 0;
    }

    if (ctx == NULL)
    {
        return 0;
    }
    if (input == NULL)
    {
        return 0;
    }

    do
    {
        char *position = input;
        char *open_tag;
        char *open_tag_name;
        int  open_tag_argc;
        char **open_tag_argv;
        char *tagstart, *tptr;
        int  size;

        /* let's avoid doing any work as long as we can */
        if (! current->output_contents)
        {
            if (looping)
            {
                current = current->next_context;
            }
            continue;
        }

        /* while we can see the beginning of a tag */
        while ((tagstart = strstr(position, otag)) != NULL)
        {
            /* copy the stuff before the tag into the output buffer */
            append_output(output, position,
                          (strlen(position) - strlen(tagstart)), &output_size);

            /* now find the end of the tag */
            tptr = strstr(tagstart, ctag);
            if (tptr == NULL)
            {
                /* The tag opened but never closed.  Bummer! */
                return 0;
            }
            /* move the position marker up to the tag's end */
            position = tptr + strlen(ctag);
            if ((strip) && (*position == '\n')) position++;

            /* copy the tag */
            size = strlen(otag);
            open_tag = (char *)malloc(tptr - tagstart - size + 1);
            strncpy(open_tag, tagstart + size,
                    tptr - tagstart - size);
            open_tag[tptr - tagstart - size] = '\0';

            /* parse the tag into parts */
            parse_tag(current, open_tag, &open_tag_name,
                      &open_tag_argc, &open_tag_argv);

            /* deal with the simple tag case */
            if (staglist_exists(simple_tags, open_tag_name))
            {
                char *result;

                if ((staglist_exec(simple_tags, open_tag_name, current, &result,
                                   open_tag_argc, open_tag_argv))
                    && (result != NULL))
                {
                    char *parsed_result;

                    parser(current, 0, result, &parsed_result);

                    append_output(output, parsed_result, strlen(parsed_result),
                                  &output_size);
                    free(result);
                    free(parsed_result);
                }
            /* deal with the tag pair case */
            } else if (tagplist_is_opentag(tag_pairs, open_tag_name))
            {
                int  depth = 1;
                char *subpos = position;
                char *close_tag_name;
                int  length;

                while ((tagstart = strstr(subpos, otag)) != NULL)
                {
                    char *close_tag;

                    /* now find the end of the tag */
                    tptr = strstr(tagstart, ctag);
                    if (tptr == NULL)
                    {
                        /* The tag opened but never closed.  Bummer! */
                        return 0;
                    }
                    /* move the position marker up to the tag's end */
                    subpos = tptr + strlen(ctag);
                    if ((strip) && (*subpos == '\n')) subpos++;


                    /* figure out the tag name */
                    close_tag  = tagstart + size;
                    close_tag += strspn(close_tag, "\011\012\013\014\015\040");

                    length = strcspn(close_tag, "\011\012\013\014\015\040");
                    if (length > strlen(close_tag) - strlen(tptr))
                    {
                        length = strlen(close_tag) - strlen(tptr);
                    }
                    close_tag_name = (char *)malloc(length + 1);
                    strncpy(close_tag_name, close_tag, length);
                    close_tag_name[length] = '\0';


                    /* if the close tag is the same as the open tag, we're
                       nesting... */
                    if (strcmp(open_tag_name, close_tag_name) == 0)
                    {
                        ++depth;
                    /* if the close tag and open tag form a pair, we're
                       un-nesting... */
                    } else if (tagplist_is_closetag(tag_pairs, open_tag_name,
                                                    close_tag_name))
                    {
                        --depth;
                    }

                    /* if depth is zero, this close tag is *the* close tag. */
                    if (depth == 0)
                    {
                        char *region;
                        context_p newcontext;

                        region = (char *)malloc(tagstart - position + 1);
                        strncpy(region, position, tagstart - position);
                        region[tagstart - position] = '\0';

                        newcontext = tagplist_exec(tag_pairs, open_tag_name,
                                                   current, open_tag_argc,
                                                   open_tag_argv);
                        if (newcontext != NULL)
                        {
                            char *parsed_region;

                            parser(newcontext, 1, region, &parsed_region);
                            append_output(output, parsed_region,
                                          strlen(parsed_region), &output_size);
                            free(parsed_region);

                            if (newcontext->anonymous)
                            {
                                context_destroy(newcontext);
                            }

                            free(close_tag_name);
                            free(region);

                            break;
                        }

                        free(region);
                    }

                    free(close_tag_name);
                }
                position = subpos;
            }

            for (i = 0; i <= open_tag_argc; i++)
            {
                free(open_tag_argv[i]);
            }
            free(open_tag_argv);
            free(open_tag);
        }

        append_output(output, position, strlen(position), &output_size);

        /* done this iteration - move to the next */
        if (looping)
        {
            current = current->next_context;
        }

    } while ((looping) && (current != NULL));

    return 1;
}



/* ====================================================================
 * NAME:          parse_tag
 *
 * DESCRIPTION:   This function takes a single tag (sans tag delimiters)
 *                and a context as input, and outputs the tagname, and
 *                appropriate argc and argv values, to be used for calling
 *                the tag's underlying function.
 *
 * RETURN VALUES: None - all output is stuffed into the output parameters
 *                tagname, argc and argv.
 *
 * BUGS:          Pointer madness with char ***argv.  (There has to be
 *                a better way!)  And the string is parsed character by
 *                character, which might not be the brightest move, but
 *                seemed natural for this application.
 * ==================================================================== */
void
parse_tag(context_p ctx, char *tag, char **tagname, int *argc, char ***argv)
{
    int length;
    char *p, *argbegin;
    char last, instring;
    char **targv;

    *argc = 0;
    targv = (char **)calloc(*argc + 1, sizeof(char **));

    tag += strspn(tag, "\011\012\013\014\015\040");
    length = strcspn(tag, "\011\012\013\014\015\040");

    targv[0] = (char *)malloc(length + 1);
    strncpy(targv[0], tag, length);
    (targv[0])[length] = '\0';

    *tagname  = targv[0];

    last      = '\0';
    instring  = 0;
    *argc     = 0;
    argbegin  = NULL;
    for (p = (tag + length); *p != (char)NULL; last = *p, ++p)
    {
        if ((! isspace((int)*p)) && (*argc == 0))
        {
            argbegin = p;
            (*argc)++;
            targv = (char **)realloc(targv, (*argc + 1) * (sizeof(char *)));
        }
        if (*p == '"')
        {
            if ((instring) && (last != '\\'))
            {
                instring = 0;
            } else if (! instring)
            {
                instring = 1;
            }
        } else if (*p == ',')
        {
            if (! instring)
            {
                /* parse the current argument string into argv */
                parse_arg(ctx, argbegin, p - argbegin, &targv[*argc]);

                /* point to the next argument string */
                argbegin = p + 1;
                (*argc)++;
                targv = (char **)realloc(targv, (*argc + 1) * (sizeof(char *)));
            }
        }
    }
    if (*argc > 0)
    {
        parse_arg(ctx, argbegin, strlen(argbegin), &targv[*argc]);
    }

    *argv = targv;
}



/* ====================================================================
 * NAME:          parse_arg
 *
 * DESCRIPTION:   Parses a string (inarg) as a single argument.  Does
 *                variable substitution and string concatentation, and
 *                outputs the result into outarg.
 *
 * RETURN VALUES: None - output is placed into outarg.
 *
 * BUGS:          Character by character parsing may be avoidable - not
 *                sure.
 * ==================================================================== */
void
parse_arg(context_p ctx, char *inarg, int size, char **outarg)
{
    char *begin, *p;
    char instring, last;
    int  index, cursize, i;


    i       = 0;
    index   = 0;
    cursize = (size * 2) + 1;
    *outarg = (char *)calloc(1, cursize);

    /* move past leading whitespace */
    for (begin = inarg; isspace((int)*begin); ++begin, ++i) ;

    instring = 0;
    last     = '\0';
    for (p = begin; i <= size; last = *p, p++, i++)
    {
        if (*p == '"')
        {
            --index;
            if (instring)
            {
                if (last == '\\')
                {
                    (*outarg)[index] = '"';
                } else {
                    instring = 0;
                }
            } else if (! instring)
            {
                instring = 1;
            }
        } else if (*p == '$')
        {
            if (instring)
            {
                (*outarg)[index] = *p;
            } else
            {
                int length;
                char *varname, *varvalue;
                char *b = ++p;

                for (++i; ((*p != (char)NULL) && (isalnum((int)*p) || (*p == '_') || (*p == '.'))); p++, i++) ;

                length = p - b;
                varname = (char *)malloc(length + 1);
                strncpy(varname, b, length);
                varname[length] = '\0';

                varvalue = context_get_value(ctx, varname);
                if (varvalue != NULL)
                {
                    while ((index + strlen(varvalue) + 1) > cursize)
                    {
                        char *t;

                        cursize = cursize * 2;
                        t = (char *)calloc(1, cursize);
                        strncpy(t, *outarg, index);

                        free(*outarg);
                        *outarg = t;
                    }

                    strncat(&((*outarg)[index]), varvalue, strlen(varvalue));
                    index += strlen(varvalue) - 1;
                    --p;
                } else
                {
                    --index;
                }
                free(varname);
            }
        } else
        {
            if (instring)
            {
                (*outarg)[index] = *p;
            } else
            {
                --index;
            }
        }


        ++index;
        if ((index + 1) >= cursize)
        {
            char *t;

            if ((index + 1) >= (cursize * 2))
            {
                cursize = index + 1;
            } else
            {
                cursize = cursize * 2;
            }
            t = (char *)calloc(1, cursize);
            strncpy(t, *outarg, index);

            free(*outarg);
            *outarg = t;
        }
    }
}



/* ====================================================================
 * NAME:          append_output
 *
 * DESCRIPTION:   Function used by parser to dynamically expand a string
 *                as needed.  This is really a glorified strncat which
 *                grows the destination string as needed.
 *
 * RETURN VALUES: None, but *output is modified.
 *
 * BUGS:          Hopefully none.
 * ==================================================================== */
void
append_output(char **output, char *append, int append_size, int *current_size)
{
    while ((strlen(*output) + append_size + 1) > *current_size) {
        char *temp;

        if ((strlen(*output) + append_size + 1) > ((*current_size) * 2))
        {
            *current_size = (strlen(*output) + append_size + 1);
        } else
        {
            *current_size = (*current_size) * 2;
        }
        temp = (char *)calloc(1, *current_size);
        strncpy(temp, *output, strlen(*output));

        free(*output);
        *output = temp;
    }

    strncat(*output, append, append_size);
    strncat(*output, "\0", 1);
}