/************************************************/
/*                                              */
/* File        : rockfifo.c                     */
/* Description : ROCK FIFO specific library     */
/*                                              */
/* Author: Sfiligoi Igor                        */
/*                                              */
/* Comment: If this module is used,             */
/*          nobody else should access the FIFO  */
/*                                              */
/* Created      : 14.02.1997                    */
/* Last modified: 19.03.1997                    */
/*                                              */
/************************************************/

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

#include "rockfifo.h"
#include "rockframes.h"

#include "rockfifo_private.h"

/**********************************************************************/
/*                                                                    */
/*                       Initialisation routines                      */
/*                                                                    */
/**********************************************************************/

int rock_fifo_open(ROCK_id       rock_id,   /* IN : standard rock id */
		   unsigned char fifotype,  /* IN : ROCK_FIFO_EFIFO or ROCK_FIFO_DFIFO */
		   unsigned char cancache,  /* IN : see ROCK_FIFO_CACHE_... constants */
                   ROCK_FIFO_id *fifo_id)   /* OUT: fifo id related to the rock */
{
 (*fifo_id) = (ROCK_FIFO_id_base *) malloc(sizeof(ROCK_FIFO_id_base));
 if ((*fifo_id)==NULL)
   { /* malloc error */
     ErrorSet(ROCK_ERROR_UNKNOWN,"rock_fifo_open","Malloc returned NULL.");
     return ROCK_ERROR_UNKNOWN;
   }

 (*fifo_id)->rock_id = rock_id;
 (*fifo_id)->head = 0;
 (*fifo_id)->tail = 0;
 (*fifo_id)->fifotype = fifotype;

 (*fifo_id)->cancache = ROCK_FIFO_CACHE_OFF;
 return rock_fifo_set_cancache((*fifo_id),cancache);
}


int rock_fifo_close(ROCK_FIFO_id   fifo_id, /* IN : fifo id */
                    ROCK_FIFO_RAW *cache)   /* OUT: the unused cache */
					    /*      should be disposed by caller */ 
{
 cache->nrels=rock_fifo_cache_get_nrels(fifo_id);
 cache->els = (unsigned int *) malloc(cache->nrels*sizeof(unsigned int));
 if (cache->els==NULL)
   { /* malloc error */
     ErrorSet(ROCK_ERROR_UNKNOWN,"rock_fifo_close","Malloc returned NULL.");
     return ROCK_ERROR_UNKNOWN;
   }

 rock_fifo_cache_read(fifo_id,cache->nrels,cache->els);
 free(fifo_id);

 return ROCK_ERROR_OK;
}		    

/**********************************************************************/
/*                                                                    */
/*                          Settings routines                         */
/*                                                                    */
/**********************************************************************/

int rock_fifo_set_cancache(ROCK_FIFO_id  fifo_id,	/* IN : fifo id */
			   unsigned char cancache)	/* IN : new cancache value */
{
 fifo_id->cancache = cancache;

 return ROCK_ERROR_OK;
}

/**********************************************************************/
/*                                                                    */
/*                          Raw read routines                         */
/*                                                                    */
/**********************************************************************/

int rock_fifo_raw_read(ROCK_FIFO_id  fifo_id,		/* IN : fifo id */
		       unsigned int *data)		/* OUT: one FIFO value */
{
 int err;

 err = rock_fifo_cache_read(fifo_id,1,data);
 if (err==ROCK_ERROR_OK)
   return ROCK_ERROR_OK;   /* was in cache, ok */

 err = rock_fifo_fillcache(fifo_id,1);

 if (err==ROCK_ERROR_OK)
   { /* data is in cache => read from cache */
     rock_fifo_cache_read(fifo_id,1,data);
     return ROCK_ERROR_OK;
   }
 else if (err==ROCK_ERROR_FIFO_EMPTY)
   return err;
 else
   { /* serious error */
     ErrorSetF(err,"rock_fifo_raw_read","rock_fifo_fillcache error: %s",ErrorGetMessage());
     return err;
   }

}

int rock_fifo_raw_blockread(ROCK_FIFO_id   fifo_id,	/* IN : fifo id */
			    unsigned int   nrels,	/* IN : max number of elements */
			    ROCK_FIFO_RAW *data)	/* OUT: FIFO data */
					    		/*      should be disposed by caller */ 
{
 int csize;
 unsigned int *actel;         /* point to the first unused space in data->els */
 int err;

 data->els = (unsigned int *) malloc(nrels*sizeof(unsigned int));
 if (data->els==NULL)
   { /* malloc error */
     ErrorSet(ROCK_ERROR_UNKNOWN,"rock_fifo_raw_blockread","Malloc returned NULL.");
     return ROCK_ERROR_UNKNOWN;
   }

 actel = data->els;
 data->nrels=0;

 while (1)
   {
     csize = rock_fifo_cache_get_nrels(fifo_id);
     if (csize>=nrels)
       { /* all needed elements are in cache */
	 data->nrels+=nrels;
	 rock_fifo_cache_read(fifo_id,nrels,actel);
	 return ROCK_ERROR_OK;
       }
     
     /* cache has not enough elements */
     data->nrels+=csize;
     rock_fifo_cache_read(fifo_id,csize,actel);
     
     /* loop */
     nrels-=csize;
     
     err = rock_fifo_fillcache(fifo_id,nrels);

     if (err!=ROCK_ERROR_OK)
       {
	 if (err!=ROCK_ERROR_FIFO_EMPTY)
	   { /* serious error, exit */
	     ErrorSetF(err,"rock_fifo_raw_blockread","rock_fifo_fillcache error: %s",ErrorGetMessage());
	     return err;
	   }
	 else
	   { /* save the elements found and exit*/
	     csize = rock_fifo_cache_get_nrels(fifo_id);
	     data->nrels+=csize;
	     rock_fifo_cache_read(fifo_id,csize,actel);
	     return ROCK_ERROR_FIFO_EMPTY;
	   }
       }
   }
}

/**********************************************************************/
/*                                                                    */
/*                          Frame read routines                       */
/*                                                                    */
/**********************************************************************/

int rock_fifo_frame_read(ROCK_FIFO_id     fifo_id,	/* IN : fifo id */
		   	 ROCK_FIFO_FRAME *data) 	/* OUT: one FIFO value */
{
 int nrels;
 unsigned int rawels[ROCK_FIFO_FRAME_SIZE];
 unsigned int softparity;
 unsigned char prec_addr;       /* needed to test that addresses go from highest to lower */
 unsigned int nrslaves[16];     /* nr of slaves with a address are in the frame */

 int err,whatis;

 /* read header */
 err = rock_fifo_raw_read(fifo_id,&(rawels[0]));

   /* exit immediatly if cannot read the header */
 if (err!=ROCK_ERROR_OK)
   if (err==ROCK_ERROR_FIFO_EMPTY)
     return err;
   else
     {
       ErrorSetF(err,"rock_fifo_frame_read","rock_fifo_raw_read error: %s",ErrorGetMessage());
       return err;
     }

 whatis= rockf_whatis(rawels[0]);
 if (whatis==ROCKF_IS_EMPTY)
   {  /* set the frame and exit */
     ROCKF_EMPTY_bits empty;
     int i;
     
     rockf_nr2empty(rawels[0],empty);
     data->header.sy = empty.sy;
     data->header.last = empty.l;
     data->header.cradd = empty.ca;
     data->header.trigger = empty.trigger;
     data->footer.last = empty.l;
     data->footer.softparity = empty.parity;
     data->footer.hardparity = empty.parity;
     
     for (i=0; i<16; i++)
       {
	 data->slv[i].nrels = 0;
	 data->slv[i].els = NULL;
       }

    return ROCK_ERROR_OK;
   }
 else if (whatis!=ROCKF_IS_HEADER)
   { /* error, return element to cache and report error */
    rock_fifo_cache_return(fifo_id,rawels,1);
    ErrorSetF(ROCK_ERROR_FIFO_FRAME_HEADER,"rock_fifo_frame_read","Header expected.");
    return ROCK_ERROR_FIFO_FRAME_HEADER;
   }

 /* header data will be saved after the full frame read */

 /* Now I have the header, start reading slaves */
 prec_addr = 255;
 {
   int i;
   for (i=0; i<16; i++)
     nrslaves[i]=0;
 }
 {
   int i;

   for (i=1; i<ROCK_FIFO_FRAME_SIZE; i++)
     {
       ROCKF_SLAVE_bits slave;

       err = rock_fifo_raw_read(fifo_id,&(rawels[i]));

       /* exit if cannot read the FIFO */
       if (err!=ROCK_ERROR_OK)
	 { /* error, return elements to cache and report error */
	   rock_fifo_cache_return(fifo_id,rawels,i);
	   if (err!=ROCK_ERROR_FIFO_EMPTY)
	     ErrorSetF(err,"rock_fifo_frame_read","rock_fifo_raw_read error: %s",ErrorGetMessage());
	   return err;
	 }

       whatis= rockf_whatis(rawels[i]);
       if (whatis==ROCKF_IS_FOOTER)
	 break;    /* exit from loop and elaborate footer */
       else if (whatis!=ROCKF_IS_SLAVE)
	 { /* error, return elements to cache and report error */
	   rock_fifo_cache_return(fifo_id,rawels,i+1);
	   ErrorSetF(ROCK_ERROR_FIFO_FRAME_SLAVE,"rock_fifo_frame_read","A header before a footer found.");
	   return ROCK_ERROR_FIFO_FRAME_SLAVE;
	 }

       /* interpret the slave */

       rockf_nr2slave(rawels[i],slave);

       if (slave.slvadd>prec_addr)
	 { /* error, return elements to cache and report error */
	   rock_fifo_cache_return(fifo_id,rawels,i+1);
	   ErrorSetF(ROCK_ERROR_FIFO_FRAME_SLAVE,"rock_fifo_frame_read","Addresses not in decreasing order.");
	   return ROCK_ERROR_FIFO_FRAME_SLAVE;
	 }

       prec_addr = slave.slvadd;
       nrslaves[slave.slvadd]++;
       /* data will be saved after the full frame read */
     }
 
  if (i>=ROCK_FIFO_FRAME_SIZE)
    { /* error, return elements to cache and report error */
      rock_fifo_cache_return(fifo_id,rawels,ROCK_FIFO_FRAME_SIZE);
      ErrorSetF(ROCK_ERROR_FIFO_FRAME_OVERFLOW,"rock_fifo_frame_read","Frame too large.");
      return ROCK_ERROR_FIFO_FRAME_OVERFLOW;
    }

  nrels=i+1;
 }

 if (nrels==2)
   { /* a header followed by a footer not allowed */
     rock_fifo_cache_return(fifo_id,rawels,nrels);
     ErrorSetF(ROCK_ERROR_FIFO_FRAME_FOOTER,"rock_fifo_frame_read","Footer immediatly after header.");
     return ROCK_ERROR_FIFO_FRAME_FOOTER;
   }

 /* set the header */
 {
   ROCKF_HEADER_bits header;
   
   rockf_nr2header(rawels[0],header);
   data->header.sy = header.sy;
   data->header.last = header.l;
   data->header.cradd = header.ca;
   data->header.trigger = header.trigger;
   
   softparity = header.parity;
 }
 
 /* set the slaves */
 {
   int i;

   /* allocate space */
   for (i=0; i<16; i++)
     {
       data->slv[i].nrels = 0;
       data->slv[i].els = (ROCK_FIFO_SLV_data *) malloc(nrslaves[i]*sizeof(ROCK_FIFO_SLV_data));
       if (data->slv[i].els==NULL)
	 { /* malloc error */
	   ErrorSet(ROCK_ERROR_UNKNOWN,"rock_fifo_frame_read","Malloc returned NULL.");
	   return ROCK_ERROR_UNKNOWN;
	 }

     }
   
   /* save data */
   for (i=1; i<(nrels-1); i++)
     {
       ROCKF_SLAVE_bits slave;
       int a_nrels;
       ROCK_FIFO_SLV_data *a_el;  /* element to write in */

       rockf_nr2slave(rawels[i],slave);
       a_nrels = data->slv[slave.slvadd].nrels++;
       a_el = &(data->slv[slave.slvadd].els[a_nrels]);

       a_el->channel = slave.channel;
       a_el->reserved = slave.reserved;
       a_el->data = slave.data;

       softparity ^= slave.parity;
     }
 }

 /*Set footer */
 {
   ROCKF_FOOTER_bits footer;

   rockf_nr2footer(rawels[nrels-1],footer);
   data->footer.last = footer.l;
   data->footer.hardparity = footer.parity;
   data->footer.softparity = softparity;
 }

 if (data->footer.hardparity!=data->footer.softparity)
   { /* report error, but do return the frame */
     ErrorSetF(ROCK_ERROR_FIFO_FRAME_PARITY,"rock_fifo_frame_read","Parity error: hard %i soft %i.",
	       data->footer.hardparity,data->footer.softparity);
     return ROCK_ERROR_FIFO_FRAME_PARITY;
   }
 else
   return ROCK_ERROR_OK;
}

	/* parity error is not considered an error */
int rock_fifo_frame_blockread(ROCK_FIFO_id      fifo_id,/* IN : fifo id */
			      unsigned int      nrels,	/* IN : max number of raw elements */
			      ROCK_FIFO_FRAMEs *data)	/* OUT: FIFO data */
					    		/*      should be disposed by caller */ 
{
  int i;
  int err;

  if (nrels==0)
    { /* nothing to do */
      data->nrframes = 0;
      data->frames = NULL;
      return ROCK_ERROR_OK;
    }

  /* alloc space for the worst case */
  data->frames = (ROCK_FIFO_FRAME *) malloc(nrels*sizeof(ROCK_FIFO_FRAME));
  if (data->frames==NULL)
    { /* malloc error */
      ErrorSet(ROCK_ERROR_UNKNOWN,"rock_fifo_frame_blockread","Malloc returned NULL.");
      return ROCK_ERROR_UNKNOWN;
    }


  for (i=0; i<nrels; i++)
    {
      err = rock_fifo_frame_read(fifo_id,&(data->frames[i]));

      if ((err!=ROCK_ERROR_OK)&&(err!=ROCK_ERROR_FIFO_FRAME_PARITY))
	{
	  if (err==ROCK_ERROR_FIFO_EMPTY)
	    break;
	  else
	    { /* a serious error occured, exit */
	      ErrorSetF(err,"rock_fifo_frame_blockread","rock_fifo_frame_read error: %s",ErrorGetMessage());
	      data->nrframes=i;
	      return err;
	    }
	}
	 
      ErrorClean(); /* clean any old error */   
    }

 data->nrframes=i;
 if (data->nrframes<nrels)
   return ROCK_ERROR_FIFO_EMPTY;
 else
   return ROCK_ERROR_OK;
}

	/* block of data allocated each time */
#define ROCK_FIFO_FS_BLOCK		1024

	/* Synch the FIFO to the start of the frame */
int rock_fifo_frame_synch(ROCK_FIFO_id   fifo_id,	/* IN : fifo id */
			  ROCK_FIFO_RAW *data)		/* OUT: FIFO data */
					    		/*      should be disposed by caller */ 
{
  int err;
  int actblock;		/* actual size of the buffer */

  data->nrels = 0;
  actblock = ROCK_FIFO_FS_BLOCK;
  data->els = (unsigned int *) malloc(actblock*sizeof(unsigned int));
  if (data->els==NULL)
    { /* malloc error */
      ErrorSet(ROCK_ERROR_UNKNOWN,"rock_fifo_frame_synch","Malloc returned NULL.");
      return ROCK_ERROR_UNKNOWN;
    }

  /* read the first element without checking the type */
  err = rock_fifo_raw_read(fifo_id,&(data->els[data->nrels]));
  if (err!=ROCK_ERROR_OK)
    {
      if (err!=ROCK_ERROR_FIFO_EMPTY)
	{ /* serious error */
	  ErrorSetF(err,"rock_fifo_frame_synch","rock_fifo_raw_read error: %s",ErrorGetMessage());
	  return err;
	}
      else
	return ROCK_ERROR_FIFO_EMPTY;
    }
  data->nrels++;

  while (1)
    {
      int whatis;

      err = rock_fifo_raw_read(fifo_id,&(data->els[data->nrels]));
      if (err!=ROCK_ERROR_OK)
	{
	  if (err!=ROCK_ERROR_FIFO_EMPTY)
	    { /* serious error */
	      ErrorSetF(err,"rock_fifo_frame_synch","rock_fifo_raw_read error: %s",ErrorGetMessage());
	      return err;
	    }
	  else
	    return ROCK_ERROR_FIFO_EMPTY;
	}

      whatis = rockf_whatis(data->els[data->nrels]);
      if ((whatis==ROCKF_IS_HEADER)||(whatis==ROCKF_IS_EMPTY))
	{ /* found, return to cache and exit */
	  rock_fifo_cache_return(fifo_id,&(data->els[data->nrels]),1);
	  return ROCK_ERROR_OK;
	}

      /* not yet syncronised */
      data->nrels++;
      if (data->nrels>=actblock)
	{ /* must resize the buffer */
	  actblock += ROCK_FIFO_FS_BLOCK;
	  data->els = (unsigned int *) realloc(data->els,actblock*sizeof(unsigned int));
	  if (data->els==NULL)
	    { /* realloc error */
	      ErrorSet(ROCK_ERROR_UNKNOWN,"rock_fifo_frame_synch","Realloc returned NULL.");
	      return ROCK_ERROR_UNKNOWN;
	    }
	}
    }
}

/**********************************************************************/
/*                                                                    */
/*                          FIFO test routines                        */
/*                                                                    */
/**********************************************************************/

int rock_fifo_isnotempty(ROCK_FIFO_id fifo_id)	/* IN : fifo id */
{
 if (rock_fifo_cache_get_nrels(fifo_id)!=0)
   { /* if data is in cache, not empty */
     return ROCK_ERROR_OK;
   }

 return rock_fifo_fillcache(fifo_id,1);
}