//
// Copyright (c) 2002-2016 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.
//
// gl_FragColor needs to broadcast to all color buffers in ES2 if
// GL_EXT_draw_buffers is explicitly enabled in a fragment shader.
//
// We emulate this by replacing all gl_FragColor with gl_FragData[0], and in the end
// of main() function, assigning gl_FragData[1], ..., gl_FragData[maxDrawBuffers-1]
// with gl_FragData[0].
//

#include "compiler/translator/EmulateGLFragColorBroadcast.h"
#include "compiler/translator/IntermNode.h"

namespace
{

class GLFragColorBroadcastTraverser : public TIntermTraverser
{
  public:
    GLFragColorBroadcastTraverser(int maxDrawBuffers)
        : TIntermTraverser(true, false, false),
          mMainSequence(nullptr),
          mGLFragColorUsed(false),
          mMaxDrawBuffers(maxDrawBuffers)
    {
    }

    void broadcastGLFragColor();

    bool isGLFragColorUsed() const { return mGLFragColorUsed; }

  protected:
    void visitSymbol(TIntermSymbol *node) override;
    bool visitAggregate(Visit visit, TIntermAggregate *node) override;

    TIntermBinary *constructGLFragDataNode(int index) const;
    TIntermBinary *constructGLFragDataAssignNode(int index) const;

  private:
    TIntermSequence *mMainSequence;
    bool mGLFragColorUsed;
    int mMaxDrawBuffers;
};

TIntermBinary *GLFragColorBroadcastTraverser::constructGLFragDataNode(int index) const
{
    TType gl_FragDataElementType = TType(EbtFloat, 4);
    TType gl_FragDataType        = gl_FragDataElementType;
    gl_FragDataType.setArraySize(mMaxDrawBuffers);

    TIntermSymbol *symbol   = new TIntermSymbol(0, "gl_FragData", gl_FragDataType);
    TIntermTyped *indexNode = TIntermTyped::CreateIndexNode(index);

    TIntermBinary *binary = new TIntermBinary(EOpIndexDirect);
    binary->setLeft(symbol);
    binary->setRight(indexNode);
    binary->setType(gl_FragDataElementType);
    return binary;
}

TIntermBinary *GLFragColorBroadcastTraverser::constructGLFragDataAssignNode(int index) const
{
    TIntermTyped *fragDataIndex = constructGLFragDataNode(index);
    TIntermTyped *fragDataZero  = constructGLFragDataNode(0);

    return new TIntermBinary(EOpAssign, fragDataIndex, fragDataZero);
}

void GLFragColorBroadcastTraverser::visitSymbol(TIntermSymbol *node)
{
    if (node->getSymbol() == "gl_FragColor")
    {
        queueReplacement(node, constructGLFragDataNode(0), OriginalNode::IS_DROPPED);
        mGLFragColorUsed = true;
    }
}

bool GLFragColorBroadcastTraverser::visitAggregate(Visit visit, TIntermAggregate *node)
{
    switch (node->getOp())
    {
        case EOpFunction:
            // Function definition.
            ASSERT(visit == PreVisit);
            if (node->getName() == "main(")
            {
                TIntermSequence *sequence = node->getSequence();
                ASSERT((sequence->size() == 1) || (sequence->size() == 2));
                if (sequence->size() == 2)
                {
                    TIntermAggregate *body = (*sequence)[1]->getAsAggregate();
                    ASSERT(body);
                    mMainSequence = body->getSequence();
                }
            }
            break;
        default:
            break;
    }
    return true;
}

void GLFragColorBroadcastTraverser::broadcastGLFragColor()
{
    ASSERT(mMaxDrawBuffers > 1);
    if (!mGLFragColorUsed)
    {
        return;
    }
    ASSERT(mMainSequence);
    // Now insert statements
    //   gl_FragData[1] = gl_FragData[0];
    //   ...
    //   gl_FragData[maxDrawBuffers - 1] = gl_FragData[0];
    for (int colorIndex = 1; colorIndex < mMaxDrawBuffers; ++colorIndex)
    {
        mMainSequence->insert(mMainSequence->end(), constructGLFragDataAssignNode(colorIndex));
    }
}

}  // namespace anonymous

void EmulateGLFragColorBroadcast(TIntermNode *root,
                                 int maxDrawBuffers,
                                 std::vector<sh::OutputVariable> *outputVariables)
{
    ASSERT(maxDrawBuffers > 1);
    GLFragColorBroadcastTraverser traverser(maxDrawBuffers);
    root->traverse(&traverser);
    if (traverser.isGLFragColorUsed())
    {
        traverser.updateTree();
        traverser.broadcastGLFragColor();
        for (auto &var : *outputVariables)
        {
            if (var.name == "gl_FragColor")
            {
                // TODO(zmo): Find a way to keep the original variable information.
                var.name       = "gl_FragData";
                var.mappedName = "gl_FragData";
                var.arraySize  = maxDrawBuffers;
            }
        }
    }
}
