/*
** surface.c for Factory 0.1
**
** Made by caput_r
** Login caput_r <caput_r@epita.fr>
**
** Started on  Tue Sep 27 01:34:52 2005 remi caput
** Last update Tue Sep 27 02:51:08 2005 remi caput
*/

#include "surface.h"

struct s_surface	gl_surface;

int					surface_internal_alpha (struct s_surface* surface, struct s_surface_texture* texture)
{
	unsigned char*	alloc;
	unsigned int	c;
	unsigned int	i;
	unsigned int	j;

	if ((surface->mode_alpha == SURFACE_ALPHA_CREATE && texture->depth == GL_RGBA) ||
		(surface->mode_alpha == SURFACE_ALPHA_SOURCE))
		return 0;

	if (texture->depth == GL_RGB)
	{
		if (!(alloc = realloc (texture->pixels, texture->height * texture->width * 4 * sizeof (*texture->pixels))))
		{
			if (texture->pixels)
				free (texture->pixels);

			return 1;
		}

		texture->pixels = alloc;
		i = texture->height * texture->width * 4;
		j = texture->height * texture->width * 3;

		while (i)
		{
			texture->pixels[i - 1] = surface->alpha_default;
			texture->pixels[i - 2] = texture->pixels[j - 1];
			texture->pixels[i - 3] = texture->pixels[j - 2];
			texture->pixels[i - 4] = texture->pixels[j - 3];

			for (c = surface->alpha_count; c--; )
				if ((texture->pixels[i - 4] == surface->alpha_array[c].red) &&
				    (texture->pixels[i - 3] == surface->alpha_array[c].green) &&
				    (texture->pixels[i - 2] == surface->alpha_array[c].blue))
					texture->pixels[i - 1] = surface->alpha_array[c].alpha;

			i -= 4;
			j -= 3;
		}

		texture->depth = GL_RGBA;
	}
	else
	{
		i = texture->height * texture->width * 4;

		while (i)
		{
			texture->pixels[i - 1] = surface->alpha_default;

			for (c = surface->alpha_count; c--; )
				if ((texture->pixels[i - 4] == surface->alpha_array[c].red) &&
				    (texture->pixels[i - 3] == surface->alpha_array[c].green) &&
				    (texture->pixels[i - 2] == surface->alpha_array[c].blue))
					texture->pixels[i - 1] = surface->alpha_array[c].alpha;

			i -= 4;
		}
	}

	return 0;
}

int					surface_internal_load_bmp (struct s_surface_texture* texture, const char* path)
{
	FILE*			file;
	unsigned short	depth;
	unsigned int	height;
	unsigned int	width;
	signed short	plane;
	int				err;

	err = !(file = fopen (path, "rb"));

	if (!err)
	{
		fseek (file, 18, SEEK_CUR);

		err = surface_internal_read (file, &width, sizeof (width)) ||
		      surface_internal_read (file, &height, sizeof (height)) ||
	          surface_internal_read (file, &plane, sizeof (plane)) ||
	          surface_internal_read (file, &depth, sizeof (depth)) ||
	          depth != 24 ||
	          plane != 1;

		if (!err)
		{
			fseek (file, 24, SEEK_CUR);

			err = !(texture->pixels = malloc (width * height * 4 * sizeof (*texture->pixels)));

			if (!err)
			{
				err = (fread (texture->pixels, sizeof (*texture->pixels), width * height * 3, file) != width * height * 3);

				if (err)
					free (texture->pixels);
			}
		}

		fclose (file);
	}

	texture->depth = GL_RGB;
	texture->height = height;
	texture->width = width;

	return err;
}

int					surface_internal_load_png (struct s_surface_texture* texture, const char* path)
{
	png_structp		png_ptr;
	png_infop		info_ptr;
	png_uint_32		height;
	png_uint_32		width;
	png_bytep*		rows;
	FILE*			fp;
	int				bit_depth;
	int				color_type;
	int				components;
	unsigned int	i;

	if ((fp = fopen (path, "rb")) == NULL)
	  return 1;

	if (!(png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)))
	{
	  fclose (fp);

	  return 1;
	}

	if (!(info_ptr = png_create_info_struct (png_ptr)))
	{
	  png_destroy_read_struct (&png_ptr, png_infopp_NULL, png_infopp_NULL);
	  fclose (fp);

	  return 1;
	}

	if (setjmp (png_jmpbuf (png_ptr)))
	{
	  png_destroy_read_struct (&png_ptr, &info_ptr, png_infopp_NULL);
	  fclose (fp);

	  return 1;
	}

	png_init_io (png_ptr, fp);
	png_read_info (png_ptr, info_ptr);

	bit_depth = png_get_bit_depth (png_ptr, info_ptr);
	color_type = png_get_color_type (png_ptr, info_ptr);

	if (color_type == PNG_COLOR_TYPE_PALETTE)
		png_set_palette_to_rgb (png_ptr);

	if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
		png_set_gray_1_2_4_to_8 (png_ptr);

	if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS))
		png_set_tRNS_to_alpha (png_ptr);

	if (bit_depth == 16)
		png_set_strip_16 (png_ptr);
	else if (bit_depth < 8)
		png_set_packing (png_ptr);

	png_read_update_info (png_ptr, info_ptr);
	png_get_IHDR (png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);

	if (color_type == PNG_COLOR_TYPE_RGB)
		components = 3;
	else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
		components = 4;
	else
		components = 0;

	if ((components != 0) && (texture->pixels = malloc (width * height * components * sizeof (*texture->pixels))))
	{
		if ((rows = malloc (height * sizeof (*rows))))
		{
			for (i = 0; i < height; ++i)
				rows[i] = (png_bytep)(texture->pixels +	((height - (i + 1)) * width * components));

			png_read_image (png_ptr, rows);
			png_read_end (png_ptr, NULL);

			free (rows);

			texture->depth = components == 3 ? GL_RGB : GL_RGBA;
			texture->height = height;
			texture->width = width;
		}
		else
			free (texture->pixels);
	}

	png_destroy_read_struct (&png_ptr, &info_ptr, png_infopp_NULL);
	fclose (fp);

	return (components == 0);
}

int					surface_internal_read (FILE* file, void* pointer, size_t size)
{
	unsigned char*	buffer = (unsigned char*)pointer;
	unsigned char	byte;
	size_t			i = 0;

#ifdef SURFACE_BENDIAN
	for (i = 0; i < size; buffer[size - ++i] = byte)
		if (fread (&byte, sizeof (byte), 1, file) != 1)
			return 1;
#else
	for (i = 0; i < size; buffer[i++] = byte)
		if (fread (&byte, sizeof (byte), 1, file) != 1)
			return 1;
#endif
	return 0;
}

int							surface_alpha_define (struct s_surface* surface, unsigned char red, unsigned char green, unsigned char blue, unsigned char alpha)
{
	struct s_surface_color*	array;
	unsigned int			i;

	if (surface->alpha_count == surface->alpha_size)
	{
		surface->alpha_size += SURFACE_REALLOC_ALPHA;

		if (!(array = realloc (surface->alpha_array, surface->alpha_size * sizeof (*surface->alpha_array))))
		{
			if (surface->alpha_array)
				free (surface->alpha_array);

			return 1;
		}

		surface->alpha_array = array;
	}

	i = surface->alpha_count++;

	surface->alpha_array[i].alpha = alpha;
	surface->alpha_array[i].blue = blue;
	surface->alpha_array[i].green = green;
	surface->alpha_array[i].red = red;

	return 0;
}

void	surface_alpha_reset (struct s_surface* surface, unsigned char value)
{
	surface->alpha_count = 0;
	surface->alpha_default= value;
}

int		surface_init (struct s_surface* surface)
{
	surface->alpha_array = NULL;
	surface->alpha_count = 0;
	surface->alpha_default = 255;
	surface->alpha_size = 0;
	surface->buffer_array = NULL;
	surface->buffer_count = 0;
	surface->buffer_size = 0;
	surface->item_array = NULL;
	surface->item_count = 0;
	surface->item_size = 0;
	surface->mode_alpha = SURFACE_ALPHA_SOURCE;
	surface->mode_mipmap = SURFACE_MIPMAP_ON;

	return 0;
}

int								surface_load (struct s_surface* surface, const char* path, GLuint* handle)
{
    struct s_surface_item*		item;
    struct s_surface_texture	texture;
	unsigned int				length;
	unsigned int				i;
	char*						buffer;
	char						ext[3];

	for (i = 0; i < surface->item_count; ++i)
		if (!strcmp (surface->buffer_array + surface->item_array[i].offset, path))
		{
			*handle = surface->item_array[i].handle;

			return 0;
		}

	length = strlen (path);

	if (surface->buffer_count + length + 1 > surface->buffer_size)
	{
		surface->buffer_size += (length + 1 < SURFACE_REALLOC_BUFFER ? SURFACE_REALLOC_BUFFER : length + 1);

		if (!(buffer = realloc (surface->buffer_array, surface->buffer_size * sizeof (*surface->buffer_array))))
			return 1;

		surface->buffer_array = buffer;
	}

	if (surface->item_count == surface->item_size)
	{
		surface->item_size += SURFACE_REALLOC_ITEM;

		if (!(item = realloc (surface->item_array, surface->item_size * sizeof (*surface->item_array))))
			return 1;

		surface->item_array = item;
	}

	if (length >= 3)
		for (i = 0; i < 3; ++i)
			if (path[length - 3 + i] >= 'a' && path[length - 3 + i] <= 'z')
				ext[i] = path[length - 3 + i] - 'a' + 'A';
			else
				ext[i] = path[length - 3 + i];
	else
		for (i = 0; i < 3; ++i)
			ext[i] = 0;

	switch (SURFACE_TYPE (ext[0], ext[1], ext[2]))
	{
		case SURFACE_TYPE ('B', 'M', 'P'):
			if (surface_internal_load_bmp (&texture, path))
				return 1;
		break;

		case SURFACE_TYPE ('P', 'N', 'G'):
			if (surface_internal_load_png (&texture, path))
				return 1;
		break;

		default:
			return 1;
		break;
	}

	if (surface_internal_alpha (surface, &texture))
	{
		free (texture.pixels);

		return 1;
	}

	glGenTextures (1, handle);
	glBindTexture (GL_TEXTURE_2D, *handle);

	if (surface->mode_mipmap == SURFACE_MIPMAP_ON)
	{
		gluBuild2DMipmaps (GL_TEXTURE_2D, texture.depth, texture.width, texture.height, texture.depth, GL_UNSIGNED_BYTE, texture.pixels);
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST_MIPMAP_NEAREST);
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	}
	else
	{
		glTexImage2D (GL_TEXTURE_2D, 0, texture.depth, texture.width, texture.height, 0, texture.depth, GL_UNSIGNED_BYTE, texture.pixels);
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	}

	item = surface->item_array + surface->item_count++;
	item->handle = *handle;
	item->offset = surface->buffer_count;

	strcpy (surface->buffer_array + surface->buffer_count, path);
	surface->buffer_count += length + 1;

	free (texture.pixels);

	return 0;
}

void	surface_mode (struct s_surface* surface, enum surface_alpha alpha, enum surface_mipmap mipmap)
{
	surface->mode_alpha = alpha;
	surface->mode_mipmap = mipmap;
}

void				surface_stop (struct s_surface* surface)
{
	unsigned int	i;

	for (i = 0; i < surface->item_count; ++i)
		glDeleteTextures (1, &surface->item_array[i].handle);

	if (surface->alpha_array)
		free (surface->alpha_array);

	if (surface->buffer_array)
		free (surface->buffer_array);

	if (surface->item_array)
		free (surface->item_array);
}
