//
// Copyright 2015 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.
//
// compiler_test.cpp:
//     utilities for compiler unit tests.

#include "tests/test_utils/compiler_test.h"

#include "angle_gl.h"
#include "compiler/translator/Compiler.h"
#include "compiler/translator/FunctionLookup.h"
#include "compiler/translator/tree_util/IntermTraverse.h"

namespace sh
{

namespace
{
constexpr char kBinaryBlob[] = "<binary blob>";
bool IsBinaryBlob(const std::string &code)
{
    return code == kBinaryBlob;
}

ImmutableString GetSymbolTableMangledName(TIntermAggregate *node)
{
    ASSERT(!node->isConstructor());
    return TFunctionLookup::GetMangledName(node->getFunction()->name().data(),
                                           *node->getSequence());
}

class FunctionCallFinder : public TIntermTraverser
{
  public:
    FunctionCallFinder(const char *functionMangledName)
        : TIntermTraverser(true, false, false),
          mFunctionMangledName(functionMangledName),
          mNodeFound(nullptr)
    {}

    bool visitAggregate(Visit visit, TIntermAggregate *node) override
    {
        if (!node->isConstructor() && GetSymbolTableMangledName(node) == mFunctionMangledName)
        {
            mNodeFound = node;
            return false;
        }
        return true;
    }

    bool isFound() const { return mNodeFound != nullptr; }
    const TIntermAggregate *getNode() const { return mNodeFound; }

  private:
    const char *mFunctionMangledName;
    TIntermAggregate *mNodeFound;
};

}  // anonymous namespace

bool compileTestShader(GLenum type,
                       ShShaderSpec spec,
                       ShShaderOutput output,
                       const std::string &shaderString,
                       ShBuiltInResources *resources,
                       const ShCompileOptions &compileOptions,
                       std::string *translatedCode,
                       std::string *infoLog)
{
    sh::TCompiler *translator = sh::ConstructCompiler(type, spec, output);
    if (!translator->Init(*resources))
    {
        SafeDelete(translator);
        return false;
    }

    const char *shaderStrings[] = {shaderString.c_str()};

    ShCompileOptions options = compileOptions;
    options.objectCode       = true;
    bool compilationSuccess  = translator->compile(shaderStrings, options);
    TInfoSink &infoSink      = translator->getInfoSink();
    if (translatedCode)
    {
        *translatedCode = infoSink.obj.isBinary() ? kBinaryBlob : infoSink.obj.c_str();
    }
    if (infoLog)
    {
        *infoLog = infoSink.info.c_str();
    }
    SafeDelete(translator);
    return compilationSuccess;
}

bool compileTestShader(GLenum type,
                       ShShaderSpec spec,
                       ShShaderOutput output,
                       const std::string &shaderString,
                       const ShCompileOptions &compileOptions,
                       std::string *translatedCode,
                       std::string *infoLog)
{
    ShBuiltInResources resources;
    sh::InitBuiltInResources(&resources);
    return compileTestShader(type, spec, output, shaderString, &resources, compileOptions,
                             translatedCode, infoLog);
}

MatchOutputCodeTest::MatchOutputCodeTest(GLenum shaderType, ShShaderOutput outputType)
    : mShaderType(shaderType), mDefaultCompileOptions{}
{
    sh::InitBuiltInResources(&mResources);
    mOutputCode[outputType]          = std::string();
}

void MatchOutputCodeTest::setDefaultCompileOptions(const ShCompileOptions &defaultCompileOptions)
{
    mDefaultCompileOptions = defaultCompileOptions;
}

void MatchOutputCodeTest::addOutputType(const ShShaderOutput outputType)
{
    mOutputCode[outputType] = std::string();
}

ShBuiltInResources *MatchOutputCodeTest::getResources()
{
    return &mResources;
}

void MatchOutputCodeTest::compile(const std::string &shaderString)
{
    compile(shaderString, mDefaultCompileOptions);
}

void MatchOutputCodeTest::compile(const std::string &shaderString,
                                  const ShCompileOptions &compileOptions)
{
    std::string infoLog;
    for (auto &code : mOutputCode)
    {
        const ShShaderOutput output = code.first;
        ShCompileOptions options    = compileOptions;
        if (output == SH_SPIRV_VULKAN_OUTPUT || output == SH_MSL_METAL_OUTPUT)
        {
            options.removeInactiveVariables = true;
            options.retainInactiveFragmentOutputs = output == SH_MSL_METAL_OUTPUT;
        }

        bool compilationSuccess =
            compileWithSettings(output, shaderString, options, &code.second, &infoLog);
        if (!compilationSuccess)
        {
            FAIL() << "Shader compilation failed:\n" << infoLog;
        }
    }
}

bool MatchOutputCodeTest::compileWithSettings(ShShaderOutput output,
                                              const std::string &shaderString,
                                              const ShCompileOptions &compileOptions,
                                              std::string *translatedCode,
                                              std::string *infoLog)
{
    return compileTestShader(mShaderType, SH_GLES3_1_SPEC, output, shaderString, &mResources,
                             compileOptions, translatedCode, infoLog);
}

bool MatchOutputCodeTest::foundInCodeRegex(ShShaderOutput output,
                                           const std::regex &regexToFind,
                                           std::smatch *match) const
{
    const auto code = mOutputCode.find(output);
    EXPECT_NE(mOutputCode.end(), code);
    if (code == mOutputCode.end())
    {
        return std::string::npos;
    }

    // No meaningful check for binary blobs
    if (IsBinaryBlob(code->second))
    {
        return true;
    }

    if (match)
    {
        return std::regex_search(code->second, *match, regexToFind);
    }
    else
    {
        return std::regex_search(code->second, regexToFind);
    }
}

bool MatchOutputCodeTest::foundInCode(ShShaderOutput output, const char *stringToFind) const
{
    const auto code = mOutputCode.find(output);
    EXPECT_NE(mOutputCode.end(), code);
    if (code == mOutputCode.end())
    {
        return std::string::npos;
    }

    // No meaningful check for binary blobs
    if (IsBinaryBlob(code->second))
    {
        return true;
    }

    return code->second.find(stringToFind) != std::string::npos;
}

bool MatchOutputCodeTest::foundInCodeInOrder(ShShaderOutput output,
                                             std::vector<const char *> stringsToFind)
{
    const auto code = mOutputCode.find(output);
    EXPECT_NE(mOutputCode.end(), code);
    if (code == mOutputCode.end())
    {
        return false;
    }

    // No meaningful check for binary blobs
    if (IsBinaryBlob(code->second))
    {
        return true;
    }

    size_t currentPos = 0;
    for (const char *stringToFind : stringsToFind)
    {
        auto position = code->second.find(stringToFind, currentPos);
        if (position == std::string::npos)
        {
            return false;
        }
        currentPos = position + strlen(stringToFind);
    }
    return true;
}

bool MatchOutputCodeTest::foundInCode(ShShaderOutput output,
                                      const char *stringToFind,
                                      const int expectedOccurrences) const
{
    const auto code = mOutputCode.find(output);
    EXPECT_NE(mOutputCode.end(), code);
    if (code == mOutputCode.end())
    {
        return false;
    }

    // No meaningful check for binary blobs
    if (IsBinaryBlob(code->second))
    {
        return true;
    }

    size_t currentPos  = 0;
    int occurencesLeft = expectedOccurrences;

    const size_t searchStringLength = strlen(stringToFind);

    while (occurencesLeft-- > 0)
    {
        auto position = code->second.find(stringToFind, currentPos);
        if (position == std::string::npos)
        {
            return false;
        }
        // Search strings should not overlap.
        currentPos = position + searchStringLength;
    }
    // Make sure that there aren't extra occurrences.
    return code->second.find(stringToFind, currentPos) == std::string::npos;
}

bool MatchOutputCodeTest::foundInCode(const char *stringToFind) const
{
    for (auto &code : mOutputCode)
    {
        if (!foundInCode(code.first, stringToFind))
        {
            return false;
        }
    }
    return true;
}

bool MatchOutputCodeTest::foundInCodeRegex(const std::regex &regexToFind, std::smatch *match) const
{
    for (auto &code : mOutputCode)
    {
        if (!foundInCodeRegex(code.first, regexToFind, match))
        {
            return false;
        }
    }
    return true;
}

bool MatchOutputCodeTest::foundInCode(const char *stringToFind, const int expectedOccurrences) const
{
    for (auto &code : mOutputCode)
    {
        if (!foundInCode(code.first, stringToFind, expectedOccurrences))
        {
            return false;
        }
    }
    return true;
}

bool MatchOutputCodeTest::foundInCodeInOrder(std::vector<const char *> stringsToFind)
{
    for (auto &code : mOutputCode)
    {
        if (!foundInCodeInOrder(code.first, stringsToFind))
        {
            return false;
        }
    }
    return true;
}

bool MatchOutputCodeTest::notFoundInCode(const char *stringToFind) const
{
    for (auto &code : mOutputCode)
    {
        // No meaningful check for binary blobs
        if (IsBinaryBlob(code.second))
        {
            continue;
        }

        if (foundInCode(code.first, stringToFind))
        {
            return false;
        }
    }
    return true;
}

std::string MatchOutputCodeTest::outputCode(ShShaderOutput output) const
{
    const auto code = mOutputCode.find(output);
    EXPECT_NE(mOutputCode.end(), code);
    if (code == mOutputCode.end())
    {
        return {};
    }

    // No meaningful check for binary blobs
    if (IsBinaryBlob(code->second))
    {
        return {};
    }

    return code->second;
}

const TIntermAggregate *FindFunctionCallNode(TIntermNode *root, const TString &functionMangledName)
{
    FunctionCallFinder finder(functionMangledName.c_str());
    root->traverse(&finder);
    return finder.getNode();
}

}  // namespace sh
