/*
   Format conversion handle routines for the HERMES library
   Copyright (c) 1998 Christian Nentwich (brn@eleet.mcb.at)
   This source code is licensed under the GNU LGPL
  
   Please refer to the file COPYING.LIB contained in the distribution for
   licensing conditions
*/

#include <stdio.h>
#include <stdlib.h>
#include "H_Conv.h"
#include "H_Format.h"
#include "H_Pal.h"
#include "Palette.h"
#include "Factory.h"
#include "Convert.h"
#include "HermConf.h"
#include "HeadC.h"
#include "HeadX86.h"


/* ConverterList is a list of HermesConverter* */

static HermesConverter **ConverterList;
static int lastConverter=0;                  /* Array size, one beyond end */

static int refcount=0;
static HermesHandle currenthandle;



HermesHandle Hermes_ConverterInstance(unsigned long flags)
{ int i;
  HermesConverter *newinstance;
  HermesConverter **newlist;


  /* Initialising, allocate initial size array of converters */

  if (!refcount) {

    ConverterList=(HermesConverter **)malloc(sizeof(HermesConverter*)*
					     HERMES_INITIAL);
    if (!ConverterList) return 0;
    
    lastConverter=HERMES_INITIAL;
    currenthandle=1;

    for (i=0;i<lastConverter;i++)
    ConverterList[i]=0;

//    printf("Creating dynamic array, size %d\n",HERMES_INITIAL);
  } 


  /* Uh oh, arrary too small, time for growth */

  if (currenthandle==lastConverter) {

    /* I'm told realloc isn't completely portable !? Let's do it by hand */

    newlist=(HermesConverter **)malloc(sizeof(HermesConverter*)*
				      (lastConverter+HERMES_GROWTH));
    if (!newlist) return 0;

    /* Copy converter pointers */

    for (i=0;i<lastConverter;i++)
    newlist[i]=ConverterList[i];

    free(ConverterList);

    /* Assign new list to old one */

    ConverterList=newlist;

    lastConverter+=HERMES_GROWTH;

//    printf("Growing dynamic array, new size %d\n",lastConverter);
  }

  /* Create a HermesConverter */

  newinstance=(HermesConverter *)malloc(sizeof(HermesConverter));
  if (!newinstance) return 0;

  /* Zero it out */

  newinstance->loopnormal=0;
  newinstance->loopstretch=0;
  newinstance->normal=0;
  newinstance->stretch=0;
  newinstance->dither=0;
  newinstance->ditherstretch=0;
  newinstance->flags=flags;

  ConverterList[currenthandle]=newinstance;

  refcount++;
  
  currenthandle++;

  return currenthandle-1;
}



void Hermes_ConverterReturn(HermesHandle handle)
{ if (handle<0 || handle>=lastConverter) return;

  /* Adjust reference count */
  refcount--;

  if (ConverterList[handle]!=0) {
    free(ConverterList[handle]);
    ConverterList[handle]=0;
  }

  /* No more references, deinitialise */

  if (!refcount) {

    if (ConverterList) {
      free(ConverterList);
      ConverterList=0;
    }

    currenthandle=0;
    lastConverter=0;
  }
}




int Hermes_ConverterRequest(HermesHandle handle,HermesFormat *source,
                            HermesFormat *dest)
{ int searchlist=0;
  int i;
  char found=0;
  HermesConverter *cnv;


  /* Check array ranges */

  if (handle<0 || handle>=lastConverter) return 0;
  if (!ConverterList[handle]) return 0;

  cnv=ConverterList[handle];



  /* Cache repeated requests of the same conversion */

  if (Hermes_FormatEquals(source,&ConverterList[handle]->source) &&
      Hermes_FormatEquals(dest,&ConverterList[handle]->dest))
  return 1;



  /* Clear the generic converter flag */

  cnv->flags&=~HERMES_CONVERT_GENERIC;



  /* If the source and destination are equal, use copy routines */

  if (Hermes_FormatEquals(source,dest)) {

    if ((source->bits&0x7)!=0 || source->bits>32 || !source->bits) return 0;

    i=(source->bits>>3)-1;
   
    if (!equalConverters[i]) return 0;
   
    Hermes_FormatCopy(source,&cnv->source);
    Hermes_FormatCopy(dest,&cnv->dest);

    cnv->loopnormal=equalConverters[i]->loopnormal;
    cnv->loopstretch=equalConverters[i]->loopstretch;
    cnv->normal=equalConverters[i]->normal;
    cnv->stretch=equalConverters[i]->stretch;

    return 1;
  }



  /* Start looking for specialised converters */

  searchlist=0xff;

  switch (source->bits) {
    case 32: if (source->r==0xff0000 && source->g==0xff00 && source->b==0xff)
             searchlist=0;
             else
	     if (source->r==0xff<<20 && source->g==0xff<<10 && source->b==0xff)
             searchlist=3;
             break;

    case 24: if (source->r==0xff0000 && source->g==0xff00 && source->b==0xff)
             searchlist=1; break;

    case 16: if (source->r==0xf800 && source->g==0x7e0 && source->b==0x1f)
             searchlist=2; 
             break;

     case 8: if (source->indexed)
             searchlist=4; 
             break;
  }


  /* We can use a quicker loop for 8 bit */

  if (searchlist!=0xff)
  if (source->bits==8) {
    for (i=0;i<numConverters[searchlist];i++) {
      if (standardConverters[searchlist][i])
      if (dest->bits==standardConverters[searchlist][i]->dest.bits)
      {
	Hermes_FormatCopy(source,&cnv->source);
        Hermes_FormatCopy(dest,&cnv->dest);

        cnv->loopnormal=standardConverters[searchlist][i]->loopnormal;
        cnv->loopstretch=standardConverters[searchlist][i]->loopstretch;

        cnv->normal=standardConverters[searchlist][i]->normal;
        cnv->stretch=standardConverters[searchlist][i]->stretch;

	cnv->dither=standardConverters[searchlist][i]->dither;
	cnv->ditherstretch=standardConverters[searchlist][i]->ditherstretch;

        return 1;
      }
    }
  }
  else
  for (i=0;i<numConverters[searchlist];i++) {
    if (standardConverters[searchlist][i])
    if (Hermes_FormatEquals(&standardConverters[searchlist][i]->source,source)&&
	Hermes_FormatEquals(&standardConverters[searchlist][i]->dest,dest))
    { Hermes_FormatCopy(source,&cnv->source);
      Hermes_FormatCopy(dest,&cnv->dest);

      cnv->loopnormal=standardConverters[searchlist][i]->loopnormal;
      cnv->loopstretch=standardConverters[searchlist][i]->loopstretch;

      cnv->normal=standardConverters[searchlist][i]->normal;
      cnv->stretch=standardConverters[searchlist][i]->stretch;

      cnv->dither=standardConverters[searchlist][i]->dither;
      cnv->ditherstretch=standardConverters[searchlist][i]->ditherstretch;

      return 1;
    }
  }


  /* Otherwise find a generic converter */

  cnv->loopnormal=0;
  cnv->loopstretch=0;
  cnv->dither=0;
  cnv->ditherstretch=0;
  cnv->flags|=HERMES_CONVERT_GENERIC;


  /* Generic routines implement whole converters not scanline converters, 
     assign placeholders */

  cnv->normal=NotApplicable;
  cnv->stretch=NotApplicable;
 
  found=0;

  switch (source->bits) {
    case 32: switch(dest->bits) {
               case 32: cnv->loopnormal=ConvertC_Generic32_Generic32;
		        found=1; break;
	       case 24: cnv->loopnormal=ConvertC_Generic32_Generic24;
		        found=1; break;
	       case 16: cnv->loopnormal=ConvertC_Generic32_Generic16;
		        found=1; break;
	        case 8: cnv->loopnormal=ConvertC_Generic32_Generic8;
		        found=1; break;
             }
             break;

    case 24: switch(dest->bits) {
               case 32: cnv->loopnormal=ConvertC_Generic24_Generic32;
		        found=1; break;
	       case 24: cnv->loopnormal=ConvertC_Generic24_Generic24;
		        found=1; break;
	       case 16: cnv->loopnormal=ConvertC_Generic24_Generic16;
		        found=1; break;
	        case 8: cnv->loopnormal=ConvertC_Generic24_Generic8;
		        found=1; break;
             }
             break;
	   
    case 16: switch(dest->bits) {
               case 32: cnv->loopnormal=ConvertC_Generic16_Generic32;
		        found=1; break;
	       case 24: cnv->loopnormal=ConvertC_Generic16_Generic24;
		        found=1; break;
	       case 16: cnv->loopnormal=ConvertC_Generic16_Generic16;
		        found=1; break;
	        case 8: cnv->loopnormal=ConvertC_Generic16_Generic8;
		        found=1; break;
             }
             break;    
  }


  if (found) {
    Hermes_FormatCopy(source,&cnv->source);
    Hermes_FormatCopy(dest,&cnv->dest);

    return 1;
  }


  /* No converter found, fail */

  return 0;
}




int Hermes_ConverterPalette(HermesHandle handle,HermesHandle sourcepal,
                            HermesHandle destpal)
{
  if (handle<0 || handle>=lastConverter) return 0;
  if (!ConverterList[handle]) return 0;

  /* Fail silently if indexed colour format */

  if (!ConverterList[handle]->source.indexed) return 1;

  ConverterList[handle]->lookup=
    Hermes_PaletteGetTable(sourcepal,&ConverterList[handle]->dest);
  
  if (!ConverterList[handle]->lookup) return 0;

  return 1;
}





int Hermes_ConverterCopy(HermesHandle handle,void *s_pixels,int s_x,int s_y,
                         int s_width,int s_height,int s_pitch,void *d_pixels,
                         int d_x,int d_y,int d_width,int d_height,int d_pitch)
{ HermesConverter *cnv;
  HermesConverterInterface iface;

  if (handle<0 || handle>=lastConverter) return 0;
  cnv=ConverterList[handle];
  if (!cnv) return 0;


  /* Returns success if height or width is zero. This is debatable.. ! */

  if (s_width<=0 || s_height<=0 || d_width<=0 || d_height<=0) return 1;
  
  iface.s_pixels=(char8 *)s_pixels;
  iface.s_width=s_width;
  iface.s_height=s_height;
  iface.s_add=s_pitch-s_width*(cnv->source.bits>>3);
  iface.s_pitch=s_pitch;

  iface.d_pixels=(char8 *)d_pixels;
  iface.d_width=d_width;
  iface.d_height=d_height;
  iface.d_add=d_pitch-d_width*(cnv->dest.bits>>3);
  iface.d_pitch=d_pitch;

  iface.s_pixels+=s_y*s_pitch+s_x*(cnv->source.bits>>3);
  iface.d_pixels+=d_y*d_pitch+d_x*(cnv->dest.bits>>3);

  iface.lookup=cnv->lookup;


  /* For generic converters, do some extra setup (find shifts, etc.) 
     TODO: Move that out of here and in the request routine ! */

  if (cnv->flags&HERMES_CONVERT_GENERIC) {
    Hermes_Calculate_Generic_Info(Hermes_Topbit(cnv->source.r),
	  			  Hermes_Topbit(cnv->source.g),
		 		  Hermes_Topbit(cnv->source.b),
				  Hermes_Topbit(cnv->dest.r),
				  Hermes_Topbit(cnv->dest.g),
				  Hermes_Topbit(cnv->dest.b),
				  &iface.info);
    iface.mask_r=cnv->dest.r;
    iface.mask_g=cnv->dest.g;
    iface.mask_b=cnv->dest.b;
  }


  /* Check for dithering. This should not be in here but in request as well */

  if (cnv->flags&HERMES_CONVERT_DITHER) {

    /* If there is a ditherer, use it else fall back to normal */

    if (cnv->dither) 
    cnv->loopnormal=cnv->dither;
  }


  /* Normal conversion */

  if (s_width==d_width && s_height==d_height) {
    if (!cnv->normal || !cnv->loopnormal) return 0;

    iface.func=cnv->normal;
    cnv->loopnormal(&iface);

    return 1;
  }
  /* Stretch conversion */
  else {
    if (!cnv->stretch || !cnv->loopstretch) return 0;
    
    iface.func=cnv->stretch;
    cnv->loopstretch(&iface);
  }

  return 1;
}

