/* Parallax for KallistiOS ##version##

   texture.c

   (c)2002 Dan Potter

*/

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <kos/img.h>
#include <kmg/kmg.h>
#include <bzlib/bzlib.h>
#include "texture.h"

/* See the header file for all comments and documentation */

/* Utility function to fill out the initial poly contexts */
static void fill_contexts(plx_texture_t * txr) {
	pvr_poly_cxt_txr(&txr->cxt_opaque, PVR_LIST_OP_POLY, txr->fmt, txr->w, txr->h,
		txr->ptr, PVR_FILTER_BILINEAR);
	pvr_poly_cxt_txr(&txr->cxt_trans, PVR_LIST_TR_POLY, txr->fmt, txr->w, txr->h,
		txr->ptr, PVR_FILTER_BILINEAR);
	pvr_poly_cxt_txr(&txr->cxt_pt, PVR_LIST_PT_POLY, txr->fmt, txr->w, txr->h,
		txr->ptr, PVR_FILTER_BILINEAR);

	plx_txr_flush_hdrs(txr);
}

plx_texture_t * plx_txr_load(const char * fn, int use_alpha, int txrload_flags) {
	kos_img_t	img;
	plx_texture_t	* txr;
	int		fnlen;

	/* What type of texture is it? */
	fnlen = strlen(fn);
	if (!strcasecmp(fn + fnlen - 3, "bz2")) {
		int error = BZ_OK;
		FILE *fp = fopen(fn, "r");
		BZFILE *bz2 = BZ2_bzReadOpen(&error, fp, 0,0, NULL, 0);
		if(error != BZ_OK)
		{
			dbglog(DBG_ERROR, "plx_txr_load: BZ2 Error: %d Unable to read '%s'\n", error, fn);
			BZ2_bzReadClose(&error, bz2);
		}
		dbglog(DBG_WARNING, "plx_txr_load: Loading File '%s'\n", fn);
		uint8 *buffer = (uint8 *)malloc(20480);
		memset(buffer, 0, 20480);
		int used = 0;
		int readIn = 1;
		int avail = 20448;
		int bufSize = 20480;
		error = BZ_OK;
		while(error == BZ_OK)
		{
			readIn = BZ2_bzRead(&error, bz2, buffer + used, avail);
			if(error == BZ_OK)
			{
				used += readIn;
				avail -= readIn;
				if(readIn && avail < 1024)
				{
					if(bufSize > 1048576)
					{
						dbglog(DBG_ERROR, "plx_txr_load: bz2 buffer too large (Over 1MB)\n");
						BZ2_bzReadClose(&error, bz2);
						fclose(fp);
						free(buffer);
						return NULL;
					}
					int oldBuf = bufSize;
					bufSize <<= 1;
					buffer = (uint8 *)realloc(buffer, bufSize);
					avail += (bufSize - oldBuf);
				}
			}
		}
		BZ2_bzReadClose(&error, bz2);
		fclose(fp);
		if(error != BZ_OK)
		{
			dbglog(DBG_ERROR, "plx_txr_load: Error closing BZ2 file Error: %d\n", error);
		}
	
		kmg_header_t hdr;
		memcpy(&hdr, buffer, sizeof(kmg_header_t));
		if(hdr.magic != KMG_MAGIC || hdr.version != KMG_VERSION || hdr.platform != KMG_PLAT_DC)
		{
			dbglog(DBG_ERROR, "plx_txr_load: KMG file corrupt '%s'\n", fn);
			dbglog(DBG_ERROR, "   Magic:%08lx version %d platform %d\n",
				hdr.magic, (int)hdr.version, (int)hdr.platform);
			free(buffer);
			return NULL;
		}
		img.w = hdr.width;
		img.h = hdr.height;
		
		int dep = 0;
		if (hdr.format & KMG_DCFMT_VQ)
			dep |= PVR_TXRLOAD_FMT_VQ;
		if (hdr.format & KMG_DCFMT_TWIDDLED)
			dep |= PVR_TXRLOAD_FMT_TWIDDLED;

		switch (hdr.format & KMG_DCFMT_MASK) {
			case KMG_DCFMT_RGB565:
				img.fmt = KOS_IMG_FMT(KOS_IMG_FMT_RGB565, dep);
				break;

			case KMG_DCFMT_ARGB4444:
				img.fmt = KOS_IMG_FMT(KOS_IMG_FMT_ARGB4444, dep);
				break;

			case KMG_DCFMT_ARGB1555:
				img.fmt = KOS_IMG_FMT(KOS_IMG_FMT_ARGB1555, dep);
				break;

			case KMG_DCFMT_YUV422:
				img.fmt = KOS_IMG_FMT(KOS_IMG_FMT_YUV422, dep);
				break;

			case KMG_DCFMT_BUMP:
				/* XXX */
				img.fmt = KOS_IMG_FMT(KOS_IMG_FMT_RGB565, dep);
				break;

			case KMG_DCFMT_4BPP_PAL:
			case KMG_DCFMT_8BPP_PAL:
			default:
				free(buffer);
				return NULL;
		}
		img.byte_count = hdr.byte_count;
		img.data = malloc(img.byte_count);
		memcpy(img.data, buffer + sizeof(kmg_header_t), img.byte_count);
		
		free(buffer);
		use_alpha = -1;
	} else {
		dbglog(DBG_WARNING, "plx_txr_load: unknown extension for file '%s'\n", fn);
		return NULL;
	}

	/* We got it -- allocate a texture struct */
	txr = (plx_texture_t *)malloc(sizeof(plx_texture_t));
	if (txr == NULL) {
		dbglog(DBG_WARNING, "plx_txr_load: can't allocate memory for texture struct for '%s'\n", fn);
		kos_img_free(&img, 0);
		return NULL;
	}

	/* Setup the struct */
	txr->ptr = pvr_mem_malloc(img.byte_count);
	txr->w = img.w;
	txr->h = img.h;
	if (use_alpha == -1) {
		/* Pull from the image source */
		switch (KOS_IMG_FMT_I(img.fmt) & KOS_IMG_FMT_MASK) {
		case KOS_IMG_FMT_RGB565:
			txr->fmt = PVR_TXRFMT_RGB565;
			break;
		case KOS_IMG_FMT_ARGB4444:
			txr->fmt = PVR_TXRFMT_ARGB4444;
			break;
		case KOS_IMG_FMT_ARGB1555:
			txr->fmt = PVR_TXRFMT_ARGB1555;
			break;
		default:
			/* shrug */
			dbglog(DBG_WARNING, "plx_txr_load: unknown format '%x'\n",
				(int)(KOS_IMG_FMT_I(img.fmt) & KOS_IMG_FMT_MASK));
			txr->fmt = PVR_TXRFMT_RGB565;
			break;
		}
	} else {
		txr->fmt = use_alpha ? PVR_TXRFMT_ARGB4444 : PVR_TXRFMT_RGB565;
	}
	if (KOS_IMG_FMT_D(img.fmt) & PVR_TXRLOAD_FMT_VQ)
		txr->fmt |= PVR_TXRFMT_VQ_ENABLE;

	/* Did we actually get the memory? */
	if (txr->ptr == NULL) {
		dbglog(DBG_WARNING, "plx_txr_load: can't allocate texture ram for '%s'\n", fn);
		kos_img_free(&img, 0);
		free(txr);
		return NULL;
	}

	/* Load it up and twiddle it */
	pvr_txr_load_kimg(&img, txr->ptr, txrload_flags);
	kos_img_free(&img, 0);

	/* Setup the poly context structs */
	fill_contexts(txr);

	return txr;
}

plx_texture_t * plx_txr_canvas(int w, int h, int fmt) {
	plx_texture_t	* txr;

	/* Allocate a texture struct */
	txr = (plx_texture_t *)malloc(sizeof(plx_texture_t));
	if (txr == NULL) {
		dbglog(DBG_WARNING, "plx_txr_canvas: can't allocate memory for %dx%d canvas texture\n", w, h);
		return NULL;
	}

	/* Setup the struct */
	txr->ptr = pvr_mem_malloc(w * h * 2);
	txr->w = w;
	txr->h = h;
	txr->fmt = fmt;

	/* Did we actually get the memory? */
	if (txr->ptr == NULL) {
		dbglog(DBG_WARNING, "plx_txr_canvas: can't allocate texture ram for %dx%d canvas texture\n", w, h);
		free(txr);
		return NULL;
	}

	/* Setup the poly context structs */
	fill_contexts(txr);

	return txr;
}

void plx_txr_destroy(plx_texture_t * txr) {
	assert( txr != NULL );
	if (txr == NULL) return;

	if (txr->ptr != NULL) {
		/* Free the PVR memory */
		pvr_mem_free(txr->ptr);
	}

	/* Free the struct itself */
	free(txr);
}

void plx_txr_setfilter(plx_texture_t * txr, int mode) {
	assert( txr != NULL );
	if (txr == NULL) return;

	txr->cxt_opaque.txr.filter = mode;
	txr->cxt_trans.txr.filter = mode;
	txr->cxt_pt.txr.filter = mode;
	plx_txr_flush_hdrs(txr);
}

void plx_txr_setuvclamp(plx_texture_t * txr, int umode, int vmode) {
	int mode;
	
	assert( txr != NULL );
	if (txr == NULL) return;

	if (umode == PLX_UV_REPEAT && vmode == PLX_UV_REPEAT)
		mode = PVR_UVCLAMP_NONE;
	else if (umode == PLX_UV_REPEAT && vmode == PLX_UV_CLAMP)
		mode = PVR_UVCLAMP_V;
	else if (umode == PLX_UV_CLAMP && vmode == PLX_UV_REPEAT)
		mode = PVR_UVCLAMP_U;
	else if (umode == PLX_UV_CLAMP && vmode == PLX_UV_CLAMP)
		mode = PVR_UVCLAMP_UV;
	else {
		assert_msg( 0, "Invalid UV clamp mode" );
		mode = PVR_UVCLAMP_NONE;
	}

	txr->cxt_opaque.txr.uv_clamp = mode;
	txr->cxt_trans.txr.uv_clamp = mode;
	txr->cxt_pt.txr.uv_clamp = mode;
	plx_txr_flush_hdrs(txr);
}

void plx_txr_flush_hdrs(plx_texture_t * txr) {
	assert( txr != NULL );
	if (txr == NULL) return;

	pvr_poly_compile(&txr->hdr_opaque, &txr->cxt_opaque);
	pvr_poly_compile(&txr->hdr_trans, &txr->cxt_trans);
	pvr_poly_compile(&txr->hdr_pt, &txr->cxt_pt);
}

void plx_txr_send_hdr(plx_texture_t * txr, int list, int flush) {
	assert( txr != NULL );
	if (txr == NULL) return;

	/* Flush the poly hdrs if necessary */
	if (flush)
		plx_txr_flush_hdrs(txr);

	/* Figure out which list to send for */
	switch (list) {
	case PVR_LIST_OP_POLY:
		pvr_prim(&txr->hdr_opaque, sizeof(txr->hdr_opaque));
		break;
	case PVR_LIST_TR_POLY:
		pvr_prim(&txr->hdr_trans, sizeof(txr->hdr_trans));
		break;
	case PVR_LIST_PT_POLY:
		pvr_prim(&txr->hdr_pt, sizeof(txr->hdr_pt));
		break;
	default:
		assert_msg( 0, "Invalid list specification" );
	}
}
