/*
 * Copyright (C) 2018-2021 Intel Corporation
 *
 * SPDX-License-Identifier: MIT
 *
 */

#include "opencl/source/accelerators/intel_accelerator.h"
#include "opencl/source/api/api.h"
#include "opencl/source/api/cl_types.h"
#include "opencl/source/command_queue/command_queue.h"
#include "opencl/source/context/context.h"
#include "opencl/source/device_queue/device_queue.h"
#include "opencl/source/helpers/base_object.h"
#include "opencl/source/mem_obj/buffer.h"
#include "opencl/source/mem_obj/mem_obj.h"
#include "opencl/source/platform/platform.h"
#include "opencl/source/program/program.h"
#include "opencl/source/sampler/sampler.h"
#include "opencl/test/unit_test/fixtures/buffer_fixture.h"
#include "opencl/test/unit_test/fixtures/cl_device_fixture.h"
#include "opencl/test/unit_test/fixtures/image_fixture.h"
#include "opencl/test/unit_test/mocks/mock_buffer.h"
#include "opencl/test/unit_test/mocks/mock_command_queue.h"
#include "opencl/test/unit_test/mocks/mock_platform.h"
#include "opencl/test/unit_test/mocks/mock_program.h"

#include "gmock/gmock.h"

namespace NEO {
typedef struct _cl_object_for_test2 *cl_object_for_test2;

struct _cl_object_for_test2 : public ClDispatch {
};

template <>
struct OpenCLObjectMapper<_cl_object_for_test2> {
    typedef struct ObjectForTest2 DerivedType;
};

template <>
struct OpenCLObjectMapper<ObjectForTest2> {
    typedef _cl_object_for_test2 BaseType;
};

struct ObjectForTest2 : public NEO::BaseObject<_cl_object_for_test2> {
    static const cl_ulong objectMagic = 0x13650a12b79ce4dfLL;
};

template <typename TypeParam>
struct BaseObjectTests : public ::testing::Test {
};

template <typename OclObject>
class MockObjectBase : public OclObject {
  public:
    using OclObject::OclObject;

    void setInvalidMagic() {
        validMagic = this->magic;
        this->magic = 0x0101010101010101LL;
    }
    void setInvalidIcdDispath() {
        this->dispatch.icdDispatch = reinterpret_cast<SDispatchTable *>(this);
    }
    void setValidMagic() {
        this->magic = validMagic;
    }

    bool isObjectValid() const {
        return this->isValid();
    }

    cl_ulong validMagic;
};

template <typename BaseType>
class MockObject : public MockObjectBase<BaseType> {};

template <>
class MockObject<Buffer> : public MockObjectBase<Buffer> {
  public:
    void setArgStateful(void *memory, bool forceNonAuxMode, bool disableL3, bool alignSizeForAuxTranslation, bool isReadOnly, const Device &device, bool useGlobalAtomics, bool areMultipleSubDevicesInContext) override {}
};

template <>
class MockObject<Program> : public MockObjectBase<Program> {
  public:
    MockObject() : MockObjectBase<Program>(nullptr, false, toClDeviceVector(*(new MockClDevice(new MockDevice())))), device(this->clDevices[0]) {}

  private:
    std::unique_ptr<ClDevice> device;
};

typedef ::testing::Types<
    MockPlatform,
    IntelAccelerator,
    //Context,
    //Program,
    //Kernel,
    //Sampler
    //others...
    MockCommandQueue,
    DeviceQueue>
    BaseObjectTypes;

typedef ::testing::Types<
    MockPlatform,
    IntelAccelerator,
    Context,
    Program,
    Buffer,
    MockCommandQueue,
    DeviceQueue>
    BaseObjectTypesForCastInvalidMagicTest;

TYPED_TEST_CASE(BaseObjectTests, BaseObjectTypes);

// "typedef" BaseObjectTests template to use with different TypeParams for testing
template <typename T>
using BaseObjectWithDefaultCtorTests = BaseObjectTests<T>;
TYPED_TEST_CASE(BaseObjectWithDefaultCtorTests, BaseObjectTypesForCastInvalidMagicTest);

TYPED_TEST(BaseObjectWithDefaultCtorTests, GivenInvalidMagicWhenCastingToObjectThenNullptrIsReturned) {
    MockObject<TypeParam> *object = new MockObject<TypeParam>;
    EXPECT_TRUE(object->isObjectValid());
    object->setInvalidMagic();
    EXPECT_FALSE(object->isObjectValid());

    auto objectCasted = castToObject<TypeParam>(object);
    EXPECT_EQ(nullptr, objectCasted);

    object->setValidMagic();
    delete object;
}

TYPED_TEST(BaseObjectWithDefaultCtorTests, whenCastToObjectWithInvalidIcdDispatchThenReturnsNullptr) {
    auto object = std::make_unique<MockObject<TypeParam>>();
    object->setInvalidIcdDispath();

    auto objectCasted = castToObject<TypeParam>(object.get());
    EXPECT_EQ(nullptr, objectCasted);
}

TYPED_TEST(BaseObjectTests, WhenRetainingAndReleasingThenObjectReferenceIsUpdated) {
    TypeParam *object = new TypeParam;
    object->retain();
    EXPECT_EQ(2, object->getReference());

    object->release();
    EXPECT_EQ(1, object->getReference());

    object->release();

    // MemoryLeakListener will detect a leak
    // if release doesn't delete memory.
}

TYPED_TEST(BaseObjectTests, GivenNullWhenCastingToObjectThenNullptrIsReturned) {
    typename TypeParam::BaseType *handle = nullptr;
    auto object = castToObject<TypeParam>(handle);
    EXPECT_EQ(nullptr, object);
}

TYPED_TEST(BaseObjectTests, WhenCastingToFromBaseTypeThenCorrectTypeIsDerived) {
    TypeParam object;
    typename TypeParam::BaseType *baseObject = &object;

    auto objectNew = castToObject<TypeParam>(baseObject);
    EXPECT_EQ(&object, objectNew);
}

TYPED_TEST(BaseObjectTests, WhenCastingToSameTypeThenSameObjectIsReturned) {
    TypeParam object;

    auto objectNew = castToObject<TypeParam>(&object);
    EXPECT_EQ(&object, objectNew);
}

TYPED_TEST(BaseObjectTests, WhenCastingToDifferentTypeThenNullptrIsReturned) {
    TypeParam object;
    typename TypeParam::BaseType *baseObject = &object;

    auto notOriginalType = reinterpret_cast<ObjectForTest2::BaseType *>(baseObject);
    auto invalidObject = castToObject<ObjectForTest2>(notOriginalType);
    EXPECT_EQ(nullptr, invalidObject);
}

TYPED_TEST(BaseObjectTests, WhenCastingToDispatchTableThenEntriesAreCorrect) {
    TypeParam objectDrv;

    // Automatic downcasting to _cl_type *.
    typename TypeParam::BaseType *objectCL = &objectDrv;
    sharingFactory.fillGlobalDispatchTable();

    // Common runtime casts to generic type assuming
    // the dispatch table is the first ptr in the structure
    auto genericObject = reinterpret_cast<ClDispatch *>(objectCL);
    EXPECT_EQ(globalDispatchTable.icdDispatch, genericObject->dispatch.icdDispatch);
    EXPECT_EQ(globalDispatchTable.crtDispatch, genericObject->dispatch.crtDispatch);

    EXPECT_EQ(reinterpret_cast<void *>(clGetKernelArgInfo), genericObject->dispatch.crtDispatch->clGetKernelArgInfo);

    EXPECT_EQ(reinterpret_cast<void *>(clGetImageParamsINTEL), genericObject->dispatch.crtDispatch->clGetImageParamsINTEL);

    EXPECT_EQ(reinterpret_cast<void *>(clCreateAcceleratorINTEL), genericObject->dispatch.crtDispatch->clCreateAcceleratorINTEL);
    EXPECT_EQ(reinterpret_cast<void *>(clGetAcceleratorInfoINTEL), genericObject->dispatch.crtDispatch->clGetAcceleratorInfoINTEL);
    EXPECT_EQ(reinterpret_cast<void *>(clRetainAcceleratorINTEL), genericObject->dispatch.crtDispatch->clRetainAcceleratorINTEL);
    EXPECT_EQ(reinterpret_cast<void *>(clReleaseAcceleratorINTEL), genericObject->dispatch.crtDispatch->clReleaseAcceleratorINTEL);

    EXPECT_EQ(reinterpret_cast<void *>(clCreatePerfCountersCommandQueueINTEL),
              genericObject->dispatch.crtDispatch->clCreatePerfCountersCommandQueueINTEL);

    EXPECT_EQ(reinterpret_cast<void *>(clSetPerformanceConfigurationINTEL),
              genericObject->dispatch.crtDispatch->clSetPerformanceConfigurationINTEL);

    // Check empty placeholder dispatch table entries are null
    EXPECT_EQ(nullptr, genericObject->dispatch.crtDispatch->placeholder12);
    EXPECT_EQ(nullptr, genericObject->dispatch.crtDispatch->placeholder13);

    EXPECT_EQ(nullptr, genericObject->dispatch.crtDispatch->placeholder18);
    EXPECT_EQ(nullptr, genericObject->dispatch.crtDispatch->placeholder19);
    EXPECT_EQ(nullptr, genericObject->dispatch.crtDispatch->placeholder20);
    EXPECT_EQ(nullptr, genericObject->dispatch.crtDispatch->placeholder21);
}

TEST(BaseObjectTests, WhenSettingSharedContextFlagThenItIsSetCorrectly) {
    MockContext newContext;

    //cast to cl_context
    cl_context clContext = &newContext;
    EXPECT_FALSE(newContext.isSharedContext);

    clContext->isSharedContext = true;

    EXPECT_TRUE(newContext.isSharedContext);
}

TYPED_TEST(BaseObjectTests, WhenTakingAndReleasingOwnershipThenOwnershipCountIsUpdated) {
    TypeParam obj;
    EXPECT_FALSE(obj.hasOwnership());

    obj.takeOwnership();
    EXPECT_TRUE(obj.hasOwnership());

    obj.takeOwnership();
    EXPECT_TRUE(obj.hasOwnership());

    obj.takeOwnership();
    EXPECT_TRUE(obj.hasOwnership());

    obj.releaseOwnership();
    EXPECT_TRUE(obj.hasOwnership());

    obj.releaseOwnership();
    EXPECT_TRUE(obj.hasOwnership());

    obj.releaseOwnership();
    EXPECT_FALSE(obj.hasOwnership());
}

TEST(CastToBuffer, WhenCastingFromMemObjThenBehavesAsExpected) {
    MockContext context;
    auto buffer = BufferHelper<>::create(&context);
    MemObj *memObj = buffer;
    cl_mem clObj = buffer;

    EXPECT_EQ(buffer, castToObject<Buffer>(clObj));
    EXPECT_EQ(memObj, castToObject<MemObj>(clObj));
    EXPECT_EQ(nullptr, castToObject<Image>(clObj));

    buffer->release();
}

TEST(CastToImage, WhenCastingFromMemObjThenBehavesAsExpected) {
    MockContext context;
    auto image = Image2dHelper<>::create(&context);
    MemObj *memObj = image;
    cl_mem clObj = image;

    EXPECT_EQ(image, castToObject<Image>(clObj));
    EXPECT_EQ(memObj, castToObject<MemObj>(clObj));
    EXPECT_EQ(nullptr, castToObject<Buffer>(clObj));

    image->release();
}

extern std::thread::id tempThreadID;
class MockBuffer : public MockBufferStorage, public Buffer {
    using MockBufferStorage::device;

  public:
    MockBuffer() : MockBufferStorage(),
                   Buffer(nullptr, ClMemoryPropertiesHelper::createMemoryProperties(CL_MEM_USE_HOST_PTR, 0, 0, MockBufferStorage::device.get()),
                          CL_MEM_USE_HOST_PTR, 0, sizeof(data), &data, &data, GraphicsAllocationHelper::toMultiGraphicsAllocation(&mockGfxAllocation), true, false, false) {
    }

    void setArgStateful(void *memory, bool forceNonAuxMode, bool disableL3, bool alignSizeForAuxTranslation, bool isReadOnly, const Device &device, bool useGlobalAtomics, bool areMultipleSubDevicesInContext) override {
    }
};

TEST(BaseObjectTest, WhenUsingOwnershipWrapperThenOwnershipIsSetCorrectly) {
    MockBuffer buffer;
    {
        TakeOwnershipWrapper<Buffer> bufferOwnership(buffer, false);
        EXPECT_FALSE(buffer.hasOwnership());
    }
    {
        TakeOwnershipWrapper<Buffer> bufferOwnership(buffer, true);
        EXPECT_TRUE(buffer.hasOwnership());
        bufferOwnership.unlock();
        EXPECT_FALSE(buffer.hasOwnership());
    }
}

TYPED_TEST(BaseObjectTests, WhenObjectIsCreatedThenNumWaitersIsZero) {
    TypeParam *object = new TypeParam;

    EXPECT_EQ(0U, object->getCond().peekNumWaiters());
    object->release();
}

TYPED_TEST(BaseObjectTests, WhenConvertingToInternalObjectThenRefApiCountIsSetToZero) {
    class ObjectForTest : public NEO::MemObj {
      public:
        ObjectForTest() : MemObj(nullptr, 0, {}, 0, 0, 0u, nullptr, nullptr, 0, false, false, false) {
        }

        void convertToInternalObject(void) {
            NEO::BaseObject<_cl_mem>::convertToInternalObject();
        }
    };
    ObjectForTest *object = new ObjectForTest;
    EXPECT_EQ(1, object->getRefApiCount());
    EXPECT_EQ(1, object->getRefInternalCount());
    object->convertToInternalObject();
    EXPECT_EQ(0, object->getRefApiCount());
    EXPECT_EQ(1, object->getRefInternalCount());
    object->decRefInternal();
}

TYPED_TEST(BaseObjectTests, GivenNullptrWhenCastingToObjectOrAbortThenExceptionIsThrown) {
    EXPECT_ANY_THROW(castToObjectOrAbort<TypeParam>(nullptr));
}

TYPED_TEST(BaseObjectTests, WhenCastingToObjectOrAbortThenCorrectTypeIsDerived) {
    TypeParam object;
    typename TypeParam::BaseType *baseObject = &object;

    auto objectNew = castToObjectOrAbort<TypeParam>(baseObject);
    EXPECT_EQ(&object, objectNew);
}

TYPED_TEST(BaseObjectTests, WhenCastToOrAbortWithDifferentTypeThenExceptionIsThrown) {
    TypeParam object;
    typename TypeParam::BaseType *baseObject = &object;

    auto notOriginalType = reinterpret_cast<ObjectForTest2::BaseType *>(baseObject);
    EXPECT_ANY_THROW(castToObjectOrAbort<ObjectForTest2>(notOriginalType));
}
} // namespace NEO
