// Copyright (c) 2016 Google 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 <memory>
#include <vector>

#include <gtest/gtest.h>

#include "opt/make_unique.h"
#include "opt/types.h"

namespace {

using namespace spvtools::opt::analysis;
using spvtools::MakeUnique;

// Fixture class providing some element types.
class SameTypeTest : public ::testing::Test {
 protected:
  virtual void SetUp() override {
    void_t_.reset(new Void());
    u32_t_.reset(new Integer(32, false));
    f64_t_.reset(new Float(64));
    v3u32_t_.reset(new Vector(u32_t_.get(), 3));
    image_t_.reset(new Image(f64_t_.get(), SpvDim2D, 1, 1, 0, 0,
                             SpvImageFormatR16, SpvAccessQualifierReadWrite));
  }

  // Element types to be used for constructing other types for testing.
  std::unique_ptr<Type> void_t_;
  std::unique_ptr<Type> u32_t_;
  std::unique_ptr<Type> f64_t_;
  std::unique_ptr<Type> v3u32_t_;
  std::unique_ptr<Type> image_t_;
};

#define TestMultipleInstancesOfTheSameType(ty, ...)                       \
  TEST_F(SameTypeTest, MultiSame##ty) {                                   \
    std::vector<std::unique_ptr<Type>> types;                             \
    for (int i = 0; i < 10; ++i) types.emplace_back(new ty(__VA_ARGS__)); \
    for (size_t i = 0; i < types.size(); ++i) {                           \
      for (size_t j = 0; j < types.size(); ++j) {                         \
        EXPECT_TRUE(types[i]->IsSame(types[j].get()))                     \
            << "expected '" << types[i]->str() << "' is the same as '"    \
            << types[j]->str() << "'";                                    \
      }                                                                   \
    }                                                                     \
  }
TestMultipleInstancesOfTheSameType(Void);
TestMultipleInstancesOfTheSameType(Bool);
TestMultipleInstancesOfTheSameType(Integer, 32, true);
TestMultipleInstancesOfTheSameType(Float, 64);
TestMultipleInstancesOfTheSameType(Vector, u32_t_.get(), 3);
TestMultipleInstancesOfTheSameType(Matrix, v3u32_t_.get(), 4);
TestMultipleInstancesOfTheSameType(Image, f64_t_.get(), SpvDimCube, 0, 0, 1, 1,
                                   SpvImageFormatRgb10A2,
                                   SpvAccessQualifierWriteOnly);
TestMultipleInstancesOfTheSameType(Sampler);
TestMultipleInstancesOfTheSameType(SampledImage, image_t_.get());
TestMultipleInstancesOfTheSameType(Array, u32_t_.get(), 10);
TestMultipleInstancesOfTheSameType(RuntimeArray, u32_t_.get());
TestMultipleInstancesOfTheSameType(Struct, std::vector<Type*>{u32_t_.get(),
                                                              f64_t_.get()});
TestMultipleInstancesOfTheSameType(Opaque, "testing rocks");
TestMultipleInstancesOfTheSameType(Pointer, u32_t_.get(), SpvStorageClassInput);
TestMultipleInstancesOfTheSameType(Function, u32_t_.get(),
                                   {f64_t_.get(), f64_t_.get()});
TestMultipleInstancesOfTheSameType(Event);
TestMultipleInstancesOfTheSameType(DeviceEvent);
TestMultipleInstancesOfTheSameType(ReserveId);
TestMultipleInstancesOfTheSameType(Queue);
TestMultipleInstancesOfTheSameType(Pipe, SpvAccessQualifierReadWrite);
TestMultipleInstancesOfTheSameType(ForwardPointer, 10, SpvStorageClassUniform);
TestMultipleInstancesOfTheSameType(PipeStorage);
TestMultipleInstancesOfTheSameType(NamedBarrier);
#undef TestMultipleInstanceOfTheSameType

TEST(Types, AllTypes) {
  // Types in this test case are only equal to themselves, nothing else.
  std::vector<std::unique_ptr<Type>> types;

  // Forward Pointer
  types.emplace_back(new ForwardPointer(10000, SpvStorageClassInput));
  types.emplace_back(new ForwardPointer(20000, SpvStorageClassInput));

  // Void, Bool
  types.emplace_back(new Void());
  auto* voidt = types.back().get();
  types.emplace_back(new Bool());
  auto* boolt = types.back().get();

  // Integer
  types.emplace_back(new Integer(32, true));
  auto* s32 = types.back().get();
  types.emplace_back(new Integer(32, false));
  types.emplace_back(new Integer(64, true));
  types.emplace_back(new Integer(64, false));
  auto* u64 = types.back().get();

  // Float
  types.emplace_back(new Float(32));
  auto* f32 = types.back().get();
  types.emplace_back(new Float(64));

  // Vector
  types.emplace_back(new Vector(s32, 2));
  types.emplace_back(new Vector(s32, 3));
  auto* v3s32 = types.back().get();
  types.emplace_back(new Vector(u64, 4));
  types.emplace_back(new Vector(f32, 3));
  auto* v3f32 = types.back().get();

  // Matrix
  types.emplace_back(new Matrix(v3s32, 3));
  types.emplace_back(new Matrix(v3s32, 4));
  types.emplace_back(new Matrix(v3f32, 4));

  // Images
  types.emplace_back(new Image(s32, SpvDim2D, 0, 0, 0, 0, SpvImageFormatRg8,
                               SpvAccessQualifierReadOnly));
  auto* image1 = types.back().get();
  types.emplace_back(new Image(s32, SpvDim2D, 0, 1, 0, 0, SpvImageFormatRg8,
                               SpvAccessQualifierReadOnly));
  types.emplace_back(new Image(s32, SpvDim3D, 0, 1, 0, 0, SpvImageFormatRg8,
                               SpvAccessQualifierReadOnly));
  types.emplace_back(new Image(voidt, SpvDim3D, 0, 1, 0, 1, SpvImageFormatRg8,
                               SpvAccessQualifierReadWrite));
  auto* image2 = types.back().get();

  // Sampler
  types.emplace_back(new Sampler());

  // Sampled Image
  types.emplace_back(new SampledImage(image1));
  types.emplace_back(new SampledImage(image2));

  // Array
  types.emplace_back(new Array(f32, 100));
  types.emplace_back(new Array(f32, 42));
  auto* a42f32 = types.back().get();
  types.emplace_back(new Array(u64, 24));

  // RuntimeArray
  types.emplace_back(new RuntimeArray(v3f32));
  types.emplace_back(new RuntimeArray(v3s32));
  auto* rav3s32 = types.back().get();

  // Struct
  types.emplace_back(new Struct(std::vector<Type*>{s32}));
  types.emplace_back(new Struct(std::vector<Type*>{s32, f32}));
  auto* sts32f32 = types.back().get();
  types.emplace_back(new Struct(std::vector<Type*>{u64, a42f32, rav3s32}));

  // Opaque
  types.emplace_back(new Opaque(""));
  types.emplace_back(new Opaque("hello"));
  types.emplace_back(new Opaque("world"));

  // Pointer
  types.emplace_back(new Pointer(f32, SpvStorageClassInput));
  types.emplace_back(new Pointer(sts32f32, SpvStorageClassFunction));
  types.emplace_back(new Pointer(a42f32, SpvStorageClassFunction));

  // Function
  types.emplace_back(new Function(voidt, {}));
  types.emplace_back(new Function(voidt, {boolt}));
  types.emplace_back(new Function(voidt, {boolt, s32}));
  types.emplace_back(new Function(s32, {boolt, s32}));

  // Event, Device Event, Reserve Id, Queue,
  types.emplace_back(new Event());
  types.emplace_back(new DeviceEvent());
  types.emplace_back(new ReserveId());
  types.emplace_back(new Queue());

  // Pipe, Forward Pointer, PipeStorage, NamedBarrier
  types.emplace_back(new Pipe(SpvAccessQualifierReadWrite));
  types.emplace_back(new Pipe(SpvAccessQualifierReadOnly));
  types.emplace_back(new ForwardPointer(1, SpvStorageClassInput));
  types.emplace_back(new ForwardPointer(2, SpvStorageClassInput));
  types.emplace_back(new ForwardPointer(2, SpvStorageClassUniform));
  types.emplace_back(new PipeStorage());
  types.emplace_back(new NamedBarrier());

  for (size_t i = 0; i < types.size(); ++i) {
    for (size_t j = 0; j < types.size(); ++j) {
      if (i == j) {
        EXPECT_TRUE(types[i]->IsSame(types[j].get()))
            << "expected '" << types[i]->str() << "' is the same as '"
            << types[j]->str() << "'";
      } else {
        EXPECT_FALSE(types[i]->IsSame(types[j].get()))
            << "expected '" << types[i]->str() << "' is different to '"
            << types[j]->str() << "'";
      }
    }
  }
}

TEST(Types, IntSignedness) {
  std::vector<bool> signednesses = {true, false, false, true};
  std::vector<std::unique_ptr<Integer>> types;
  for (bool s : signednesses) {
    types.emplace_back(new Integer(32, s));
  }
  for (size_t i = 0; i < signednesses.size(); i++) {
    EXPECT_EQ(signednesses[i], types[i]->IsSigned());
  }
}

TEST(Types, IntWidth) {
  std::vector<uint32_t> widths = {1, 2, 4, 8, 16, 32, 48, 64, 128};
  std::vector<std::unique_ptr<Integer>> types;
  for (uint32_t w : widths) {
    types.emplace_back(new Integer(w, true));
  }
  for (size_t i = 0; i < widths.size(); i++) {
    EXPECT_EQ(widths[i], types[i]->width());
  }
}

TEST(Types, FloatWidth) {
  std::vector<uint32_t> widths = {1, 2, 4, 8, 16, 32, 48, 64, 128};
  std::vector<std::unique_ptr<Float>> types;
  for (uint32_t w : widths) {
    types.emplace_back(new Float(w));
  }
  for (size_t i = 0; i < widths.size(); i++) {
    EXPECT_EQ(widths[i], types[i]->width());
  }
}

TEST(Types, VectorElementCount) {
  auto s32 = MakeUnique<Integer>(32, true);
  for (uint32_t c : {2, 3, 4}) {
    auto s32v = MakeUnique<Vector>(s32.get(), c);
    EXPECT_EQ(c, s32v->element_count());
  }
}

TEST(Types, MatrixElementCount) {
  auto s32 = MakeUnique<Integer>(32, true);
  auto s32v4 = MakeUnique<Vector>(s32.get(), 4);
  for (uint32_t c : {1, 2, 3, 4, 10, 100}) {
    auto s32m = MakeUnique<Matrix>(s32v4.get(), c);
    EXPECT_EQ(c, s32m->element_count());
  }
}

}  // anonymous namespace
