//
// Copyright (c) 2002-2014 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

#include "compiler/translator/LoopInfo.h"

namespace sh
{

namespace
{

int EvaluateIntConstant(TIntermConstantUnion *node)
{
    ASSERT(node && node->getUnionArrayPointer());
    return node->getIConst(0);
}

int GetLoopIntIncrement(TIntermLoop *node)
{
    TIntermNode *expr = node->getExpression();
    // for expression has one of the following forms:
    //     loop_index++
    //     loop_index--
    //     loop_index += constant_expression
    //     loop_index -= constant_expression
    //     ++loop_index
    //     --loop_index
    // The last two forms are not specified in the spec, but I am assuming
    // its an oversight.
    TIntermUnary *unOp = expr->getAsUnaryNode();
    TIntermBinary *binOp = unOp ? NULL : expr->getAsBinaryNode();

    TOperator op = EOpNull;
    TIntermConstantUnion *incrementNode = NULL;
    if (unOp)
    {
        op = unOp->getOp();
    }
    else if (binOp)
    {
        op = binOp->getOp();
        ASSERT(binOp->getRight());
        incrementNode = binOp->getRight()->getAsConstantUnion();
        ASSERT(incrementNode);
    }

    int increment = 0;
    // The operator is one of: ++ -- += -=.
    switch (op)
    {
      case EOpPostIncrement:
      case EOpPreIncrement:
        ASSERT(unOp && !binOp);
        increment = 1;
        break;
      case EOpPostDecrement:
      case EOpPreDecrement:
        ASSERT(unOp && !binOp);
        increment = -1;
        break;
      case EOpAddAssign:
        ASSERT(!unOp && binOp);
        increment = EvaluateIntConstant(incrementNode);
        break;
      case EOpSubAssign:
        ASSERT(!unOp && binOp);
        increment = - EvaluateIntConstant(incrementNode);
        break;
      default:
        UNREACHABLE();
    }

    return increment;
}

}  // namespace anonymous

TLoopIndexInfo::TLoopIndexInfo()
    : mId(-1),
      mType(EbtVoid),
      mInitValue(0),
      mStopValue(0),
      mIncrementValue(0),
      mOp(EOpNull),
      mCurrentValue(0)
{
}

void TLoopIndexInfo::fillInfo(TIntermLoop *node)
{
    if (node == NULL)
        return;

    // Here we assume all the operations are valid, because the loop node is
    // already validated in ValidateLimitations.
    TIntermSequence *declSeq = node->getInit()->getAsDeclarationNode()->getSequence();
    TIntermBinary *declInit = (*declSeq)[0]->getAsBinaryNode();
    TIntermSymbol *symbol = declInit->getLeft()->getAsSymbolNode();

    mId = symbol->getId();
    mType = symbol->getBasicType();

    if (mType == EbtInt)
    {
        TIntermConstantUnion* initNode = declInit->getRight()->getAsConstantUnion();
        mInitValue = EvaluateIntConstant(initNode);
        mCurrentValue = mInitValue;
        mIncrementValue = GetLoopIntIncrement(node);

        TIntermBinary* binOp = node->getCondition()->getAsBinaryNode();
        mStopValue = EvaluateIntConstant(
            binOp->getRight()->getAsConstantUnion());
        mOp = binOp->getOp();
    }
}

bool TLoopIndexInfo::satisfiesLoopCondition() const
{
    // Relational operator is one of: > >= < <= == or !=.
    switch (mOp)
    {
      case EOpEqual:
        return (mCurrentValue == mStopValue);
      case EOpNotEqual:
        return (mCurrentValue != mStopValue);
      case EOpLessThan:
        return (mCurrentValue < mStopValue);
      case EOpGreaterThan:
        return (mCurrentValue > mStopValue);
      case EOpLessThanEqual:
        return (mCurrentValue <= mStopValue);
      case EOpGreaterThanEqual:
        return (mCurrentValue >= mStopValue);
      default:
        UNREACHABLE();
        return false;
    }
}

TLoopInfo::TLoopInfo()
    : loop(NULL)
{
}

TLoopInfo::TLoopInfo(TIntermLoop *node)
    : loop(node)
{
    index.fillInfo(node);
}

TIntermLoop *TLoopStack::findLoop(TIntermSymbol *symbol)
{
    if (!symbol)
        return NULL;
    for (iterator iter = begin(); iter != end(); ++iter)
    {
        if (iter->index.getId() == symbol->getId())
            return iter->loop;
    }
    return NULL;
}

TLoopIndexInfo *TLoopStack::getIndexInfo(TIntermSymbol *symbol)
{
    if (!symbol)
        return NULL;
    for (iterator iter = begin(); iter != end(); ++iter)
    {
        if (iter->index.getId() == symbol->getId())
            return &(iter->index);
    }
    return NULL;
}

void TLoopStack::step()
{
    ASSERT(!empty());
    rbegin()->index.step();
}

bool TLoopStack::satisfiesLoopCondition()
{
    ASSERT(!empty());
    return rbegin()->index.satisfiesLoopCondition();
}

bool TLoopStack::needsToReplaceSymbolWithValue(TIntermSymbol *symbol)
{
    TIntermLoop *loop = findLoop(symbol);
    return loop && loop->getUnrollFlag();
}

int TLoopStack::getLoopIndexValue(TIntermSymbol *symbol)
{
    TLoopIndexInfo *info = getIndexInfo(symbol);
    ASSERT(info);
    return info->getCurrentValue();
}

void TLoopStack::push(TIntermLoop *loop)
{
    TLoopInfo info(loop);
    push_back(info);
}

void TLoopStack::pop()
{
    pop_back();
}

}  // namespace sh
