/************************************************/
/*                                              */
/* File        : rockfifo_private.c             */
/* Description : ROCK FIFO specific library     */
/*               private part                   */
/*                                              */
/* Author: Sfiligoi Igor                        */
/*                                              */
/* Created      : 17.02.1997                    */
/* Last modified: 12.03.1997                    */
/*                                              */
/************************************************/

#include <stdlib.h>
#include <Error.h>

#include "rockfifo_private.h"

	/* return the number of elements held in the cache */
unsigned int rock_fifo_cache_get_nrels(ROCK_FIFO_id fifo_id)
{
 if (fifo_id->head==fifo_id->tail)
   return 0; /* empty */
 else if (fifo_id->head<fifo_id->tail)
   return fifo_id->tail-fifo_id->head;
 else
   return ROCK_FIFO_CACHE_SIZE+fifo_id->tail-fifo_id->head;
}

int rock_fifo_cache_read(ROCK_FIFO_id fifo_id,	/* IN : fifo id */
			 unsigned int nrels,	/* IN : nr. of elements to read */
			 unsigned int *data)	/* OUT: data from the cache, must be allocated by caller */
{
  int csize;
  int i;

  csize = rock_fifo_cache_get_nrels(fifo_id);

  if (csize<nrels)
    return ROCK_ERROR_FIFO_EMPTY;

  for (i=0; i<nrels; i++)
    {
      data[i] = rockb_get_fifodata(fifo_id->cache[fifo_id->head]);
      fifo_id->head++;
      if (fifo_id->head>=ROCK_FIFO_CACHE_SIZE)
	fifo_id->head=0;   /* wrap */
    }
  return ROCK_ERROR_OK;
}

int rock_fifo_cache_read_bits(ROCK_FIFO_id fifo_id,	/* IN : fifo id */
			      unsigned int nrels,	/* IN : nr. of elements to read */
			      ROCK_EDFIFO_bits *data)	/* OUT: data from the cache, must be allocated by caller */
{
  int csize;
  int i;

  csize = rock_fifo_cache_get_nrels(fifo_id);

  if (csize<nrels)
    return ROCK_ERROR_FIFO_EMPTY;

  for (i=0; i<nrels; i++)
    {
      rockb_nr2edfifo(fifo_id->cache[fifo_id->head],data[i]);
      fifo_id->head++;
      if (fifo_id->head>=ROCK_FIFO_CACHE_SIZE)
	fifo_id->head=0;   /* wrap */
    }
  return ROCK_ERROR_OK;
}

int rock_fifo_cache_return_bits(ROCK_FIFO_id  fifo_id,	/* IN : fifo_id */
				ROCK_EDFIFO_bits *data,	/* IN : data to be returned to the cache */
				unsigned int  nrels)	/* IN : nr. of elements of data to return */
{
  int i;
  
  for (i=nrels; i>0; i--)
    {
      if (fifo_id->head>0)
	fifo_id->head--;
      else
	fifo_id->head = ROCK_FIFO_CACHE_SIZE-1;

      if (fifo_id->head==fifo_id->tail)
	{ /* ERROR, cache overflow */
	  ErrorSet(ROCK_ERROR_UNKNOWN,"rock_fifo_cache_return","Cache overflow.");
	  return ROCK_ERROR_UNKNOWN;
	}

      fifo_id->cache[fifo_id->head] = rockb_edfifo2nr(data[i-1]);
    }

  return ROCK_ERROR_OK;
}

void rock_fifo_flags(ROCK_FIFO_id       fifo_id,  /* IN : fifo id */
		     ROCKB_EDFIFO_bits *flags)    /* OUT: only ff,hf and ef elements are valid */
{
  ROCK_FIFO_bits bits;

  rock_read_fifo(fifo_id->rock_id,bits);
  if (fifo_id->fifotype==ROCK_FIFO_EFIFO)
    {
      flags->ff = bits.eff;
      flags->hf = bits.ehf;
      flags->ef = bits.eef;
    }
  else
    {
      flags->ff = bits.dff;
      flags->hf = bits.dhf;
      flags->ef = bits.def;
    }
}

/* this routine needs the data to be at the fisical start of the cache */
/* no range checking is done */
/* read the data field by field */
int rock_fifo_fillcache_singular(ROCK_FIFO_id fifo_id,	/* IN : fifo id */
				 unsigned int nrels)	/* IN : nr of elements to read, ignore actual size */
{
  ROCKB_EDFIFO_bits bits;
  unsigned int i;

  rock_fifo_flags(fifo_id,&bits);

  for (i=0; (bits.ef==ROCK_BIT_OFF)&&(i<nrels); i++)
    {
      if (fifo_id->fifotype==ROCK_FIFO_EFIFO)
	rock_read_efifo(fifo_id->rock_id,bits);
      else
	rock_read_dfifo(fifo_id->rock_id,bits);

      if (bits.nvd==ROCK_BIT_ON)
	break;  /* exit as if an Empty Fifo was found */
      else
	{
	  fifo_id->cache[fifo_id->tail++] = rockb_edfifo2nr(bits);
	}
    }
 
  if (i<nrels)
    return ROCK_ERROR_FIFO_EMPTY;
  else
    return ROCK_ERROR_OK;
}

/* this routine needs the data to be at the fisical start of the cache */
/* no range checking is done */
/* read the data field by field */
int rock_fifo_fillcache_block(ROCK_FIFO_id fifo_id,	/* IN : fifo id */
			      unsigned int nrels)	/* IN : nr of elements to read, ignore actual size */
{
  int err;
  unsigned int readels;
  unsigned char oldboe;

  /* set boe off to prevent errors */
  oldboe = rock_get_boe(fifo_id->rock_id);
  if (oldboe!=ROCK_BIT_OFF)
    rock_set_boe(fifo_id->rock_id,ROCK_BIT_OFF);

  readels = nrels;

  if (fifo_id->fifotype==ROCK_FIFO_EFIFO)
    err = rockh_readfifo_efifo(fifo_id->rock_id,readels,&(fifo_id->cache[fifo_id->tail]));
  else
    err = rockh_readfifo_dfifo(fifo_id->rock_id,readels,&(fifo_id->cache[fifo_id->tail]));

  if (oldboe!=ROCK_BIT_OFF)
    rock_set_boe(fifo_id->rock_id,oldboe);

  fifo_id->tail+=readels;

  if ((err==ROCK_ERROR_OK)&&(readels!=nrels))
    return ROCK_ERROR_FIFO_EMPTY;
  else
    return err;
}

int rock_fifo_fillcache(ROCK_FIFO_id fifo_id,	/* IN : fifo id */
		        unsigned int nrels)	/* IN : fill the cache at least with the nr elements */
						/*      if more than ROCK_FIFO_FIFO_SIZE, truncated to ROCK_FIFO_FIFO_SIZE */ 
{
  int csize;
  unsigned int minread;
  unsigned int maxread;     /* max elements that can be read */
  int err;

  if (nrels>ROCK_FIFO_FIFO_SIZE)
    nrels=ROCK_FIFO_FIFO_SIZE;

  csize = rock_fifo_cache_get_nrels(fifo_id);

  if (csize>=nrels)
    return ROCK_ERROR_OK;  /* nothing to do */

  if (csize>0)
    { /* put the data at fisical start of the cache */
      unsigned int *buffer;
  
      buffer = (unsigned int *) malloc(csize*sizeof(unsigned int));
      rock_fifo_cache_read(fifo_id,csize,buffer);
      memcpy(fifo_id->cache,buffer,csize*sizeof(unsigned int));
      fifo_id->head = 0;
      fifo_id->tail = csize;
      free(buffer);
    }
  else
    { /* this simplifies the cache */
      fifo_id->head=0;
      fifo_id->tail=0;
    }

  /* now all the data is at the fisical start of the cache */

  /* read only the missing elements */
  minread = nrels - csize;
  maxread = ROCK_FIFO_FIFO_SIZE - csize;

  err = ROCK_ERROR_OK;

  switch (fifo_id->cancache)
    {
    case ROCK_FIFO_CACHE_OFF:
      {
	ROCKB_EDFIFO_bits flags;

	rock_fifo_flags(fifo_id,&flags);

	if (flags.ef==ROCK_BIT_ON)
	  {
	    /* fifo empty, do nothing */
	  }
	else if (flags.hf==ROCK_BIT_OFF)
	  { /* less than HF */
	    err = rock_fifo_fillcache_singular(fifo_id,minread);
	  }
	else if (flags.ff==ROCK_BIT_OFF)
	  { /* more than half */
	    if (minread>ROCK_FIFO_FIFO_SIZE_HALF)
	      { /* minread is larger than half cache */
		err = rock_fifo_fillcache_block(fifo_id,ROCK_FIFO_FIFO_SIZE_HALF);
		if (err==ROCK_ERROR_OK)
		  err = rock_fifo_fillcache_singular(fifo_id,minread-ROCK_FIFO_FIFO_SIZE_HALF);
	      }
	    else
	      { /* minread if less than half cache */
		err = rock_fifo_fillcache_block(fifo_id,minread);
	      }
	  }
	else
	  { /* full */
	    err = rock_fifo_fillcache_block(fifo_id,minread);
	  }
      }
      break;
    case ROCK_FIFO_CACHE_MINIMAL:
      {
	ROCKB_EDFIFO_bits flags;

	rock_fifo_flags(fifo_id,&flags);

	if (flags.ef==ROCK_BIT_ON)
	  {
	    /* fifo empty, do nothing */
	  }
	else if (flags.hf==ROCK_BIT_OFF)
	  { /* less than HF */
	    err = rock_fifo_fillcache_singular(fifo_id,minread);
	  }
	else if (flags.ff==ROCK_BIT_OFF)
	  { /* more than half */
	    if (minread>ROCK_FIFO_FIFO_SIZE_HALF)
	      { /* minread is larger than half cache */
		err = rock_fifo_fillcache_block(fifo_id,ROCK_FIFO_FIFO_SIZE_HALF);
		if (err==ROCK_ERROR_OK)
		  err = rock_fifo_fillcache_singular(fifo_id,minread-ROCK_FIFO_FIFO_SIZE_HALF);
	      }
	    else
	      { /* minread if less than half cache => read half cache*/
		if (maxread>ROCK_FIFO_FIFO_SIZE_HALF)
		  err = rock_fifo_fillcache_block(fifo_id,ROCK_FIFO_FIFO_SIZE_HALF);
		else
		  err = rock_fifo_fillcache_block(fifo_id,maxread);
	      }
	  }
	else
	  { /* full */
	    err = rock_fifo_fillcache_block(fifo_id,maxread);
	  }
      }
      break;
    case ROCK_FIFO_CACHE_ADVANCED:
      {
	ROCKB_EDFIFO_bits flags;

	rock_fifo_flags(fifo_id,&flags);

	if (flags.ef==ROCK_BIT_ON)
	  {
	    /* fifo empty, do nothing */
	  }
	else if (flags.hf==ROCK_BIT_ON)
	  { /* less than HF */
	    err = rock_fifo_fillcache_singular(fifo_id,maxread);
	  }
	else if (flags.ff==ROCK_BIT_OFF)
	  { /* more than half */
	    if (maxread>ROCK_FIFO_FIFO_SIZE_HALF)
	      { /* maxread is larger than half cache */
		err = rock_fifo_fillcache_block(fifo_id,ROCK_FIFO_FIFO_SIZE_HALF);
		if (err==ROCK_ERROR_OK)
		  err = rock_fifo_fillcache_singular(fifo_id,maxread-ROCK_FIFO_FIFO_SIZE_HALF);
	      }
	    else
	      { /* maxread if less than half cache */
		err = rock_fifo_fillcache_block(fifo_id,maxread);
	      }
	  }
	else
	  { /* full */
	    err = rock_fifo_fillcache_block(fifo_id,maxread);
	  }
      }
      break;
    case ROCK_FIFO_CACHE_BLOCK:
      err = rock_fifo_fillcache_block(fifo_id,minread);
      break;
    case ROCK_FIFO_CACHE_BLOCK_ADVANCED:
      err = rock_fifo_fillcache_block(fifo_id,maxread);
      break;
    default:
      /* ERROR */
      ErrorSetF(ROCK_ERROR_UNKNOWN,"rock_fifo_fillcache","Invalid cancache: %i",fifo_id->cancache);
      return ROCK_ERROR_UNKNOWN;
    }

  if ((err!=ROCK_ERROR_OK)&&(err!=ROCK_ERROR_FIFO_EMPTY))
    { /* serious error */
      ErrorSetF(err,"rock_fifo_fillcache","Error found: %s",ErrorGetMessage());
      return err;
    }

  /* no error */
  /* reget csize */
  csize = rock_fifo_cache_get_nrels(fifo_id);
  if (csize<nrels)
    return ROCK_ERROR_FIFO_EMPTY;  /* the fillcache has not obtained enough elements */
  else
    return ROCK_ERROR_OK;
}