#pragma once

#include <cstdint>
#include <cstring>

#include "engine/palette.h"
#include "utils/attributes.h"

namespace devilution {

enum class BlitType : uint8_t {
	Transparent,
	Pixels,
	Fill
};

struct BlitCommand {
	BlitType type;
	const uint8_t *srcEnd; // Pointer past the end of the command.
	unsigned length;       // Number of pixels this command will write.
	uint8_t color;         // For `BlitType::Pixel` and `BlitType::Fill` only.
};

DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitFillDirect(uint8_t *dst, unsigned length, uint8_t color)
{
	std::memset(dst, color, length);
}

DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitPixelsDirect(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, unsigned length)
{
	std::memcpy(dst, src, length);
}

struct BlitDirect {
	DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void operator()(BlitCommand cmd, uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src)
	{
		switch (cmd.type) {
		case BlitType::Fill:
			BlitFillDirect(dst, cmd.length, cmd.color);
			return;
		case BlitType::Pixels:
			BlitPixelsDirect(dst, src, cmd.length);
			return;
		case BlitType::Transparent:
			return;
		}
	}
};

DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitFillWithMap(uint8_t *dst, unsigned length, uint8_t color, const uint8_t *DVL_RESTRICT colorMap)
{
	assert(length != 0);
	std::memset(dst, colorMap[color], length);
}

DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitPixelsWithMap(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, unsigned length, const uint8_t *DVL_RESTRICT colorMap)
{
	assert(length != 0);
	const uint8_t *end = src + length;
	while (src + 3 < end) {
		*dst++ = colorMap[*src++];
		*dst++ = colorMap[*src++];
		*dst++ = colorMap[*src++];
		*dst++ = colorMap[*src++];
	}
	while (src < end) {
		*dst++ = colorMap[*src++];
	}
}

DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitFillBlended(uint8_t *dst, unsigned length, uint8_t color)
{
	assert(length != 0);
	const uint8_t *end = dst + length;
	const std::array<uint8_t, 256> &tbl = paletteTransparencyLookup[color];
	while (dst + 3 < end) {
		*dst = tbl[*dst];
		++dst;
		*dst = tbl[*dst];
		++dst;
		*dst = tbl[*dst];
		++dst;
		*dst = tbl[*dst];
		++dst;
	}
	while (dst < end) {
		*dst = tbl[*dst];
		++dst;
	}
}

DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitPixelsBlended(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, unsigned length)
{
	assert(length != 0);
	const uint8_t *end = src + length;
	while (src + 3 < end) {
		*dst = paletteTransparencyLookup[*dst][*src++];
		++dst;
		*dst = paletteTransparencyLookup[*dst][*src++];
		++dst;
		*dst = paletteTransparencyLookup[*dst][*src++];
		++dst;
		*dst = paletteTransparencyLookup[*dst][*src++];
		++dst;
	}
	while (src < end) {
		*dst = paletteTransparencyLookup[*dst][*src++];
		++dst;
	}
}

struct BlitWithMap {
	const uint8_t *DVL_RESTRICT colorMap;

	DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void operator()(BlitCommand cmd, uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src) const
	{
		switch (cmd.type) {
		case BlitType::Fill:
			BlitFillWithMap(dst, cmd.length, cmd.color, colorMap);
			return;
		case BlitType::Pixels:
			BlitPixelsWithMap(dst, src, cmd.length, colorMap);
			return;
		case BlitType::Transparent:
			return;
		}
	}
};

DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void BlitPixelsBlendedWithMap(uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src, unsigned length, const uint8_t *DVL_RESTRICT colorMap)
{
	assert(length != 0);
	const uint8_t *end = src + length;
	while (src + 3 < end) {
		*dst = paletteTransparencyLookup[*dst][colorMap[*src++]];
		++dst;
		*dst = paletteTransparencyLookup[*dst][colorMap[*src++]];
		++dst;
		*dst = paletteTransparencyLookup[*dst][colorMap[*src++]];
		++dst;
		*dst = paletteTransparencyLookup[*dst][colorMap[*src++]];
		++dst;
	}
	while (src < end) {
		*dst = paletteTransparencyLookup[*dst][colorMap[*src++]];
		++dst;
	}
}

struct BlitBlendedWithMap {
	const uint8_t *colorMap;

	DVL_ALWAYS_INLINE DVL_ATTRIBUTE_HOT void operator()(BlitCommand cmd, uint8_t *DVL_RESTRICT dst, const uint8_t *DVL_RESTRICT src) const
	{
		switch (cmd.type) {
		case BlitType::Fill:
			BlitFillBlended(dst, cmd.length, colorMap[cmd.color]);
			return;
		case BlitType::Pixels:
			BlitPixelsBlendedWithMap(dst, src, cmd.length, colorMap);
			return;
		case BlitType::Transparent:
			return;
		}
	}
};

} // namespace devilution
