// Copyright 2019 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 "gtest/gtest.h"
#include "src/amberscript/parser.h"

namespace amber {
namespace amberscript {

using AmberScriptParserTest = testing::Test;

TEST_F(AmberScriptParserTest, Pipeline) {
  std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
SHADER fragment my_fragment GLSL
# GLSL Shader
END

PIPELINE graphics my_pipeline
  ATTACH my_shader
  ATTACH my_fragment
END
)";

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

  auto script = parser.GetScript();
  EXPECT_EQ(2U, script->GetShaders().size());

  const auto& pipelines = script->GetPipelines();
  ASSERT_EQ(1U, pipelines.size());

  const auto* pipeline = pipelines[0].get();
  EXPECT_EQ("my_pipeline", pipeline->GetName());
  EXPECT_EQ(PipelineType::kGraphics, pipeline->GetType());

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

  ASSERT_TRUE(shaders[0].GetShader() != nullptr);
  EXPECT_EQ("my_shader", shaders[0].GetShader()->GetName());
  EXPECT_EQ(kShaderTypeVertex, shaders[0].GetShader()->GetType());
  EXPECT_EQ(static_cast<uint32_t>(0),
            shaders[0].GetShaderOptimizations().size());

  ASSERT_TRUE(shaders[1].GetShader() != nullptr);
  EXPECT_EQ("my_fragment", shaders[1].GetShader()->GetName());
  EXPECT_EQ(kShaderTypeFragment, shaders[1].GetShader()->GetType());
  EXPECT_EQ(static_cast<uint32_t>(0),
            shaders[1].GetShaderOptimizations().size());

  EXPECT_EQ(pipelines[0]->GetPipelineData()->GetPatchControlPoints(), 3u);
}

TEST_F(AmberScriptParserTest, PipelineMissingEnd) {
  std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
PIPELINE graphics my_pipeline
  ATTACH my_shader
)";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("5: PIPELINE missing END command", r.Error());
}

TEST_F(AmberScriptParserTest, PipelineWithExtraParams) {
  std::string in = R"(
PIPELINE graphics my_pipeline INVALID
  ATTACH my_shader
END
)";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("2: extra parameters after PIPELINE command: INVALID", r.Error());
}

TEST_F(AmberScriptParserTest, PipelineInvalidType) {
  std::string in = "PIPELINE my_name\nEND";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("1: unknown pipeline type: my_name", r.Error());
}

TEST_F(AmberScriptParserTest, PipelineMissingName) {
  std::string in = "PIPELINE compute\nEND";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("2: invalid token when looking for pipeline name", r.Error());
}

TEST_F(AmberScriptParserTest, PipelineWithInvalidTokenType) {
  std::string in = "PIPELINE 123 my_pipeline\nEND";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("1: invalid token when looking for pipeline type", r.Error());
}

TEST_F(AmberScriptParserTest, PipelineWithInvalidTokenName) {
  std::string in = "PIPELINE compute 123\nEND";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("1: invalid token when looking for pipeline name", r.Error());
}

TEST_F(AmberScriptParserTest, PipelineEmpty) {
  std::string in = "PIPELINE compute my_pipeline\nEND";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("compute pipeline requires a compute shader", r.Error());
}

TEST_F(AmberScriptParserTest, PipelineWithUnknownCommand) {
  std::string in = R"(
PIPELINE compute my_pipeline
  SHADER vertex my_shader PASSTHROUGH
END)";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("3: unknown token in pipeline block: SHADER", r.Error());
}

TEST_F(AmberScriptParserTest, DuplicatePipelineName) {
  std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
SHADER fragment my_fragment GLSL
# Fragment shader
END

PIPELINE graphics my_pipeline
  ATTACH my_shader
  ATTACH my_fragment
END
PIPELINE graphics my_pipeline
  ATTACH my_shader
  ATTACH my_fragment
END)";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("14: duplicate pipeline name provided", r.Error());
}

TEST_F(AmberScriptParserTest, PipelineDefaultColorBuffer) {
  std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
SHADER fragment my_fragment GLSL
# GLSL Shader
END

PIPELINE graphics my_pipeline
  ATTACH my_shader
  ATTACH my_fragment
END
PIPELINE graphics my_pipeline2
  ATTACH my_shader
  ATTACH my_fragment
END)";

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

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

  ASSERT_EQ(1U, pipelines[0]->GetColorAttachments().size());
  const auto& buf1 = pipelines[0]->GetColorAttachments()[0];
  ASSERT_TRUE(buf1.buffer != nullptr);

  Buffer* buffer1 = buf1.buffer;
  EXPECT_EQ(FormatType::kB8G8R8A8_UNORM, buffer1->GetFormat()->GetFormatType());
  EXPECT_EQ(0u, buf1.location);
  EXPECT_EQ(250u * 250u, buffer1->ElementCount());
  EXPECT_EQ(250u * 250u * 4u, buffer1->ValueCount());
  EXPECT_EQ(250u * 250u * 4u * sizeof(uint8_t), buffer1->GetSizeInBytes());

  ASSERT_EQ(1U, pipelines[1]->GetColorAttachments().size());
  const auto& buf2 = pipelines[1]->GetColorAttachments()[0];
  ASSERT_TRUE(buf2.buffer != nullptr);
  ASSERT_EQ(buffer1, buf2.buffer);
  EXPECT_EQ(0u, buf2.location);
  EXPECT_EQ(FormatType::kB8G8R8A8_UNORM,
            buf2.buffer->GetFormat()->GetFormatType());
  EXPECT_EQ(250u * 250u, buf2.buffer->ElementCount());
  EXPECT_EQ(250u * 250u * 4u, buf2.buffer->ValueCount());
  EXPECT_EQ(250u * 250u * 4u * sizeof(uint8_t), buf2.buffer->GetSizeInBytes());
}

TEST_F(AmberScriptParserTest, PipelineDefaultColorBufferMismatchSize) {
  std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
SHADER fragment my_fragment GLSL
# GLSL Shader
END

PIPELINE graphics my_pipeline
  ATTACH my_shader
  ATTACH my_fragment
END
PIPELINE graphics my_pipeline2
  ATTACH my_shader
  ATTACH my_fragment
  FRAMEBUFFER_SIZE 256 256
END)";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());

  EXPECT_EQ("shared framebuffer must have same size over all PIPELINES",
            r.Error());
}

TEST_F(AmberScriptParserTest, PipelinePolygonMode) {
  std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
SHADER fragment my_fragment GLSL
# GLSL Shader
END

PIPELINE graphics my_pipeline_default
  ATTACH my_shader
  ATTACH my_fragment
  FRAMEBUFFER_SIZE 256 256
END
PIPELINE graphics my_pipeline_fill
  ATTACH my_shader
  ATTACH my_fragment
  POLYGON_MODE fill
  FRAMEBUFFER_SIZE 256 256
END
PIPELINE graphics my_pipeline_line
  ATTACH my_shader
  ATTACH my_fragment
  POLYGON_MODE line
  FRAMEBUFFER_SIZE 256 256
END
PIPELINE graphics my_pipeline_point
  ATTACH my_shader
  ATTACH my_fragment
  POLYGON_MODE point
  FRAMEBUFFER_SIZE 256 256
END)";

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

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

  auto mode0 = pipelines[0]->GetPipelineData()->GetPolygonMode();
  ASSERT_EQ(mode0, PolygonMode::kFill);
  auto mode1 = pipelines[1]->GetPipelineData()->GetPolygonMode();
  ASSERT_EQ(mode1, PolygonMode::kFill);
  auto mode2 = pipelines[2]->GetPipelineData()->GetPolygonMode();
  ASSERT_EQ(mode2, PolygonMode::kLine);
  auto mode3 = pipelines[3]->GetPipelineData()->GetPolygonMode();
  ASSERT_EQ(mode3, PolygonMode::kPoint);
}

TEST_F(AmberScriptParserTest, PipelineMissingPolygonMode) {
  std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
SHADER fragment my_fragment GLSL
# GLSL Shader
END

PIPELINE graphics my_pipeline
  ATTACH my_shader
  ATTACH my_fragment
  POLYGON_MODE
  FRAMEBUFFER_SIZE 256 256
END)";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());

  EXPECT_EQ("11: missing mode in POLYGON_MODE command", r.Error());
}

TEST_F(AmberScriptParserTest, PipelineInvalidPolygonMode) {
  std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
SHADER fragment my_fragment GLSL
# GLSL Shader
END

PIPELINE graphics my_pipeline
  ATTACH my_shader
  ATTACH my_fragment
  POLYGON_MODE foo
  FRAMEBUFFER_SIZE 256 256
END)";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());

  EXPECT_EQ("10: invalid polygon mode: foo", r.Error());
}

TEST_F(AmberScriptParserTest, DerivePipeline) {
  std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
SHADER fragment my_fragment GLSL
# GLSL Shader
END
SHADER fragment other_fragment GLSL
# GLSL Shader
END
BUFFER buf1 DATA_TYPE int32 SIZE 20 FILL 5
BUFFER buf2 DATA_TYPE int32 SIZE 20 FILL 7

PIPELINE graphics parent_pipeline
  ATTACH my_shader
  ATTACH my_fragment
  BIND BUFFER buf1 AS storage DESCRIPTOR_SET 1 BINDING 3
END

DERIVE_PIPELINE child_pipeline FROM parent_pipeline
  ATTACH other_fragment
  BIND BUFFER buf2 AS storage DESCRIPTOR_SET 1 BINDING 3
END
)";

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

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

  const auto* pipeline1 = pipelines[0].get();
  auto buffers1 = pipeline1->GetBuffers();
  ASSERT_EQ(1U, buffers1.size());
  EXPECT_EQ("buf1", buffers1[0].buffer->GetName());
  EXPECT_EQ(1u, buffers1[0].descriptor_set);
  EXPECT_EQ(3u, buffers1[0].binding);

  auto shaders1 = pipeline1->GetShaders();
  ASSERT_EQ(2U, shaders1.size());
  EXPECT_EQ("my_shader", shaders1[0].GetShader()->GetName());
  EXPECT_EQ("my_fragment", shaders1[1].GetShader()->GetName());

  const auto* pipeline2 = pipelines[1].get();
  EXPECT_EQ("child_pipeline", pipeline2->GetName());

  auto buffers2 = pipeline2->GetBuffers();
  ASSERT_EQ(1U, buffers2.size());
  EXPECT_EQ("buf2", buffers2[0].buffer->GetName());
  EXPECT_EQ(1u, buffers2[0].descriptor_set);
  EXPECT_EQ(3u, buffers2[0].binding);

  auto shaders2 = pipeline2->GetShaders();
  ASSERT_EQ(2U, shaders2.size());
  EXPECT_EQ("my_shader", shaders2[0].GetShader()->GetName());
  EXPECT_EQ("other_fragment", shaders2[1].GetShader()->GetName());
}

TEST_F(AmberScriptParserTest, DerivePipelineMissingEnd) {
  std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
SHADER fragment my_fragment GLSL
# GLSL Shader
END

PIPELINE graphics parent_pipeline
  ATTACH my_shader
  ATTACH my_fragment
END

DERIVE_PIPELINE derived_pipeline FROM parent_pipeline
)";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("13: DERIVE_PIPELINE missing END command", r.Error());
}

TEST_F(AmberScriptParserTest, DerivePipelineMissingPipelineName) {
  std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
SHADER fragment my_fragment GLSL
# GLSL Shader
END

PIPELINE graphics parent_pipeline
  ATTACH my_shader
  ATTACH my_fragment
END

DERIVE_PIPELINE FROM parent_pipeline
END
)";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("12: missing pipeline name for DERIVE_PIPELINE command", r.Error());
}

TEST_F(AmberScriptParserTest, DerivePipelineMissingFrom) {
  std::string in = R"(
DERIVE_PIPELINE derived_pipeline parent_pipeline
END
)";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("2: missing FROM in DERIVE_PIPELINE command", r.Error());
}

TEST_F(AmberScriptParserTest, DerivePipelineMissingParentPipelineName) {
  std::string in = R"(
DERIVE_PIPELINE derived_pipeline FROM
END
)";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("3: missing parent pipeline name in DERIVE_PIPELINE command",
            r.Error());
}

TEST_F(AmberScriptParserTest, DerivePipelineUnknownParentPipeline) {
  std::string in = R"(
DERIVE_PIPELINE derived_pipeline FROM parent_pipeline
END
)";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("2: unknown parent pipeline in DERIVE_PIPELINE command", r.Error());
}

TEST_F(AmberScriptParserTest, DerivePipelineDuplicatePipelineName) {
  std::string in = R"(
SHADER vertex my_shader PASSTHROUGH
SHADER fragment my_fragment GLSL
# GLSL Shader
END

PIPELINE graphics parent_pipeline
  ATTACH my_shader
  ATTACH my_fragment
END

DERIVE_PIPELINE parent_pipeline FROM parent_pipeline
END
)";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("12: duplicate pipeline name for DERIVE_PIPELINE command",
            r.Error());
}

TEST_F(AmberScriptParserTest, DerivePipelineNoParams) {
  std::string in = R"(
DERIVE_PIPELINE
END
)";

  Parser parser;
  Result r = parser.Parse(in);
  ASSERT_FALSE(r.IsSuccess());
  EXPECT_EQ("3: missing pipeline name for DERIVE_PIPELINE command", r.Error());
}

TEST_F(AmberScriptParserTest, DerivePipelineSpecialized) {
  std::string in = R"(
SHADER compute my_shader GLSL
#shaders
END
PIPELINE compute p1
  ATTACH my_shader SPECIALIZE 3 AS uint32 4
END
DERIVE_PIPELINE p2 FROM p1
END
)";

  Parser parser;
  Result r = parser.Parse(in);
  EXPECT_EQ("", r.Error());
  ASSERT_TRUE(r.IsSuccess());

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

  const auto* p1 = pipelines[0].get();
  const auto& s1 = p1->GetShaders();
  ASSERT_EQ(1U, s1.size());

  EXPECT_EQ(1u, s1[0].GetSpecialization().size());
  EXPECT_EQ(4u, s1[0].GetSpecialization().at(3));

  const auto* p2 = pipelines[1].get();
  const auto& s2 = p2->GetShaders();
  ASSERT_EQ(1U, s2.size());

  EXPECT_EQ(1u, s2[0].GetSpecialization().size());
  EXPECT_EQ(4u, s2[0].GetSpecialization().at(3));
}

TEST_F(AmberScriptParserTest, PipelinePatchControlPoints) {
  std::string in = R"(
DEVICE_FEATURE tessellationShader

SHADER vertex my_shader PASSTHROUGH
SHADER fragment my_fragment GLSL
# GLSL Shader
END

SHADER tessellation_control my_tesc GLSL
# GLSL Shader
END

SHADER tessellation_evaluation my_tese GLSL
# GLSL Shader
END

PIPELINE graphics my_pipeline
  ATTACH my_shader
  ATTACH my_tesc
  ATTACH my_tese
  ATTACH my_fragment

  PATCH_CONTROL_POINTS 4
END
)";

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

  auto script = parser.GetScript();
  const auto& pipelines = script->GetPipelines();
  ASSERT_EQ(1U, pipelines.size());

  EXPECT_EQ(pipelines[0]->GetPipelineData()->GetPatchControlPoints(), 4u);
}

TEST_F(AmberScriptParserTest, PipelineDerivePatchControlPoints) {
  std::string in = R"(
DEVICE_FEATURE tessellationShader

SHADER vertex my_shader PASSTHROUGH
SHADER fragment my_fragment GLSL
# GLSL Shader
END

SHADER tessellation_control my_tesc GLSL
# GLSL Shader
END

SHADER tessellation_evaluation my_tese GLSL
# GLSL Shader
END

PIPELINE graphics my_pipeline
  ATTACH my_shader
  ATTACH my_tesc
  ATTACH my_tese
  ATTACH my_fragment

  PATCH_CONTROL_POINTS 4
END

DERIVE_PIPELINE child_pipeline FROM my_pipeline
END
)";

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

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

  EXPECT_EQ(pipelines[0]->GetPipelineData()->GetPatchControlPoints(), 4u);
  EXPECT_EQ(pipelines[1]->GetPipelineData()->GetPatchControlPoints(), 4u);
}

}  // namespace amberscript
}  // namespace amber
