/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#include "MemMappedFile.hpp"
#include "Base/Logger.hpp"
#ifdef _MSC_VER
#include <windows.h>
#else
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#endif

namespace OpenVulkano
{
	class MemMappedFile::Internal final
	{
	public:
		void* address = nullptr;
		size_t size = 0;
#ifdef _MSC_VER
		HANDLE fileHandle;
		HANDLE fileMappingHandle;

		Internal(const char* file, FileMode fileMode)
		{ //TODO handle other file modes than read
			fileHandle = CreateFile(
				TEXT(file),   // file to open
				GENERIC_READ,       // open for reading
				0,                  // do not share
				NULL,               // default security
				OPEN_EXISTING,      // existing file only
				FILE_ATTRIBUTE_NORMAL, // normal file
				NULL);              // no attribute template
			if (fileHandle == INVALID_HANDLE_VALUE)
			{
				Logger::FILESYS->critical("Failed to open file: {}", file);
				return;
			}

			LARGE_INTEGER fileSize;
			if (!GetFileSizeEx(fileHandle, &fileSize))
			{
				Logger::FILESYS->critical("Failed to get size of file: {}", file);
				CloseHandle(fileHandle);
				return;
			}
			size = fileSize.QuadPart;

			fileMappingHandle = CreateFileMapping(
				fileHandle,
				NULL,               // default security
				PAGE_READONLY,      // read-only access
				0,                  // maximum object size (high-order)
				0,                  // maximum object size (low-order)
				NULL);              // name of the mapping object
			if (fileMappingHandle == 0)
			{
				Logger::FILESYS->critical("Failed to create file mapping handle: {}", file);
				CloseHandle(fileHandle);
				size = 0;
				return;
			}

			address = MapViewOfFile(
				fileMappingHandle, // handle to the map object
				FILE_MAP_READ,    // read access
				0,                 // max. object size
				0,                 // size of the hFile
				0);                // map the entire file

			if (address == nullptr)
			{
				Logger::FILESYS->critical("Failed to create file mapping: {}", file);
				CloseHandle(fileMappingHandle);
				CloseHandle(fileHandle);
				size = 0;
			}
		}

		~Internal()
		{
			UnmapViewOfFile(address);
			CloseHandle(fileMappingHandle);
			CloseHandle(fileHandle);
		}
#else
		int fileHandle;
		struct stat fileStat;

		Internal(const char* file, FileMode fileMode)
		{
			fileHandle = open(file, fileMode);
			if (fstat(fileHandle, &fileStat) == -1)
			{
				Logger::FILESYS->critical("Failed to query stats for file: {}", file);
				return;
			}
			address = mmap(nullptr, fileStat.st_size, fileMode + 1, MAP_PRIVATE, fileHandle, 0);
			if (address == MAP_FAILED)
			{
				Logger::FILESYS->critical("Failed to memory map file: {}", file);
				address = nullptr;
			}
			size = fileStat.st_size;
		}

		~Internal()
		{
			munmap(address, fileStat.st_size);
			close(fileHandle);
		}
#endif
	};

	MemMappedFile::MemMappedFile(const std::filesystem::path& path, FileMode fileMode)
	{
		m_internal = std::make_shared<Internal>(path.string().c_str(), fileMode);
		m_data = m_internal->address;
		m_size = (m_data) ? m_internal->size : 0;
	}
}
