/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
//
// def-proc.c
//
// specialized markup processors
//
// Copyright (c) 1995-96 Jim Nelson.  Permission to distribute
// granted by the author.  No warranties are made on the fitness of this
// source code.
//
*/

#include "def-proc.h"

#include "defs.h"
#include "htp-files.h"
#include "macro.h"
#include "option.h"

/*
// Block macro destructor callback ... used whenever a block macro is
// destroyed with a RemoveVariable() or ClearVariableList()
*/
void BlockDestructor(const char *name, void *value, uint type, uint flags,
    void *param)
{
    STREAM *stream = (STREAM*) value;
    UNREF_PARAM(name);
    UNREF_PARAM(type);
    UNREF_PARAM(flags);

    CloseStream(stream);
    FreeMemory(stream);

    /* DEF macros use param */
    if(param != NULL)
    {
        FreeMemory(param);
    }
}

BOOL ReadBlockToFile(TASK *task, BOOL expand, const char *tag, 
                     STREAM *blockFile) {
    char *plaintext;
    char ch;
    HTML_MARKUP newHtml;
    BOOL result;
    static uint blockDepth = 0;
    uint markupType;
    STREAM *oldOutFile;
    uint markupResult;

    HtpMsg(MSG_INFO, task->infile, "reading %s block to memory",
           expand ? "expanded" : "raw");

    oldOutFile = task->outfile;
    task->outfile = blockFile;

    /* Skip EOL, but put it back if it's another character */
    if (GetStreamChar(task->infile, &ch) && ch != '\n')
    {
        UnreadStreamChar(task->infile, ch);
    }

    task->outfile->name = DuplicateString(task->infile->name);
    task->outfile->lineNumber = task->infile->lineNumber;

    /* start copying the file into the temporary file, looking for the */
    /* BLOCK or /BLOCK tag if block macro, DEF or /DEF otherwise ... */
    /* just squirrel away all other tags and text */
    for(;;)
    {
        result = ReadHtmlMarkup(task->infile, blockFile,
                                &markupType, &newHtml);
        if(result == ERROR)
        {
            task->outfile = oldOutFile;
            return FALSE;
        }
        else if(result == FALSE)
        {
            /* end-of-file encountered before end-of-block */
            HtpMsg(MSG_ERROR, task->infile,
                   "EOF encountered before %s in line %d was closed",
                   tag, task->outfile->lineNumber);
            task->outfile = oldOutFile;
            return FALSE;
        }

        if ((markupType & MARKUP_TYPE_HTP) && blockDepth == 0
            && newHtml.tag[0] == '/'
            && stricmp(tag, newHtml.tag+1) == 0)
        {
            /* found the end of the macro block */
            DestroyMarkupStruct(&newHtml);
            break;
        }

        plaintext = NULL;
        if (expand) {
            /* give the default processor a chance to expand macros, etc. */
            markupResult = ExpandAll(task, &newHtml, 
                                     &plaintext, markupType);

        } else {

            if(markupType & MARKUP_TYPE_HTP)
            {
                /* check for embedded block declarations */
                if (stricmp(tag, newHtml.tag) == 0)
                {
                    /* add to the block macro depth and continue */
                    blockDepth++;
                }
                else if (newHtml.tag[0] == '/'
                         && stricmp(tag, newHtml.tag+1) == 0)
                {
                    /* depth has decreased one */
                    blockDepth--;
                }
            }

            /* if continuing, then the plaintext is put into the
             * output stream as-is ... there is no case where the
             * processor continues scanning but discards a markup
             */
            if(MarkupToPlaintext(&newHtml, &plaintext) == FALSE)
            {
                HtpMsg(MSG_ERROR, task->infile,
                       "unable to build plain text from markup (out of memory?)");
                task->outfile = oldOutFile;
                return MARKUP_ERROR;
            }
            markupResult = MARKUP_OKAY;
        }
        
        switch(markupResult)
        {
            case MARKUP_OKAY:
            {
                /* add the markup to the output file as it should appear */
                
                StreamPrintF(blockFile, "%c%s%c", 
			      MARKUP_OPEN_DELIM(markupType),
                              plaintext, MARKUP_CLOSE_DELIM(markupType));
            }
            break;

            case NEW_MARKUP:
            case MARKUP_REPLACED:
            {
                /* the markup has been replaced by a normal string */
                StreamPrintF(blockFile, "%s", plaintext);
            }
            break;

            case DISCARD_MARKUP:
            {
                /* markup will not be included in final output */
            }
            break;

            case MARKUP_ERROR:
            {
                /* (need to destroy plaintext buffer before exiting) */
                FreeMemory(plaintext);
                task->outfile = oldOutFile;
                return FALSE;
            }

            default:
            {
                FreeMemory(plaintext);
                printf("%s: serious internal error\n", PROGRAM_NAME);
                exit(1);
            }
        }

        /* destroy the HTML markup, not needed any longer */
        DestroyMarkupStruct(&newHtml);

        /* destroy the plaintext buffer */
        if (plaintext)
            FreeMemory(plaintext);
    }

    task->outfile = oldOutFile;
    return TRUE;
}

BOOL ReadinBlock(TASK *task, HTML_MARKUP *htmlMarkup, STREAM *blockStream) {
    BOOL expand = UnlinkBoolAttributeInMarkup(htmlMarkup, "EXPAND");

    if (htmlMarkup->attrib != NULL) {
        HtpMsg(MSG_WARNING, task->infile, 
               "Unhandled %s attribute in %s markup", 
               htmlMarkup->attrib->name, htmlMarkup->tag);
    }

    /* try and create the temporary file */
    if(CreateBufferWriter(blockStream, htmlMarkup->tag) == FALSE)
    {
        HtpMsg(MSG_ERROR, task->infile,
	       "unable to create stream for %s macro", htmlMarkup->tag);
        return FALSE;
    }

    if (ReadBlockToFile(task, expand, htmlMarkup->tag, blockStream) == FALSE) {
        CloseStream(blockStream);
        FreeMemory(blockStream);
        return FALSE;
    }
    FlushBufferWriter(blockStream);
    return TRUE;
}

uint BlockProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
{
    char *defName, *param;
    STREAM *stream;
    BOOL blockMacro;
    uint macroType;
    VARSTORE *varstore;
    HTML_ATTRIBUTE *attrib;

    UNREF_PARAM(newPlaintext);

    /* first: is this a BLOCK macro or a DEF macro?  This function is */
    /* overloaded to handle both types, and must internally change its */
    /* functionality for each type */
    /* if this is false, this is a DEF macro */
    if(stricmp(htmlMarkup->tag, "BLOCK") == 0)
    {
        blockMacro = TRUE;
        macroType = VAR_TYPE_BLOCK_MACRO;
        /* no extra varstore parameter for a block macro */
        param = NULL;
    }
    else
    {
        blockMacro = FALSE;
        if (stricmp(htmlMarkup->tag, "BLOCKDEF") == 0)
        {
            macroType = VAR_TYPE_BLOCKDEF_MACRO;
        }
        else 
        {
            assert(stricmp(htmlMarkup->tag, "DEF") == 0);
            macroType = VAR_TYPE_DEF_MACRO;
        }

        /* get the OPTION parameter */
        attrib = UnlinkAttributeInMarkup(htmlMarkup, "OPTION");
        if (attrib != NULL) {
            FreeMemory(attrib->name);
            param = attrib->value;
            if (attrib->whitespace)
                FreeMemory(attrib->whitespace);
            FreeMemory(attrib);
        }
        else
        {
            param = NULL;
        }
    }
    
    /* check if name is present */
    attrib = UnlinkAttributeInMarkup(htmlMarkup, "NAME");
    if (attrib != NULL && attrib->value != NULL) {

        /* new syntax */
        FreeMemory(attrib->name);
        defName = attrib->value;
        if (attrib->whitespace)
            FreeMemory(attrib->whitespace);
        FreeMemory(attrib);

    } else {
        /* The only attribute is the macro name without value 
         * (which can be "name" though).
         */
        if (attrib == NULL) {
            attrib = htmlMarkup->attrib;
            if(attrib == NULL)
            {
                HtpMsg(MSG_ERROR, task->infile, 
                       "%s markup without name", htmlMarkup->tag);
                return MARKUP_ERROR;
            }
            htmlMarkup->attrib = attrib->next;
        }

        defName = attrib->name;
        if (attrib->whitespace)
            FreeMemory(attrib->whitespace);
        if (attrib->value != NULL || htmlMarkup->attrib != NULL)
        {
            HtpMsg(MSG_ERROR, task->infile, 
                   "bad %s markup", htmlMarkup->tag);
            if (attrib->value != NULL)
                FreeMemory(attrib->value);
            FreeMemory(attrib);
            return MARKUP_ERROR;
        }
        FreeMemory(attrib);

        /* old style only allowed for block macros */
        if(!blockMacro)
        {
            HtpMsg(MSG_ERROR, task->infile, 
                   "%s requires NAME attribute", htmlMarkup->tag);
            return MARKUP_ERROR;
        }
    }

    /* store the block file name and the block macro name as a variable */
    varstore = task->varstore;
    if (UnlinkBoolAttributeInMarkup(htmlMarkup, "GLOBAL")) {
        while (!varstore->isGlobal)
            varstore = varstore->parent;
    }

    stream = (STREAM*) AllocMemory(sizeof(STREAM));
    if (!ReadinBlock(task, htmlMarkup, stream))
    {
        FreeMemory(defName);
        if (param != NULL)
            FreeMemory(param);
        return MARKUP_ERROR;
    }

    if(StoreVariable(varstore, defName, stream, macroType, 
                     VAR_FLAG_DEALLOC_NAME, param, BlockDestructor) == FALSE)
    {
        HtpMsg(MSG_ERROR, task->infile, 
               "unable to store macro information (out of memory?)");
        FreeMemory(defName);
        if (param != NULL)
            FreeMemory(param);
        return MARKUP_ERROR;
    }

    return DISCARD_MARKUP;
}   


uint UndefProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
{
    HTML_ATTRIBUTE *attrib;
    uint type;

    UNREF_PARAM(newPlaintext);

    attrib = htmlMarkup->attrib;

    /* need at least one attribute to undef */
    if(htmlMarkup->attrib == NULL)
    {
        HtpMsg(MSG_ERROR, task->infile, "UNDEF requires at least one name");
        return MARKUP_ERROR;
    }

    /* walk the list of attributes, deleting them as found */
    while (attrib != NULL)
    {
        /* only remove it if it is a [BLOCK]DEF macro */
        type = GetVariableType(task->varstore, attrib->name);
        if(type == VAR_TYPE_DEF_MACRO || type == VAR_TYPE_BLOCKDEF_MACRO)
        {
            RemoveVariable(task->varstore, attrib->name);
            HtpMsg(MSG_INFO, task->infile, "metatag \"%s\" removed", 
                   attrib->name);
        } 
        else 
        {
            HtpMsg(MSG_ERROR, task->infile,
                   "metatag \"%s\" never defined", attrib->name);

            return MARKUP_ERROR;
        }
        attrib = attrib->next;
    }

    return DISCARD_MARKUP;
}
