/*
 * Copyright 2011-2012 Branimir Karadzic. All rights reserved.
 * License: http://www.opensource.org/licenses/BSD-2-Clause
 */

#include "bgfx_p.h"
#include "dds.h"

namespace bgfx
{

#define DDS_MAGIC MAKEFOURCC('D','D','S',' ')
#define DDS_HEADER_SIZE 124
#define DDS_IMAGE_DATA_OFFSET (DDS_HEADER_SIZE + 4)

#define DDS_DXT1 MAKEFOURCC('D', 'X', 'T', '1')
#define DDS_DXT2 MAKEFOURCC('D', 'X', 'T', '2')
#define DDS_DXT3 MAKEFOURCC('D', 'X', 'T', '3')
#define DDS_DXT4 MAKEFOURCC('D', 'X', 'T', '4')
#define DDS_DXT5 MAKEFOURCC('D', 'X', 'T', '5')

#define DDSD_CAPS                   0x00000001
#define DDSD_HEIGHT                 0x00000002
#define DDSD_WIDTH                  0x00000004
#define DDSD_PITCH                  0x00000008
#define DDSD_PIXELFORMAT            0x00001000
#define DDSD_MIPMAPCOUNT            0x00020000
#define DDSD_LINEARSIZE             0x00080000
#define DDSD_DEPTH                  0x00800000

#define DDPF_ALPHAPIXELS            0x00000001
#define DDPF_ALPHA                  0x00000002
#define DDPF_FOURCC                 0x00000004
#define DDPF_INDEXED                0x00000020
#define DDPF_RGB                    0x00000040
#define DDPF_YUV                    0x00000200
#define DDPF_LUMINANCE              0x00020000

#define DDSCAPS_COMPLEX             0x00000008
#define DDSCAPS_TEXTURE             0x00001000
#define DDSCAPS_MIPMAP              0x00400000

#define DDSCAPS2_CUBEMAP            0x00000200
#define DDSCAPS2_CUBEMAP_POSITIVEX  0x00000400
#define DDSCAPS2_CUBEMAP_NEGATIVEX  0x00000800
#define DDSCAPS2_CUBEMAP_POSITIVEY  0x00001000
#define DDSCAPS2_CUBEMAP_NEGATIVEY  0x00002000
#define DDSCAPS2_CUBEMAP_POSITIVEZ  0x00004000
#define DDSCAPS2_CUBEMAP_NEGATIVEZ  0x00008000
#define DDSCAPS2_VOLUME             0x00200000

bool isDds(const Memory* _mem)
{
	StreamRead stream(_mem->data, _mem->size);

	uint32_t magic;
	stream.read(magic);

	return DDS_MAGIC == magic;
}

uint32_t bitRangeConvert(uint32_t _in, uint32_t _from, uint32_t _to)
{
	uint32_t tmp0   = uint32_sll(1, _to);
	uint32_t tmp1   = uint32_sll(1, _from);
	uint32_t tmp2   = uint32_dec(tmp0);
	uint32_t tmp3   = uint32_dec(tmp1);
	uint32_t tmp4   = uint32_mul(_in, tmp2);
	uint32_t tmp5   = uint32_add(tmp3, tmp4);
	uint32_t tmp6   = uint32_srl(tmp5, _from);
	uint32_t tmp7   = uint32_add(tmp5, tmp6);
	uint32_t result = uint32_srl(tmp7, _from);

	return result;
}

void decodeBlockDxt(uint8_t _dst[16*4], const uint8_t _src[8])
{
	uint8_t colors[4*3];

	uint32_t c0 = _src[0] | (_src[1] << 8);
	colors[0] = bitRangeConvert( (c0>> 0)&0x1f, 5, 8);
	colors[1] = bitRangeConvert( (c0>> 5)&0x3f, 6, 8);
	colors[2] = bitRangeConvert( (c0>>11)&0x1f, 5, 8);

	uint32_t c1 = _src[2] | (_src[3] << 8);
	colors[3] = bitRangeConvert( (c1>> 0)&0x1f, 5, 8);
	colors[4] = bitRangeConvert( (c1>> 5)&0x3f, 6, 8);
	colors[5] = bitRangeConvert( (c1>>11)&0x1f, 5, 8);

	colors[6] = (2*colors[0] + colors[3]) / 3;
	colors[7] = (2*colors[1] + colors[4]) / 3;
	colors[8] = (2*colors[2] + colors[5]) / 3;

	colors[ 9] = (colors[0] + 2*colors[3]) / 3;
	colors[10] = (colors[1] + 2*colors[4]) / 3;
	colors[11] = (colors[2] + 2*colors[5]) / 3;

	for (uint32_t ii = 0, next = 8*4; ii < 16*4; ii += 4, next += 2)
	{
		int idx = ( (_src[next>>3] >> (next & 7)) & 3) * 3;
		_dst[ii+0] = colors[idx+0];
		_dst[ii+1] = colors[idx+1];
		_dst[ii+2] = colors[idx+2];
	}
}

void decodeBlockDxt1(uint8_t _dst[16*4], const uint8_t _src[8])
{
	uint8_t colors[4*4];

	uint32_t c0 = _src[0] | (_src[1] << 8);
	colors[0] = bitRangeConvert( (c0>> 0)&0x1f, 5, 8);
	colors[1] = bitRangeConvert( (c0>> 5)&0x3f, 6, 8);
	colors[2] = bitRangeConvert( (c0>>11)&0x1f, 5, 8);
	colors[3] = 255;

	uint32_t c1 = _src[2] | (_src[3] << 8);
	colors[4] = bitRangeConvert( (c1>> 0)&0x1f, 5, 8);
	colors[5] = bitRangeConvert( (c1>> 5)&0x3f, 6, 8);
	colors[6] = bitRangeConvert( (c1>>11)&0x1f, 5, 8);
	colors[7] = 255;

	if (c0 > c1)
	{
		colors[ 8] = (2*colors[0] + colors[4]) / 3;
		colors[ 9] = (2*colors[1] + colors[5]) / 3;
		colors[10] = (2*colors[2] + colors[6]) / 3;
		colors[11] = 255;

		colors[12] = (colors[0] + 2*colors[4]) / 3;
		colors[13] = (colors[1] + 2*colors[5]) / 3;
		colors[14] = (colors[2] + 2*colors[6]) / 3;
		colors[15] = 255;
	}
	else
	{
		colors[ 8] = (colors[0] + colors[4]) / 2;
		colors[ 9] = (colors[1] + colors[5]) / 2;
		colors[10] = (colors[2] + colors[6]) / 2;
		colors[11] = 255;
		
		colors[12] = 0;
		colors[13] = 0;
		colors[14] = 0;
		colors[15] = 0;
	}

	for (uint32_t ii = 0, next = 8*4; ii < 16*4; ii += 4, next += 2)
	{
		int idx = ( (_src[next>>3] >> (next & 7)) & 3) * 4;
		_dst[ii+0] = colors[idx+0];
		_dst[ii+1] = colors[idx+1];
		_dst[ii+2] = colors[idx+2];
		_dst[ii+3] = colors[idx+3];
	}
}

void decodeBlockDxt23A(uint8_t _dst[16*4], const uint8_t _src[8])
{
	for (uint32_t ii = 3, next = 0; ii < 16*4; ii += 4, next += 4)
	{
		uint32_t c0 = (_src[next>>3] >> (next&7) ) & 0xf;
		_dst[ii] = bitRangeConvert(c0, 4, 8);
	}
}

void decodeBlockDxt45A(uint8_t _dst[16*4], const uint8_t _src[8])
{
	uint8_t alpha[8];
	alpha[0] = _src[0];
	alpha[1] = _src[1];

	if (alpha[0] > alpha[1])
	{
		alpha[2] = (6*alpha[0] + 1*alpha[1]) / 7;
		alpha[3] = (5*alpha[0] + 2*alpha[1]) / 7;
		alpha[4] = (4*alpha[0] + 3*alpha[1]) / 7;
		alpha[5] = (3*alpha[0] + 4*alpha[1]) / 7;
		alpha[6] = (2*alpha[0] + 5*alpha[1]) / 7;
		alpha[7] = (1*alpha[0] + 6*alpha[1]) / 7;
	}
	else
	{
		alpha[2] = (4*alpha[0] + 1*alpha[1]) / 5;
		alpha[3] = (3*alpha[0] + 2*alpha[1]) / 5;
		alpha[4] = (2*alpha[0] + 3*alpha[1]) / 5;
		alpha[5] = (1*alpha[0] + 4*alpha[1]) / 5;
		alpha[6] = 0;
		alpha[7] = 255;
	}

	for (uint32_t ii = 3, next = 8*2; ii < 16*4; ii += 4, ++next)
	{
		uint32_t bit = (_src[next>>3] >> (next&7) ) & 1;
		uint32_t idx = bit;
		++next;

		bit = (_src[next>>3] >> (next&7) ) & 1;
		idx += bit << 1;
		++next;

		bit = (_src[next>>3] >> (next&7) ) & 1;
		idx += bit << 2;

		_dst[ii] = alpha[idx & 7];
	}
}

uint32_t Mip::getDecodedSize() const
{
	return m_width*m_height*4;
}

void Mip::decode(uint8_t* _dst)
{
	const uint8_t* src = m_data;

	if (0 != m_type)
	{
		uint32_t width = m_width/4;
		uint32_t height = m_height/4;
		uint32_t pitch = m_width*4;

		uint8_t temp[16*4];

		switch (m_type)
		{
		case 1:
			for (uint32_t yy = 0; yy < height; ++yy)
			{
				for (uint32_t xx = 0; xx < width; ++xx)
				{
					decodeBlockDxt1(temp, src);
					src += 8;

					uint8_t* dst = &_dst[(yy*pitch+xx*4)*4];
					memcpy(dst, temp, 16);
					memcpy(&dst[pitch], &temp[16], 16);
					memcpy(&dst[2*pitch], &temp[32], 16);
					memcpy(&dst[3*pitch], &temp[48], 16);
				}
			}
			break;

		case 2:
			for (uint32_t yy = 0; yy < height; ++yy)
			{
				for (uint32_t xx = 0; xx < width; ++xx)
				{
					decodeBlockDxt23A(temp, src);
					src += 8;
					decodeBlockDxt(temp, src);
					src += 8;

					uint8_t* dst = &_dst[(yy*pitch+xx*4)*4];
					memcpy(dst, temp, 16);
					memcpy(&dst[pitch], &temp[16], 16);
					memcpy(&dst[2*pitch], &temp[32], 16);
					memcpy(&dst[3*pitch], &temp[48], 16);
				}
			}
			break;

		case 3:
			for (uint32_t yy = 0; yy < height; ++yy)
			{
				for (uint32_t xx = 0; xx < width; ++xx)
				{
					decodeBlockDxt45A(temp, src);
					src += 8;
					decodeBlockDxt(temp, src);
					src += 8;

					uint8_t* dst = &_dst[(yy*pitch+xx*4)*4];
					memcpy(dst, temp, 16);
					memcpy(&dst[pitch], &temp[16], 16);
					memcpy(&dst[2*pitch], &temp[32], 16);
					memcpy(&dst[3*pitch], &temp[48], 16);
				}
			}
			break;
		}
	}
	else
	{
		uint32_t width = m_width;
		uint32_t height = m_height;

		if (m_bpp == 1
		||  m_bpp == 4)
		{
			uint32_t pitch = m_width*m_bpp;
			memcpy(_dst, src, pitch*height);
		}
		else
		{
			uint32_t pitch = m_width*4;
			for (uint32_t yy = 0; yy < height; ++yy)
			{
				uint8_t* dst = &_dst[yy*pitch];

				for (uint32_t xx = 0; xx < width; ++xx)
				{
					memcpy(dst, src, 3);
					dst[3] = 255;
					dst += 4;
					src += 3;
				}
			}
		}
	}
}

bool parseDds(Dds& _dds, const Memory* _mem)
{
	StreamRead stream(_mem->data, _mem->size);

	uint32_t magic;
	stream.read(magic);

	if (DDS_MAGIC != magic)
	{
		return false;
	}

	uint32_t headerSize;
	stream.read(headerSize);

	if (headerSize < DDS_HEADER_SIZE)
	{
		return false;
	}

	uint32_t flags;
	stream.read(flags);

	if ( (flags & (DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH|DDSD_PIXELFORMAT) ) != (DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH|DDSD_PIXELFORMAT) )
	{
		return false;
	}

	uint32_t height;
	stream.read(height);

	uint32_t width;
	stream.read(width);

	uint32_t pitch;
	stream.read(pitch);

	uint32_t depth;
	stream.read(depth);

	uint32_t mips;
	stream.read(mips);

	stream.skip(44); // reserved

	stream.skip(4); // pixel format size

	uint32_t pixelFlags;
	stream.read(pixelFlags);

	uint32_t fourcc;
	stream.read(fourcc);

	uint32_t rgbCount;
	stream.read(rgbCount);

	uint32_t rbitmask;
	stream.read(rbitmask);

	uint32_t gbitmask;
	stream.read(gbitmask);

	uint32_t bbitmask;
	stream.read(bbitmask);

	uint32_t abitmask;
	stream.read(abitmask);

	uint32_t caps[4];
	stream.read(caps);

	if ( (caps[0] & DDSCAPS_TEXTURE) == 0)
	{
		return false;
	}

	stream.skip(4); // reserved

	uint8_t bpp = 1;
	uint8_t blockSize = 1;
	uint8_t type = 0;
	bool hasAlpha = pixelFlags & DDPF_ALPHAPIXELS;

	if (pixelFlags & DDPF_FOURCC)
	{
		switch (fourcc)
		{
		case DDS_DXT1:
			type = 1;
			blockSize = 8;
			break;

		case DDS_DXT2:
		case DDS_DXT3:
			type = 2;
			blockSize = 16;
			break;

		case DDS_DXT4:
		case DDS_DXT5:
			type = 3;
			blockSize = 16;
			break;
		}
	}
	else
	{
		switch (pixelFlags)
		{
		case DDPF_RGB:
			blockSize *= 3;
			bpp = 3;
			break;

		case DDPF_RGB|DDPF_ALPHAPIXELS:
			blockSize *= 4;
			bpp = 4;
			break;

		case DDPF_LUMINANCE:
		case DDPF_INDEXED:
		case DDPF_ALPHA:
			bpp = 1;
			break;

		default:
			bpp = 0;
			break;
		}
	}

	_dds.m_width = width;
	_dds.m_height = height;
	_dds.m_depth = depth;
	_dds.m_blockSize = blockSize;
	_dds.m_numMips = (caps[0] & DDSCAPS_MIPMAP) ? mips : 1;
	_dds.m_bpp = bpp;
	_dds.m_type = type;
	_dds.m_hasAlpha = hasAlpha;

	return true;
}

bool getRawImageData(const Dds& _dds, uint8_t _index, const Memory* _mem, Mip& _mip)
{
	uint32_t width = _dds.m_width;
	uint32_t height = _dds.m_height;
	uint32_t blockSize = _dds.m_blockSize;
	uint32_t offset = DDS_IMAGE_DATA_OFFSET;
	uint8_t bpp = _dds.m_bpp;
	uint8_t type = _dds.m_type;
	bool hasAlpha = _dds.m_hasAlpha;

	for (uint8_t ii = 0, num = _dds.m_numMips; ii < num; ++ii)
	{
		width = uint32_max(1, width);
		height = uint32_max(1, height);

		uint32_t size = width*height*blockSize;
		if (0 != type)
		{
			width = uint32_max(1, (width + 3)>>2);
			height = uint32_max(1, (height + 3)>>2);
			size = width*height*blockSize;

			width <<= 2;
			height <<= 2;
		}

		if (ii == _index)
		{
			_mip.m_width = width;
			_mip.m_height = height;
			_mip.m_blockSize = blockSize;
			_mip.m_size = size;
			_mip.m_data = _mem->data + offset;
			_mip.m_bpp = bpp;
			_mip.m_type = type;
			_mip.m_hasAlpha = hasAlpha;
			return true;
		}

		offset += size;

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

	return false;
}

} // namespace bgfx
