/*
 * Author: Max Lingua <sunmax@libero.it>
 */

#include <stdlib.h>
#include <stdio.h>

#include "main/glheader.h"
#include "main/macros.h"
#include "main/mtypes.h"
#include "main/simple_list.h"
#include "main/enums.h"

#include "main/mm.h"
#include "s3v_context.h"
#include "s3v_lock.h"
#include "s3v_tex.h"

void s3vSwapOutTexObj(s3vContextPtr vmesa, s3vTextureObjectPtr t);
void s3vUpdateTexLRU( s3vContextPtr vmesa, s3vTextureObjectPtr t );


void s3vDestroyTexObj(s3vContextPtr vmesa, s3vTextureObjectPtr t)
{
#if TEX_DEBUG_ON
   static unsigned int times=0;
   DEBUG_TEX(("*** s3vDestroyTexObj: #%i ***\n", ++times));
#endif

   if (!t) return;

/* FIXME: useful? */
#if _TEXFLUSH
	if (vmesa)
		DMAFLUSH();
#endif

   /* This is sad - need to sync *in case* we upload a texture
    * to this newly free memory...
    */
   if (t->MemBlock) {
      mmFreeMem(t->MemBlock);
      t->MemBlock = 0;

      if (vmesa && t->age > vmesa->dirtyAge)
	     vmesa->dirtyAge = t->age;
   }

   if (t->globj)
      t->globj->DriverData = NULL;

   if (vmesa) {
      if (vmesa->CurrentTexObj[0] == t) {
         	vmesa->CurrentTexObj[0] = 0;
        	vmesa->dirty &= ~S3V_UPLOAD_TEX0;
      }

#if 0
      if (vmesa->CurrentTexObj[1] == t) {
         vmesa->CurrentTexObj[1] = 0;
         vmesa->dirty &= ~S3V_UPLOAD_TEX1;
      }
#endif
   }

   remove_from_list(t);
   FREE(t);
}


void s3vSwapOutTexObj(s3vContextPtr vmesa, s3vTextureObjectPtr t)
{
/*   int i; */
#if TEX_DEBUG_ON
   static unsigned int times=0;
   DEBUG_TEX(("*** s3vSwapOutTexObj: #%i ***\n", ++times));
#endif

   if (t->MemBlock) {

      mmFreeMem(t->MemBlock);
      t->MemBlock = 0;

      if (t->age > vmesa->dirtyAge)
         vmesa->dirtyAge = t->age;
   
      t->dirty_images = ~0; 
      move_to_tail(&(vmesa->SwappedOut), t);
   }
}


/* Upload an image from mesa's internal copy.
 */

static void s3vUploadTexLevel( s3vContextPtr vmesa, s3vTextureObjectPtr t,
				int level )
{
	__DRIscreenPrivate *sPriv = vmesa->driScreen;
	const struct gl_texture_image *image = t->image[level].image;
	int i,j;
	int l2d;
	/* int offset = 0; */
	int words;
	GLuint* dest;
#if TEX_DEBUG_ON
	static unsigned int times=0;
#endif
	if ( !image ) return;
	if (image->Data == 0) return;

	DEBUG_TEX(("*** s3vUploadTexLevel: #%i ***\n", ++times));
	DEBUG_TEX(("level = %i\n", level));

	l2d = 5; /* 32bits per texel == 1<<5 */
/*
	if (level == 0) 
		;
*/
	DEBUG_TEX(("t->image[%i].offset = 0x%x\n",
		level, t->image[level].offset));
		
	t->TextureBaseAddr[level] = (GLuint)(t->BufAddr + t->image[level].offset
		+ _TEXALIGN) & (GLuint)(~_TEXALIGN);
	dest = (GLuint*)(sPriv->pFB + t->TextureBaseAddr[level]); 

	DEBUG_TEX(("sPriv->pFB = 0x%x\n", sPriv->pFB));
	DEBUG_TEX(("dest = 0x%x\n", dest));
	DEBUG_TEX(("dest - sPriv->pFB = 0x%x\n", ((int)dest - (int)sPriv->pFB)));

	/* NOTE: we implicitly suppose t->texelBytes == 2 */

	words = (image->Width * image->Height) >> 1;

	DEBUG_TEX(("\n\n"));

	switch (t->image[level].internalFormat) {
	case GL_RGB:
	case 3:
	{
		GLubyte *src = (GLubyte *)image->Data;

		DEBUG_TEX(("GL_RGB:\n"));
/*
		if (level == 0)
     			;
*/
		/* The UGLY way, and SLOW : use DMA FIXME ! */

		for (i = 0; i < words; i++) {
		unsigned int data;
		/* data = PACK_COLOR_565(src[0],src[1],src[2]); */
		data = S3VIRGEPACKCOLOR555(src[0],src[1],src[2],255)
			|(S3VIRGEPACKCOLOR555(src[3],src[4],src[5],255)<<16);

		*dest++ = data;
	 	/* src += 3; */
		src +=6;
      	}
	}
	break;

	case GL_RGBA:
	case 4:
	{
		GLubyte *src = (GLubyte *)image->Data;

		DEBUG_TEX(("GL_RGBA:\n"));
/*
		if (level == 0)
			;
*/
		for (i = 0; i < words; i++) {		
		unsigned int data;
		
		/* data = PACK_COLOR_8888(src[0],src[1],src[2],src[3]); */
		data = S3VIRGEPACKCOLOR4444(src[0], src[1],src[2], src[3])
		| (S3VIRGEPACKCOLOR4444(src[4], src[5], src[6], src[7]) << 16);
		
		*dest++ = data;
		/* src += 4; */
		src += 8;
		}
	}
	break;

	case GL_LUMINANCE:
	{
		GLubyte *src = (GLubyte *)image->Data;

		DEBUG_TEX(("GL_LUMINANCE:\n"));
/*
		if (level == 0)
			;
*/
		for (i = 0; i < words; i++) {
		unsigned int data;
		
		/* data = PACK_COLOR_888(src[0],src[0],src[0]); */
		data = S3VIRGEPACKCOLOR4444(src[0],src[0],src[0],src[0])
		| (S3VIRGEPACKCOLOR4444(src[1],src[1],src[1],src[1]) << 16);
		 
		*dest++ = data;
		/* src ++; */
		src +=2;
		}
	}
	break;

	case GL_INTENSITY:
	{
		GLubyte *src = (GLubyte *)image->Data;

		DEBUG_TEX(("GL_INTENSITY:\n"));
/*	
		if (level == 0)
			;
*/
		for (i = 0; i < words; i++) {
		unsigned int data;
		
		/* data = PACK_COLOR_8888(src[0],src[0],src[0],src[0]); */
		data = S3VIRGEPACKCOLOR4444(src[0],src[0],src[0],src[0])
	        | (S3VIRGEPACKCOLOR4444(src[1],src[1],src[1],src[1]) << 16);

		*dest++ = data; 
		/* src ++; */
		src += 2;
		}
	}
	break;

	case GL_LUMINANCE_ALPHA:
	{
		GLubyte *src = (GLubyte *)image->Data;

		DEBUG_TEX(("GL_LUMINANCE_ALPHA:\n"));
/*
		if (level == 0)
			;
*/
		for (i = 0; i < words; i++) {
		unsigned int data;
		
		/* data = PACK_COLOR_8888(src[0],src[0],src[0],src[1]); */
		data = S3VIRGEPACKCOLOR4444(src[0],src[0],src[0],src[1])
	        | (S3VIRGEPACKCOLOR4444(src[2],src[2],src[2],src[3]) << 16);
		
		*dest++ = data;
		/* src += 2; */
		src += 4;
		}
	}
	break;

	case GL_ALPHA:
	{
		GLubyte *src = (GLubyte *)image->Data;

		DEBUG_TEX(("GL_ALPHA:\n"));
/*
		if (level == 0)
			;
*/
		for (i = 0; i < words; i++) {
		unsigned int data;
		
		/* data = PACK_COLOR_8888(255,255,255,src[0]); */
		data = S3VIRGEPACKCOLOR4444(255,255,255,src[0])
		| (S3VIRGEPACKCOLOR4444(255,255,255,src[1]) << 16);
		
		*dest++ = data;
		/* src += 1; */
		src += 2;
		}
	}
	break;

	/* TODO: Translate color indices *now*:
	 */
	case GL_COLOR_INDEX:
	{
	
		GLubyte *dst = (GLubyte *)(t->BufAddr + t->image[level].offset);
		GLubyte *src = (GLubyte *)image->Data;

		DEBUG_TEX(("GL_COLOR_INDEX:\n"));

		for (j = 0 ; j < image->Height ; j++, dst += t->Pitch) {
			for (i = 0 ; i < image->Width ; i++) {
				dst[i] = src[0];
				src += 1;
			}
		}
	}
	break;

	default:
		fprintf(stderr, "Not supported texture format %s\n",
			_mesa_lookup_enum_by_nr(image->_BaseFormat));
	}

	DEBUG_TEX(("words = %i\n\n", words));
}

void s3vPrintLocalLRU( s3vContextPtr vmesa )
{
   s3vTextureObjectPtr t;
   int sz = 1 << (vmesa->s3vScreen->logTextureGranularity);

#if TEX_DEBUG_ON
   static unsigned int times=0;
   DEBUG_TEX(("*** s3vPrintLocalLRU: #%i ***\n", ++times));
#endif

   foreach( t, &vmesa->TexObjList ) {
      if (!t->globj)
         fprintf(stderr, "Placeholder %d at %x sz %x\n",
		    t->MemBlock->ofs / sz,
		    t->MemBlock->ofs,
		    t->MemBlock->size);
      else
         fprintf(stderr, "Texture at %x sz %x\n",
		    t->MemBlock->ofs,
		    t->MemBlock->size);

   }
}

void s3vPrintGlobalLRU( s3vContextPtr vmesa )
{
   int i, j;
   S3VTexRegionPtr list = vmesa->sarea->texList;
#if TEX_DEBUG_ON
   static unsigned int times=0;
   DEBUG_TEX(("*** s3vPrintGlobalLRU: #%i ***\n", ++times));
#endif

   for (i = 0, j = S3V_NR_TEX_REGIONS ; i < S3V_NR_TEX_REGIONS ; i++) {
      fprintf(stderr, "list[%d] age %d next %d prev %d\n",
	      j, list[j].age, list[j].next, list[j].prev);
      j = list[j].next;
      if (j == S3V_NR_TEX_REGIONS) break;
   }

   if (j != S3V_NR_TEX_REGIONS)
      fprintf(stderr, "Loop detected in global LRU\n");
}


void s3vResetGlobalLRU( s3vContextPtr vmesa )
{
   S3VTexRegionPtr list = vmesa->sarea->texList;
   int sz = 1 << vmesa->s3vScreen->logTextureGranularity;
   int i;

#if TEX_DEBUG_ON
   static unsigned int times=0;
   DEBUG_TEX(("*** s3vResetGlobalLRU: #%i ***\n", ++times));
#endif

   /* (Re)initialize the global circular LRU list.  The last element
    * in the array (S3V_NR_TEX_REGIONS) is the sentinal.  Keeping it
    * at the end of the array allows it to be addressed rationally
    * when looking up objects at a particular location in texture
    * memory.
    */
   for (i = 0 ; (i+1) * sz <= vmesa->s3vScreen->textureSize ; i++) {
      list[i].prev = i-1;
      list[i].next = i+1;
      list[i].age = 0;
   }

   i--;
   list[0].prev = S3V_NR_TEX_REGIONS;
   list[i].prev = i-1;
   list[i].next = S3V_NR_TEX_REGIONS;
   list[S3V_NR_TEX_REGIONS].prev = i;
   list[S3V_NR_TEX_REGIONS].next = 0;
   vmesa->sarea->texAge = 0;
}


void s3vUpdateTexLRU( s3vContextPtr vmesa, s3vTextureObjectPtr t )
{
/*
   int i;
   int logsz = vmesa->s3vScreen->logTextureGranularity;
   int start = t->MemBlock->ofs >> logsz;
   int end = (t->MemBlock->ofs + t->MemBlock->size - 1) >> logsz;
   S3VTexRegionPtr list = vmesa->sarea->texList;
*/

#if TEX_DEBUG_ON
   static unsigned int times=0;
   DEBUG_TEX(("*** s3vUpdateTexLRU: #%i ***\n", ++times));
#endif

   vmesa->texAge = ++vmesa->sarea->texAge;

   /* Update our local LRU
    */
   move_to_head( &(vmesa->TexObjList), t );

   /* Update the global LRU
    */
#if 0
   for (i = start ; i <= end ; i++) {

      list[i].in_use = 1;
      list[i].age = vmesa->texAge;

      /* remove_from_list(i)
       */
      list[(unsigned)list[i].next].prev = list[i].prev;
      list[(unsigned)list[i].prev].next = list[i].next;

      /* insert_at_head(list, i)
       */
      list[i].prev = S3V_NR_TEX_REGIONS;
      list[i].next = list[S3V_NR_TEX_REGIONS].next;
      list[(unsigned)list[S3V_NR_TEX_REGIONS].next].prev = i;
      list[S3V_NR_TEX_REGIONS].next = i;
   }
#endif
}


/* Called for every shared texture region which has increased in age
 * since we last held the lock.
 *
 * Figures out which of our textures have been ejected by other clients,
 * and pushes a placeholder texture onto the LRU list to represent
 * the other client's textures.
 */
void s3vTexturesGone( s3vContextPtr vmesa,
		       GLuint offset,
		       GLuint size,
		       GLuint in_use )
{
   s3vTextureObjectPtr t, tmp;
#if TEX_DEBUG_ON
   static unsigned int times=0;
   DEBUG_TEX(("*** s3vTexturesGone: #%i ***\n", ++times));
#endif

   foreach_s ( t, tmp, &vmesa->TexObjList ) {

      if (t->MemBlock->ofs >= offset + size ||
	  t->MemBlock->ofs + t->MemBlock->size <= offset)
         continue;

      /* It overlaps - kick it off.  Need to hold onto the currently bound
       * objects, however.
       */
	  s3vSwapOutTexObj( vmesa, t );
   }

   if (in_use) {
		   t = (s3vTextureObjectPtr) calloc(1,sizeof(*t));
		   if (!t) return;

		   t->MemBlock = mmAllocMem( vmesa->texHeap, size, 0, offset);
		   insert_at_head( &vmesa->TexObjList, t );
   }

   /* Reload any lost textures referenced by current vertex buffer.
	*/
#if 0
   if (vmesa->vertex_buffer) {
		   int i, j;

		   fprintf(stderr, "\n\nreload tex\n");

		   for (i = 0 ; i < vmesa->statenr ; i++) {
				   for (j = 0 ; j < 2 ; j++) {
						   s3vTextureObjectPtr t = vmesa->state_tex[j][i];
						   if (t) {
								   if (t->MemBlock == 0)
										   s3vUploadTexImages( vmesa, t );
						   }
				   }
		   }

		   /* Hard to do this with the lock held:
			*/
		   /*        S3V_FIREVERTICES( vmesa ); */
   }
#endif
}


/* This is called with the lock held.  May have to eject our own and/or
 * other client's texture objects to make room for the upload.
 */
void s3vUploadTexImages( s3vContextPtr vmesa, s3vTextureObjectPtr t )
{
	int i;
	int ofs;
	int numLevels;
#if TEX_DEBUG_ON
	static unsigned int times=0;
	static unsigned int try=0;

	DEBUG_TEX(("*** s3vUploadTexImages: #%i ***\n", ++times));
	DEBUG_TEX(("vmesa->texHeap = 0x%x; t->totalSize = %i\n",
		(unsigned int)vmesa->texHeap, t->totalSize));
#endif

	/* Do we need to eject LRU texture objects?
	 */
	if (!t->MemBlock) {

		while (1)
		{
			/* int try = 0; */
			DEBUG_TEX(("trying to alloc mem for tex (try %i)\n", ++try));

			t->MemBlock = mmAllocMem( vmesa->texHeap, t->totalSize, 12, 0 );

			if (t->MemBlock)
				break;

			if (vmesa->TexObjList.prev == vmesa->CurrentTexObj[0]) {
/*			    || vmesa->TexObjList.prev == vmesa->CurrentTexObj[1]) {
				fprintf(stderr, "Hit bound texture in upload\n");
				s3vPrintLocalLRU( vmesa ); */
				return;
			}

			if (vmesa->TexObjList.prev == &(vmesa->TexObjList)) {
/*				fprintf(stderr, "Failed to upload texture, sz %d\n",
					t->totalSize);
				mmDumpMemInfo( vmesa->texHeap ); */
				return;
			}

			DEBUG_TEX(("swapping out: %p\n", vmesa->TexObjList.prev));
			s3vSwapOutTexObj( vmesa, vmesa->TexObjList.prev );
		}

	ofs = t->MemBlock->ofs;

	t->BufAddr = vmesa->s3vScreen->texOffset + ofs;

	DEBUG_TEX(("ofs = 0x%x\n", ofs));
	DEBUG_TEX(("t->BufAddr = 0x%x\n", t->BufAddr));

/* FIXME: check if we need it */
#if 0
	if (t == vmesa->CurrentTexObj[0]) {
		vmesa->dirty |= S3V_UPLOAD_TEX0; 
		vmesa->restore_primitive = -1; 
	}
#endif

#if 0
	if (t == vmesa->CurrentTexObj[1])
		vmesa->dirty |= S3V_UPLOAD_TEX1;
#endif

	s3vUpdateTexLRU( vmesa, t );
	}

#if 0
	if (vmesa->dirtyAge >= GET_DISPATCH_AGE(vmesa))
		s3vWaitAgeLocked( vmesa, vmesa->dirtyAge );
#endif

#if _TEXLOCK
	S3V_SIMPLE_FLUSH_LOCK(vmesa);
#endif
	numLevels = t->lastLevel - t->firstLevel + 1;
	for (i = 0 ; i < numLevels ; i++)
		if (t->dirty_images & (1<<i))
			s3vUploadTexLevel( vmesa, t, i );

	t->dirty_images = 0;
#if _TEXLOCK
	S3V_SIMPLE_UNLOCK(vmesa);
#endif
}
