/*
  This file is part of Raul.
  Copyright 2007-2017 David Robillard <http://drobilla.net>

  Raul is free software: you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
  Foundation, either version 3 of the License, or any later version.

  Raul is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with Raul.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <cassert>
#include <cstdio>
#include <thread>
#include <vector>

#include "raul/Maid.hpp"

using Raul::Maid;

static const size_t n_threads         = 8;
static const size_t n_junk_per_thread = 1 << 18;

static std::atomic<size_t> n_junk(0);
static std::atomic<size_t> n_finished_threads(0);

class Junk : public Maid::Disposable {
public:
	explicit Junk(int v) : val(v) { ++n_junk; }
	~Junk() { --n_junk; }

	int val;
};

static void
litter(Maid* maid)
{
	for (size_t i = 0; i < n_junk_per_thread; ++i) {
		Maid::managed_ptr<Junk> a = maid->make_managed<Junk>(i);
	}

	++n_finished_threads;
}

static void
test()
{
	Maid maid;

	// Check basic single-threaded correctness
	{
		assert(n_junk == 0);
		Maid::managed_ptr<Junk> a = maid.make_managed<Junk>(1);
		assert(n_junk == 1);
		Maid::managed_ptr<Junk> b = maid.make_managed<Junk>(2);
		assert(n_junk == 2);
	}

	maid.dispose(nullptr);  // Mustn't crash

	// All referenes dropped, but deletion deferred
	assert(n_junk == 2);

	// Trigger actual deletion
	maid.cleanup();
	assert(n_junk == 0);
	assert(maid.empty());

	// Create some threads to produce garbage
	std::vector<std::thread> litterers;
	for (size_t i = 0; i < n_threads; ++i) {
		litterers.push_back(std::thread(litter, &maid));
	}

	// Wait for some garbage to show up if necessary (unlikely)
	size_t initial_n_junk = n_junk;
	while (maid.empty()) {
		std::this_thread::sleep_for(std::chrono::milliseconds(1));
		initial_n_junk = n_junk;
	}

	printf("Starting with %zu initial bits of junk\n", initial_n_junk);

	// Ensure we're actually cleaning things up concurrently
	maid.cleanup();
	assert(n_junk != initial_n_junk);

	// Continue cleaning up as long as threads are running
	size_t n_cleanup_calls = 1;
	while (n_finished_threads < n_threads) {
		maid.cleanup();
		++n_cleanup_calls;
	}

	printf("Called cleanup %zu times\n", n_cleanup_calls);

	// Join litterer threads
	for (auto& t : litterers) {
		t.join();
	}

	// Clean up any leftover garbage (unlikely/impossible?)
	maid.cleanup();
	assert(n_junk == 0);

	// Allocate a new object, then let it and the Maid go out of scope
	Maid::managed_ptr<Junk> c = maid.make_managed<Junk>(5);
	assert(n_junk == 1);
}

int
main(int argc, char** argv)
{
	assert(n_junk == 0);
	test();
	assert(n_junk == 0);
	return 0;
}