#include <cassert>
#include <cstring>
#include <cstdint>

#include "Common/Data/Convert/ColorConv.h"
#include "Common/GPU/thin3d.h"
#include "Common/GPU/ShaderWriter.h"
#include "Common/Log.h"
#include "Common/System/Display.h"

namespace Draw {

size_t DataFormatSizeInBytes(DataFormat fmt) {
	switch (fmt) {
	case DataFormat::R8_UNORM: return 1;
	case DataFormat::R8G8_UNORM: return 2;
	case DataFormat::R8G8B8_UNORM: return 3;

	case DataFormat::R4G4_UNORM_PACK8: return 1;
	case DataFormat::R4G4B4A4_UNORM_PACK16: return 2;
	case DataFormat::B4G4R4A4_UNORM_PACK16: return 2;
	case DataFormat::A4R4G4B4_UNORM_PACK16: return 2;
	case DataFormat::R5G5B5A1_UNORM_PACK16: return 2;
	case DataFormat::B5G5R5A1_UNORM_PACK16: return 2;
	case DataFormat::R5G6B5_UNORM_PACK16: return 2;
	case DataFormat::B5G6R5_UNORM_PACK16: return 2;
	case DataFormat::A1R5G5B5_UNORM_PACK16: return 2;

	case DataFormat::R8G8B8A8_UNORM:
	case DataFormat::R8G8B8A8_UNORM_SRGB: return 4;
	case DataFormat::B8G8R8A8_UNORM:
	case DataFormat::B8G8R8A8_UNORM_SRGB: return 4;

	case DataFormat::R8G8B8A8_SNORM: return 4;
	case DataFormat::R8G8B8A8_UINT: return 4;
	case DataFormat::R8G8B8A8_SINT: return 4;

	case DataFormat::R16_UNORM: return 2;

	case DataFormat::R16_FLOAT: return 2;
	case DataFormat::R16G16_FLOAT: return 4;
	case DataFormat::R16G16B16A16_FLOAT: return 8;
	case DataFormat::R32_FLOAT: return 4;
	case DataFormat::R32G32_FLOAT: return 8;
	case DataFormat::R32G32B32_FLOAT: return 12;
	case DataFormat::R32G32B32A32_FLOAT: return 16;

	case DataFormat::S8: return 1;
	case DataFormat::D16: return 2;
	case DataFormat::D16_S8: return 3;
	case DataFormat::D24_S8: return 4;
	case DataFormat::D32F: return 4;
	// Or maybe 8...
	case DataFormat::D32F_S8: return 5;

	default:
		return 0;
	}
}

const char *DataFormatToString(DataFormat fmt) {
	switch (fmt) {
	case DataFormat::R8_UNORM: return "R8_UNORM";
	case DataFormat::R8G8_UNORM: return "R8G8_UNORM";
	case DataFormat::R8G8B8A8_UNORM: return "R8G8B8A8_UNORM";
	case DataFormat::B8G8R8A8_UNORM: return "B8G8R8A8_UNORM";
	case DataFormat::R16_UNORM: return "R16_UNORM";
	case DataFormat::R16_FLOAT: return "R16_FLOAT";
	case DataFormat::R32_FLOAT: return "R32_FLOAT";

	case DataFormat::S8: return "S8";
	case DataFormat::D16: return "D16";
	case DataFormat::D16_S8: return "D16_S8";
	case DataFormat::D24_S8: return "D24_S8";
	case DataFormat::D32F: return "D32F";
	case DataFormat::D32F_S8: return "D32F_S8";

	default:
		return "(N/A)";
	}
}

bool DataFormatIsDepthStencil(DataFormat fmt) {
	switch (fmt) {
	case DataFormat::D16:
	case DataFormat::D16_S8:
	case DataFormat::D24_S8:
	case DataFormat::S8:
	case DataFormat::D32F:
	case DataFormat::D32F_S8:
		return true;
	default:
		return false;
	}
}

// We don't bother listing the formats that are irrelevant for PPSSPP, like BC6 (HDR format)
// or weird-shaped ASTC formats. We only support 4x4 block size formats for now.
// If you pass in a blockSize parameter, it receives byte count that a 4x4 block takes in this format.
bool DataFormatIsBlockCompressed(DataFormat fmt, int *blockSize) {
	switch (fmt) {
	case DataFormat::BC1_RGBA_UNORM_BLOCK:
	case DataFormat::BC4_UNORM_BLOCK:
	case DataFormat::ETC2_R8G8B8_UNORM_BLOCK:
		if (blockSize) *blockSize = 8;  // 64 bits
		return true;
	case DataFormat::BC2_UNORM_BLOCK:
	case DataFormat::BC3_UNORM_BLOCK:
	case DataFormat::BC5_UNORM_BLOCK:
	case DataFormat::BC7_UNORM_BLOCK:
	case DataFormat::ETC2_R8G8B8A1_UNORM_BLOCK:
	case DataFormat::ETC2_R8G8B8A8_UNORM_BLOCK:
	case DataFormat::ASTC_4x4_UNORM_BLOCK:
		if (blockSize) *blockSize = 16;  // 128 bits
		return true;
	default:
		if (blockSize) *blockSize = 0;
		return false;
	}
}

int DataFormatNumChannels(DataFormat fmt) {
	switch (fmt) {
	case DataFormat::D16:
	case DataFormat::D32F:
	case DataFormat::R8_UNORM:
	case DataFormat::R16_UNORM:
	case DataFormat::R16_FLOAT:
	case DataFormat::R32_FLOAT:
		return 1;
	case DataFormat::R8G8B8A8_UNORM:
	case DataFormat::R8G8B8A8_UNORM_SRGB:
	case DataFormat::B8G8R8A8_UNORM:
	case DataFormat::B8G8R8A8_UNORM_SRGB:
		return 4;
	default:
		return 0;
	}
}

RefCountedObject::~RefCountedObject() {
	const int rc = refcount_.load();
	_dbg_assert_msg_(rc == 0xDEDEDE, "Unexpected refcount %d in object of type '%s'", rc, name_);
}

bool RefCountedObject::Release() {
	if (refcount_ > 0 && refcount_ < 10000) {
		if (--refcount_ == 0) {
			// Make it very obvious if we try to free this again.
			refcount_ = 0xDEDEDE;
			delete this;
			return true;
		}
	} else {
		// No point in printing the name here if the object has already been free-d, it'll be corrupt and dangerous to print.
		_dbg_assert_msg_(false, "Refcount (%d) invalid for object %p - corrupt?", refcount_.load(), this);
	}
	return false;
}

bool RefCountedObject::ReleaseAssertLast() {
	bool released = Release();
	_dbg_assert_msg_(released, "RefCountedObject: Expected to be the last reference, but isn't! (%s)", name_);
	return released;
}

const UniformBufferDesc vsColBufDesc { sizeof(VsColUB), {
	{ "WorldViewProj", 0, -1, UniformType::MATRIX4X4, 0 },
	{ "TintSaturation", 4, -1, UniformType::FLOAT2, 64 },
} };

static_assert(SEM_TEXCOORD0 == 3, "Semantic shader hardcoded in glsl above.");

const UniformBufferDesc vsTexColBufDesc{ sizeof(VsTexColUB),{
	{ "WorldViewProj", 0, -1, UniformType::MATRIX4X4, 0 },
	{ "TintSaturation", 4, -1, UniformType::FLOAT2, 64 },
} };

ShaderModule *CreateShader(DrawContext *draw, ShaderStage stage, const std::vector<ShaderSource> &sources) {
	uint32_t supported = draw->GetSupportedShaderLanguages();
	for (const auto &iter : sources) {
		if ((uint32_t)iter.lang & supported) {
			return draw->CreateShaderModule(stage, iter.lang, (const uint8_t *)iter.src, strlen(iter.src));
		}
	}
	return nullptr;
}

static const InputDef g_inputs[] = {
	{ "vec3", "Position", Draw::SEM_POSITION },
	{ "vec4", "Color0", Draw::SEM_COLOR0 },
	{ "vec2", "TexCoord0", Draw::SEM_TEXCOORD0 },
};

static const VaryingDef g_varyings[] = {
	{ "vec4", "oColor0", Draw::SEM_COLOR0, 0, "lowp" },
};

static const VaryingDef g_varyingsTex[] = {
	{ "vec4", "oColor0", Draw::SEM_COLOR0, 0, "lowp" },
	{ "vec2", "oTexCoord0", Draw::SEM_TEXCOORD0, 1, "highp" },
};

static const SamplerDef g_samplers[] = {
	{ 0, "tex" },
};

const UniformDef g_uniforms[] = {
	{ "mat4", "WorldViewProj", 0 },
	{ "vec2", "TintSaturation", 1 },
};

static ShaderModule *GenerateVShader(DrawContext *draw, VertexShaderPreset preset, bool tintSupported) {
	const ShaderLanguageDesc &shaderLanguageDesc = draw->GetShaderLanguageDesc();
	char code[2048];
	ShaderWriter vsWriter(code, shaderLanguageDesc, ShaderStage::Vertex);

	const bool texCoords = preset != VS_COLOR_2D;
	const bool tint = tintSupported && (preset == VS_TEXTURE_COLOR_2D || preset == VS_COLOR_2D);

	if (tint) {
		vsWriter.C(R"(
vec3 rgb2hsv(vec3 c) {
	vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
	vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
	vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
	float d = q.x - min(q.w, q.y);
	float e = 1.0e-10;
	return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c) {
	vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
	vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
	return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
)");
	}

	vsWriter.BeginVSMain(g_inputs, g_uniforms, texCoords ? Slice(g_varyingsTex) : Slice(g_varyings));
	vsWriter.C("gl_Position = mul(WorldViewProj, vec4(Position, 1.0));\n");
	if (tint) {
		vsWriter.C(R"(
		vec3 hsv = rgb2hsv(Color0.xyz);
		hsv.x += TintSaturation.x;
		hsv.y *= TintSaturation.y;
		oColor0 = vec4(hsv2rgb(hsv), Color0.w);
)");
	} else {
		vsWriter.C("oColor0 = Color0;\n");
	}
	if (texCoords) {
		vsWriter.C("oTexCoord0 = TexCoord0;\n");
	}
	vsWriter.EndVSMain(texCoords ? Slice(g_varyingsTex) : Slice(g_varyings));
	return draw->CreateShaderModule(ShaderStage::Vertex, shaderLanguageDesc.shaderLanguage, (const uint8_t *)code, strlen(code));
}

static ShaderModule *GenerateFShader(DrawContext *draw, FragmentShaderPreset preset) {
	const ShaderLanguageDesc &shaderLanguageDesc = draw->GetShaderLanguageDesc();
	char code[2048];
	ShaderWriter fsWriter(code, shaderLanguageDesc, ShaderStage::Fragment);

	const bool texturing = preset != FS_COLOR_2D;
	const bool rbSwizzle = preset == FS_TEXTURE_COLOR_2D_RB_SWIZZLE;

	fsWriter.DeclareSamplers(g_samplers);

	// NOTE (first argument): We do not declare the uniforms here, as they aren't used in any of the preset shaders.
	// Declaring them currently causes a precision clash with some GLSL compilers, see issue #21904.
	// So if we need them in the future, that needs to be dealt with.
	fsWriter.BeginFSMain(Slice<UniformDef>::empty(), texturing ? Slice(g_varyingsTex) : Slice(g_varyings));
	if (texturing) {
		fsWriter.C("vec4 col = ");
		fsWriter.SampleTexture2D("tex", "oTexCoord0");
		if (rbSwizzle) {
			fsWriter.C(".zyxw * oColor0;\n");
		} else {
			fsWriter.C(" * oColor0;\n");
		}
		if (preset == FS_TEXTURE_COLOR_2D_ALPHA_TO_GRAY) {
			fsWriter.C("col.rgb = splat3(col.a);\n");
		}
	} else {
		fsWriter.C("vec4 col = oColor0;\n");
	}
	fsWriter.C("col.rgb *= oColor0.a;\n");  // premultiply alpha
	fsWriter.EndFSMain("col");
	return draw->CreateShaderModule(ShaderStage::Fragment, shaderLanguageDesc.shaderLanguage, (const uint8_t *)code, strlen(code));
}

bool DrawContext::CreatePresets() {
	INFO_LOG(Log::G3D, "Creating thin3d preset shaders...");
	bool tintSupported = !bugs_.Has(Bugs::RASPBERRY_SHADER_COMP_HANG);

	for (int i = 0; i < VS_MAX_PRESET; i++) {
		vsPresets_[i] = GenerateVShader(this, (VertexShaderPreset)i, tintSupported);
		if (!vsPresets_[i]) {
			WARN_LOG(Log::G3D, "Failed to create vertex shader preset %d", i);
			return false;
		}
	}
	for (int i = 0; i < FS_MAX_PRESET; i++) {
		fsPresets_[i] = GenerateFShader(this, (FragmentShaderPreset)i);
		if (!fsPresets_[i]) {
			WARN_LOG(Log::G3D, "Failed to create fragment shader preset %d", i);
			return false;
		}
	}
	return true;
}

void DrawContext::DestroyPresets() {
	for (int i = 0; i < VS_MAX_PRESET; i++) {
		if (vsPresets_[i]) {
			vsPresets_[i]->Release();
			vsPresets_[i] = nullptr;
		}
	}
	for (int i = 0; i < FS_MAX_PRESET; i++) {
		if (fsPresets_[i]) {
			fsPresets_[i]->Release();
			fsPresets_[i] = nullptr;
		}
	}
}

void ConvertFromRGBA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format) {
	// Must skip stride in the cases below.  Some games pack data into the cracks, like MotoGP.
	const uint32_t *src32 = (const uint32_t *)src;

	if (format == Draw::DataFormat::R8G8B8A8_UNORM) {
		uint32_t *dst32 = (uint32_t *)dst;
		if (src == dst) {
			return;
		} else {
			for (uint32_t y = 0; y < height; ++y) {
				memcpy(dst32, src32, width * 4);
				src32 += srcStride;
				dst32 += dstStride;
			}
		}
	} else if (format == Draw::DataFormat::R8G8B8_UNORM) {
		for (uint32_t y = 0; y < height; ++y) {
			ConvertRGBA8888ToRGB888(dst, src32, width);
			src32 += srcStride;
			dst += dstStride * 3;
		}
	} else {
		// But here it shouldn't matter if they do intersect
		uint16_t *dst16 = (uint16_t *)dst;
		switch (format) {
		case Draw::DataFormat::R5G6B5_UNORM_PACK16: // BGR 565
			for (uint32_t y = 0; y < height; ++y) {
				ConvertRGBA8888ToRGB565(dst16, src32, width);
				src32 += srcStride;
				dst16 += dstStride;
			}
			break;
		case Draw::DataFormat::A1R5G5B5_UNORM_PACK16: // ABGR 1555
			for (uint32_t y = 0; y < height; ++y) {
				ConvertRGBA8888ToRGBA5551(dst16, src32, width);
				src32 += srcStride;
				dst16 += dstStride;
			}
			break;
		case Draw::DataFormat::A4R4G4B4_UNORM_PACK16: // ABGR 4444
			for (uint32_t y = 0; y < height; ++y) {
				ConvertRGBA8888ToRGBA4444(dst16, src32, width);
				src32 += srcStride;
				dst16 += dstStride;
			}
			break;
		case Draw::DataFormat::R8G8B8A8_UNORM:
		case Draw::DataFormat::UNDEFINED:
		default:
			WARN_LOG(Log::G3D, "Unable to convert from format: %d", (int)format);
			break;
		}
	}
}

void ConvertFromBGRA8888(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format) {
	// Must skip stride in the cases below.  Some games pack data into the cracks, like MotoGP.
	const uint32_t *src32 = (const uint32_t *)src;

	if (format == Draw::DataFormat::B8G8R8A8_UNORM) {
		uint32_t *dst32 = (uint32_t *)dst;
		if (src == dst) {
			return;
		} else {
			for (uint32_t y = 0; y < height; ++y) {
				memcpy(dst32, src32, width * 4);
				src32 += srcStride;
				dst32 += dstStride;
			}
		}
	} else if (format == Draw::DataFormat::R8G8B8A8_UNORM) {
		uint32_t *dst32 = (uint32_t *)dst;
		for (uint32_t y = 0; y < height; ++y) {
			ConvertBGRA8888ToRGBA8888(dst32, src32, width);
			src32 += srcStride;
			dst32 += dstStride;
		}
	} else if (format == Draw::DataFormat::R8G8B8_UNORM) {
		for (uint32_t y = 0; y < height; ++y) {
			ConvertBGRA8888ToRGB888(dst, src32, width);
			src32 += srcStride;
			dst += dstStride * 3;
		}
	} else {
		// But here it shouldn't matter if they do intersect
		uint16_t *dst16 = (uint16_t *)dst;
		switch (format) {
		case Draw::DataFormat::R5G6B5_UNORM_PACK16: // BGR 565
			for (uint32_t y = 0; y < height; ++y) {
				ConvertBGRA8888ToRGB565(dst16, src32, width);
				src32 += srcStride;
				dst16 += dstStride;
			}
			break;
		case Draw::DataFormat::A1R5G5B5_UNORM_PACK16: // ABGR 1555
			for (uint32_t y = 0; y < height; ++y) {
				ConvertBGRA8888ToRGBA5551(dst16, src32, width);
				src32 += srcStride;
				dst16 += dstStride;
			}
			break;
		case Draw::DataFormat::A4R4G4B4_UNORM_PACK16: // ABGR 4444
			for (uint32_t y = 0; y < height; ++y) {
				ConvertBGRA8888ToRGBA4444(dst16, src32, width);
				src32 += srcStride;
				dst16 += dstStride;
			}
			break;
		case Draw::DataFormat::R8G8B8A8_UNORM:
		case Draw::DataFormat::UNDEFINED:
		default:
			WARN_LOG(Log::G3D, "Unable to convert from format to BGRA: %d", (int)format);
			break;
		}
	}
}

void ConvertToD32F(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format) {
	if (format == Draw::DataFormat::D32F) {
		const float *src32 = (const float *)src;
		float *dst32 = (float *)dst;
		if (src == dst) {
			return;
		} else {
			for (uint32_t y = 0; y < height; ++y) {
				memcpy(dst32, src32, width * 4);
				src32 += srcStride;
				dst32 += dstStride;
			}
		}
	} else if (format == Draw::DataFormat::D16) {
		const uint16_t *src16 = (const uint16_t *)src;
		float *dst32 = (float *)dst;
		for (uint32_t y = 0; y < height; ++y) {
			for (uint32_t x = 0; x < width; ++x) {
				dst32[x] = (float)(int)src16[x] / 65535.0f;
			}
			src16 += srcStride;
			dst32 += dstStride;
		}
	} else if (format == Draw::DataFormat::D24_S8) {
		const uint32_t *src32 = (const uint32_t *)src;
		float *dst32 = (float *)dst;
		for (uint32_t y = 0; y < height; ++y) {
			for (uint32_t x = 0; x < width; ++x) {
				dst32[x] = (src32[x] & 0x00FFFFFF) / 16777215.0f;
			}
			src32 += srcStride;
			dst32 += dstStride;
		}
	} else {
		assert(false);
	}
}

// TODO: This is missing the conversion to the quarter-range we use if depth clamp is not available.
// That conversion doesn't necessarily belong here in thin3d, though.
void ConvertToD16(uint8_t *dst, const uint8_t *src, uint32_t dstStride, uint32_t srcStride, uint32_t width, uint32_t height, DataFormat format) {
	if (format == Draw::DataFormat::D32F) {
		const float *src32 = (const float *)src;
		uint16_t *dst16 = (uint16_t *)dst;
		if (src == dst) {
			return;
		} else {
			for (uint32_t y = 0; y < height; ++y) {
				for (uint32_t x = 0; x < width; ++x) {
					dst16[x] = (uint16_t)(src32[x] * 65535.0f);
				}
				src32 += srcStride;
				dst16 += dstStride;
			}
		}
	} else if (format == Draw::DataFormat::D16) {
		_assert_(src != dst);
		const uint16_t *src16 = (const uint16_t *)src;
		uint16_t *dst16 = (uint16_t *)dst;
		for (uint32_t y = 0; y < height; ++y) {
			memcpy(dst16, src16, width * 2);
			src16 += srcStride;
			dst16 += dstStride;
		}
	} else if (format == Draw::DataFormat::D24_S8) {
		_assert_(src != dst);
		const uint32_t *src32 = (const uint32_t *)src;
		uint16_t *dst16 = (uint16_t *)dst;
		for (uint32_t y = 0; y < height; ++y) {
			for (uint32_t x = 0; x < width; ++x) {
				dst16[x] = (src32[x] & 0x00FFFFFF) >> 8;
			}
			src32 += srcStride;
			dst16 += dstStride;
		}
	} else {
		assert(false);
	}
}

const char *Bugs::GetBugName(uint32_t bug) {
	switch (bug) {
	case NO_DEPTH_CANNOT_DISCARD_STENCIL_MALI: return "NO_DEPTH_CANNOT_DISCARD_STENCIL_MALI";
	case NO_DEPTH_CANNOT_DISCARD_STENCIL_ADRENO: return "NO_DEPTH_CANNOT_DISCARD_STENCIL_ADRENO";
	case DUAL_SOURCE_BLENDING_BROKEN: return "DUAL_SOURCE_BLENDING_BROKEN";
	case ANY_MAP_BUFFER_RANGE_SLOW: return "ANY_MAP_BUFFER_RANGE_SLOW";
	case PVR_GENMIPMAP_HEIGHT_GREATER: return "PVR_GENMIPMAP_HEIGHT_GREATER";
	case BROKEN_NAN_IN_CONDITIONAL: return "BROKEN_NAN_IN_CONDITIONAL";
	case COLORWRITEMASK_BROKEN_WITH_DEPTHTEST: return "COLORWRITEMASK_BROKEN_WITH_DEPTHTEST";
	case BROKEN_FLAT_IN_SHADER: return "BROKEN_FLAT_IN_SHADER";
	case EQUAL_WZ_CORRUPTS_DEPTH: return "EQUAL_WZ_CORRUPTS_DEPTH";
	case RASPBERRY_SHADER_COMP_HANG: return "RASPBERRY_SHADER_COMP_HANG";
	case MALI_CONSTANT_LOAD_BUG: return "MALI_CONSTANT_LOAD_BUG";
	case SUBPASS_FEEDBACK_BROKEN: return "SUBPASS_FEEDBACK_BROKEN";
	case ADRENO_RESOURCE_DEADLOCK: return "ADRENO_RESOURCE_DEADLOCK";
	case PVR_BAD_16BIT_TEXFORMATS: return "PVR_BAD_16BIT_TEXFORMATS";
	case EMPTY_RENDERPASS_BROKEN_MALI: return "EMPTY_RENDERPASS_BROKEN_MALI";
	default: return "(N/A)";
	}
}

const char *PresentModeToString(PresentMode presentMode) {
	// All 8 possible cases, with three flags, for simplicity.
	switch ((int)presentMode) {
	case 0: return "NONE";
	case (int)PresentMode::FIFO: return "FIFO";
	case (int)PresentMode::IMMEDIATE: return "IMMEDIATE";
	case (int)PresentMode::MAILBOX: return "MAILBOX";
	case ((int)PresentMode::FIFO | (int)PresentMode::MAILBOX) : return "FIFO|MAILBOX";
	case ((int)PresentMode::FIFO | (int)PresentMode::IMMEDIATE) : return "FIFO|IMMEDIATE";
	case ((int)PresentMode::MAILBOX | (int)PresentMode::IMMEDIATE) : return "MAILBOX|IMMEDIATE";  // Not gonna happen
	case ((int)PresentMode::FIFO | (int)PresentMode::MAILBOX | (int)PresentMode::IMMEDIATE) : return "FIFO|MAILBOX|IMMEDIATE";
	default:
		return "INVALID";
	}
}

}  // namespace Draw
