//
// Copyright (C) 2013 LunarG, Inc.
//
// 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 3Dlabs Inc. Ltd. 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 HOLDERS 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.
//

//
// Do sub tree walks for
// 1) inductive loop bodies to see if the inductive variable is modified
// 2) array-index expressions to see if they are "constant-index-expression"
//
// These are per Appendix A of ES 2.0:
//
// "Within the body of the loop, the loop index is not statically assigned to nor is it used as the
// argument to a function out or inout parameter."
//
// "The following are constant-index-expressions:
//  - Constant expressions
//  - Loop indices as defined in section 4
//  - Expressions composed of both of the above"
//
// N.B.: assuming the last rule excludes function calls
//

#include "ParseHelper.h"

namespace QtShaderTools {
namespace glslang {

//
// The inductive loop-body traverser.
//
// Just look at things that might modify the loop index.
//

class TInductiveTraverser : public TIntermTraverser {
public:
    TInductiveTraverser(int id, TSymbolTable& st)
    : loopId(id), symbolTable(st), bad(false)  { }

    virtual bool visitBinary(TVisit, TIntermBinary* node);
    virtual bool visitUnary(TVisit, TIntermUnary* node);
    virtual bool visitAggregate(TVisit, TIntermAggregate* node);

    int loopId;           // unique ID of the symbol that's the loop inductive variable
    TSymbolTable& symbolTable;
    bool bad;
    TSourceLoc badLoc;

protected:
    TInductiveTraverser(TInductiveTraverser&);
    TInductiveTraverser& operator=(TInductiveTraverser&);
};

// check binary operations for those modifying the loop index
bool TInductiveTraverser::visitBinary(TVisit /* visit */, TIntermBinary* node)
{
    if (node->modifiesState() && node->getLeft()->getAsSymbolNode() &&
                                 node->getLeft()->getAsSymbolNode()->getId() == loopId) {
        bad = true;
        badLoc = node->getLoc();
    }

    return true;
}

// check unary operations for those modifying the loop index
bool TInductiveTraverser::visitUnary(TVisit /* visit */, TIntermUnary* node)
{
    if (node->modifiesState() && node->getOperand()->getAsSymbolNode() &&
                                 node->getOperand()->getAsSymbolNode()->getId() == loopId) {
        bad = true;
        badLoc = node->getLoc();
    }

    return true;
}

// check function calls for arguments modifying the loop index
bool TInductiveTraverser::visitAggregate(TVisit /* visit */, TIntermAggregate* node)
{
    if (node->getOp() == EOpFunctionCall) {
        // see if an out or inout argument is the loop index
        const TIntermSequence& args = node->getSequence();
        for (int i = 0; i < (int)args.size(); ++i) {
            if (args[i]->getAsSymbolNode() && args[i]->getAsSymbolNode()->getId() == loopId) {
                TSymbol* function = symbolTable.find(node->getName());
                const TType* type = (*function->getAsFunction())[i].type;
                if (type->getQualifier().storage == EvqOut ||
                    type->getQualifier().storage == EvqInOut) {
                    bad = true;
                    badLoc = node->getLoc();
                }
            }
        }
    }

    return true;
}

//
// External function to call for loop check.
//
void TParseContext::inductiveLoopBodyCheck(TIntermNode* body, int loopId, TSymbolTable& symbolTable)
{
    TInductiveTraverser it(loopId, symbolTable);

    if (body == nullptr)
        return;

    body->traverse(&it);

    if (it.bad)
        error(it.badLoc, "inductive loop index modified", "limitations", "");
}

//
// The "constant-index-expression" tranverser.
//
// Just look at things that can form an index.
//

class TIndexTraverser : public TIntermTraverser {
public:
    TIndexTraverser(const TIdSetType& ids) : inductiveLoopIds(ids), bad(false) { }
    virtual void visitSymbol(TIntermSymbol* symbol);
    virtual bool visitAggregate(TVisit, TIntermAggregate* node);
    const TIdSetType& inductiveLoopIds;
    bool bad;
    TSourceLoc badLoc;

protected:
    TIndexTraverser(TIndexTraverser&);
    TIndexTraverser& operator=(TIndexTraverser&);
};

// make sure symbols are inductive-loop indexes
void TIndexTraverser::visitSymbol(TIntermSymbol* symbol)
{
    if (inductiveLoopIds.find(symbol->getId()) == inductiveLoopIds.end()) {
        bad = true;
        badLoc = symbol->getLoc();
    }
}

// check for function calls, assuming they are bad; spec. doesn't really say
bool TIndexTraverser::visitAggregate(TVisit /* visit */, TIntermAggregate* node)
{
    if (node->getOp() == EOpFunctionCall) {
        bad = true;
        badLoc = node->getLoc();
    }

    return true;
}

//
// External function to call for loop check.
//
void TParseContext::constantIndexExpressionCheck(TIntermNode* index)
{
#ifndef GLSLANG_WEB
    TIndexTraverser it(inductiveLoopIds);

    index->traverse(&it);

    if (it.bad)
        error(it.badLoc, "Non-constant-index-expression", "limitations", "");
#endif
}

} // end namespace glslang
} // namespace QtShaderTools
