//	Darkstar Engine  (c) UnderWorld Interactive Studio (all coding by Svarog    2007-2008)
//
//      Данный файл является частью Darkstar Engine. Любое использование кода файла
//      без согласия компании, является нарушением закона об авторском праве (в слу-
//      чае если не оговорено обратное).
//
//      Имя файла: loadableTexture_common.cpp
//      Краткое описание: текстура, загружаемая из файла
//

#include <Darkstar/Resource System/Textures/loadableTexture.hpp>
#include <Darkstar/math.hpp>
#include <ddraw.h>

using namespace ker;
using namespace rs_private;
using namespace rs;

// cross platform additional functions for loadable textures

_Masks::_Masks()
{
	rmask = gmask = bmask = amask = 0;
};

// cross-platform functions for LoadableTexture class

unsigned int LoadableTexture::_getNextMipLevelSize(const unsigned int width,
														  const unsigned int height, const unsigned int depth, 
														  const unsigned int bpp, const rs::DSEFORMAT& format) const 
{
	if(format == rs::DFMT_BC1_UNORM)
		return max(1, width/4)*max(1, height/4)*depth*8;
	if(format == rs::DFMT_BC2_UNORM || format == rs::DFMT_BC3_UNORM)
		return max(1, width/4)*max(1, height/4)*depth*16;
	return width*height*depth*bpp;
};

unsigned int LoadableTexture::_getRowPitch(const rs::DSEFORMAT& pixel_format, const unsigned int width, const unsigned int bpp)
{
	if(pixel_format == rs::DFMT_BC1_UNORM)
		return width << 1;
	else
		if(pixel_format == rs::DFMT_BC2_UNORM || pixel_format == rs::DFMT_BC3_UNORM)
			return width << 2;
		else
			return width*bpp;
};

// common functions to LoadableTexture class

// convertData using only for non-fourcc formats
template <typename T>
bool LoadableTexture::_convertData(const unsigned int bpp, const shared_array<T>& data, shared_array<DWORD>& converted_data)
{
	unsigned int ashift, bshift, gshift, rshift;
	unsigned int asize, bsize, gsize, rsize;
	ashift = _getShiftAndSize(masks.amask, asize);
	bshift = _getShiftAndSize(masks.bmask, bsize);
	gshift = _getShiftAndSize(masks.gmask, gsize);
	rshift = _getShiftAndSize(masks.rmask, rsize);
	float afactor = 1;
	float bfactor = 1;
	float gfactor = 1;
	float rfactor = 1;
	if(asize < 8)
		afactor = 255.0f/(powf(2, (float)asize) - 1);
	if(bsize < 8)
		bfactor = 255.0f/(powf(2, (float)bsize) - 1);
	if(gsize < 8)
		gfactor = 255.0f/(powf(2, (float)gsize) - 1);
	if(rsize < 8)
		rfactor = 255.0f/(powf(2, (float)rsize) - 1);
	unsigned char red, green, blue, alpha;
	T color;
	unsigned int tmp_width = width;
	unsigned int tmp_height = height;
	unsigned int tmp_depth = depth;
	unsigned int new_size = 0;
	for(unsigned int i = 0; i < mip_count; ++i)
	{
		new_size += _getNextMipLevelSize(tmp_width, tmp_height, tmp_depth, 4, DFMT_R8G8B8A8_UNORM);
		_reduceTmpValues(tmp_width, tmp_height, tmp_depth);
	};
	try
	{
		if(type == TEXTURE_CUBE)
			new_size *= 6;
		converted_data.reset(new DWORD[math::ceil_divf(new_size, sizeof(DWORD))]);
	}
	catch(std::bad_alloc&)
	{
		lwar << "Cannot find enough memory to converted data";
		return false;
	};
	unsigned int converted_texel = 0;
	char* first_src_texel = (char*)data.get();
	for(int m = 0; m < (type == TEXTURE_CUBE ? 6 : 1); ++m)
	{
		tmp_width = width;
		tmp_height = height;
		tmp_depth = depth;
		for(unsigned int k = 0; k < mip_count; ++k)
		{
			for(unsigned int l = 0; l < tmp_depth; ++l)
				for(unsigned int i = 0; i < tmp_height; ++i)
				{
					for(unsigned int j = 0; j < tmp_width; ++j)
					{
						color = *((T*)(first_src_texel + converted_texel*bpp));
						red = (unsigned char)(((color & masks.rmask) >> rshift)*rfactor);
						green = (unsigned char)(((color & masks.gmask) >> gshift)*gfactor);
						blue = (unsigned char)(((color & masks.bmask) >> bshift)*bfactor);
						alpha = (unsigned char)(((color & masks.amask) >> ashift)*afactor);
						converted_data[converted_texel] = _getConvertedColor(red, green, blue, alpha);
						++converted_texel;
					};
				};
			_reduceTmpValues(tmp_width, tmp_height, tmp_depth);
		};
	};
	_setCorrectConvertedFormat();
	return true;
};

template<typename T>
bool LoadableTexture::_loadTextureData(DFile& file, const unsigned int bpp, const unsigned char cube_faces)
{
	unsigned int mip_count_in_file = mip_count;
	// mip_count is number of real number of mip levels with taking into account quality level
	mip_count -= start_mip;

	unsigned int number_of_subresources = type == TEXTURE_CUBE ? mip_count*6 : mip_count;

	unsigned int num_of_faces_in_file;

	shared_array<DSUBRES_DATA> sub_data(new DSUBRES_DATA[number_of_subresources]);

	// for 3d or 2d texture this is size of texture data overall. And for cube texture this is size
	// of one face of the texture
	unsigned int data_size = 0;
	// shifts to next mip levels from the start of texture data
	shared_array<unsigned int> pointers(new unsigned int[mip_count]);
	shared_array<unsigned int> row_pitches(new unsigned int[mip_count]);
	shared_array<unsigned int> slice_pitches(new unsigned int[mip_count]);

	unsigned int data_to_skip = 0;

	_fillImageProperties(bpp, mip_count_in_file, cube_faces, num_of_faces_in_file, pointers, row_pitches, slice_pitches, data_size, data_to_skip);

	for(unsigned int i = 0; i < mip_count_in_file; ++i)
		sub_data[i].slice_pitch = slice_pitches[i];

	// if texture is cube texture then data_size is only size of one face of cube map, so we 
	// need to multiple data_size by number of faces in file
	unsigned int one_face_size = data_size;

	shared_array<T> file_data;

	if(!_getCorrectFileData<T>(file, data_size, data_to_skip, one_face_size, num_of_faces_in_file, cube_faces, file_data))
	{
		lwar << "Cannot get correct file data";
		return false;
	};

	// if pixel_format is not correct now then we must convert data to another format. New format
	// will be DXGI_FORMAT_R8G8B8A8_UNORM
	shared_array<DWORD> converted_data;
	unsigned int tmp_width, tmp_height, tmp_depth;
	if(pixel_format == DFMT_UNKNOWN)
	{
		lwar << "Need to convert data in file " << file.getFilename();
		if(!_convertData<T>(bpp, file_data, converted_data))
		{
			lwar << "Cannot convert data";
			return false;
		};
		DWORD* cur_pointer = converted_data.get();
		tmp_width = width;
		tmp_height = height;
		tmp_depth = depth;
		for(unsigned int i = 0; i < mip_count; ++i)
		{
			pointers[i] = _getNextMipLevelSize(tmp_width, tmp_height, tmp_depth, 4, pixel_format);
			row_pitches[i] = _getRowPitch(pixel_format, tmp_width, 4);
			sub_data[i].slice_pitch = pointers[i] / tmp_depth;
			_reduceTmpValues(tmp_width, tmp_height, tmp_depth);
		};
		for(int j = 0; j < (type == TEXTURE_CUBE ? 6 : 1); ++j)
			for(unsigned int i = 0; i < mip_count; ++i)
			{
				unsigned int pitch = j*mip_count;
				sub_data[i + pitch].pointer = cur_pointer;
				sub_data[i + pitch].row_pitch = row_pitches[i];
				cur_pointer += pointers[i] / sizeof(DWORD);
			};
	}
	else
	{
		T* cur_pointer = file_data.get();
		for(int j = 0; j < (type == TEXTURE_CUBE ? 6 : 1); ++j)
			for(unsigned int i = 0; i < mip_count; ++i)
			{
				unsigned int pitch = j*mip_count;
				sub_data[i + pitch].pointer = cur_pointer;
				sub_data[i + pitch].row_pitch = row_pitches[i];
				cur_pointer += pointers[i] / sizeof(T);
			};
	};
	switch(type)
	{
	case TEXTURE2D:
		{
			if(!_createTexture2D(sub_data))
			{
				lwar << "Cannot create 2D texture";
				return false;
			};
			return true;
		};break;
	case TEXTURE_VOLUME:
		{
			if(!_createTextureVolume(sub_data))
			{
				lwar << "Cannot create 3D texture";
				return false;
			};
			return true;
		};break;
	case TEXTURE_CUBE:
		{
			if(!_createTextureCube(sub_data))
			{
				lwar << "Cannot create cube map";
				return false;
			};
			return true;
		};break;
	};
	return true;
};

void LoadableTexture::_fillImageProperties(const unsigned int bpp, const unsigned int mip_count_in_file, const unsigned int cube_faces, unsigned int& num_of_faces_in_file, shared_array<unsigned int>& pointers, shared_array<unsigned int>& row_pitches, shared_array<unsigned int>& slice_pitches, unsigned int& data_size, unsigned int& data_to_skip)
{
	unsigned int tmp_width = width;
	unsigned int tmp_height = height;
	unsigned int tmp_depth = depth;

	unsigned int old_tmp_width;
	unsigned int old_tmp_height;

	bool need_to_reduce = false;

	// cube_faces has from one to six "1" in his binary format. Number of "1" is number of cube map
	// faces that retained in current file
	num_of_faces_in_file = 1;
	if(type == TEXTURE_CUBE)
		_getShiftAndSize(cube_faces, num_of_faces_in_file);

	if(type == TEXTURE_CUBE && num_of_faces_in_file < 6)
		lwar << "This file has dx 7.0 or lower format. Number of cube face is less than 6";

	for(unsigned int i = 0; i < mip_count_in_file; ++i)
	{
		// if DXT1, DXT3 or DXT5 then width and height of each mip level must divide by 4
		if(pixel_format == DFMT_BC1_UNORM || pixel_format == DFMT_BC2_UNORM || pixel_format == DFMT_BC3_UNORM)
		{
			old_tmp_height = tmp_height;
			old_tmp_width = tmp_width;
			need_to_reduce = setCorrectSizeToDXT(tmp_width, tmp_height);
		};
		// width must divide by 2
		if(pixel_format == DFMT_G8R8_G8B8_UNORM || pixel_format == DFMT_R8G8_B8G8_UNORM)
		{
			old_tmp_width = tmp_width;
			old_tmp_height = tmp_height;
			if(tmp_width % 2)
			{
				old_tmp_width = tmp_width;
				tmp_width += 1;
				need_to_reduce = true;
			};
		};

		// if pixel_format in this moment is unknown that pixel_format is not fourcc type
		// so next mip level size calculate with only tmp_width, tmp_height, tmp_depth and bpp
		unsigned int tmp_size = _getNextMipLevelSize(tmp_width, tmp_height, tmp_depth, bpp, pixel_format);
		data_size += tmp_size;
		if(i >= start_mip)
		{
			unsigned int index = i - start_mip;
			row_pitches[index] = _getRowPitch(pixel_format, tmp_width, bpp);
			// this value used only by volume texture. If texture is volume texture then number of subresources
			// equal to mip_count so all SysMemSlicePitch will be correct
			slice_pitches[index] = tmp_size/tmp_depth;
			pointers[index] = tmp_size;
		}
		else
			data_to_skip += tmp_size;

		if(need_to_reduce)
		{		
			tmp_width = old_tmp_width;
			tmp_height = old_tmp_height;
			need_to_reduce = false;
		};
		_reduceTmpValues(tmp_width, tmp_height, tmp_depth);
	};

	if(type == TEXTURE_CUBE)
		data_size *= num_of_faces_in_file;
};

template<typename T>
bool LoadableTexture::_getCorrectFileData(ker::DFile& file, unsigned int& data_size, const unsigned int data_to_skip, const unsigned int one_face_size, const unsigned int num_of_faces_in_file, const unsigned int cube_faces, shared_array<T>& file_data)
{
	try
	{ 
		// ceilf is used to 24 bit format, in other cases data_size / sizeof(T) is integer
		file_data.reset(new T[math::ceil_divf(data_size, sizeof(T))]);
	}
	catch(std::bad_alloc&)
	{
		lerr << "Cannot find enough memory to texture data";
		return false;
	};

	file.read(data_size, file_data.get());

	if(data_to_skip > 0)
	{
		shared_array<T> data_without_skip;
		unsigned int size_without_offset = one_face_size - data_to_skip;
		unsigned int real_data_size = type == TEXTURE_CUBE ? size_without_offset*num_of_faces_in_file : size_without_offset;
		try
		{
			data_without_skip.reset(new T[math::ceil_divf(real_data_size, sizeof(T))]);
		}
		catch(std::bad_alloc&)
		{
			lerr << "Cannot find enough memory to allocate data for texture: " << file.getFilename();
			return false;
		};
		char* dst = (char*)data_without_skip.get();
		char* src = (char*)file_data.get();
		for(unsigned int i = 0; i < num_of_faces_in_file; ++i)
		{
			memcpy(dst, src + data_to_skip, size_without_offset);
			dst += size_without_offset;
			src += one_face_size;
		};
		file_data = data_without_skip;
		_reduceTmpValues(width, height, depth, start_mip);
		// can be some artifacts because real width and height of mip_level can be other than 
		// width and height with padding. For example width = 17 and height = 18 after alignment
		// width = 20 and height = 20 (in other case CreateTexture will not be complete)
		if(pixel_format == DFMT_BC1_UNORM || pixel_format == DFMT_BC2_UNORM || pixel_format == DFMT_BC3_UNORM)
			setCorrectSizeToDXT(width, height);
		if(pixel_format == DFMT_G8R8_G8B8_UNORM || pixel_format == DFMT_R8G8_B8G8_UNORM)
			width = (width % 2) == 1 ? width + 1 : width;
		data_size -= data_to_skip*num_of_faces_in_file;
	};

	// if texture is cube map then we need to fill all 6 faces. If face's data is not save in file
	// then it will be 0 on each pixel on each mip_level
	if(type == TEXTURE_CUBE && num_of_faces_in_file != 6)
	{
		unsigned int full_size = one_face_size*6;
		shared_array<T> cube_map_full;
		try
		{
			cube_map_full.reset(new T[math::ceil_divf(full_size, sizeof(T))]);
		}
		catch(std::bad_alloc&)
		{
			lerr << "Cannot find enough memory to allocate data from file: " << file.getFilename();
			return false;
		};
		memset(cube_map_full.get(), 0, full_size);
		char* dst = (char*)cube_map_full.get();
		char* src = (char*)file_data.get();
		for(unsigned int i = 0, j = 0; i < 6; ++i)
		{
			unsigned int cur_cube_face = (unsigned int)powf(2.0f, (float)i);
			dst += i*one_face_size;
			if(cube_faces & cur_cube_face)
			{
				memcpy(dst, src, one_face_size);
				++j;
				src += j*one_face_size;
			};
		};
		file_data = cube_map_full;
	};
	return true;
};

bool LoadableTexture::load(const std::string& filename, const unsigned int quality)
{
	DFile file(filename);
	if(!file.getIsLoaded())
	{
		lwar << "Cannot open file: " << filename;
		return _makeProxyTexture(filename);
	};
	DDSURFACEDESC2 desc;
	DWORD magic;
	file.read(sizeof(DWORD), &magic);
	if(magic != 0x20534444)
	{
		lwar << "DDS file format is not correct: " << filename;
		return _makeProxyTexture(filename);
	};
	if(!file.read(sizeof(DDSURFACEDESC2), &desc))
	{
		lwar << "Cannot read header from file: " << filename;
		return _makeProxyTexture(filename);
	};
	if(desc.dwSize != 124)
	{
		lwar << "DDS file format is not correct: " << filename;
		return _makeProxyTexture(filename);
	};

	width = desc.dwWidth;
	height = desc.dwHeight;
	bool is_volume = (desc.dwFlags & DDSD_DEPTH) > 0;
	depth = is_volume ? desc.dwDepth : 1;
	DDPIXELFORMAT ddpf = desc.ddpfPixelFormat;
	unsigned int bpp = 0;
	pixel_format = DFMT_UNKNOWN;
	if(ddpf.dwFlags & DDPF_FOURCC)
	{		
		bpp = getBitsInPixel(ddpf.dwFourCC);
		pixel_format = _tryToSetPixelFormat(ddpf.dwFourCC);
		if(pixel_format == DFMT_UNKNOWN)
		{
			lwar << "Loader not support this pixel format. Filename : " << filename;
			return _makeProxyTexture(filename);
		};
	}
	else
	{
		bpp = ddpf.dwRGBBitCount;
		masks.rmask = ddpf.dwRBitMask;
		masks.gmask = ddpf.dwGBitMask;
		masks.bmask = ddpf.dwBBitMask;
		bool is_signed = false;
		if((ddpf.dwFlags & DDPF_RGB) || (ddpf.dwFlags & DDPF_ALPHA) || (ddpf.dwFlags & DDPF_LUMINANCE))
			masks.amask = ddpf.dwFlags & DDPF_ALPHAPIXELS ? ddpf.dwRGBAlphaBitMask : 0;
		else
			if(ddpf.dwFlags & DDPF_BUMPDUDV)
			{
				is_signed = true;
				masks.amask = ddpf.dwYUVAlphaBitMask;
			}
			else
			{
				lwar << "Loader not support this pixel format. Filename: " << filename;
				return _makeProxyTexture(filename);
			};
		pixel_format = _tryToSetPixelFormat(is_signed, bpp);
	};
	mip_count = desc.dwFlags & DDSD_MIPMAPCOUNT ? desc.dwMipMapCount : 1;
	start_mip = quality;
	if(quality >= mip_count)
	{
		lwar << "Quality is greater or equal than mip_count so quality will be set to mip_count-1";
		start_mip = mip_count-1;
	};
	bool is_cubemap = (desc.ddsCaps.dwCaps & DDSCAPS_COMPLEX) && (desc.ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP);
	unsigned int bytes_per_pixel = bpp >> 3;
	boost::function3<bool, DFile&, const unsigned int, const unsigned char> _loadTextureData;
	if(bytes_per_pixel == 4 || bytes_per_pixel == 3)
		_loadTextureData = boost::bind(&LoadableTexture::_loadTextureData<DWORD>, this, _1, _2, _3);
	else
		if(bytes_per_pixel == 2)
			_loadTextureData = boost::bind(&LoadableTexture::_loadTextureData<WORD>, this, _1, _2, _3);
		else
			_loadTextureData = boost::bind(&LoadableTexture::_loadTextureData<unsigned char>, this, _1, _2, _3);
	type = is_volume ? TEXTURE_VOLUME : (is_cubemap ? TEXTURE_CUBE : TEXTURE2D);
	unsigned char cube_faces = 0;
	if(is_cubemap)
	{
		if(desc.ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEX)
			cube_faces |= PX;
		if(desc.ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEX)
			cube_faces |= NX;
		if(desc.ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEY)
			cube_faces |= PY;
		if(desc.ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEY)
			cube_faces |= NY;
		if(desc.ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEZ)
			cube_faces |= PZ;
		if(desc.ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ)
			cube_faces |= NZ;
	};
	if(!_loadTextureData(file, bytes_per_pixel, cube_faces))
	{
		lwar << "Cannot load texture: " << filename;
		return _makeProxyTexture(filename);
	};
	return true;
};