/*
    libmaus2
    Copyright (C) 2020 German Tischler-Höhle

    This program 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
    (at your option) any later version.

    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <libmaus2/arch/CacheLineSize.hpp>
#include <stdexcept>

#include <libmaus2/LibMausConfig.hpp>

#if defined(_WIN32)
#include <libmaus2/LibMausWindows.hpp>
#endif

#if defined(LIBMAUS2_HAVE_UNISTD_H)
#include <unistd.h>
#endif

#if defined(__APPLE__)
#include <sys/sysctl.h>
#endif

std::mutex libmaus2::arch::CacheLineSize::cachelinesizelock;
std::atomic<int> libmaus2::arch::CacheLineSize::cachelinesizeinitcomplete(0);
std::atomic<unsigned int> libmaus2::arch::CacheLineSize::cachelinesize(0);

/**
 * @return size of a (level 1) cache line in bytes
 **/
#if defined(LIBMAUS2_HAVE_SYSCONF) && defined(_SC_LEVEL1_DCACHE_LINESIZE)
static uint64_t getCacheLineSizeLocal()
{
	return sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
}
#elif defined(_WIN32)
static uint64_t getCacheLineSizeLocal()
{
	uint64_t cachelinesize = 0;
	DWORD bufsize = 0;
	GetLogicalProcessorInformation(0, &bufsize);

	struct LocalAutoArray
	{
		uint8_t * p;

		LocalAutoArray(uint64_t const n)
		: p(nullptr)
		{
			p = new uint8_t[n];
		}
		~LocalAutoArray()
		{
			delete [] p;
		}
	};

	LocalAutoArray Abuffer(bufsize);
	SYSTEM_LOGICAL_PROCESSOR_INFORMATION * const buffer = reinterpret_cast<SYSTEM_LOGICAL_PROCESSOR_INFORMATION *>(Abuffer.p);
	GetLogicalProcessorInformation(&buffer[0], &bufsize);

	for (uint64_t i = 0; i != bufsize / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i)
	{
		if (buffer[i].Relationship == RelationCache && buffer[i].Cache.Level == 1) {
			cachelinesize = buffer[i].Cache.LineSize;
			break;
		}
	}

	return cachelinesize;
}
#elif defined(__APPLE__)
static uint64_t getCacheLineSizeLocal()
{
	uint64_t cachelinesize = 0;
	size_t cachelinesizelen = sizeof(cachelinesize);

	int const sysctlretname = sysctlbyname("hw.cachelinesize", &cachelinesize, &cachelinesizelen, 0, 0);

	if ( sysctlretname )
		throw std::runtime_error("libmaus2::arch::CacheLineSize: sysctlbyname(\"hw.cachelinesize\", &cachelinesize, &cachelinesizelen, 0, 0) returned error code");

	return cachelinesize;
}
#else
static uint64_t getCacheLineSizeLocal()
{
	throw std::runtime_error("libmaus2::arch::CacheLineSize: no method for cache line size detection available");
}
#endif

std::size_t libmaus2::arch::CacheLineSize::getCacheLineSize()
{
	std::lock_guard<std::mutex> slock(cachelinesizelock);

	if ( ! cachelinesizeinitcomplete.load() )
	{
		cachelinesize.store(getCacheLineSizeLocal());
		cachelinesizeinitcomplete.store(1);
	}

	std::size_t const lcachelinesize = cachelinesize.load();

	return lcachelinesize;
}
