//
// Copyright 2002 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/BuiltInFunctionEmulator.h"
#include "angle_gl.h"
#include "compiler/translator/Symbol.h"
#include "compiler/translator/tree_util/IntermTraverse.h"

namespace sh
{

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

    bool visitUnary(Visit visit, TIntermUnary *node) override
    {
        if (node->getFunction())
        {
            bool needToEmulate = mEmulator.setFunctionCalled(node->getFunction());
            if (needToEmulate)
                node->setUseEmulatedFunction();
        }
        return true;
    }

    bool visitAggregate(Visit visit, TIntermAggregate *node) override
    {
        // Here we handle all the math built-in functions, not just the ones that are currently
        // identified as problematic.
        if (!BuiltInGroup::IsMath(node->getOp()))
        {
            return true;
        }
        bool needToEmulate = mEmulator.setFunctionCalled(node->getFunction());
        if (needToEmulate)
            node->setUseEmulatedFunction();
        return true;
    }

  private:
    BuiltInFunctionEmulator &mEmulator;
};

BuiltInFunctionEmulator::BuiltInFunctionEmulator() {}

void BuiltInFunctionEmulator::addEmulatedFunction(const TSymbolUniqueId &uniqueId,
                                                  const char *emulatedFunctionDefinition)
{
    mEmulatedFunctions[uniqueId.get()] = std::string(emulatedFunctionDefinition);
}

void BuiltInFunctionEmulator::addEmulatedFunctionWithDependency(
    const TSymbolUniqueId &dependency,
    const TSymbolUniqueId &uniqueId,
    const char *emulatedFunctionDefinition)
{
    mEmulatedFunctions[uniqueId.get()]    = std::string(emulatedFunctionDefinition);
    mFunctionDependencies[uniqueId.get()] = dependency.get();
}

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

void BuiltInFunctionEmulator::outputEmulatedFunctions(TInfoSinkBase &out) const
{
    for (const auto &function : mFunctions)
    {
        const char *body = findEmulatedFunction(function);
        ASSERT(body);
        out << body;
        out << "\n\n";
    }
}

const char *BuiltInFunctionEmulator::findEmulatedFunction(int uniqueId) const
{
    for (const auto &queryFunction : mQueryFunctions)
    {
        const char *result = queryFunction(uniqueId);
        if (result)
        {
            return result;
        }
    }

    const auto &result = mEmulatedFunctions.find(uniqueId);
    if (result != mEmulatedFunctions.end())
    {
        return result->second.c_str();
    }

    return nullptr;
}

bool BuiltInFunctionEmulator::setFunctionCalled(const TFunction *function)
{
    ASSERT(function != nullptr);
    return setFunctionCalled(function->uniqueId().get());
}

bool BuiltInFunctionEmulator::setFunctionCalled(int uniqueId)
{
    if (!findEmulatedFunction(uniqueId))
    {
        return false;
    }

    for (size_t i = 0; i < mFunctions.size(); ++i)
    {
        if (mFunctions[i] == uniqueId)
            return true;
    }
    // If the function depends on another, mark the dependency as called.
    auto dependency = mFunctionDependencies.find(uniqueId);
    if (dependency != mFunctionDependencies.end())
    {
        setFunctionCalled((*dependency).second);
    }
    mFunctions.push_back(uniqueId);
    return true;
}

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

    if (mEmulatedFunctions.empty() && mQueryFunctions.empty())
        return;

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

void BuiltInFunctionEmulator::cleanup()
{
    mFunctions.clear();
    mFunctionDependencies.clear();
}

void BuiltInFunctionEmulator::addFunctionMap(BuiltinQueryFunc queryFunc)
{
    mQueryFunctions.push_back(queryFunc);
}

// static
void BuiltInFunctionEmulator::WriteEmulatedFunctionName(TInfoSinkBase &out, const char *name)
{
    ASSERT(name[strlen(name) - 1] != '(');
    out << name << "_emu";
}

}  // namespace sh
