//
// Copyright (c) 2002-2011 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 "angle_gl.h"
#include "compiler/translator/BuiltInFunctionEmulator.h"
#include "compiler/translator/SymbolTable.h"

class BuiltInFunctionEmulator::BuiltInFunctionEmulationMarker : public TIntermTraverser
{
  public:
    BuiltInFunctionEmulationMarker(BuiltInFunctionEmulator& emulator)
        : mEmulator(emulator)
    {
    }

    virtual bool visitUnary(Visit visit, TIntermUnary* node)
    {
        if (visit == PreVisit) {
            bool needToEmulate = mEmulator.SetFunctionCalled(
                node->getOp(), node->getOperand()->getType());
            if (needToEmulate)
                node->setUseEmulatedFunction();
        }
        return true;
    }

    virtual bool visitAggregate(Visit visit, TIntermAggregate* node)
    {
        if (visit == PreVisit) {
            // Here we handle all the built-in functions instead of the ones we
            // currently identified as problematic.
            switch (node->getOp()) {
                case EOpLessThan:
                case EOpGreaterThan:
                case EOpLessThanEqual:
                case EOpGreaterThanEqual:
                case EOpVectorEqual:
                case EOpVectorNotEqual:
                case EOpMod:
                case EOpPow:
                case EOpAtan:
                case EOpMin:
                case EOpMax:
                case EOpClamp:
                case EOpMix:
                case EOpStep:
                case EOpSmoothStep:
                case EOpDistance:
                case EOpDot:
                case EOpCross:
                case EOpFaceForward:
                case EOpReflect:
                case EOpRefract:
                case EOpOuterProduct:
                case EOpMul:
                    break;
                default:
                    return true;
            };
            const TIntermSequence& sequence = *(node->getSequence());
            bool needToEmulate = false;
            // Right now we only handle built-in functions with two or three parameters.
            if (sequence.size() == 2)
            {
                TIntermTyped* param1 = sequence[0]->getAsTyped();
                TIntermTyped* param2 = sequence[1]->getAsTyped();
                if (!param1 || !param2)
                    return true;
                needToEmulate = mEmulator.SetFunctionCalled(
                    node->getOp(), param1->getType(), param2->getType());
            }
            else if (sequence.size() == 3)
            {
                TIntermTyped* param1 = sequence[0]->getAsTyped();
                TIntermTyped* param2 = sequence[1]->getAsTyped();
                TIntermTyped* param3 = sequence[2]->getAsTyped();
                if (!param1 || !param2 || !param3)
                    return true;
                needToEmulate = mEmulator.SetFunctionCalled(
                    node->getOp(), param1->getType(), param2->getType(), param3->getType());
            }
            else
            {
                return true;
            }

            if (needToEmulate)
                node->setUseEmulatedFunction();
        }
        return true;
    }

  private:
    BuiltInFunctionEmulator& mEmulator;
};

BuiltInFunctionEmulator::BuiltInFunctionEmulator()
{}

void BuiltInFunctionEmulator::addEmulatedFunction(
    TOperator op, const TType& param,
    const char* emulatedFunctionDefinition)
{
    mEmulatedFunctions[FunctionId(op, param)] =
        std::string(emulatedFunctionDefinition);
}

void BuiltInFunctionEmulator::addEmulatedFunction(
    TOperator op, const TType& param1, const TType& param2,
    const char* emulatedFunctionDefinition)
{
    mEmulatedFunctions[FunctionId(op, param1, param2)] =
        std::string(emulatedFunctionDefinition);
}

void BuiltInFunctionEmulator::addEmulatedFunction(
    TOperator op, const TType& param1, const TType& param2, const TType& param3,
    const char* emulatedFunctionDefinition)
{
    mEmulatedFunctions[FunctionId(op, param1, param2, param3)] =
        std::string(emulatedFunctionDefinition);
}

bool BuiltInFunctionEmulator::IsOutputEmpty() const
{
    return (mFunctions.size() == 0);
}

void BuiltInFunctionEmulator::OutputEmulatedFunctions(
    TInfoSinkBase& out) const
{
    for (size_t i = 0; i < mFunctions.size(); ++i) {
        out << mEmulatedFunctions.find(mFunctions[i])->second << "\n\n";
    }
}

bool BuiltInFunctionEmulator::SetFunctionCalled(
    TOperator op, const TType& param)
{
    return SetFunctionCalled(FunctionId(op, param));
}

bool BuiltInFunctionEmulator::SetFunctionCalled(
    TOperator op, const TType& param1, const TType& param2)
{
    return SetFunctionCalled(FunctionId(op, param1, param2));
}

bool BuiltInFunctionEmulator::SetFunctionCalled(
    TOperator op, const TType& param1, const TType& param2, const TType& param3)
{
    return SetFunctionCalled(FunctionId(op, param1, param2, param3));
}

bool BuiltInFunctionEmulator::SetFunctionCalled(
    const FunctionId& functionId) {
    if (mEmulatedFunctions.find(functionId) != mEmulatedFunctions.end())
    {
        for (size_t i = 0; i < mFunctions.size(); ++i) {
            if (mFunctions[i] == functionId)
                return true;
        }
        mFunctions.push_back(functionId);
        return true;
    }
    return false;
}

void BuiltInFunctionEmulator::MarkBuiltInFunctionsForEmulation(
    TIntermNode* root)
{
    ASSERT(root);

    if (mEmulatedFunctions.empty())
        return;

    BuiltInFunctionEmulationMarker marker(*this);
    root->traverse(&marker);
}

void BuiltInFunctionEmulator::Cleanup()
{
    mFunctions.clear();
}

//static
TString BuiltInFunctionEmulator::GetEmulatedFunctionName(
    const TString& name)
{
    ASSERT(name[name.length() - 1] == '(');
    return "webgl_" + name.substr(0, name.length() - 1) + "_emu(";
}

BuiltInFunctionEmulator::FunctionId::FunctionId
    (TOperator op, const TType& param)
    : mOp(op),
      mParam1(param),
      mParam2(EbtVoid),
      mParam3(EbtVoid)
{
}

BuiltInFunctionEmulator::FunctionId::FunctionId
    (TOperator op, const TType& param1, const TType& param2)
    : mOp(op),
      mParam1(param1),
      mParam2(param2),
      mParam3(EbtVoid)
{
}

BuiltInFunctionEmulator::FunctionId::FunctionId
    (TOperator op, const TType& param1, const TType& param2, const TType& param3)
    : mOp(op),
      mParam1(param1),
      mParam2(param2),
      mParam3(param3)
{
}

bool BuiltInFunctionEmulator::FunctionId::operator==
    (const BuiltInFunctionEmulator::FunctionId& other) const
{
    return (mOp == other.mOp &&
        mParam1 == other.mParam1 &&
        mParam2 == other.mParam2 &&
        mParam3 == other.mParam3);
}

bool BuiltInFunctionEmulator::FunctionId::operator<
    (const BuiltInFunctionEmulator::FunctionId& other) const
{
    if (mOp != other.mOp)
        return mOp < other.mOp;
    if (mParam1 != other.mParam1)
        return mParam1 < other.mParam1;
    if (mParam2 != other.mParam2)
        return mParam2 < other.mParam2;
    if (mParam3 != other.mParam3)
       return mParam3 < other.mParam3;
    return false; // all fields are equal
}
