/* Copyright (c) 2013-2017 Jeffrey Pfau
 *
 * 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 http://mozilla.org/MPL/2.0/. */
#include <mgba/core/mem-search.h>

#include <mgba/core/core.h>
#include <mgba/core/interface.h>

DEFINE_VECTOR(mCoreMemorySearchResults, struct mCoreMemorySearchResult);

static bool _op(int32_t value, int32_t match, enum mCoreMemorySearchOp op) {
	switch (op) {
	case mCORE_MEMORY_SEARCH_GREATER:
		return value > match;
	case mCORE_MEMORY_SEARCH_LESS:
		return value < match;
	case mCORE_MEMORY_SEARCH_EQUAL:
	case mCORE_MEMORY_SEARCH_DELTA:
		return value == match;
	}
	return false;
}

static size_t _search32(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint32_t value32, enum mCoreMemorySearchOp op, struct mCoreMemorySearchResults* out, size_t limit) {
	const uint32_t* mem32 = mem;
	size_t found = 0;
	uint32_t start = block->start;
	uint32_t end = size; // TODO: Segments
	size_t i;
	// TODO: Big endian
	for (i = 0; (!limit || found < limit) && i < end; i += 4) {
		if (_op(mem32[i >> 2], value32, op)) {
			struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
			res->address = start + i;
			res->type = mCORE_MEMORY_SEARCH_INT;
			res->width = 4;
			res->segment = -1; // TODO
			res->guessDivisor = 1;
			res->guessMultiplier = 1;
			res->oldValue = value32;
			++found;
		}
	}
	return found;
}

static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint16_t value16, enum mCoreMemorySearchOp op, struct mCoreMemorySearchResults* out, size_t limit) {
	const uint16_t* mem16 = mem;
	size_t found = 0;
	uint32_t start = block->start;
	uint32_t end = size; // TODO: Segments
	size_t i;
	// TODO: Big endian
	for (i = 0; (!limit || found < limit) && i < end; i += 2) {
		if (_op(mem16[i >> 1], value16, op)) {
			struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
			res->address = start + i;
			res->type = mCORE_MEMORY_SEARCH_INT;
			res->width = 2;
			res->segment = -1; // TODO
			res->guessDivisor = 1;
			res->guessMultiplier = 1;
			res->oldValue = value16;
			++found;
		}
	}
	return found;
}

static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint8_t value8, enum mCoreMemorySearchOp op, struct mCoreMemorySearchResults* out, size_t limit) {
	const uint8_t* mem8 = mem;
	size_t found = 0;
	uint32_t start = block->start;
	uint32_t end = size; // TODO: Segments
	size_t i;
	for (i = 0; (!limit || found < limit) && i < end; ++i) {
		if (_op(mem8[i], value8, op)) {
			struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
			res->address = start + i;
			res->type = mCORE_MEMORY_SEARCH_INT;
			res->width = 1;
			res->segment = -1; // TODO
			res->guessDivisor = 1;
			res->guessMultiplier = 1;
			res->oldValue = value8;
			++found;
		}
	}
	return found;
}

static size_t _searchInt(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) {
	if (params->align == params->width || params->align == -1) {
		switch (params->width) {
		case 4:
			return _search32(mem, size, block, params->valueInt, params->op, out, limit);
		case 2:
			return _search16(mem, size, block, params->valueInt, params->op, out, limit);
		case 1:
			return _search8(mem, size, block, params->valueInt, params->op, out, limit);
		}
	}
	return 0;
}

static size_t _searchStr(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const char* valueStr, int len, struct mCoreMemorySearchResults* out, size_t limit) {
	const char* memStr = mem;
	size_t found = 0;
	uint32_t start = block->start;
	uint32_t end = size; // TODO: Segments
	size_t i;
	for (i = 0; (!limit || found < limit) && i < end - len; ++i) {
		if (!memcmp(valueStr, &memStr[i], len)) {
			struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out);
			res->address = start + i;
			res->type = mCORE_MEMORY_SEARCH_STRING;
			res->width = len;
			res->segment = -1; // TODO
			++found;
		}
	}
	return found;
}

static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) {
	// TODO: As str

	char* end;
	int64_t value;

	size_t found = 0;

	struct mCoreMemorySearchResults tmp;
	mCoreMemorySearchResultsInit(&tmp, 0);

	// Decimal:
	value = strtoll(params->valueStr, &end, 10);
	if (end && !end[0]) {
		if ((params->width == -1 && value > 0x10000) || params->width == 4) {
			found += _search32(mem, size, block, value, params->op, out, limit ? limit - found : 0);
		} else if ((params->width == -1 && value > 0x100) || params->width == 2) {
			found += _search16(mem, size, block, value, params->op, out, limit ? limit - found : 0);
		} else {
			found += _search8(mem, size, block, value, params->op, out, limit ? limit - found : 0);
		}

		uint32_t divisor = 1;
		while (value && !(value % 10)) {
			mCoreMemorySearchResultsClear(&tmp);
			value /= 10;
			divisor *= 10;

			if ((params->width == -1 && value > 0x10000) || params->width == 4) {
				found += _search32(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0);
			} else if ((params->width == -1 && value > 0x100) || params->width == 2) {
				found += _search16(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0);
			} else {
				found += _search8(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0);
			}
			size_t i;
			for (i = 0; i < mCoreMemorySearchResultsSize(&tmp); ++i) {
				struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(&tmp, i);
				res->guessDivisor = divisor;
				*mCoreMemorySearchResultsAppend(out) = *res;
			}
		}
	}

	// Hex:
	value = strtoll(params->valueStr, &end, 16);
	if (end && !end[0]) {
		if ((params->width == -1 && value > 0x10000) || params->width == 4) {
			found += _search32(mem, size, block, value, params->op, out, limit ? limit - found : 0);
		} else if ((params->width == -1 && value > 0x100) || params->width == 2) {
			found += _search16(mem, size, block, value, params->op, out, limit ? limit - found : 0);
		} else {
			found += _search8(mem, size, block, value, params->op, out, limit ? limit - found : 0);
		}

		uint32_t divisor = 1;
		while (value && !(value & 0xF)) {
			mCoreMemorySearchResultsClear(&tmp);
			value >>= 4;
			divisor <<= 4;

			if ((params->width == -1 && value > 0x10000) || params->width == 4) {
				found += _search32(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0);
			} else if ((params->width == -1 && value > 0x100) || params->width == 2) {
				found += _search16(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0);
			} else {
				found += _search8(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0);
			}
			size_t i;
			for (i = 0; i < mCoreMemorySearchResultsSize(&tmp); ++i) {
				struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(&tmp, i);
				res->guessDivisor = divisor;
				*mCoreMemorySearchResultsAppend(out) = *res;
			}
		}
	}

	mCoreMemorySearchResultsDeinit(&tmp);
	return found;
}

static size_t _search(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) {
	switch (params->type) {
	case mCORE_MEMORY_SEARCH_INT:
		return _searchInt(mem, size, block, params, out, limit);
	case mCORE_MEMORY_SEARCH_STRING:
		return _searchStr(mem, size, block, params->valueStr, params->width, out, limit);
	case mCORE_MEMORY_SEARCH_GUESS:
		return _searchGuess(mem, size, block, params, out, limit);
	}
	return 0;
}

void mCoreMemorySearch(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) {
	const struct mCoreMemoryBlock* blocks;
	size_t nBlocks = core->listMemoryBlocks(core, &blocks);
	size_t found = 0;

	size_t b;
	for (b = 0; (!limit || found < limit) && b < nBlocks; ++b) {
		size_t size;
		const struct mCoreMemoryBlock* block = &blocks[b];
		if (!(block->flags & params->memoryFlags)) {
			continue;
		}
		void* mem = core->getMemoryBlock(core, block->id, &size);
		if (!mem) {
			continue;
		}
		if (size > block->end - block->start) {
			size = block->end - block->start; // TOOD: Segments
		}
		found += _search(mem, size, block, params, out, limit ? limit - found : 0);
	}
}

bool _testGuess(struct mCore* core, struct mCoreMemorySearchResult* res, const struct mCoreMemorySearchParams* params) {
	int64_t value;
	int32_t offset = 0;
	char* end;
	if (params->op == mCORE_MEMORY_SEARCH_DELTA) {
		offset = res->oldValue;
	}

	value = strtoll(params->valueStr, &end, 10);
	if (end) {
		res->oldValue += value;
		if (_op(core->rawRead8(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) {
			return true;
		}
		if (!(res->address & 1) && (res->width >= 2 || res->width == -1) && _op(core->rawRead16(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) {
			return true;
		}
		if (!(res->address & 3) && (res->width >= 4 || res->width == -1) && _op(core->rawRead32(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) {
			return true;
		}
		res->oldValue -= value;
	}

	value = strtoll(params->valueStr, &end, 16);
	if (end) {
		res->oldValue += value;
		if (_op(core->rawRead8(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) {
			return true;
		}
		if (!(res->address & 1) && (res->width >= 2 || res->width == -1) && _op(core->rawRead16(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) {
			return true;
		}
		if (!(res->address & 3) && (res->width >= 4 || res->width == -1) && _op(core->rawRead32(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) {
			return true;
		}
		res->oldValue -= value;
	}
	return false;
}

void mCoreMemorySearchRepeat(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* inout) {
	size_t i;
	for (i = 0; i < mCoreMemorySearchResultsSize(inout); ++i) {
		struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(inout, i);
		switch (res->type) {
		case mCORE_MEMORY_SEARCH_INT:
			if (params->type == mCORE_MEMORY_SEARCH_GUESS) {
				if (!_testGuess(core, res, params)) {
					*res = *mCoreMemorySearchResultsGetPointer(inout, mCoreMemorySearchResultsSize(inout) - 1);
					mCoreMemorySearchResultsResize(inout, -1);
					--i;
				}
			} else if (params->type == mCORE_MEMORY_SEARCH_INT) {
				int32_t oldValue = params->valueInt;
				if (params->op == mCORE_MEMORY_SEARCH_DELTA) {
					oldValue += res->oldValue;
				}
				int32_t value = 0;
				switch (params->width) {
				case 1:
					value = core->rawRead8(core, res->address, res->segment);
					break;
				case 2:
					value = core->rawRead16(core, res->address, res->segment);
					break;
				case 4:
					value = core->rawRead32(core, res->address, res->segment);
					break;
				default:
					break;
				}
				if (!_op(value, oldValue, params->op)) {
					*res = *mCoreMemorySearchResultsGetPointer(inout, mCoreMemorySearchResultsSize(inout) - 1);
					mCoreMemorySearchResultsResize(inout, -1);
					--i;
				} else {
					res->oldValue = value;
				}
			}
			break;
		case mCORE_MEMORY_SEARCH_STRING:
		case mCORE_MEMORY_SEARCH_GUESS:
			// TOOD
			break;
		}
	}
}
