// Copyright 2018 The Amber Authors.
//
// 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 parseried.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "src/vkscript/parser.h"

#include <vector>

#include "gtest/gtest.h"
#include "src/format.h"

namespace amber {
namespace vkscript {

using VkScriptParserTest = testing::Test;

TEST_F(VkScriptParserTest, RequireBlockNoArgumentFeatures) {
  struct {
    const char* name;
  } features[] = {{"robustBufferAccess"},
                  {"fullDrawIndexUint32"},
                  {"imageCubeArray"},
                  {"independentBlend"},
                  {"geometryShader"},
                  {"tessellationShader"},
                  {"sampleRateShading"},
                  {"dualSrcBlend"},
                  {"logicOp"},
                  {"multiDrawIndirect"},
                  {"drawIndirectFirstInstance"},
                  {"depthClamp"},
                  {"depthBiasClamp"},
                  {"fillModeNonSolid"},
                  {"depthBounds"},
                  {"wideLines"},
                  {"largePoints"},
                  {"alphaToOne"},
                  {"multiViewport"},
                  {"samplerAnisotropy"},
                  {"textureCompressionETC2"},
                  {"textureCompressionASTC_LDR"},
                  {"textureCompressionBC"},
                  {"occlusionQueryPrecise"},
                  {"pipelineStatisticsQuery"},
                  {"vertexPipelineStoresAndAtomics"},
                  {"fragmentStoresAndAtomics"},
                  {"shaderTessellationAndGeometryPointSize"},
                  {"shaderImageGatherExtended"},
                  {"shaderStorageImageExtendedFormats"},
                  {"shaderStorageImageMultisample"},
                  {"shaderStorageImageReadWithoutFormat"},
                  {"shaderStorageImageWriteWithoutFormat"},
                  {"shaderUniformBufferArrayDynamicIndexing"},
                  {"shaderSampledImageArrayDynamicIndexing"},
                  {"shaderStorageBufferArrayDynamicIndexing"},
                  {"shaderStorageImageArrayDynamicIndexing"},
                  {"shaderClipDistance"},
                  {"shaderCullDistance"},
                  {"shaderFloat64"},
                  {"shaderInt64"},
                  {"shaderInt16"},
                  {"shaderResourceResidency"},
                  {"shaderResourceMinLod"},
                  {"sparseBinding"},
                  {"sparseResidencyBuffer"},
                  {"sparseResidencyImage2D"},
                  {"sparseResidencyImage3D"},
                  {"sparseResidency2Samples"},
                  {"sparseResidency4Samples"},
                  {"sparseResidency8Samples"},
                  {"sparseResidency16Samples"},
                  {"sparseResidencyAliased"},
                  {"variableMultisampleRate"},
                  {"inheritedQueries"},
                  {"VariablePointerFeatures.variablePointers"},
                  {"VariablePointerFeatures.variablePointersStorageBuffer"}};

  for (const auto& feature : features) {
    std::string in = std::string("[require]\n") + feature.name + "\n";

    Parser parser;
    parser.SkipValidationForTest();
    Result r = parser.Parse(in);
    ASSERT_TRUE(r.IsSuccess()) << r.Error();

    auto script = parser.GetScript();
    auto feats = script->GetRequiredFeatures();
    ASSERT_EQ(1U, feats.size());
    EXPECT_EQ(feature.name, feats[0]);
  }
}

TEST_F(VkScriptParserTest, RequireBlockExtensions) {
  std::string block = R"([require]
VK_KHR_storage_buffer_storage_class
VK_KHR_variable_pointers
VK_KHR_get_physical_device_properties2)";

  Parser parser;
  parser.SkipValidationForTest();
  Result r = parser.Parse(block);
  ASSERT_TRUE(r.IsSuccess()) << r.Error();

  auto script = parser.GetScript();
  auto device_exts = script->GetRequiredDeviceExtensions();
  ASSERT_EQ(2U, device_exts.size());
  EXPECT_EQ("VK_KHR_storage_buffer_storage_class", device_exts[0]);
  EXPECT_EQ("VK_KHR_variable_pointers", device_exts[1]);

  auto inst_exts = script->GetRequiredInstanceExtensions();
  ASSERT_EQ(1U, inst_exts.size());
  EXPECT_EQ("VK_KHR_get_physical_device_properties2", inst_exts[0]);
}

TEST_F(VkScriptParserTest, RequireBlockFramebuffer) {
  std::string block = "[require]\nframebuffer R32G32B32A32_SFLOAT";

  Parser parser;
  parser.SkipValidationForTest();
  Result r = parser.Parse(block);
  ASSERT_TRUE(r.IsSuccess());

  auto script = parser.GetScript();
  const auto& bufs = script->GetBuffers();
  ASSERT_EQ(1U, bufs.size());
  EXPECT_EQ(FormatType::kR32G32B32A32_SFLOAT,
            bufs[0]->GetFormat()->GetFormatType());
}

TEST_F(VkScriptParserTest, RequireBlockDepthStencil) {
  std::string block = "[require]\ndepthstencil D24_UNORM_S8_UINT";

  Parser parser;
  parser.SkipValidationForTest();
  Result r = parser.Parse(block);
  ASSERT_TRUE(r.IsSuccess()) << r.Error();

  auto script = parser.GetScript();
  const auto& bufs = script->GetBuffers();
  ASSERT_EQ(2U, bufs.size());
  EXPECT_EQ(FormatType::kD24_UNORM_S8_UINT,
            bufs[1]->GetFormat()->GetFormatType());
}

TEST_F(VkScriptParserTest, RequireFbSize) {
  std::string block = "[require]\nfbsize 300 400";

  Parser parser;
  parser.SkipValidationForTest();
  Result r = parser.Parse(block);
  ASSERT_TRUE(r.IsSuccess()) << r.Error();

  auto script = parser.GetScript();
  const auto& pipelines = script->GetPipelines();
  ASSERT_EQ(1U, pipelines.size());
  EXPECT_EQ(300u, pipelines[0]->GetFramebufferWidth());
  EXPECT_EQ(400u, pipelines[0]->GetFramebufferHeight());
}

TEST_F(VkScriptParserTest, RequireFbSizeMissingSize) {
  std::string block = "[require]\nfbsize";

  Parser parser;
  Result r = parser.Parse(block);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("2: Missing width and height for fbsize command", r.Error());
}

TEST_F(VkScriptParserTest, RequireFbSizeMissingValue) {
  std::string block = "[require]\nfbsize 200";

  Parser parser;
  Result r = parser.Parse(block);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("2: Missing height for fbsize command", r.Error());
}

TEST_F(VkScriptParserTest, RequireFbSizeExtraParams) {
  std::string block = "[require]\nfbsize 200 300 EXTRA";

  Parser parser;
  Result r = parser.Parse(block);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("2: Failed to parse requirements block: invalid token: EXTRA",
            r.Error());
}

TEST_F(VkScriptParserTest, RequireFbSizeInvalidFirstParam) {
  std::string block = "[require]\nfbsize INVALID 200";

  Parser parser;
  Result r = parser.Parse(block);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("2: Invalid width for fbsize command", r.Error());
}

TEST_F(VkScriptParserTest, RequireFbSizeInvalidSecondParam) {
  std::string block = "[require]\nfbsize 200 INVALID";

  Parser parser;
  Result r = parser.Parse(block);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("2: Invalid height for fbsize command", r.Error());
}

TEST_F(VkScriptParserTest, RequireBlockMultipleLines) {
  std::string block = R"([require]
# Requirements block stuff.
depthstencil D24_UNORM_S8_UINT
sparseResidency4Samples
framebuffer R32G32B32A32_SFLOAT
# More comments
inheritedQueries # line comment
)";

  Parser parser;
  parser.SkipValidationForTest();
  Result r = parser.Parse(block);
  ASSERT_TRUE(r.IsSuccess()) << r.Error();

  auto script = parser.GetScript();
  const auto& bufs = script->GetBuffers();
  ASSERT_EQ(2U, bufs.size());
  EXPECT_EQ(FormatType::kR32G32B32A32_SFLOAT,
            bufs[0]->GetFormat()->GetFormatType());

  EXPECT_EQ(FormatType::kD24_UNORM_S8_UINT,
            bufs[1]->GetFormat()->GetFormatType());

  auto feats = script->GetRequiredFeatures();
  EXPECT_EQ("sparseResidency4Samples", feats[0]);
  EXPECT_EQ("inheritedQueries", feats[1]);
}

TEST_F(VkScriptParserTest, IndicesBlock) {
  std::string block = "[indices]\n1 2 3";

  Parser parser;
  parser.SkipValidationForTest();
  Result r = parser.Parse(block);
  ASSERT_TRUE(r.IsSuccess()) << r.Error();

  auto script = parser.GetScript();
  const auto& bufs = script->GetBuffers();
  ASSERT_EQ(2U, bufs.size());

  EXPECT_TRUE(bufs[1]->GetFormat()->IsUint32());
  EXPECT_EQ(3U, bufs[1]->ElementCount());
  EXPECT_EQ(3U, bufs[1]->ValueCount());
  EXPECT_EQ(3U * sizeof(uint32_t), bufs[1]->GetSizeInBytes());

  const auto* data = bufs[1]->GetValues<uint32_t>();
  EXPECT_EQ(1u, data[0]);
  EXPECT_EQ(2u, data[1]);
  EXPECT_EQ(3u, data[2]);
}

TEST_F(VkScriptParserTest, IndicesBlockMultipleLines) {
  std::string block = R"([indices]
# comment line
1 2 3   4 5 6
# another comment
7 8 9  10 11 12
)";

  Parser parser;
  parser.SkipValidationForTest();
  Result r = parser.Parse(block);
  ASSERT_TRUE(r.IsSuccess()) << r.Error();

  auto script = parser.GetScript();
  const auto& bufs = script->GetBuffers();
  ASSERT_EQ(2U, bufs.size());

  const auto* data = bufs[1]->GetValues<uint32_t>();
  std::vector<uint16_t> results = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
  ASSERT_EQ(results.size(), bufs[1]->ValueCount());
  for (size_t i = 0; i < results.size(); ++i) {
    EXPECT_EQ(results[i], data[i]);
  }
}

TEST_F(VkScriptParserTest, IndicesBlockBadValue) {
  std::string block = "[indices]\n1 a 3";

  Parser parser;
  Result r = parser.Parse(block);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("1: Invalid value in indices block: a", r.Error());
}

TEST_F(VkScriptParserTest, IndicesBlockValueTooLarge) {
  std::string block = "[indices]\n100000000000 3";

  Parser parser;
  Result r = parser.Parse(block);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("1: Value too large in indices block: 100000000000", r.Error());
}

TEST_F(VkScriptParserTest, VertexDataEmpty) {
  std::string block = "[vertex data]\n#comment\n";

  Parser parser;
  parser.SkipValidationForTest();
  Result r = parser.Parse(block);
  ASSERT_TRUE(r.IsSuccess());

  auto script = parser.GetScript();
  EXPECT_EQ(1U, script->GetBuffers().size());
}

TEST_F(VkScriptParserTest, VertexDataHeaderFormatString) {
  std::string block = "[vertex data]\n0/R32G32_SFLOAT 1/A8B8G8R8_UNORM_PACK32";

  Parser parser;
  parser.SkipValidationForTest();
  Result r = parser.Parse(block);
  ASSERT_TRUE(r.IsSuccess()) << r.Error();

  auto script = parser.GetScript();
  const auto& bufs = script->GetBuffers();
  ASSERT_EQ(3U, bufs.size());

  ASSERT_EQ(1U, script->GetPipelines().size());
  const auto* pipeline = script->GetPipelines()[0].get();

  ASSERT_EQ(2U, pipeline->GetVertexBuffers().size());
  const auto& pipeline_buffers = pipeline->GetVertexBuffers();

  EXPECT_EQ(static_cast<uint8_t>(0U), pipeline_buffers[0].location);
  EXPECT_EQ(FormatType::kR32G32_SFLOAT, bufs[1]->GetFormat()->GetFormatType());
  EXPECT_EQ(static_cast<uint32_t>(0), bufs[1]->ElementCount());

  EXPECT_EQ(1U, pipeline_buffers[1].location);
  EXPECT_EQ(FormatType::kA8B8G8R8_UNORM_PACK32,
            bufs[2]->GetFormat()->GetFormatType());
  EXPECT_EQ(static_cast<uint32_t>(0), bufs[2]->ElementCount());
}

TEST_F(VkScriptParserTest, VertexDataHeaderGlslString) {
  std::string block = "[vertex data]\n0/float/vec2 1/int/vec3";

  Parser parser;
  parser.SkipValidationForTest();
  Result r = parser.Parse(block);
  ASSERT_TRUE(r.IsSuccess()) << r.Error();

  auto script = parser.GetScript();
  const auto& bufs = script->GetBuffers();
  ASSERT_EQ(3U, bufs.size());

  ASSERT_EQ(1U, script->GetPipelines().size());
  const auto* pipeline = script->GetPipelines()[0].get();

  ASSERT_EQ(2U, pipeline->GetVertexBuffers().size());
  const auto& pipeline_buffers = pipeline->GetVertexBuffers();

  EXPECT_EQ(static_cast<uint8_t>(0U), pipeline_buffers[0].location);

  EXPECT_EQ(FormatType::kR32G32_SFLOAT, bufs[1]->GetFormat()->GetFormatType());

  auto& segs1 = bufs[1]->GetFormat()->GetSegments();
  ASSERT_EQ(2U, segs1.size());
  EXPECT_EQ(FormatMode::kSFloat, segs1[0].GetFormatMode());
  EXPECT_EQ(FormatMode::kSFloat, segs1[1].GetFormatMode());
  EXPECT_EQ(static_cast<uint32_t>(0), bufs[1]->ElementCount());

  EXPECT_EQ(1U, pipeline_buffers[1].location);
  EXPECT_EQ(FormatType::kR32G32B32_SINT, bufs[2]->GetFormat()->GetFormatType());

  auto& segs2 = bufs[2]->GetFormat()->GetSegments();
  ASSERT_EQ(4u, segs2.size());
  EXPECT_EQ(FormatMode::kSInt, segs2[0].GetFormatMode());
  EXPECT_EQ(FormatMode::kSInt, segs2[1].GetFormatMode());
  EXPECT_EQ(FormatMode::kSInt, segs2[2].GetFormatMode());
  EXPECT_TRUE(segs2[3].IsPadding());
  EXPECT_EQ(static_cast<uint32_t>(0), bufs[2]->ElementCount());
}

TEST_F(VkScriptParserTest, TestBlock) {
  std::string block = R"([test]
clear color 255 255 255 0
clear depth 10
clear stencil 2
clear)";

  Parser parser;
  parser.SkipValidationForTest();
  Result r = parser.Parse(block);
  ASSERT_TRUE(r.IsSuccess()) << r.Error();

  auto script = parser.GetScript();
  const auto& cmds = script->GetCommands();
  ASSERT_EQ(4U, cmds.size());

  ASSERT_TRUE(cmds[0]->IsClearColor());
  auto* color_cmd = cmds[0]->AsClearColor();
  EXPECT_FLOAT_EQ(255.f, color_cmd->GetR());
  EXPECT_FLOAT_EQ(255.f, color_cmd->GetG());
  EXPECT_FLOAT_EQ(255.f, color_cmd->GetB());
  EXPECT_FLOAT_EQ(0.0f, color_cmd->GetA());

  ASSERT_TRUE(cmds[1]->IsClearDepth());
  EXPECT_EQ(10U, cmds[1]->AsClearDepth()->GetValue());

  ASSERT_TRUE(cmds[2]->IsClearStencil());
  EXPECT_EQ(2U, cmds[2]->AsClearStencil()->GetValue());

  EXPECT_TRUE(cmds[3]->IsClear());
}

TEST_F(VkScriptParserTest, VertexDataRows) {
  std::string block = R"([vertex data]
# Vertex data
0/R32G32B32_SFLOAT  1/R8G8B8_UNORM
-1    -1 0.25       255 128 1  # ending comment
# Another Row
0.25  -1 0.25       255 128 255
)";

  Parser parser;
  parser.SkipValidationForTest();
  Result r = parser.Parse(block);
  ASSERT_TRUE(r.IsSuccess()) << r.Error();

  auto script = parser.GetScript();
  const auto& bufs = script->GetBuffers();
  ASSERT_EQ(3U, bufs.size());

  std::vector<float> seg_0 = {-1.f, -1.f, 0.25f, 0, 0.25f, -1.f, 0.25f, 0};
  const auto* values_0 = bufs[1]->GetValues<float>();
  for (size_t i = 0; i < seg_0.size(); ++i) {
    EXPECT_FLOAT_EQ(seg_0[i], values_0[i]);
  }

  std::vector<uint8_t> seg_1 = {255, 128, 1, 0, 255, 128, 255, 0};
  const auto* values_1 = bufs[2]->GetValues<uint8_t>();
  for (size_t i = 0; i < seg_1.size(); ++i) {
    EXPECT_EQ(seg_1[i], values_1[i]);
  }
}

TEST_F(VkScriptParserTest, VertexDataShortRow) {
  std::string block = R"([vertex data]
0/R32G32B32_SFLOAT  1/R8G8B8_UNORM
-1    -1 0.25       255 0 0
0.25  -1 0.25       255 0
)";

  Parser parser;
  Result r = parser.Parse(block);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("3: Too few cells in given vertex data row", r.Error());
}

TEST_F(VkScriptParserTest, VertexDataIncorrectValue) {
  std::string block = R"([vertex data]
0/R32G32B32_SFLOAT  1/R8G8B8_UNORM
-1    -1 0.25       255 StringValue 0
0.25  -1 0.25       255 0 0
)";

  Parser parser;
  Result r = parser.Parse(block);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("2: Invalid vertex data value: StringValue", r.Error());
}

TEST_F(VkScriptParserTest, VertexDataRowsWithHex) {
  std::string block = R"([vertex data]
0/A8B8G8R8_UNORM_PACK32
0xff0000ff
0xffff0000
)";

  Parser parser;
  parser.SkipValidationForTest();
  Result r = parser.Parse(block);
  ASSERT_TRUE(r.IsSuccess()) << r.Error();

  auto script = parser.GetScript();
  const auto& bufs = script->GetBuffers();
  ASSERT_EQ(2U, bufs.size());

  std::vector<uint32_t> seg_0 = {0xff0000ff, 0xffff0000};
  const auto* values_0 = bufs[1]->GetValues<uint32_t>();
  ASSERT_EQ(seg_0.size(), bufs[1]->ValueCount());

  for (size_t i = 0; i < seg_0.size(); ++i) {
    EXPECT_EQ(seg_0[i], values_0[i]);
  }
}

TEST_F(VkScriptParserTest, VertexDataRowsWithHexWrongColumn) {
  std::string block = R"([vertex data]
0/R32G32B32_SFLOAT  1/R8G8B8_UNORM
-1    -1 0.25       0xffff0000
0.25  -1 0.25       255 0
)";

  Parser parser;
  Result r = parser.Parse(block);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("2: Invalid vertex data value: 0xffff0000", r.Error());
}

TEST_F(VkScriptParserTest, ErrorLineNumberBug195) {
  std::string input = R"([compute shader]
#version 430

void main() {
}

[test]
# Error must report "9: Unknown command: unknown"
unknown
})";

  Parser parser;
  Result r = parser.Parse(input);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("9: Unknown command: unknown", r.Error());
}

}  // namespace vkscript
}  // namespace amber
