// Copyright (c) 2015-2016 The Khronos Group Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <algorithm>
#include <vector>

#include "unit_spirv.h"
#include "spirv/1.0/GLSL.std.450.h"

namespace {

/// Context for an extended instruction.
///
/// Information about a GLSL extended instruction (including its opname, return
/// type, etc.) and related instructions used to generate the return type and
/// constant as the operands. Used in generating extended instruction tests.
struct ExtInstContext {
  const char* extInstOpName;
  const char* extInstOperandVars;
  /// The following fields are used to check the SPIR-V binary representation
  /// of this instruction.
  uint32_t extInstOpcode;  ///< Opcode value for this extended instruction.
  uint32_t extInstLength;  ///< Wordcount of this extended instruction.
  std::vector<uint32_t> extInstOperandIds;  ///< Ids for operands.
};

using ExtInstGLSLstd450RoundTripTest = ::testing::TestWithParam<ExtInstContext>;

TEST_P(ExtInstGLSLstd450RoundTripTest, ParameterizedExtInst) {
  spv_context context = spvContextCreate(SPV_ENV_UNIVERSAL_1_0);
  const std::string spirv = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical Simple
OpEntryPoint Vertex %2 "main"
%3 = OpTypeVoid
%4 = OpTypeFunction %3
%2 = OpFunction %3 None %5
%6 = OpLabel
%8 = OpExtInst %7 %1 )" + std::string(GetParam().extInstOpName) +
                            " " + GetParam().extInstOperandVars + R"(
OpReturn
OpFunctionEnd
)";
  const std::string spirv_header =
      R"(; SPIR-V
; Version: 1.0
; Generator: Khronos SPIR-V Tools Assembler; 0
; Bound: 9
; Schema: 0)";
  spv_binary binary;
  spv_diagnostic diagnostic;
  spv_result_t error = spvTextToBinary(context, spirv.c_str(), spirv.size(),
                                       &binary, &diagnostic);
  if (error) {
    spvDiagnosticPrint(diagnostic);
    spvDiagnosticDestroy(diagnostic);
    ASSERT_EQ(SPV_SUCCESS, error)
        << "Source was: " << std::endl
        << spirv << std::endl
        << "Test case for : " << GetParam().extInstOpName << std::endl;
  }

  // Check we do have the extended instruction's corresponding binary code in
  // the generated SPIR-V binary.
  std::vector<uint32_t> expected_contains(
      {12 /*OpExtInst*/ | GetParam().extInstLength << 16, 7 /*return type*/,
       8 /*result id*/, 1 /*glsl450 import*/, GetParam().extInstOpcode});
  for (uint32_t operand : GetParam().extInstOperandIds) {
    expected_contains.push_back(operand);
  }
  EXPECT_NE(binary->code + binary->wordCount,
            std::search(binary->code, binary->code + binary->wordCount,
                        expected_contains.begin(), expected_contains.end()))
      << "Cannot find\n"
      << spvtest::WordVector(expected_contains).str() << "in\n"
      << spvtest::WordVector(*binary).str();

  // Check round trip gives the same text.
  spv_text output_text = nullptr;
  error = spvBinaryToText(context, binary->code, binary->wordCount,
                          SPV_BINARY_TO_TEXT_OPTION_NONE, &output_text,
                          &diagnostic);

  if (error) {
    spvDiagnosticPrint(diagnostic);
    spvDiagnosticDestroy(diagnostic);
    ASSERT_EQ(SPV_SUCCESS, error);
  }
  EXPECT_EQ(spirv_header + spirv, output_text->str);
  spvTextDestroy(output_text);
  spvContextDestroy(context);
}

INSTANTIATE_TEST_CASE_P(
    ExtInstParameters, ExtInstGLSLstd450RoundTripTest,
    ::testing::ValuesIn(std::vector<ExtInstContext>({
        // We are only testing the correctness of encoding and decoding here.
        // Semantic correctness should be the responsibility of validator. So
        // some of the instructions below have incorrect operand and/or return
        // types, e.g, Modf, ModfStruct, etc.
        {"Round", "%5", 1, 6, {5}},
        {"RoundEven", "%5", 2, 6, {5}},
        {"Trunc", "%5", 3, 6, {5}},
        {"FAbs", "%5", 4, 6, {5}},
        {"SAbs", "%5", 5, 6, {5}},
        {"FSign", "%5", 6, 6, {5}},
        {"SSign", "%5", 7, 6, {5}},
        {"Floor", "%5", 8, 6, {5}},
        {"Ceil", "%5", 9, 6, {5}},
        {"Fract", "%5", 10, 6, {5}},
        {"Radians", "%5", 11, 6, {5}},
        {"Degrees", "%5", 12, 6, {5}},
        {"Sin", "%5", 13, 6, {5}},
        {"Cos", "%5", 14, 6, {5}},
        {"Tan", "%5", 15, 6, {5}},
        {"Asin", "%5", 16, 6, {5}},
        {"Acos", "%5", 17, 6, {5}},
        {"Atan", "%5", 18, 6, {5}},
        {"Sinh", "%5", 19, 6, {5}},
        {"Cosh", "%5", 20, 6, {5}},
        {"Tanh", "%5", 21, 6, {5}},
        {"Asinh", "%5", 22, 6, {5}},
        {"Acosh", "%5", 23, 6, {5}},
        {"Atanh", "%5", 24, 6, {5}},
        {"Atan2", "%5 %5", 25, 7, {5, 5}},
        {"Pow", "%5 %5", 26, 7, {5, 5}},
        {"Exp", "%5", 27, 6, {5}},
        {"Log", "%5", 28, 6, {5}},
        {"Exp2", "%5", 29, 6, {5}},
        {"Log2", "%5", 30, 6, {5}},
        {"Sqrt", "%5", 31, 6, {5}},
        {"InverseSqrt", "%5", 32, 6, {5}},
        {"Determinant", "%5", 33, 6, {5}},
        {"MatrixInverse", "%5", 34, 6, {5}},
        {"Modf", "%5 %5", 35, 7, {5, 5}},
        {"ModfStruct", "%5", 36, 6, {5}},
        {"FMin", "%5 %5", 37, 7, {5, 5}},
        {"UMin", "%5 %5", 38, 7, {5, 5}},
        {"SMin", "%5 %5", 39, 7, {5, 5}},
        {"FMax", "%5 %5", 40, 7, {5, 5}},
        {"UMax", "%5 %5", 41, 7, {5, 5}},
        {"SMax", "%5 %5", 42, 7, {5, 5}},
        {"FClamp", "%5 %5 %5", 43, 8, {5, 5, 5}},
        {"UClamp", "%5 %5 %5", 44, 8, {5, 5, 5}},
        {"SClamp", "%5 %5 %5", 45, 8, {5, 5, 5}},
        {"FMix", "%5 %5 %5", 46, 8, {5, 5, 5}},
        {"IMix", "%5 %5 %5", 47, 8, {5, 5, 5}},  // Bug 15452. Reserved.
        {"Step", "%5 %5", 48, 7, {5, 5}},
        {"SmoothStep", "%5 %5 %5", 49, 8, {5, 5, 5}},
        {"Fma", "%5 %5 %5", 50, 8, {5, 5, 5}},
        {"Frexp", "%5 %5", 51, 7, {5, 5}},
        {"FrexpStruct", "%5", 52, 6, {5}},
        {"Ldexp", "%5 %5", 53, 7, {5, 5}},
        {"PackSnorm4x8", "%5", 54, 6, {5}},
        {"PackUnorm4x8", "%5", 55, 6, {5}},
        {"PackSnorm2x16", "%5", 56, 6, {5}},
        {"PackUnorm2x16", "%5", 57, 6, {5}},
        {"PackHalf2x16", "%5", 58, 6, {5}},
        {"PackDouble2x32", "%5", 59, 6, {5}},
        {"UnpackSnorm2x16", "%5", 60, 6, {5}},
        {"UnpackUnorm2x16", "%5", 61, 6, {5}},
        {"UnpackHalf2x16", "%5", 62, 6, {5}},
        {"UnpackSnorm4x8", "%5", 63, 6, {5}},
        {"UnpackUnorm4x8", "%5", 64, 6, {5}},
        {"UnpackDouble2x32", "%5", 65, 6, {5}},
        {"Length", "%5", 66, 6, {5}},
        {"Distance", "%5 %5", 67, 7, {5, 5}},
        {"Cross", "%5 %5", 68, 7, {5, 5}},
        {"Normalize", "%5", 69, 6, {5}},
        // clang-format off
        {"FaceForward", "%5 %5 %5", 70, 8, {5, 5, 5}},
        // clang-format on
        {"Reflect", "%5 %5", 71, 7, {5, 5}},
        {"Refract", "%5 %5 %5", 72, 8, {5, 5, 5}},
        {"FindILsb", "%5", 73, 6, {5}},
        {"FindSMsb", "%5", 74, 6, {5}},
        {"FindUMsb", "%5", 75, 6, {5}},
        {"InterpolateAtCentroid", "%5", 76, 6, {5}},
        // clang-format off
        {"InterpolateAtSample", "%5 %5", 77, 7, {5, 5}},
        {"InterpolateAtOffset", "%5 %5", 78, 7, {5, 5}},
        // clang-format on
        {"NMin", "%5 %5", 79, 7, {5, 5}},
        {"NMax", "%5 %5", 80, 7, {5, 5}},
        {"NClamp", "%5 %5 %5", 81, 8, {5, 5, 5}},
    })), );

}  // anonymous namespace
