/*
 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
 *
 * Squid software is distributed under GPLv2+ license and includes
 * contributions from numerous individuals and organizations.
 * Please see the COPYING and CONTRIBUTORS files for details.
 */

/* DEBUG: section 86    ESI processing */

#include "squid.h"

/* MS Visual Studio Projects are monolithic, so we need the following
 * #if to exclude the ESI code from compile process when not needed.
 */
#if (USE_SQUID_ESI == 1)

#include "esi/Assign.h"
#include "esi/Context.h"
#include "esi/Sequence.h"
#include "HttpReply.h"

ESIAssign::~ESIAssign()
{
    if (value)
        delete value;
}

ESIAssign::ESIAssign (ESIAssign const &old) : parent (NULL), varState (NULL), name (old.name), value (old.value ? new ESIVariableExpression (*old.value): NULL), variable (NULL), unevaluatedVariable(old.unevaluatedVariable)
{}

ESIAssign::ESIAssign (esiTreeParentPtr aParent, int attrcount, char const **attr, ESIContext *aContext) : parent (aParent), varState (NULL), name(), value (NULL), variable (NULL), unevaluatedVariable()
{
    /* TODO: grab content IFF no value was specified */
    assert (aContext);

    for (int i = 0; i < attrcount && attr[i]; i += 2) {
        if (!strcmp(attr[i],"name")) {
            /* the variables name is ...  */
            debugs(86, 5, "ESIAssign::ESIAssign: Variable name '" << attr[i+1] << "'");
            /* If there are duplicate name attributes, we simply use the
             * last one
             */
            name = attr[i+1];
        } else if (!strcmp(attr[i],"value")) {
            /* short form assignment:  */
            debugs(86, 5, "ESIAssign::ESIAssign: Unevaluated variable '" << attr[i+1] << "'");
            /* Again, if there are duplicate attributes, we use the last */
            unevaluatedVariable = attr[i+1];
        } else {
            /* ignore mistyped attributes. TODO:? error on these for user feedback - config parameter needed
             */
        }
    }

    varState = cbdataReference(aContext->varState);
}

void
ESIAssign::evaluateVariable()
{
    if (variable.getRaw())
        variable->process (false);

    variable = NULL;

    if (unevaluatedVariable.size()) {
        varState->feedData(unevaluatedVariable.rawBuf(), unevaluatedVariable.size());
        char const *result = varState->extractChar ();

        /* Consider activating this, when we want to evaluate variables to a
         * value
         */
        // setTestResult(ESIExpression::Evaluate (expression));

        value = new ESIVariableExpression (result);

        safe_free (result);
    }
}

void
ESIAssign::provideData (ESISegment::Pointer data, ESIElement * source)
{
    assert (source == variable.getRaw());
    char *result = data->listToChar();
    unevaluatedVariable = result;
    safe_free (result);
}

esiProcessResult_t
ESIAssign::process (int dovars)
{
    assert (varState);

    if (!value)
        evaluateVariable();

    if (!value)
        return ESI_PROCESS_COMPLETE;

    varState->addVariable (name.rawBuf(), name.size(), value);

    value = NULL;

    debugs(86, 5, "ESIAssign: Processed " << this);

    return ESI_PROCESS_COMPLETE;
}

void
ESIAssign::render(ESISegment::Pointer)
{}

ESIAssign::Pointer
ESIAssign::makeCacheable() const
{
    ESIAssign *result = new ESIAssign (*this);

    if (variable.getRaw())
        result->variable = variable->makeCacheable();

    return result;
}

ESIAssign::Pointer
ESIAssign::makeUsable(esiTreeParentPtr aParent, ESIVarState &aVarState) const
{
    ESIAssign *result = new ESIAssign (*this);
    result->parent = aParent;
    result->varState = cbdataReference(&aVarState);

    if (variable.getRaw())
        result->variable = variable->makeUsable(result, aVarState);

    return result;
}

void
ESIAssign::finish()
{
    if (varState)
        cbdataReferenceDone (varState);

    if (parent.getRaw())
        parent = NULL;
}

bool
ESIAssign::addElement(ESIElement::Pointer anElement)
{
    /* we have a value, drop the element on the floor */

    if (unevaluatedVariable.size())
        return true;

    if (!variable.getRaw())
        variable = new esiSequence (this, false);

    return variable->addElement (anElement);
}

ESIVariableExpression::~ESIVariableExpression()
{}

ESIVariableExpression::ESIVariableExpression (String const &aString) : expression (aString)
{}

void
ESIVariableExpression::eval (ESIVarState &state, char const *subref, char const *defaultOnEmpty) const
{
    /* XXX: Implement evaluation of the expression */
    ESISegment::ListAppend (state.getOutput(), expression.rawBuf(), expression.size());
}

#endif /* USE_SQUID_ESI == 1 */

