#include "piprotectedvariable.h"
#include "pistring.h"
#include "pithread.h"
#include "piliterals_time.h"

#include "gtest/gtest.h"
#include <atomic>

// Basic functionality tests
TEST(PIProtectedVariable_Basic, BasicFunctionality) {
	// Test basic set/get with different types
	PIProtectedVariable<int> pvInt;
	PIProtectedVariable<double> pvDouble;
	PIProtectedVariable<PIString> pvString;

	pvInt.set(123);
	pvDouble.set(3.14159);
	pvString.set(PIString("Hello, World!"));

	EXPECT_EQ(pvInt.get(), 123);
	EXPECT_DOUBLE_EQ(pvDouble.get(), 3.14159);
	EXPECT_EQ(pvString.get(), PIString("Hello, World!"));

	// Test operator=
	pvInt = 999;
	EXPECT_EQ(pvInt.get(), 999);

	// Test getRef() with Pointer
	struct TestStruct {
		int x = 10;
		int y = 20;
		int getValue() const { return x + y; }
	};

	PIProtectedVariable<TestStruct> pvStruct;

	auto ptr = pvStruct.getRef();
	EXPECT_EQ(ptr->x, 10);
	EXPECT_EQ(ptr->y, 20);
	EXPECT_EQ(ptr->getValue(), 30);

	// Modify through pointer
	*ptr   = TestStruct();
	ptr->x = 100;
	EXPECT_EQ(pvStruct.get().x, 100);

	// Test for Pointer
	pvInt.set(42);
	auto ptr1 = pvInt.getRef();
	EXPECT_EQ(*ptr1, 42);
	*ptr1 = 55;
	EXPECT_EQ(*ptr1, 55);
	auto ptr2 = std::move(ptr1);
	EXPECT_EQ(*ptr2, 55);
	*ptr2 = 100;
	EXPECT_EQ(*ptr2, 100);
	auto ptr3 = pvInt.getRef();
	EXPECT_EQ(*ptr3, 100);
	*ptr3 = 333;
	EXPECT_EQ(*ptr3, 333);
	EXPECT_EQ(pvInt.get(), 333);
}

// Thread safety tests
TEST(PIProtectedVariable_ThreadSafety, ConcurrentReadWrite) {
	PIProtectedVariable<int> pv;

	std::atomic<int> writeCount(0);
	std::atomic<int> readCount(0);
	std::atomic<int> invalidReads(0);
	const int NUM_ITERATIONS = 1000;
	const int NUM_WRITERS    = 10;
	const int NUM_READERS    = 20;
	const int TOTAL_WRITES   = NUM_WRITERS * NUM_ITERATIONS;

	// Collect thread handles for joining
	PIVector<PIThread *> threads;

	// Create writer threads
	for (int i = 0; i < NUM_WRITERS; ++i) {
		threads.push_back(new PIThread([&pv, &writeCount]() {
			for (int j = 0; j < NUM_ITERATIONS; ++j) {
				auto val = pv.getRef();
				(*val)++;
				auto val2 = pv.getRef();
				writeCount++;
				ASSERT_EQ(writeCount, *val2);
			}
		}));
	}

	// Create reader threads
	for (int i = 0; i < NUM_READERS; ++i) {
		threads.push_back(new PIThread([&pv, &invalidReads, &readCount]() {
			for (int j = 0; j < NUM_ITERATIONS; ++j) {
				auto val = pv.get();
				readCount++;
				// Value should always be in valid range [0, TOTAL_WRITES]
				if (val < 0 || val > TOTAL_WRITES) {
					invalidReads++;
				}
			}
		}));
	}

	threads.forEach([](PIThread * & t) {t->startOnce();});
	threads.forEach([](PIThread * & t) {t->waitForFinish(2_s);});
	piDeleteAll(threads);

	// Verify results
	EXPECT_EQ(writeCount, TOTAL_WRITES);
	EXPECT_EQ(readCount, NUM_READERS * NUM_ITERATIONS);
	EXPECT_EQ(invalidReads, 0) << "All reads should return valid values in range [0, " << TOTAL_WRITES << "]";

	// Final value should be TOTAL_WRITES
	int finalVal = pv.get();
	EXPECT_EQ(finalVal, TOTAL_WRITES);
}
