#pragma once

// Sources
// https://learn.microsoft.com/en-us/windows/uwp/gaming/complete-code-for-ddstextureloader
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header-dxt10
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-pixelformat

#if (__cpp_constexpr == 201304) || (_MSC_VER > 1900)
#define ddspp_constexpr constexpr
#else
#define ddspp_constexpr const
#endif

namespace ddspp
{
	namespace internal
	{
		static ddspp_constexpr unsigned int DDS_MAGIC      = 0x20534444;

		static ddspp_constexpr unsigned int DDS_ALPHAPIXELS = 0x00000001;
		static ddspp_constexpr unsigned int DDS_ALPHA       = 0x00000002; // DDPF_ALPHA
		static ddspp_constexpr unsigned int DDS_FOURCC      = 0x00000004; // DDPF_FOURCC
		static ddspp_constexpr unsigned int DDS_RGB         = 0x00000040; // DDPF_RGB
		static ddspp_constexpr unsigned int DDS_RGBA        = 0x00000041; // DDPF_RGB | DDPF_ALPHAPIXELS
		static ddspp_constexpr unsigned int DDS_YUV         = 0x00000200; // DDPF_YUV
		static ddspp_constexpr unsigned int DDS_LUMINANCE   = 0x00020000; // DDPF_LUMINANCE
		static ddspp_constexpr unsigned int DDS_LUMINANCEA  = 0x00020001; // DDPF_LUMINANCE | DDPF_ALPHAPIXELS
		
		static ddspp_constexpr unsigned int DDS_PAL8       = 0x00000020; // DDPF_PALETTEINDEXED8
		static ddspp_constexpr unsigned int DDS_BUMPDUDV   = 0x00080000; // DDPF_BUMPDUDV

		static ddspp_constexpr unsigned int DDS_HEADER_FLAGS_CAPS        = 0x00000001; // DDSD_CAPS
		static ddspp_constexpr unsigned int DDS_HEADER_FLAGS_HEIGHT      = 0x00000002; // DDSD_HEIGHT
		static ddspp_constexpr unsigned int DDS_HEADER_FLAGS_WIDTH       = 0x00000004; // DDSD_WIDTH
		static ddspp_constexpr unsigned int DDS_HEADER_FLAGS_PITCH       = 0x00000008; // DDSD_PITCH
		static ddspp_constexpr unsigned int DDS_HEADER_FLAGS_PIXELFORMAT = 0x00001000; // DDSD_PIXELFORMAT
		static ddspp_constexpr unsigned int DDS_HEADER_FLAGS_MIPMAP      = 0x00020000; // DDSD_MIPMAPCOUNT
		static ddspp_constexpr unsigned int DDS_HEADER_FLAGS_LINEARSIZE  = 0x00080000; // DDSD_LINEARSIZE
		static ddspp_constexpr unsigned int DDS_HEADER_FLAGS_VOLUME      = 0x00800000; // DDSD_DEPTH

		static ddspp_constexpr unsigned int DDS_HEADER_CAPS_COMPLEX      = 0x00000008; // DDSCAPS_COMPLEX
		static ddspp_constexpr unsigned int DDS_HEADER_CAPS_MIPMAP       = 0x00400000; // DDSCAPS_MIPMAP
		static ddspp_constexpr unsigned int DDS_HEADER_CAPS_TEXTURE      = 0x00001000; // DDSCAPS_TEXTURE

		static ddspp_constexpr unsigned int DDS_HEADER_CAPS2_CUBEMAP           = 0x00000200; // DDSCAPS2_CUBEMAP
		static ddspp_constexpr unsigned int DDS_HEADER_CAPS2_CUBEMAP_POSITIVEX = 0x00000600; // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX
		static ddspp_constexpr unsigned int DDS_HEADER_CAPS2_CUBEMAP_NEGATIVEX = 0x00000a00; // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX
		static ddspp_constexpr unsigned int DDS_HEADER_CAPS2_CUBEMAP_POSITIVEY = 0x00001200; // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY
		static ddspp_constexpr unsigned int DDS_HEADER_CAPS2_CUBEMAP_NEGATIVEY = 0x00002200; // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY
		static ddspp_constexpr unsigned int DDS_HEADER_CAPS2_CUBEMAP_POSITIVEZ = 0x00004200; // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ
		static ddspp_constexpr unsigned int DDS_HEADER_CAPS2_CUBEMAP_NEGATIVEZ = 0x00008200; // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ
		static ddspp_constexpr unsigned int DDS_HEADER_CAPS2_VOLUME            = 0x00200000; // DDSCAPS2_VOLUME
		static ddspp_constexpr unsigned int DDS_HEADER_CAPS2_CUBEMAP_ALLFACES  = 
			DDS_HEADER_CAPS2_CUBEMAP_POSITIVEX | DDS_HEADER_CAPS2_CUBEMAP_NEGATIVEX | DDS_HEADER_CAPS2_CUBEMAP_POSITIVEY | 
			DDS_HEADER_CAPS2_CUBEMAP_NEGATIVEY | DDS_HEADER_CAPS2_CUBEMAP_POSITIVEZ | DDS_HEADER_CAPS2_CUBEMAP_NEGATIVEZ;

		static ddspp_constexpr unsigned int DXGI_MISC_FLAG_CUBEMAP = 0x2; // https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_resource_misc_flag
		static ddspp_constexpr unsigned int DDS_MISC_FLAGS2_ALPHA_MODE_MASK = 0x7;

		#define ddspp_make_fourcc(a, b, c, d) ((a) + ((b) << 8) + ((c) << 16) + ((d) << 24))

		// FOURCC constants
		static ddspp_constexpr unsigned int FOURCC_DXT1 = ddspp_make_fourcc('D', 'X', 'T', '1'); // BC1_UNORM
		static ddspp_constexpr unsigned int FOURCC_DXT2 = ddspp_make_fourcc('D', 'X', 'T', '2'); // BC2_UNORM
		static ddspp_constexpr unsigned int FOURCC_DXT3 = ddspp_make_fourcc('D', 'X', 'T', '3'); // BC2_UNORM
		static ddspp_constexpr unsigned int FOURCC_DXT4 = ddspp_make_fourcc('D', 'X', 'T', '4'); // BC3_UNORM
		static ddspp_constexpr unsigned int FOURCC_DXT5 = ddspp_make_fourcc('D', 'X', 'T', '5'); // BC3_UNORM
		static ddspp_constexpr unsigned int FOURCC_ATI1 = ddspp_make_fourcc('A', 'T', 'I', '1'); // BC4_UNORM
		static ddspp_constexpr unsigned int FOURCC_BC4U = ddspp_make_fourcc('B', 'C', '4', 'U'); // BC4_UNORM
		static ddspp_constexpr unsigned int FOURCC_BC4S = ddspp_make_fourcc('B', 'C', '4', 'S'); // BC4_SNORM
		static ddspp_constexpr unsigned int FOURCC_ATI2 = ddspp_make_fourcc('A', 'T', 'I', '2'); // BC5_UNORM
		static ddspp_constexpr unsigned int FOURCC_BC5U = ddspp_make_fourcc('B', 'C', '5', 'U'); // BC5_UNORM
		static ddspp_constexpr unsigned int FOURCC_BC5S = ddspp_make_fourcc('B', 'C', '5', 'S'); // BC5_SNORM
		static ddspp_constexpr unsigned int FOURCC_RGBG = ddspp_make_fourcc('R', 'G', 'B', 'G'); // R8G8_B8G8_UNORM
		static ddspp_constexpr unsigned int FOURCC_GRBG = ddspp_make_fourcc('G', 'R', 'G', 'B'); // G8R8_G8B8_UNORM
		static ddspp_constexpr unsigned int FOURCC_YUY2 = ddspp_make_fourcc('Y', 'U', 'Y', '2'); // YUY2
		static ddspp_constexpr unsigned int FOURCC_DXT10 = ddspp_make_fourcc('D', 'X', '1', '0'); // DDS extension header

		// These values come from the original D3D9 D3DFORMAT values https://learn.microsoft.com/en-us/windows/win32/direct3d9/d3dformat
		static ddspp_constexpr unsigned int FOURCC_RGB8        =  20;
		static ddspp_constexpr unsigned int FOURCC_A8R8G8B8    =  21;
		static ddspp_constexpr unsigned int FOURCC_X8R8G8B8    =  22;
		static ddspp_constexpr unsigned int FOURCC_R5G6B5      =  23; // B5G6R5_UNORM   (needs swizzling)
		static ddspp_constexpr unsigned int FOURCC_X1R5G5B5    =  24;
		static ddspp_constexpr unsigned int FOURCC_RGB5A1      =  25; // B5G5R5A1_UNORM (needs swizzling)
		static ddspp_constexpr unsigned int FOURCC_RGBA4       =  26; // B4G4R4A4_UNORM (needs swizzling)
		static ddspp_constexpr unsigned int FOURCC_R3G3B2      =  27;
		static ddspp_constexpr unsigned int FOURCC_A8          =  28;
		static ddspp_constexpr unsigned int FOURCC_A8R3G3B2    =  29;
		static ddspp_constexpr unsigned int FOURCC_X4R4G4B4    =  30;
		static ddspp_constexpr unsigned int FOURCC_A2B10G10R10 =  31;
		static ddspp_constexpr unsigned int FOURCC_A8B8G8R8    =  32;
		static ddspp_constexpr unsigned int FOURCC_X8B8G8R8    =  33;
		static ddspp_constexpr unsigned int FOURCC_G16R16      =  34;
		static ddspp_constexpr unsigned int FOURCC_A2R10G10B10 =  35;
		static ddspp_constexpr unsigned int FOURCC_RGBA16U     =  36; // R16G16B16A16_UNORM
		static ddspp_constexpr unsigned int FOURCC_RGBA16S     = 110; // R16G16B16A16_SNORM
		static ddspp_constexpr unsigned int FOURCC_R16F        = 111; // R16_FLOAT
		static ddspp_constexpr unsigned int FOURCC_RG16F       = 112; // R16G16_FLOAT
		static ddspp_constexpr unsigned int FOURCC_RGBA16F     = 113; // R16G16B16A16_FLOAT
		static ddspp_constexpr unsigned int FOURCC_R32F        = 114; // R32_FLOAT
		static ddspp_constexpr unsigned int FOURCC_RG32F       = 115; // R32G32_FLOAT
		static ddspp_constexpr unsigned int FOURCC_RGBA32F     = 116; // R32G32B32A32_FLOAT

		struct PixelFormat
		{
			unsigned int size;
			unsigned int flags;
			unsigned int fourCC;
			unsigned int RGBBitCount;
			unsigned int RBitMask;
			unsigned int GBitMask;
			unsigned int BBitMask;
			unsigned int ABitMask;
		};

		static_assert(sizeof(PixelFormat) == 32, "PixelFormat size mismatch");

		inline ddspp_constexpr bool is_rgba_mask(const PixelFormat& ddspf, unsigned int rmask, unsigned int gmask, unsigned int bmask, unsigned int amask)
		{
			return (ddspf.RBitMask == rmask) && (ddspf.GBitMask == gmask) && (ddspf.BBitMask == bmask) && (ddspf.ABitMask == amask);
		}

		inline ddspp_constexpr bool is_rgb_mask(const PixelFormat& ddspf, unsigned int rmask, unsigned int gmask, unsigned int bmask)
		{
			return (ddspf.RBitMask == rmask) && (ddspf.GBitMask == gmask) && (ddspf.BBitMask == bmask);
		}
	}

	using namespace internal;

	// https://docs.microsoft.com/en-us/windows/desktop/api/d3d11/ne-d3d11-d3d11_resource_dimension
	enum DXGIResourceDimension : unsigned char
	{
		DXGI_Unknown,
		DXGI_Buffer,
		DXGI_Texture1D,
		DXGI_Texture2D,
		DXGI_Texture3D
	};

	// Matches DXGI_FORMAT https://docs.microsoft.com/en-us/windows/desktop/api/dxgiformat/ne-dxgiformat-dxgi_format
	enum DXGIFormat : unsigned int
	{
		UNKNOWN	                    = 0,
		R32G32B32A32_TYPELESS       = 1,
		R32G32B32A32_FLOAT          = 2,
		R32G32B32A32_UINT           = 3,
		R32G32B32A32_SINT           = 4,
		R32G32B32_TYPELESS          = 5,
		R32G32B32_FLOAT             = 6,
		R32G32B32_UINT              = 7,
		R32G32B32_SINT              = 8,
		R16G16B16A16_TYPELESS       = 9,
		R16G16B16A16_FLOAT          = 10,
		R16G16B16A16_UNORM          = 11,
		R16G16B16A16_UINT           = 12,
		R16G16B16A16_SNORM          = 13,
		R16G16B16A16_SINT           = 14,
		R32G32_TYPELESS             = 15,
		R32G32_FLOAT                = 16,
		R32G32_UINT                 = 17,
		R32G32_SINT                 = 18,
		R32G8X24_TYPELESS           = 19,
		D32_FLOAT_S8X24_UINT        = 20,
		R32_FLOAT_X8X24_TYPELESS    = 21,
		X32_TYPELESS_G8X24_UINT     = 22,
		R10G10B10A2_TYPELESS        = 23,
		R10G10B10A2_UNORM           = 24,
		R10G10B10A2_UINT            = 25,
		R11G11B10_FLOAT             = 26,
		R8G8B8A8_TYPELESS           = 27,
		R8G8B8A8_UNORM              = 28,
		R8G8B8A8_UNORM_SRGB         = 29,
		R8G8B8A8_UINT               = 30,
		R8G8B8A8_SNORM              = 31,
		R8G8B8A8_SINT               = 32,
		R16G16_TYPELESS             = 33,
		R16G16_FLOAT                = 34,
		R16G16_UNORM                = 35,
		R16G16_UINT                 = 36,
		R16G16_SNORM                = 37,
		R16G16_SINT                 = 38,
		R32_TYPELESS                = 39,
		D32_FLOAT                   = 40,
		R32_FLOAT                   = 41,
		R32_UINT                    = 42,
		R32_SINT                    = 43,
		R24G8_TYPELESS              = 44,
		D24_UNORM_S8_UINT           = 45,
		R24_UNORM_X8_TYPELESS       = 46,
		X24_TYPELESS_G8_UINT        = 47,
		R8G8_TYPELESS               = 48,
		R8G8_UNORM                  = 49,
		R8G8_UINT                   = 50,
		R8G8_SNORM                  = 51,
		R8G8_SINT                   = 52,
		R16_TYPELESS                = 53,
		R16_FLOAT                   = 54,
		D16_UNORM                   = 55,
		R16_UNORM                   = 56,
		R16_UINT                    = 57,
		R16_SNORM                   = 58,
		R16_SINT                    = 59,
		R8_TYPELESS                 = 60,
		R8_UNORM                    = 61,
		R8_UINT                     = 62,
		R8_SNORM                    = 63,
		R8_SINT                     = 64,
		A8_UNORM                    = 65,
		R1_UNORM                    = 66,
		R9G9B9E5_SHAREDEXP          = 67,
		R8G8_B8G8_UNORM             = 68,
		G8R8_G8B8_UNORM             = 69,
		BC1_TYPELESS                = 70,
		BC1_UNORM                   = 71,
		BC1_UNORM_SRGB              = 72,
		BC2_TYPELESS                = 73,
		BC2_UNORM                   = 74,
		BC2_UNORM_SRGB              = 75,
		BC3_TYPELESS                = 76,
		BC3_UNORM                   = 77,
		BC3_UNORM_SRGB              = 78,
		BC4_TYPELESS                = 79,
		BC4_UNORM                   = 80,
		BC4_SNORM                   = 81,
		BC5_TYPELESS                = 82,
		BC5_UNORM                   = 83,
		BC5_SNORM                   = 84,
		B5G6R5_UNORM                = 85,
		B5G5R5A1_UNORM              = 86,
		B8G8R8A8_UNORM              = 87,
		B8G8R8X8_UNORM              = 88,
		R10G10B10_XR_BIAS_A2_UNORM  = 89,
		B8G8R8A8_TYPELESS           = 90,
		B8G8R8A8_UNORM_SRGB         = 91,
		B8G8R8X8_TYPELESS           = 92,
		B8G8R8X8_UNORM_SRGB         = 93,
		BC6H_TYPELESS               = 94,
		BC6H_UF16                   = 95,
		BC6H_SF16                   = 96,
		BC7_TYPELESS                = 97,
		BC7_UNORM                   = 98,
		BC7_UNORM_SRGB              = 99,
		AYUV                        = 100,
		Y410                        = 101,
		Y416                        = 102,
		NV12                        = 103,
		P010                        = 104,
		P016                        = 105,
		OPAQUE_420                  = 106,
		YUY2                        = 107,
		Y210                        = 108,
		Y216                        = 109,
		NV11                        = 110,
		AI44                        = 111,
		IA44                        = 112,
		P8                          = 113,
		A8P8                        = 114,
		B4G4R4A4_UNORM              = 115,
		
		P208                        = 130,
		V208                        = 131,
		V408                        = 132,
		ASTC_4X4_TYPELESS           = 133,
		ASTC_4X4_UNORM              = 134,
		ASTC_4X4_UNORM_SRGB         = 135,
		ASTC_5X4_TYPELESS           = 137,
		ASTC_5X4_UNORM              = 138,
		ASTC_5X4_UNORM_SRGB         = 139,
		ASTC_5X5_TYPELESS           = 141,
		ASTC_5X5_UNORM              = 142,
		ASTC_5X5_UNORM_SRGB         = 143,

		ASTC_6X5_TYPELESS           = 145,
		ASTC_6X5_UNORM              = 146,
		ASTC_6X5_UNORM_SRGB         = 147,

		ASTC_6X6_TYPELESS           = 149,
		ASTC_6X6_UNORM              = 150,
		ASTC_6X6_UNORM_SRGB         = 151,

		ASTC_8X5_TYPELESS           = 153,
		ASTC_8X5_UNORM              = 154,
		ASTC_8X5_UNORM_SRGB         = 155,

		ASTC_8X6_TYPELESS           = 157,
		ASTC_8X6_UNORM              = 158,
		ASTC_8X6_UNORM_SRGB         = 159,

		ASTC_8X8_TYPELESS           = 161,
		ASTC_8X8_UNORM              = 162,
		ASTC_8X8_UNORM_SRGB         = 163,

		ASTC_10X5_TYPELESS          = 165,
		ASTC_10X5_UNORM             = 166,
		ASTC_10X5_UNORM_SRGB        = 167,

		ASTC_10X6_TYPELESS          = 169,
		ASTC_10X6_UNORM             = 170,
		ASTC_10X6_UNORM_SRGB        = 171,

		ASTC_10X8_TYPELESS          = 173,
		ASTC_10X8_UNORM             = 174,
		ASTC_10X8_UNORM_SRGB        = 175,

		ASTC_10X10_TYPELESS         = 177,
		ASTC_10X10_UNORM            = 178,
		ASTC_10X10_UNORM_SRGB       = 179,

		ASTC_12X10_TYPELESS         = 181,
		ASTC_12X10_UNORM            = 182,
		ASTC_12X10_UNORM_SRGB       = 183,

		ASTC_12X12_TYPELESS         = 185,
		ASTC_12X12_UNORM            = 186,
		ASTC_12X12_UNORM_SRGB       = 187,

		FORCE_UINT                  = 0xffffffff
	};

	struct Header
	{
		unsigned int size;
		unsigned int flags;
		unsigned int height;
		unsigned int width;
		unsigned int pitchOrLinearSize;
		unsigned int depth;
		unsigned int mipMapCount;
		unsigned int reserved1[11];
		PixelFormat ddspf;
		unsigned int caps;
		unsigned int caps2;
		unsigned int caps3;
		unsigned int caps4;
		unsigned int reserved2;
	};

	static_assert(sizeof(Header) == 124, "DDS Header size mismatch");

	struct HeaderDXT10
	{
		DXGIFormat dxgiFormat;
		DXGIResourceDimension resourceDimension;
		unsigned int miscFlag;
		unsigned int arraySize;
		unsigned int reserved;
	};

	static_assert(sizeof(HeaderDXT10) == 20, "DDS DX10 Extended Header size mismatch");

	// Maximum possible size of header. Use this to read in only the header, decode, seek to the real header size, then read in the rest of the image data
	ddspp_constexpr int MAX_HEADER_SIZE = sizeof(DDS_MAGIC) + sizeof(Header) + sizeof(HeaderDXT10);

	enum Result : unsigned char
	{
		Success,
		Error
	};

	enum TextureType : unsigned char
	{
		Texture1D,
		Texture2D,
		Texture3D,
		Cubemap,
	};

	struct Descriptor
	{
		DXGIFormat format;
		TextureType type;
		unsigned int width;
		unsigned int height;
		unsigned int depth;
		unsigned int numMips;
		unsigned int arraySize;
		unsigned int rowPitch; // Row pitch for mip 0
		unsigned int depthPitch; // Size of mip 0
		unsigned int bitsPerPixelOrBlock; // If compressed bits per block, else bits per pixel
		unsigned int blockWidth; // Width of block in pixels (1 if uncompressed)
		unsigned int blockHeight;// Height of block in pixels (1 if uncompressed)
		bool compressed;
		bool srgb;
		unsigned int headerSize; // Actual size of header, use this to get to image data
	};

	inline ddspp_constexpr bool is_dxt10(const Header& header)
	{
		const PixelFormat& ddspf = header.ddspf;
		return (ddspf.flags & DDS_FOURCC) && (ddspf.fourCC == FOURCC_DXT10);
	}

	inline ddspp_constexpr bool is_compressed(DXGIFormat format)
	{
		return (format >= BC1_UNORM && format <= BC5_SNORM) || 
		       (format >= BC6H_TYPELESS && format <= BC7_UNORM_SRGB) || 
		       (format >= ASTC_4X4_TYPELESS && format <= ASTC_12X12_UNORM_SRGB);
	}

	inline ddspp_constexpr bool is_srgb(DXGIFormat format)
	{
		switch(format)
		{
			case R8G8B8A8_UNORM_SRGB:
			case BC1_UNORM_SRGB:
			case BC2_UNORM_SRGB:
			case BC3_UNORM_SRGB:
			case B8G8R8A8_UNORM_SRGB:
			case B8G8R8X8_UNORM_SRGB:
			case BC7_UNORM_SRGB:
			case ASTC_4X4_UNORM_SRGB:
			case ASTC_5X4_UNORM_SRGB:
			case ASTC_5X5_UNORM_SRGB:
			case ASTC_6X5_UNORM_SRGB:
			case ASTC_6X6_UNORM_SRGB:
			case ASTC_8X5_UNORM_SRGB:
			case ASTC_8X6_UNORM_SRGB:
			case ASTC_8X8_UNORM_SRGB:
			case ASTC_10X5_UNORM_SRGB:
			case ASTC_10X6_UNORM_SRGB:
			case ASTC_10X8_UNORM_SRGB:
			case ASTC_10X10_UNORM_SRGB:
			case ASTC_12X10_UNORM_SRGB:
			case ASTC_12X12_UNORM_SRGB:
				return true;
			default:
				return false;
		}
	}

	inline ddspp_constexpr unsigned int get_bits_per_pixel_or_block(DXGIFormat format)
	{
		if(format >= ASTC_4X4_TYPELESS && format <= ASTC_12X12_UNORM_SRGB)
		{
			return 128; // All ASTC blocks are the same size
		}

		switch(format)
		{
			case R1_UNORM:
				return 1;
			case R8_TYPELESS:
			case R8_UNORM:
			case R8_UINT:
			case R8_SNORM:
			case R8_SINT:
			case A8_UNORM:
			case AI44:
			case IA44:
			case P8:
				return 8;
			case NV12:
			case OPAQUE_420:
			case NV11:
				return 12;
			case R8G8_TYPELESS:
			case R8G8_UNORM:
			case R8G8_UINT:
			case R8G8_SNORM:
			case R8G8_SINT:
			case R16_TYPELESS:
			case R16_FLOAT:
			case D16_UNORM:
			case R16_UNORM:
			case R16_UINT:
			case R16_SNORM:
			case R16_SINT:
			case B5G6R5_UNORM:
			case B5G5R5A1_UNORM:
			case A8P8:
			case B4G4R4A4_UNORM:
				return 16;
			case P010:
			case P016:
				return 24;
			case BC1_UNORM:
			case BC1_UNORM_SRGB:
			case BC1_TYPELESS:
			case BC4_UNORM:
			case BC4_SNORM:
			case BC4_TYPELESS:
			case R16G16B16A16_TYPELESS:
			case R16G16B16A16_FLOAT:
			case R16G16B16A16_UNORM:
			case R16G16B16A16_UINT:
			case R16G16B16A16_SNORM:
			case R16G16B16A16_SINT:
			case R32G32_TYPELESS:
			case R32G32_FLOAT:
			case R32G32_UINT:
			case R32G32_SINT:
			case R32G8X24_TYPELESS:
			case D32_FLOAT_S8X24_UINT:
			case R32_FLOAT_X8X24_TYPELESS:
			case X32_TYPELESS_G8X24_UINT:
			case Y416:
			case Y210:
			case Y216:
				return 64;
			case R32G32B32_TYPELESS:
			case R32G32B32_FLOAT:
			case R32G32B32_UINT:
			case R32G32B32_SINT:
				return 96;
			case BC2_UNORM:
			case BC2_UNORM_SRGB:
			case BC2_TYPELESS:
			case BC3_UNORM:
			case BC3_UNORM_SRGB:
			case BC3_TYPELESS:
			case BC5_UNORM:
			case BC5_SNORM:
			case BC6H_UF16:
			case BC6H_SF16:
			case BC7_UNORM:
			case BC7_UNORM_SRGB:
			case R32G32B32A32_TYPELESS:
			case R32G32B32A32_FLOAT:
			case R32G32B32A32_UINT:
			case R32G32B32A32_SINT:
				return 128;
			default:
				return 32; // Most formats are 32 bits per pixel
				break;
		}
	}

	inline ddspp_constexpr void get_block_size(DXGIFormat format, unsigned int& blockWidth, unsigned int& blockHeight)
	{
		switch(format)
		{
			case BC1_UNORM:
			case BC1_UNORM_SRGB:
			case BC1_TYPELESS:
			case BC4_UNORM:
			case BC4_SNORM:
			case BC4_TYPELESS:
			case BC2_UNORM:
			case BC2_UNORM_SRGB:
			case BC2_TYPELESS:
			case BC3_UNORM:
			case BC3_UNORM_SRGB:
			case BC3_TYPELESS:
			case BC5_UNORM:
			case BC5_SNORM:
			case BC6H_UF16:
			case BC6H_SF16:
			case BC7_UNORM:
			case BC7_UNORM_SRGB:
			case ASTC_4X4_TYPELESS:
			case ASTC_4X4_UNORM:
			case ASTC_4X4_UNORM_SRGB:
				blockWidth = 4; blockHeight = 4;
				break;
			case ASTC_5X4_TYPELESS:
			case ASTC_5X4_UNORM:
			case ASTC_5X4_UNORM_SRGB:
				blockWidth = 5; blockHeight = 4;
				break;
			case ASTC_5X5_TYPELESS:
			case ASTC_5X5_UNORM:
			case ASTC_5X5_UNORM_SRGB:
				blockWidth = 5; blockHeight = 5;
				break;
			case ASTC_6X5_TYPELESS:
			case ASTC_6X5_UNORM:
			case ASTC_6X5_UNORM_SRGB:
				blockWidth = 6; blockHeight = 5;
			case ASTC_6X6_TYPELESS:
			case ASTC_6X6_UNORM:
			case ASTC_6X6_UNORM_SRGB:
				blockWidth = 6; blockHeight = 6;
			case ASTC_8X5_TYPELESS:
			case ASTC_8X5_UNORM:
			case ASTC_8X5_UNORM_SRGB:
				blockWidth = 8; blockHeight = 5;
			case ASTC_8X6_TYPELESS:
			case ASTC_8X6_UNORM:
			case ASTC_8X6_UNORM_SRGB:
				blockWidth = 8; blockHeight = 6;
			case ASTC_8X8_TYPELESS:
			case ASTC_8X8_UNORM:
			case ASTC_8X8_UNORM_SRGB:
				blockWidth = 8; blockHeight = 8;
			case ASTC_10X5_TYPELESS:
			case ASTC_10X5_UNORM:
			case ASTC_10X5_UNORM_SRGB:
				blockWidth = 10; blockHeight = 5;
			case ASTC_10X6_TYPELESS:
			case ASTC_10X6_UNORM:
			case ASTC_10X6_UNORM_SRGB:
				blockWidth = 10; blockHeight = 6;
			case ASTC_10X8_TYPELESS:
			case ASTC_10X8_UNORM:
			case ASTC_10X8_UNORM_SRGB:
				blockWidth = 10; blockHeight = 8;
			case ASTC_10X10_TYPELESS:
			case ASTC_10X10_UNORM:
			case ASTC_10X10_UNORM_SRGB:
				blockWidth = 10; blockHeight = 10;
			case ASTC_12X10_TYPELESS:
			case ASTC_12X10_UNORM:
			case ASTC_12X10_UNORM_SRGB:
				blockWidth = 12; blockHeight = 10;
			case ASTC_12X12_TYPELESS:
			case ASTC_12X12_UNORM:
			case ASTC_12X12_UNORM_SRGB:
				blockWidth = 12; blockHeight = 12;
			default:
				blockWidth = 1; blockHeight = 1;
				break;
		}

		return;
	}

	inline ddspp_constexpr unsigned int get_row_pitch(unsigned int width, unsigned int bitsPerPixelOrBlock, unsigned int blockWidth, unsigned int mip)
	{
		// Shift width by mipmap index, round to next block size and round to next byte (for the rare less than 1 byte per pixel formats)
		// E.g. width = 119, mip = 3, BC1 compression
		// ((((119 >> 2) + 4 - 1) / 4) * 64) / 8 = 64 bytes
		return ((((width >> mip) + blockWidth - 1) / blockWidth) * bitsPerPixelOrBlock + 7) / 8;
	}

	// Returns number of bytes for each row of a given mip. Valid range is [0, desc.numMips)
	inline ddspp_constexpr unsigned int get_row_pitch(const Descriptor& desc, unsigned int mip)
	{
		return get_row_pitch(desc.width, desc.bitsPerPixelOrBlock, desc.blockWidth, mip);
	}

	inline Result decode_header(unsigned char* sourceData, Descriptor& desc)
	{
		unsigned int magic = *reinterpret_cast<unsigned int*>(sourceData); // First 4 bytes are the magic DDS number

		if (magic != DDS_MAGIC)
		{
			return ddspp::Error;
		}

		const Header header = *reinterpret_cast<const Header*>(sourceData + sizeof(DDS_MAGIC));
		const PixelFormat& ddspf = header.ddspf;
		bool dxt10Extension = is_dxt10(header);
		const HeaderDXT10 dxt10Header = *reinterpret_cast<const HeaderDXT10*>(sourceData + sizeof(DDS_MAGIC) + sizeof(Header));

		// Read basic data from the header
		desc.width = header.width > 0 ? header.width : 1;
		desc.height = header.height > 0 ? header.height : 1;
		desc.depth = header.depth > 0 ? header.depth : 1;
		desc.numMips = header.mipMapCount > 0 ? header.mipMapCount : 1;

		// Set some sensible defaults
		desc.arraySize = 1;
		desc.srgb = false;
		desc.type = Texture2D;
		desc.format = UNKNOWN;

		if (dxt10Extension)
		{
			desc.format = dxt10Header.dxgiFormat;

			desc.arraySize = dxt10Header.arraySize;

			switch(dxt10Header.resourceDimension)
			{
				case DXGI_Texture1D:
					desc.depth = 1;
					desc.type = Texture1D;
					break;
				case DXGI_Texture2D:
					desc.depth = 1;

					if(dxt10Header.miscFlag & DXGI_MISC_FLAG_CUBEMAP)
					{
						desc.type = Cubemap;
					}
					else
					{
						desc.type = Texture2D;
					}

					break;
				case DXGI_Texture3D:
					desc.type = Texture3D;
					desc.arraySize = 1; // There are no 3D texture arrays
					break;
				default:
					break;
			}
		}
		else
		{
			if(ddspf.flags & DDS_FOURCC)
			{
				unsigned int fourCC = ddspf.fourCC;

				switch(fourCC)
				{
					// Compressed
					case FOURCC_DXT1:    desc.format = BC1_UNORM; break;
					case FOURCC_DXT2:    // fallthrough
					case FOURCC_DXT3:    desc.format = BC2_UNORM; break;
					case FOURCC_DXT4:    // fallthrough
					case FOURCC_DXT5:    desc.format = BC3_UNORM; break;
					case FOURCC_ATI1:    // fallthrough
					case FOURCC_BC4U:    desc.format = BC4_UNORM; break;
					case FOURCC_BC4S:    desc.format = BC4_SNORM; break;
					case FOURCC_ATI2:    // fallthrough
					case FOURCC_BC5U:    desc.format = BC5_UNORM; break;
					case FOURCC_BC5S:    desc.format = BC5_SNORM; break;

						// Video
					case FOURCC_RGBG:    desc.format = R8G8_B8G8_UNORM; break;
					case FOURCC_GRBG:    desc.format = G8R8_G8B8_UNORM; break;
					case FOURCC_YUY2:    desc.format = YUY2; break;

						// Packed
					case FOURCC_R5G6B5:  desc.format = B5G6R5_UNORM; break;
					case FOURCC_RGB5A1:  desc.format = B5G5R5A1_UNORM; break;
					case FOURCC_RGBA4:   desc.format = B4G4R4A4_UNORM; break;

						// Uncompressed
					case FOURCC_A8:          desc.format = R8_UNORM; break;
					case FOURCC_A2B10G10R10: desc.format = R10G10B10A2_UNORM; break;
					case FOURCC_RGBA16U:     desc.format = R16G16B16A16_UNORM; break;
					case FOURCC_RGBA16S:     desc.format = R16G16B16A16_SNORM; break;
					case FOURCC_R16F:        desc.format = R16_FLOAT; break;
					case FOURCC_RG16F:       desc.format = R16G16_FLOAT; break;
					case FOURCC_RGBA16F:     desc.format = R16G16B16A16_FLOAT; break;
					case FOURCC_R32F:        desc.format = R32_FLOAT; break;
					case FOURCC_RG32F:       desc.format = R32G32_FLOAT; break;
					case FOURCC_RGBA32F:     desc.format = R32G32B32A32_FLOAT; break;
					default: break;
				}
			}
			else if(ddspf.flags & DDS_RGB)
			{
				switch(ddspf.RGBBitCount)
				{
				case 32:
					if(is_rgba_mask(ddspf, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000))
					{
						desc.format = R8G8B8A8_UNORM;
					}
					else if(is_rgba_mask(ddspf, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000))
					{
						desc.format = B8G8R8A8_UNORM;
					}
					else if(is_rgba_mask(ddspf, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000))
					{
						desc.format = B8G8R8X8_UNORM;
					}

					// No DXGI format maps to (0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000) aka D3DFMT_X8B8G8R8
					// No DXGI format maps to (0x000003ff, 0x000ffc00, 0x3ff00000, 0xc0000000) aka D3DFMT_A2R10G10B10

					// Note that many common DDS reader/writers (including D3DX) swap the
					// the RED/BLUE masks for 10:10:10:2 formats. We assume
					// below that the 'backwards' header mask is being used since it is most
					// likely written by D3DX. The more robust solution is to use the 'DX10'
					// header extension and specify the DXGI_FORMAT_R10G10B10A2_UNORM format directly

					// For 'correct' writers, this should be 0x000003ff, 0x000ffc00, 0x3ff00000 for RGB data
					else if (is_rgba_mask(ddspf, 0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000))
					{
						desc.format = R10G10B10A2_UNORM;
					}
					else if (is_rgba_mask(ddspf, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000))
					{
						desc.format = R16G16_UNORM;
					}
					else if (is_rgba_mask(ddspf, 0xffffffff, 0x00000000, 0x00000000, 0x00000000))
					{
						// The only 32-bit color channel format in D3D9 was R32F
						desc.format = R32_FLOAT; // D3DX writes this out as a FourCC of 114
					}
					break;
				case 24:
					if(is_rgb_mask(ddspf, 0x00ff0000, 0x0000ff00, 0x000000ff))
					{
						desc.format = B8G8R8X8_UNORM;
					}
					break;
				case 16:
					if (is_rgba_mask(ddspf, 0x7c00, 0x03e0, 0x001f, 0x8000))
					{
						desc.format = B5G5R5A1_UNORM;
					}
					else if (is_rgba_mask(ddspf, 0xf800, 0x07e0, 0x001f, 0x0000))
					{
						desc.format = B5G6R5_UNORM;
					}
					else if (is_rgba_mask(ddspf, 0x0f00, 0x00f0, 0x000f, 0xf000))
					{
						desc.format = B4G4R4A4_UNORM;
					}
					// No DXGI format maps to (0x7c00, 0x03e0, 0x001f, 0x0000) aka D3DFMT_X1R5G5B5.
					// No DXGI format maps to (0x0f00, 0x00f0, 0x000f, 0x0000) aka D3DFMT_X4R4G4B4.
					// No 3:3:2, 3:3:2:8, or paletted DXGI formats aka D3DFMT_A8R3G3B2, D3DFMT_R3G3B2, D3DFMT_P8, D3DFMT_A8P8, etc.
					break;
				default:
					break;
				}
			}
			else if (ddspf.flags & DDS_LUMINANCE)
			{
				switch(ddspf.RGBBitCount)
				{
				case 16:
					if (is_rgba_mask(ddspf, 0x0000ffff, 0x00000000, 0x00000000, 0x00000000))
					{
						desc.format = R16_UNORM; // D3DX10/11 writes this out as DX10 extension.
					}
					if (is_rgba_mask(ddspf, 0x000000ff, 0x00000000, 0x00000000, 0x0000ff00))
					{
						desc.format = R8G8_UNORM; // D3DX10/11 writes this out as DX10 extension.
					}
					break;
				case 8:
					if (is_rgba_mask(ddspf, 0x000000ff, 0x00000000, 0x00000000, 0x00000000))
					{
						desc.format = R8_UNORM; // D3DX10/11 writes this out as DX10 extension
					}

					// No DXGI format maps to (0x0f, 0x00, 0x00, 0xf0) aka D3DFMT_A4L4

					if (is_rgba_mask(ddspf, 0x000000ff, 0x00000000, 0x00000000, 0x0000ff00))
					{
						desc.format = R8G8_UNORM; // Some DDS writers assume the bitcount should be 8 instead of 16
					}
					break;
				}
			}
			else if (ddspf.flags & DDS_ALPHA)
			{
				if (ddspf.RGBBitCount == 8)
				{
					desc.format = A8_UNORM;
				}
			}
			else if (ddspf.flags & DDS_BUMPDUDV)
			{
				if (ddspf.RGBBitCount == 32)
				{
					if (is_rgba_mask(ddspf, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000))
					{
						desc.format = R8G8B8A8_SNORM; // D3DX10/11 writes this out as DX10 extension
					}
					if (is_rgba_mask(ddspf, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000))
					{
						desc.format = R16G16_SNORM; // D3DX10/11 writes this out as DX10 extension
					}

					// No DXGI format maps to (0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000) aka D3DFMT_A2W10V10U10
				}
				else if (ddspf.RGBBitCount == 16)
				{
					if (is_rgba_mask(ddspf, 0x000000ff, 0x0000ff00, 0x00000000, 0x00000000))
					{
						desc.format = R8G8_SNORM; // D3DX10/11 writes this out as DX10 extension
					}
				}
			}

			if((header.flags & DDS_HEADER_FLAGS_VOLUME) || (header.caps2 & DDS_HEADER_CAPS2_VOLUME))
			{
				desc.type = Texture3D;
			}
			else if (header.caps2 & DDS_HEADER_CAPS2_CUBEMAP)
			{
				if((header.caps2 & DDS_HEADER_CAPS2_CUBEMAP_ALLFACES) != DDS_HEADER_CAPS2_CUBEMAP_ALLFACES)
				{
					return ddspp::Error;
				}

				desc.type = Cubemap;
				desc.arraySize = 1;
				desc.depth = 1;
			}
			else
			{
				desc.type = Texture2D;
			}
		}

		desc.compressed = is_compressed(desc.format);
		desc.srgb = is_srgb(desc.format);
		desc.bitsPerPixelOrBlock = get_bits_per_pixel_or_block(desc.format);
		get_block_size(desc.format, desc.blockWidth, desc.blockHeight);
		
		desc.rowPitch = get_row_pitch(desc.width, desc.bitsPerPixelOrBlock, desc.blockWidth, 0);
		desc.depthPitch = desc.rowPitch * desc.height / desc.blockHeight;
		desc.headerSize = sizeof(DDS_MAGIC) + sizeof(Header) + (dxt10Extension ? sizeof(HeaderDXT10) : 0);

		return ddspp::Success;
	}

	inline void encode_header(const DXGIFormat format, const unsigned int width, const unsigned int height, const unsigned int depth,
	                          const TextureType type, const unsigned int mipCount, const unsigned int arraySize, 
	                          Header& header, HeaderDXT10& dxt10Header)
	{
		header.size = sizeof(Header);

		// Fill in header flags
		header.flags = DDS_HEADER_FLAGS_CAPS | DDS_HEADER_FLAGS_HEIGHT | DDS_HEADER_FLAGS_WIDTH | DDS_HEADER_FLAGS_PIXELFORMAT;
		header.caps = DDS_HEADER_CAPS_TEXTURE;
		header.caps2 = 0;

		if (mipCount > 1)
		{
			header.flags |= DDS_HEADER_FLAGS_MIPMAP;
			header.caps |= DDS_HEADER_CAPS_COMPLEX | DDS_HEADER_CAPS_MIPMAP;
		}

		unsigned int bitsPerPixelOrBlock = get_bits_per_pixel_or_block(format);

		if (is_compressed(format))
		{
			header.flags |= DDS_HEADER_FLAGS_LINEARSIZE;
			unsigned int blockWidth, blockHeight;
			get_block_size(format, blockWidth, blockHeight);
			header.pitchOrLinearSize = width * height * bitsPerPixelOrBlock / (8 * blockWidth * blockHeight);
		}
		else
		{
			header.flags |= DDS_HEADER_FLAGS_PITCH;
			header.pitchOrLinearSize = width * bitsPerPixelOrBlock / 8;
		}

		header.height = height;
		header.width = width;
		header.depth = depth;
		header.mipMapCount = mipCount;
		header.reserved1[0] = header.reserved1[1] = header.reserved1[2] = 0;
		header.reserved1[3] = header.reserved1[4] = header.reserved1[5] = 0;
		header.reserved1[6] = header.reserved1[7] = header.reserved1[8] = 0;
		header.reserved1[9] = header.reserved1[10] = 0;
		
		// Fill in pixel format
		PixelFormat& ddspf = header.ddspf;
		ddspf.size = sizeof(PixelFormat);
		ddspf.fourCC = FOURCC_DXT10;
		ddspf.flags = 0;
		ddspf.flags |= DDS_FOURCC;

		dxt10Header.dxgiFormat = format;
		dxt10Header.arraySize = arraySize;
		dxt10Header.miscFlag = 0;
		
		if (type == Texture1D)
		{
			dxt10Header.resourceDimension = DXGI_Texture1D;
		}
		else if (type == Texture2D)
		{
			dxt10Header.resourceDimension = DXGI_Texture2D;
		}
		else if (type == Cubemap)
		{
			dxt10Header.resourceDimension = DXGI_Texture2D;
			dxt10Header.miscFlag |= DXGI_MISC_FLAG_CUBEMAP;
			header.caps |= DDS_HEADER_CAPS_COMPLEX;
			header.caps2 |= DDS_HEADER_CAPS2_CUBEMAP | DDS_HEADER_CAPS2_CUBEMAP_ALLFACES;
		}
		else if (type == Texture3D)
		{
			dxt10Header.resourceDimension = DXGI_Texture3D;
			header.flags |= DDS_HEADER_FLAGS_VOLUME;
			header.caps2 |= DDS_HEADER_CAPS2_VOLUME;
		}

		// dxt10Header.miscFlag TODO Alpha Mode

		// Unused
		header.caps3 = 0;
		header.caps4 = 0;
		header.reserved2 = 0;
	}

	// Returns the offset from the base pointer to the desired mip and slice
	// Slice is either a texture from an array, a face from a cubemap, or a 2D slice of a volume texture
	inline ddspp_constexpr unsigned int get_offset(const Descriptor& desc, const unsigned int mip, const unsigned int slice)
	{
		// The mip/slice arrangement is different between texture arrays and volume textures
		//
		// Arrays
		//  __________  _____  __  __________  _____  __  __________  _____  __ 
		// |          ||     ||__||          ||     ||__||          ||     ||__|
		// |          ||_____|    |          ||_____|    |          ||_____|
		// |          |           |          |           |          |
		// |__________|           |__________|           |__________|
		//
		// Volume
		//  __________  __________  __________  _____  _____  _____  __  __  __ 
		// |          ||          ||          ||     ||     ||     ||__||__||__|
		// |          ||          ||          ||_____||_____||_____|
		// |          ||          ||          |
		// |__________||__________||__________|
		//

		unsigned long long offset = 0;
		unsigned long long mip0Size = desc.depthPitch * 8; // Work in bits

		if (desc.type == Texture3D)
		{
			for (unsigned int m = 0; m < mip; ++m)
			{
				unsigned long long mipSize = mip0Size >> 2 * m;
				offset += mipSize * desc.numMips;
			}

			unsigned long long lastMip = mip0Size >> 2 * mip;

			offset += lastMip * slice;
		}
		else
		{
			unsigned long long mipChainSize = 0;
			unsigned int width = desc.width;
			unsigned int height = desc.height;

			for (unsigned int m = 0; m < desc.numMips; ++m)
			{
				unsigned long long blocksX = (width + desc.blockWidth - 1) / desc.blockWidth;
				unsigned long long blocksY = (height + desc.blockHeight - 1) / desc.blockHeight;
				unsigned long long mipSize = blocksX * blocksY * desc.bitsPerPixelOrBlock;
				mipChainSize += mipSize;

				width >>= 1;
				height >>= 1;
			}

			offset += mipChainSize * slice;

			width = desc.width;
			height = desc.height;
			for (unsigned int m = 0; m < mip; ++m)
			{
				unsigned long long blocksX = (width + desc.blockWidth - 1) / desc.blockWidth;
				unsigned long long blocksY = (height + desc.blockHeight - 1) / desc.blockHeight;
				unsigned long long mipSize = blocksX * blocksY * desc.bitsPerPixelOrBlock;
				offset += mipSize > desc.bitsPerPixelOrBlock ? mipSize : desc.bitsPerPixelOrBlock;

				width >>= 1;
				height >>= 1;
			}
		}

		offset /= 8; // Back to bytes

		return (unsigned int)offset;
	}
}
