/*
 * TraceFileIO.cpp
 *
 * This source file is part of the FoundationDB open source project
 *
 * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "fdbrpc/TraceFileIO.h"

#if CENABLED(0, NOT_IN_CLEAN)
// TO RUN: Set the name of file you wish to track
std::string debugFileName = "storage-3807bf710afaa6d39d23db60d6e687c8.fdb-wal";
std::map<int64_t, uint8_t*> debugFileData;
std::map<int64_t, uint8_t*> debugFileMask;
std::map<int64_t, int64_t> debugFileRegions;
uint8_t* debugFileSetPage;

// Setup memory data structures for file debugging.
// TO RUN: setup debugFileRegions map with the regions of the file you wish to track.  Keys of the map are offsets in
// the file, and values are the lengths of the region
void debugFileSetup() {
	debugFileRegions[0] = 1e8;
	for (auto itr = debugFileRegions.begin(); itr != debugFileRegions.end(); ++itr) {
		debugFileData[itr->first] = (uint8_t*)malloc(itr->second);
		memset(debugFileData[itr->first], 0, itr->second);

		debugFileMask[itr->first] = (uint8_t*)malloc(itr->second);
		memset(debugFileMask[itr->first], 0, itr->second);
	}

	debugFileSetPage = (uint8_t*)malloc(4096);
	memset(debugFileSetPage, 1, 4096);
}

// Trim a file path to the file name
std::string debugFileTrim(std::string filename) {
	int index = filename.find_last_of("/");
	if (index == filename.npos)
		return filename;
	else
		return filename.substr(index + 1);
}

// Checks if a block of memory has been written
bool debugFileIsSet(uint8_t* storeMask, int64_t offset, int64_t length) {
	for (int64_t i = 0; i < length; i += 4096)
		if (memcmp(&storeMask[offset + i], debugFileSetPage, std::min((int64_t)4096, length - i)))
			return false;

	return true;
}

// Checks that a given block of data is the same as what has been written by a call to debugFileSet
void debugFileCheck(std::string context, std::string file, const void* data, int64_t offset, int length) {
	if (debugFileRegions.empty())
		debugFileSetup();
	if (debugFileTrim(file) == debugFileName) {
		bool found = false;
		for (auto itr = debugFileRegions.begin(); itr != debugFileRegions.end(); ++itr) {
			if (offset + length > itr->first && offset < itr->first + itr->second) {
				found = true;
				uint8_t* storeData = debugFileData[itr->first];
				uint8_t* storeMask = debugFileMask[itr->first];

				ASSERT(storeData && storeMask);

				int64_t dataOffset = std::max((int64_t)0, itr->first - offset);
				int64_t dbgOffset = std::max((int64_t)0, offset - itr->first);
				int64_t cmpLength = std::min(length - dataOffset, itr->second - dbgOffset);
				ASSERT(cmpLength > 0);
				TraceEvent("DebugFileCheck")
				    .detail("Context", context)
				    .detail("Filename", file)
				    .detail("Offset", offset)
				    .detail("Length", length);
				bool success = true;
				if (debugFileIsSet(storeMask, dbgOffset, cmpLength)) {
					//TraceEvent("DebugFileCheckMemCmp").detail("Context", context).detail("Filename", file).detail("Offset", offset).detail("Length", length).detail("DataOffset", dataOffset).detail("DbgOffset", dbgOffset).detail("OverlapLength", cmpLength);
					if (memcmp(&((uint8_t*)data)[dataOffset], &storeData[dbgOffset], cmpLength))
						success = false;
				} else {
					TraceEvent("DebugFileUnsetCheck")
					    .detail("Context", context)
					    .detail("Filename", file)
					    .detail("Offset", offset)
					    .detail("Length", length);
					for (int64_t i = 0; i < cmpLength; i++) {
						if (storeMask[dbgOffset + i] && storeData[dbgOffset + i] != ((uint8_t*)data)[dataOffset + i]) {
							success = false;
							break;
						}
					}
				}

				if (!success)
					TraceEvent(SevWarnAlways, "DebugFileFail")
					    .detail("Context", context)
					    .detail("Filename", file)
					    .detail("Offset", offset)
					    .detail("Length", length);
			}
		}
		if (!found)
			TraceEvent("DebugFileSkippingCheck")
			    .detail("Context", context)
			    .detail("Filename", file)
			    .detail("Offset", offset)
			    .detail("Length", length);
	}
}

// Updates the in-memory copy of tracked data at a given offset
void debugFileSet(std::string context, std::string file, const void* data, int64_t offset, int length) {
	if (debugFileRegions.empty())
		debugFileSetup();
	if (debugFileTrim(file) == debugFileName) {
		bool found = false;
		for (auto itr = debugFileRegions.begin(); itr != debugFileRegions.end(); ++itr) {
			if (offset + length > itr->first && offset < itr->first + itr->second) {
				found = true;

				uint8_t* storeData = debugFileData[itr->first];
				uint8_t* storeMask = debugFileMask[itr->first];
				ASSERT(storeData && storeMask);

				int64_t dataOffset = std::max((int64_t)0, itr->first - offset);
				int64_t dbgOffset = std::max((int64_t)0, offset - itr->first);
				int64_t cmpLength = std::min(length - dataOffset, itr->second - dbgOffset);
				ASSERT(cmpLength > 0);
				TraceEvent("DebugFileSet")
				    .detail("Context", context)
				    .detail("Filename", file)
				    .detail("Offset", offset)
				    .detail("Length", length);
				//TraceEvent("DebugFileSetMemCpy").detail("Context", context).detail("Filename", file).detail("Offset", offset).detail("Length", length).detail("DataOffset", dataOffset).detail("DbgOffset", dbgOffset).detail("OverlapLength", std::min(length - dataOffset, itr->second - dbgOffset));
				memcpy(&storeData[dbgOffset], &((uint8_t*)data)[dataOffset], cmpLength);
				memset(&storeMask[dbgOffset], 1, cmpLength);
			}
		}
		if (!found)
			TraceEvent("DebugFileSkippingSet")
			    .detail("Context", context)
			    .detail("Filename", file)
			    .detail("Offset", offset)
			    .detail("Length", length);
	}
}

// Updates the in-memory copy of tracked data to account for truncates (this simply invalidates any data after truncate
// point)
void debugFileTruncate(std::string context, std::string file, int64_t offset) {
	if (debugFileRegions.empty())
		debugFileSetup();
	if (debugFileTrim(file) == debugFileName) {
		bool found = false;
		for (auto itr = debugFileRegions.begin(); itr != debugFileRegions.end(); ++itr) {
			if (itr->first + itr->second > offset) {
				found = true;
				TraceEvent("DebugFileTruncate")
				    .detail("Context", context)
				    .detail("Filename", file)
				    .detail("Offset", offset);
				uint8_t* storeMask = debugFileMask[itr->first];
				ASSERT(storeMask);

				int64_t dbgOffset = std::max((int64_t)0, offset - itr->first);
				memset(&storeMask[dbgOffset], 0, itr->second - dbgOffset);
			}
		}
		if (!found)
			TraceEvent("DebugFileSkippingTruncate")
			    .detail("Context", context)
			    .detail("Filename", file)
			    .detail("Offset", offset);
	}
}

#else
void debugFileCheck(std::string context, std::string file, const void* data, int64_t offset, int length) {}
void debugFileSet(std::string context, std::string file, const void* data, int64_t offset, int length) {}
void debugFileTruncate(std::string context, std::string file, int64_t offset) {}
#endif
