//
// Copyright 2022 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.
//
// trace_interpreter.cpp:
//   Parser and interpreter for the C-based replays.
//

#include "trace_interpreter.h"
#include "common/unsafe_buffers.h"

#include "anglebase/no_destructor.h"
#include "common/gl_enum_utils.h"
#include "common/string_utils.h"
#include "trace_fixture.h"

#define USE_SYSTEM_ZLIB
#include "compression_utils_portable.h"

namespace angle
{
namespace
{
bool ShouldParseFile(const std::string &file)
{
    return EndsWith(file, ".c") || EndsWith(file, ".cpp");
}

void ReplayTraceFunction(const TraceFunction &func, const TraceFunctionMap &customFunctions)
{
    for (const CallCapture &call : func)
    {
        ReplayTraceFunctionCall(call, customFunctions);
    }
}

class Parser : angle::NonCopyable
{
  public:
    Parser(const std::string &stream,
           TraceFunctionMap &functionsIn,
           TraceStringMap &stringsIn,
           bool verboseLogging)
        : mStream(stream),
          mFunctions(functionsIn),
          mStrings(stringsIn),
          mIndex(0),
          mVerboseLogging(verboseLogging)
    {}

    void parse()
    {
        while (mIndex < mStream.size())
        {
            if (peek() == '#' || peek() == '/')
            {
                skipLine();
            }
            else if (peek() == 'v')
            {
                ASSERT(check("void "));
                readFunction();
            }
            else if (peek() == 'c')
            {
                ASSERT(check("const "));
                readMultilineString();
            }
            else
            {
                printf("Unexpected character: '%c'\n", peek());
                UNREACHABLE();
            }
        }
    }

  private:
    ANGLE_INLINE char peek() const { return mStream[mIndex]; }

    ANGLE_INLINE char look(size_t ahead) const { return mStream[mIndex + ahead]; }

    ANGLE_INLINE void advance() { mIndex++; }

    ANGLE_INLINE void advanceTo(char delim)
    {
        while (peek() != delim)
        {
            advance();
        }
    }

    bool check(const char *forString) const
    {
        return mStream.substr(mIndex, strlen(forString)) == forString;
    }

    void skipLine()
    {
        advanceTo('\n');
        skipWhitespace();
    }

    void skipWhitespace()
    {
        while (isspace(peek()))
        {
            advance();
        }
    }

    void skipNonWhitespace()
    {
        while (!isspace(peek()))
        {
            advance();
        }
    }

    // In our simplified trace C, every line that begins with a } either ends a function or a
    // string. All lines inside the function begin with whitespace. So to find the end of the
    // function we just need to scan for a line beginning with }.
    void skipFunction()
    {
        while (peek() != '}')
        {
            advanceTo('\n');
            advance();
        }
        advance();
        skipWhitespace();
    }

    void readStringAppend(std::string *stringOut, char delim)
    {
        while (peek() != delim)
        {
            if (peek() == '\\')
            {
                advance();
                switch (peek())
                {
                    case 'n':
                        *stringOut += '\n';
                        break;
                    case '\"':
                        *stringOut += '\"';
                        break;
                    case '\\':
                        *stringOut += '\\';
                        break;
                    default:
                        printf("Unrecognized escape character: \\%c\n", peek());
                        UNREACHABLE();
                        break;
                }
            }
            else
            {
                *stringOut += peek();
            }
            advance();
        }
    }

    void readToken(Token &token, char delim)
    {
        size_t startIndex = mIndex;
        advanceTo(delim);
        size_t tokenSize = mIndex - startIndex;
        ASSERT(tokenSize < kMaxTokenSize);
        ANGLE_UNSAFE_TODO(memcpy(token, &mStream[startIndex], tokenSize));
        ANGLE_UNSAFE_TODO(token[mIndex - startIndex]) = 0;
    }

    void skipCast()
    {
        if (peek() == '(')
        {
            advanceTo(')');
            advance();
        }
    }

    void skipComments()
    {
        while (peek() == '/')
        {
            skipLine();
        }
    }

    void readFunction()
    {
        std::string funcName;
        TraceFunction func;

        // Skip past the "void" return value.
        skipNonWhitespace();
        advance();
        readStringAppend(&funcName, '(');
        if (mVerboseLogging)
        {
            printf("function: %s\n", funcName.c_str());
        }

        // Skip this function because of the switch statements.
        if (funcName == "ReplayFrame")
        {
            skipFunction();
            return;
        }

        skipLine();
        ASSERT(peek() == '{');
        skipLine();
        while (peek() != '}')
        {
            skipComments();

            Token nameToken;
            readToken(nameToken, '(');
            advance();
            ParamBuffer params;
            Token paramTokens[kMaxParameters];
            size_t numParams = 0;
            skipCast();
            size_t tokenStart = mIndex;
            while (peek() != ';')
            {
                // Skip casts.
                if (peek() == ',' || (peek() == ')' && mIndex != tokenStart))
                {
                    ASSERT(numParams < kMaxParameters);
                    size_t tokenSize = mIndex - tokenStart;
                    ASSERT(tokenSize < kMaxTokenSize);
                    Token &token = ANGLE_UNSAFE_TODO(paramTokens[numParams++]);

                    ANGLE_UNSAFE_TODO(memcpy(token, &mStream[tokenStart], tokenSize));
                    ANGLE_UNSAFE_TODO(token[tokenSize]) = 0;
                    advance();
                    skipWhitespace();
                    skipCast();
                    tokenStart = mIndex;
                }
                else
                {
                    advance();
                }
            }

            // Turn on if you want more spam.
            // if (mVerboseLogging)
            //{
            //    printf("call: %s(", nameToken);
            //    for (size_t paramIndex = 0; paramIndex < numParams; ++paramIndex)
            //    {
            //        if (paramIndex > 0)
            //        {
            //            printf(", ");
            //        }
            //        printf("%s", paramTokens[paramIndex]);
            //    }
            //    printf(")\n");
            //}

            // We pass in the strings for specific use with C string array parameters.
            CallCapture call = ParseCallCapture(nameToken, numParams, paramTokens, mStrings);
            func.push_back(std::move(call));
            skipLine();
        }
        skipLine();

        addFunction(funcName, func);
    }

    void readMultilineString()
    {
        std::string name;
        TraceString traceStr;

        while (peek() != 'g')
        {
            advance();
        }
        ASSERT(check("glShaderSource") || check("glTransformFeedbackVaryings"));

        readStringAppend(&name, '[');
        if (mVerboseLogging)
        {
            printf("string: %s\n", name.c_str());
        }
        skipLine();
        std::string str;
        while (peek() != '}')
        {
            advance();
            readStringAppend(&str, '\"');
            advance();
            if (peek() == ',')
            {
                traceStr.strings.push_back(std::move(str));
            }
            skipLine();
        }
        skipLine();

        for (const std::string &cppstr : traceStr.strings)
        {
            traceStr.pointers.push_back(cppstr.c_str());
        }

        mStrings[name] = std::move(traceStr);
    }

    void addFunction(const std::string &funcName, TraceFunction &func)
    {
        // Run initialize immediately so we can load the binary data.
        if (funcName == "InitReplay")
        {
            ReplayTraceFunction(func, {});
            func.clear();
        }
        mFunctions[funcName] = std::move(func);
    }

    const std::string &mStream;
    TraceFunctionMap &mFunctions;
    TraceStringMap &mStrings;
    size_t mIndex;
    bool mVerboseLogging = false;
};

void PackResourceID(ParamBuffer &params, const Token &token)
{
    ASSERT(token[0] == 'g');
    const char *start = ANGLE_UNSAFE_TODO(strrchr(token, '['));
    ASSERT(start != nullptr && EndsWith(token, "]"));
    uint32_t value = static_cast<uint32_t>(atoi(ANGLE_UNSAFE_TODO(start + 1)));
    if (BeginsWith(token, "gShaderProgramMap"))
    {
        gl::ShaderProgramID id = {value};
        params.addUnnamedParam(ParamType::TShaderProgramID, id);
    }
    else if (BeginsWith(token, "gBufferMap"))
    {
        gl::BufferID id = {value};
        params.addUnnamedParam(ParamType::TBufferID, id);
    }
    else if (BeginsWith(token, "gTextureMap"))
    {
        gl::TextureID id = {value};
        params.addUnnamedParam(ParamType::TTextureID, id);
    }
    else if (BeginsWith(token, "gRenderbufferMap"))
    {
        gl::RenderbufferID id = {value};
        params.addUnnamedParam(ParamType::TRenderbufferID, id);
    }
    else if (BeginsWith(token, "gFramebufferMap"))
    {
        gl::FramebufferID id = {value};
        params.addUnnamedParam(ParamType::TFramebufferID, id);
    }
    else if (BeginsWith(token, "gSyncMap"))
    {
        gl::SyncID id = {value};
        params.addUnnamedParam(ParamType::TSyncID, id);
    }
    else if (BeginsWith(token, "gTransformFeedbackMap"))
    {
        gl::TransformFeedbackID id = {value};
        params.addUnnamedParam(ParamType::TTransformFeedbackID, id);
    }
    else if (BeginsWith(token, "gVertexArrayMap"))
    {
        gl::VertexArrayID id = {value};
        params.addUnnamedParam(ParamType::TVertexArrayID, id);
    }
    else if (BeginsWith(token, "gQueryMap"))
    {
        gl::QueryID id = {value};
        params.addUnnamedParam(ParamType::TQueryID, id);
    }
    else if (BeginsWith(token, "gSamplerMap"))
    {
        gl::SamplerID id = {value};
        params.addUnnamedParam(ParamType::TSamplerID, id);
    }
    else
    {
        ANGLE_UNSAFE_TODO(printf("Unknown resource map: %s\n", token));
        UNREACHABLE();
    }
}

template <typename IntT>
void PackIntParameter(ParamBuffer &params, ParamType paramType, const Token &token)
{
    IntT value;

    if (token[0] == 'G')
    {
        ASSERT(BeginsWith(token, "GL_"));
        if (ANGLE_UNSAFE_TODO(strchr(token, '|')) == 0)
        {
            value = static_cast<IntT>(gl::StringToGLenum(token));
        }
        else
        {
            value = static_cast<IntT>(gl::StringToGLbitfield(token));
        }
    }
    else
    {
        if (!isdigit(token[0]) && !(token[0] == '-' && isdigit(token[1])))
        {
            ANGLE_UNSAFE_TODO(printf("Expected number, got %s\n", token));
            UNREACHABLE();
        }
        if (token[0] == '0' && token[1] == 'x')
        {
            value = static_cast<IntT>(ANGLE_UNSAFE_TODO(strtol(token, nullptr, 16)));
        }
        else
        {
            value = static_cast<IntT>(atoi(token));
        }
    }

    params.addUnnamedParam(paramType, value);
}

uint32_t GetStringArrayOffset(const Token &token, const char *prefixString)
{
    const char *offsetString = &ANGLE_UNSAFE_TODO(token[strlen(prefixString)]);
    return atoi(offsetString);
}

template <typename PointerT>
void PackMemPointer(ParamBuffer &params, ParamType paramType, uint32_t offset, uint8_t *mem)
{
    ASSERT(gBinaryData);
    params.addUnnamedParam(paramType, reinterpret_cast<PointerT>(&ANGLE_UNSAFE_TODO(mem[offset])));
}

template <typename T>
void PackMutablePointerParameter(ParamBuffer &params, ParamType paramType, const Token &token)
{
    if (token[0] == '0' && token[1] == 0)
    {
        params.addUnnamedParam(paramType, reinterpret_cast<T *>(0));
    }
    else if (token[0] == '&')
    {
        ASSERT(BeginsWith(token, "&gReadBuffer[") && EndsWith(token, "]"));
        uint32_t offset = GetStringArrayOffset(token, "&gReadBuffer[");
        PackMemPointer<T *>(params, paramType, offset, gReadBuffer);
    }
    else if (token[0] == 'g')
    {
        ANGLE_UNSAFE_TODO(ASSERT(strcmp(token, "gReadBuffer") == 0));
        params.addUnnamedParam(paramType, reinterpret_cast<T *>(gReadBuffer));
    }
    else
    {
        UNREACHABLE();
    }
}

template <typename T>
void PackConstPointerParameter(ParamBuffer &params, ParamType paramType, const Token &token)
{
    // Handle nullptr, the literal "0".
    if (token[0] == '0' && token[1] == 0)
    {
        params.addUnnamedParam(paramType, reinterpret_cast<const T *>(0));
    }
    else if (token[0] == '&')
    {
        ASSERT(BeginsWith(token, "&gBinaryData[") && EndsWith(token, "]"));
        uint32_t offset = GetStringArrayOffset(token, "&gReadBuffer[");
        PackMemPointer<const T *>(params, paramType, offset, gBinaryData);
    }
    else if (token[0] == 'g')
    {
        if (ANGLE_UNSAFE_TODO(strcmp(token, "gResourceIDBuffer")) == 0)
        {
            params.addUnnamedParam(paramType, reinterpret_cast<const T *>(gResourceIDBuffer));
        }
        else if (BeginsWith(token, "gClientArrays"))
        {
            uint32_t offset = GetStringArrayOffset(token, "gClientArrays[");
            params.addUnnamedParam(
                paramType, reinterpret_cast<const T *>(ANGLE_UNSAFE_TODO(gClientArrays[offset])));
        }
        else
        {
            ANGLE_UNSAFE_TODO(printf("Unexpected token: %s\n", token));
            UNREACHABLE();
        }
    }
    else
    {
        ASSERT(isdigit(token[0]));
        uint32_t offset = atoi(token);
        params.addUnnamedParam(paramType,
                               reinterpret_cast<const T *>(static_cast<uintptr_t>(offset)));
    }
}

class TraceInterpreter : angle::NonCopyable
{
  public:
    TraceInterpreter()  = default;
    ~TraceInterpreter() = default;

    void replayFrame(uint32_t frameIndex);
    void setupReplay();
    void resetReplay();
    void setupFirstFrame();
    const char *getSerializedContextState(uint32_t frameIndex);

  private:
    void runTraceFunction(const char *name) const;
    void parseTraceUncompressed();
    void parseTraceGz();

    TraceFunctionMap mTraceFunctions;
    TraceStringMap mTraceStrings;
    bool mVerboseLogging = true;
};

void TraceInterpreter::replayFrame(uint32_t frameIndex)
{
    char funcName[kMaxTokenSize];
    snprintf(funcName, kMaxTokenSize, "ReplayFrame%u", frameIndex);
    runTraceFunction(funcName);
}

void TraceInterpreter::parseTraceUncompressed()
{
    for (const std::string &file : gTraceInfo.traceFiles)
    {
        if (!ShouldParseFile(file))
        {
            if (mVerboseLogging)
            {
                printf("Skipping function parsing for %s.\n", file.c_str());
            }
            continue;
        }

        if (mVerboseLogging)
        {
            printf("Parsing functions from %s\n", file.c_str());
        }
        std::stringstream pathStream;
        pathStream << gBinaryDataDir << GetPathSeparator() << file;
        std::string path = pathStream.str();

        std::string fileData;
        if (!ReadFileToString(path, &fileData))
        {
            UNREACHABLE();
        }

        Parser parser(fileData, mTraceFunctions, mTraceStrings, mVerboseLogging);
        parser.parse();
    }
}

void TraceInterpreter::parseTraceGz()
{
    if (mVerboseLogging)
    {
        printf("Parsing functions from %s\n", gTraceGzPath.c_str());
    }

    FILE *fp = fopen(gTraceGzPath.c_str(), "rb");
    if (fp == 0)
    {
        printf("Error loading trace (gz) from: %s\n", gTraceGzPath.c_str());
        exit(1);
    }

    fseek(fp, 0, SEEK_END);
    long size = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    std::vector<uint8_t> compressedData(size);
    (void)ANGLE_UNSAFE_TODO(fread(compressedData.data(), 1, size, fp));

    uint32_t uncompressedSize =
        zlib_internal::GetGzipUncompressedSize(compressedData.data(), compressedData.size());

    std::string uncompressedData(uncompressedSize, 0);
    uLong destLen = uncompressedSize;
    int zResult = zlib_internal::GzipUncompressHelper((uint8_t *)uncompressedData.data(), &destLen,
                                                      compressedData.data(),
                                                      static_cast<uLong>(compressedData.size()));

    if (zResult != Z_OK)
    {
        printf("Failure to decompress gz trace: %s\n", gTraceGzPath.c_str());
        exit(1);
    }

    Parser parser(uncompressedData, mTraceFunctions, mTraceStrings, mVerboseLogging);
    parser.parse();
}

void TraceInterpreter::setupReplay()
{
    if (!gTraceGzPath.empty())
    {
        parseTraceGz();
    }
    else
    {
        parseTraceUncompressed();
    }

    if (mTraceFunctions.count("SetupReplay") == 0)
    {
        printf("Did not find a SetupReplay function to run among %zu parsed functions.\n",
               mTraceFunctions.size());
        exit(1);
    }

    runTraceFunction("SetupReplay");
}

void TraceInterpreter::resetReplay()
{
    runTraceFunction("ResetReplay");
}

void TraceInterpreter::setupFirstFrame()
{
    runTraceFunction("SetupFirstFrame");
}

const char *TraceInterpreter::getSerializedContextState(uint32_t frameIndex)
{
    // TODO: Necessary for complete self-testing. http://anglebug.com/42266250
    UNREACHABLE();
    return nullptr;
}

void TraceInterpreter::runTraceFunction(const char *name) const
{
    auto iter = mTraceFunctions.find(name);
    if (iter == mTraceFunctions.end())
    {
        ANGLE_UNSAFE_TODO(printf("Cannot find function: %s\n", name));
        UNREACHABLE();
    }
    const TraceFunction &func = iter->second;
    ReplayTraceFunction(func, mTraceFunctions);
}

TraceInterpreter &GetInterpreter()
{
    static angle::base::NoDestructor<std::unique_ptr<TraceInterpreter>> sTraceInterpreter(
        new TraceInterpreter());
    return *sTraceInterpreter.get()->get();
}
}  // anonymous namespace

template <>
void PackParameter<uint32_t>(ParamBuffer &params, const Token &token, const TraceStringMap &strings)
{
    if (token[0] == 'g')
    {
        PackResourceID(params, token);
    }
    else
    {
        PackIntParameter<uint32_t>(params, ParamType::TGLuint, token);
    }
}

template <>
void PackParameter<int32_t>(ParamBuffer &params, const Token &token, const TraceStringMap &strings)
{
    if (BeginsWith(token, "gUniformLocations"))
    {
        const char *start = ANGLE_UNSAFE_TODO(strrchr(token, '['));
        ASSERT(start != nullptr && EndsWith(token, "]"));
        int32_t value           = atoi(ANGLE_UNSAFE_TODO(start + 1));
        gl::UniformLocation loc = {value};
        params.addUnnamedParam(ParamType::TUniformLocation, loc);
    }
    else
    {
        PackIntParameter<int32_t>(params, ParamType::TGLint, token);
    }
}

template <>
void PackParameter<void *>(ParamBuffer &params, const Token &token, const TraceStringMap &strings)
{
    void *value = 0;
    params.addUnnamedParam(ParamType::TvoidPointer, value);
}

template <>
void PackParameter<const int32_t *>(ParamBuffer &params,
                                    const Token &token,
                                    const TraceStringMap &strings)
{
    PackConstPointerParameter<int32_t>(params, ParamType::TGLintConstPointer, token);
}

template <>
void PackParameter<void **>(ParamBuffer &params, const Token &token, const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<int32_t *>(ParamBuffer &params,
                              const Token &token,
                              const TraceStringMap &strings)
{
    PackMutablePointerParameter<int32_t>(params, ParamType::TGLintPointer, token);
}

template <>
void PackParameter<uint64_t>(ParamBuffer &params, const Token &token, const TraceStringMap &strings)
{
    params.addUnnamedParam(
        ParamType::TGLuint64,
        static_cast<GLuint64>(ANGLE_UNSAFE_TODO(std::strtoull(token, nullptr, 10))));
}

template <>
void PackParameter<int64_t>(ParamBuffer &params, const Token &token, const TraceStringMap &strings)
{
    params.addUnnamedParam(
        ParamType::TGLint64,
        static_cast<GLint64>(ANGLE_UNSAFE_TODO(std::strtoll(token, nullptr, 10))));
}

template <>
void PackParameter<const int64_t *>(ParamBuffer &params,
                                    const Token &token,
                                    const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<int64_t *>(ParamBuffer &params,
                              const Token &token,
                              const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<uint64_t *>(ParamBuffer &params,
                               const Token &token,
                               const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<const char *>(ParamBuffer &params,
                                 const Token &token,
                                 const TraceStringMap &strings)
{
    if (token[0] == '"')
    {
        ASSERT(EndsWith(token, "\""));

        ParamCapture param(params.getNextParamName(), ParamType::TGLcharConstPointer);
        std::vector<uint8_t> data(&token[1], &ANGLE_UNSAFE_TODO(token[strlen(token) - 1]));
        data.push_back(0);
        param.data.push_back(std::move(data));
        param.value.GLcharConstPointerVal = reinterpret_cast<const char *>(param.data[0].data());
        params.addParam(std::move(param));
    }
    else
    {
        PackConstPointerParameter<char>(params, ParamType::TGLcharConstPointer, token);
    }
}

template <>
void PackParameter<const void *>(ParamBuffer &params,
                                 const Token &token,
                                 const TraceStringMap &strings)
{
    PackConstPointerParameter<void>(params, ParamType::TvoidConstPointer, token);
}

template <>
void PackParameter<uint32_t *>(ParamBuffer &params,
                               const Token &token,
                               const TraceStringMap &strings)
{
    PackMutablePointerParameter<uint32_t>(params, ParamType::TGLuintPointer, token);
}

template <>
void PackParameter<const uint32_t *>(ParamBuffer &params,
                                     const Token &token,
                                     const TraceStringMap &strings)
{
    PackConstPointerParameter<uint32_t>(params, ParamType::TGLuintConstPointer, token);
}

template <>
void PackParameter<float>(ParamBuffer &params, const Token &token, const TraceStringMap &strings)
{
    params.addUnnamedParam(ParamType::TGLfloat, std::stof(token));
}

template <>
void PackParameter<uint8_t>(ParamBuffer &params, const Token &token, const TraceStringMap &strings)
{
    PackIntParameter<uint8_t>(params, ParamType::TGLubyte, token);
}

template <>
void PackParameter<float *>(ParamBuffer &params, const Token &token, const TraceStringMap &strings)
{
    PackMutablePointerParameter<float>(params, ParamType::TGLfloatPointer, token);
}

template <>
void PackParameter<const float *>(ParamBuffer &params,
                                  const Token &token,
                                  const TraceStringMap &strings)
{
    PackConstPointerParameter<float>(params, ParamType::TGLfloatConstPointer, token);
}

template <>
void PackParameter<GLsync>(ParamBuffer &params, const Token &token, const TraceStringMap &strings)
{
    PackResourceID(params, token);
}

template <>
void PackParameter<const char *const *>(ParamBuffer &params,
                                        const Token &token,
                                        const TraceStringMap &strings)
{
    // Find the string that corresponds to "token". Currently we only support string arrays.
    auto iter = strings.find(token);
    if (iter == strings.end())
    {
        ANGLE_UNSAFE_TODO(printf("Could not find string: %s\n", token));
        UNREACHABLE();
    }
    const TraceString &traceStr = iter->second;
    params.addUnnamedParam(ParamType::TGLcharConstPointerPointer, traceStr.pointers.data());
}

template <>
void PackParameter<const char **>(ParamBuffer &params,
                                  const Token &token,
                                  const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<GLDEBUGPROCKHR>(ParamBuffer &params,
                                   const Token &token,
                                   const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<EGLDEBUGPROCKHR>(ParamBuffer &params,
                                    const Token &token,
                                    const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<const struct AHardwareBuffer *>(ParamBuffer &params,
                                                   const Token &token,
                                                   const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<EGLSetBlobFuncANDROID>(ParamBuffer &params,
                                          const Token &token,
                                          const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<EGLGetBlobFuncANDROID>(ParamBuffer &params,
                                          const Token &token,
                                          const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<int16_t>(ParamBuffer &params, const Token &token, const TraceStringMap &strings)
{
    PackIntParameter<int16_t>(params, ParamType::TGLshort, token);
}

template <>
void PackParameter<const int16_t *>(ParamBuffer &params,
                                    const Token &token,
                                    const TraceStringMap &strings)
{
    PackConstPointerParameter<int16_t>(params, ParamType::TGLshortConstPointer, token);
}

template <>
void PackParameter<char *>(ParamBuffer &params, const Token &token, const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<unsigned char *>(ParamBuffer &params,
                                    const Token &token,
                                    const TraceStringMap &strings)
{
    PackMutablePointerParameter<GLubyte>(params, ParamType::TGLubytePointer, token);
}

template <>
void PackParameter<const void *const *>(ParamBuffer &params,
                                        const Token &token,
                                        const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<const uint64_t *>(ParamBuffer &params,
                                     const Token &token,
                                     const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<GLGETBLOBPROCANGLE>(ParamBuffer &params,
                                       const Token &token,
                                       const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<GLSETBLOBPROCANGLE>(ParamBuffer &params,
                                       const Token &token,
                                       const TraceStringMap &strings)
{
    UNREACHABLE();
}

#if defined(ANGLE_PLATFORM_WINDOWS)
template <>
void PackParameter<EGLNativeDisplayType>(ParamBuffer &params,
                                         const Token &token,
                                         const TraceStringMap &strings)
{
    UNREACHABLE();
}
#endif  // defined(ANGLE_PLATFORM_WINDOWS)

#if defined(ANGLE_PLATFORM_WINDOWS) || defined(ANGLE_PLATFORM_ANDROID)
template <>
void PackParameter<EGLNativeWindowType>(ParamBuffer &params,
                                        const Token &token,
                                        const TraceStringMap &strings)
{
    UNREACHABLE();
}

template <>
void PackParameter<EGLNativePixmapType>(ParamBuffer &params,
                                        const Token &token,
                                        const TraceStringMap &strings)
{
    UNREACHABLE();
}
#endif  // defined(ANGLE_PLATFORM_WINDOWS) || defined(ANGLE_PLATFORM_ANDROID)

#if defined(ANGLE_PLATFORM_APPLE) || !defined(ANGLE_IS_64_BIT_CPU)
template <>
void PackParameter<const long *>(ParamBuffer &params,
                                 const Token &token,
                                 const TraceStringMap &strings)
{
    PackConstPointerParameter<int64_t>(params, ParamType::TGLuint64ConstPointer, token);
}

template <>
void PackParameter<long *>(ParamBuffer &params, const Token &token, const TraceStringMap &strings)
{
    PackMutablePointerParameter<int64_t>(params, ParamType::TGLint64Pointer, token);
}

template <>
void PackParameter<long>(ParamBuffer &params, const Token &token, const TraceStringMap &strings)
{
    PackIntParameter<int64_t>(params, ParamType::TGLint64, token);
}

template <>
void PackParameter<unsigned long>(ParamBuffer &params,
                                  const Token &token,
                                  const TraceStringMap &strings)
{
    PackIntParameter<uint64_t>(params, ParamType::TGLuint64, token);
}
#endif  // defined(ANGLE_PLATFORM_APPLE) || !defined(ANGLE_IS_64_BIT_CPU)

GLuint GetResourceIDMapValue(ResourceIDType resourceIDType, GLuint key)
{
    switch (resourceIDType)
    {
        case ResourceIDType::Buffer:
            return ANGLE_UNSAFE_TODO(gBufferMap[key]);
        case ResourceIDType::FenceNV:
            return ANGLE_UNSAFE_TODO(gFenceNVMap[key]);
        case ResourceIDType::Framebuffer:
            return ANGLE_UNSAFE_TODO(gFramebufferMap[key]);
        case ResourceIDType::ProgramPipeline:
            return ANGLE_UNSAFE_TODO(gProgramPipelineMap[key]);
        case ResourceIDType::Query:
            return ANGLE_UNSAFE_TODO(gQueryMap[key]);
        case ResourceIDType::Renderbuffer:
            return ANGLE_UNSAFE_TODO(gRenderbufferMap[key]);
        case ResourceIDType::Sampler:
            return ANGLE_UNSAFE_TODO(gSamplerMap[key]);
        case ResourceIDType::Semaphore:
            return ANGLE_UNSAFE_TODO(gSemaphoreMap[key]);
        case ResourceIDType::ShaderProgram:
            return ANGLE_UNSAFE_TODO(gShaderProgramMap[key]);
        case ResourceIDType::Texture:
            return ANGLE_UNSAFE_TODO(gTextureMap[key]);
        case ResourceIDType::TransformFeedback:
            return ANGLE_UNSAFE_TODO(gTransformFeedbackMap[key]);
        case ResourceIDType::VertexArray:
            return ANGLE_UNSAFE_TODO(gVertexArrayMap[key]);
        default:
            printf("Incompatible resource ID type: %d\n", static_cast<int>(resourceIDType));
            UNREACHABLE();
            return 0;
    }
}

}  // namespace angle

extern "C" {
void SetupReplay()
{
    angle::GetInterpreter().setupReplay();
}

void ReplayFrame(uint32_t frameIndex)
{
    angle::GetInterpreter().replayFrame(frameIndex);
}

void ResetReplay()
{
    angle::GetInterpreter().resetReplay();
}

const char *GetSerializedContextState(uint32_t frameIndex)
{
    return angle::GetInterpreter().getSerializedContextState(frameIndex);
}
}  // extern "C"
