/*************************************************************** 

   pdliisdisplay.c                                     

****************************************************************/

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#include "pdl.h"      /* Data structure declarations */
#include "pdlcore.h"  /* Core declarations */

#include "libiis.h"

/*
   iis_display()

   Subroutine to display a C array in an IIS device (e.g. SAOimage) 
   
   Based on iis_display() in libiis.c (v1.0) but hacked to loop over
   datatypes.
   
*/

int iis_display(void *f, int datatype, int nx, int ny, float fmin, float fmax, int frame) {

   unsigned short hdr[8];
   unsigned char *data;
   int j,nlines, x,y, offx, offy, nx2, ny2, baseX, baseY;
   int ntrans;
   float xx, yx, xy, yy, xo, yo; int w_type; /* WCS */
   char wcsbuf[SZ_WCSTEXT];
   int chan;

   chan = iis_chan(frame);

   /* Work out how many lines to transfer at a go */

   ntrans = BUFFSZ/frameX; 
   if (ntrans<1) 
      ntrans = 1;

   /* Send WCS info */

   hdr[TRANSFER_ID] = IWRITE | PACKED;
   hdr[THING_COUNT] = -SZ_WCSTEXT;
   hdr[SUB_UNIT]   = WCS;
   hdr[CHECK_SUM]  = 0;
   hdr[X_REGISTER] = 0;
   hdr[Y_REGISTER] = 0;
   hdr[Z_REGISTER] = chan;
   hdr[T_REGISTER] = fbconfig-1; /* fbconfig number  */
   iis_checksum(hdr);
   iis_write((char*)hdr, 8*sizeof(short)); 

   offx = 0; offy = 0;    /* Centre image in frame if small */
   if (nx<frameX)
      offx = (frameX-nx)/2;
   if (ny<frameY)
      offy = (frameY-ny)/2;

   nx2 = nx; ny2 = ny;  baseX=0; baseY=0; /* Truncate image if too big */
   if (nx>frameX) {
      nx2=frameX; baseX = (nx-frameX)/2;
   }
   if (ny>frameY) {
      ny2=frameY; baseY = (ny-frameY)/2;
   }

   /* Note my WCS is zero offset! */

   xx=1; yx=0; xy=0; yy=-1; xo=baseX-offx; yo=baseY+frameY-offy-1; w_type=1;

   sprintf (wcsbuf, "%s\n%f %f %f %f %f %f %f %f %d","perlDL rules!", 
            xx, yx, xy, yy, xo, yo, fmin, fmax, w_type);
   iis_write((char*)wcsbuf, SZ_WCSTEXT*sizeof(char)); 
   
   /* Reset the frame buffer */

   hdr[TRANSFER_ID] = fbconfig-1; /* fbconfig number  */ 
   hdr[THING_COUNT] = 0;
   hdr[SUB_UNIT]   = FEEDBACK;
   hdr[CHECK_SUM]  = 0;
   hdr[X_REGISTER] = 0;
   hdr[Y_REGISTER] = 0;
   hdr[Z_REGISTER] = chan;
   hdr[T_REGISTER] = 0;
   iis_checksum(hdr);
   iis_write((char*)hdr, 8*sizeof(short));

   /* Allocate buffer for data transfers */

   data = (unsigned char*) calloc(ntrans*frameX, sizeof(unsigned char)); 
   if (data==NULL)
      iis_error("iis_display: out of memory for buffer","");

   GENERICLOOP(datatype)

        generic *ff = (generic *) f;
        generic *val, sval;
        
        for (y = 0; y < ny2; y+=ntrans) {
     
           nlines = ntrans;  /* Number of lines to transfer */
           if (y+ntrans>ny2)
              nlines = ny2 - y;
     
           /* create header */
           hdr[TRANSFER_ID] = IWRITE | PACKED | BLOCKXFER;
           hdr[THING_COUNT] = -nlines*frameX;
           hdr[SUB_UNIT] = REFRESH;
           hdr[CHECK_SUM] = 0;
           hdr[X_REGISTER] = ADVXONTC;
           hdr[Y_REGISTER] = ADVYONXOV+frameY-y-nlines-offy;
           hdr[Z_REGISTER] = chan;
           hdr[T_REGISTER] = ALLBITPL;
           iis_checksum(hdr);
           iis_write((char*)hdr, 8*sizeof(short));
     
           for (j=0; j<nlines; j++) {
               val = ff+nx*(baseY+y+nlines-j-1); /* Start of data */
               for (x = 0; x < nx2; x++) {
                  sval = FMIN+(val[x+baseX]-fmin)*(FMAX-FMIN)/(fmax-fmin);
                  if (sval<FMIN)
                     sval = FMIN;
                  if (sval>FMAX)
                     sval = FMAX;
                  data[j*frameX+x+offx] = (unsigned char) sval;
               }
           }
           iis_write((char*)data, nlines*frameX*sizeof(char) );
        }
   
    ENDGENERICLOOP

   free(data);
}

/* Redefine iis_error to use Perl's croak() */

void iis_error( char* error1, char*error2 ) {
      croak (error1,error2);
}


/*****************************************************/


/* Rest of the subroutines are identical to libiis.c v1.0 */


/******************* iis_cur ************************/

/* Return cursor position and character typed */

void iis_cur(float*x, float*y, char* ch) {

   unsigned short hdr[8];
   short buf[SZ_WCSTEXT];
   int   nbytes,wcs;

   /* Send read request */

   hdr[TRANSFER_ID] = IREAD;
   hdr[THING_COUNT] = 0;
   hdr[SUB_UNIT]   = IMCURSOR;
   hdr[CHECK_SUM]  = 0;
   hdr[X_REGISTER] = 0;
   hdr[Y_REGISTER] = 0;
   hdr[Z_REGISTER] = 0;
   hdr[T_REGISTER] = 0;
   iis_checksum(hdr);
   iis_write((char*)hdr, 8*sizeof(short)); 

   /* Read however many bytes it send in this case */

   if ((nbytes = read (iispipe_i, buf, SZ_WCSTEXT)) <= 0)
      iis_error ("iis_cur: cannot read IIS pipe\n","");

   if (sscanf ((char*)buf, "%f %f %d %c", x, y, &wcs, ch) != 4) 
      iis_error ("iis_cur: can't parse '%s'\n", (char*)buf);
}


/******************* iis_drawcirc *******************/

/* Draw a circle on the image display at a given position */

void iis_drawcirc(float xcen, float ycen, float radius, int colour, int frame) {

   unsigned short hdr[8];
   unsigned char *data;
   int i,j,y;
   int ymin,ymax,ntrans,nlines,nbytes;
   float xcen2,ycen2,dd;
   char wcsbuf[SZ_WCSTEXT];
   float xx, yx, xy, yy, xo, yo;	/* wcs matrix values */
   float xx2, yx2, xy2, yy2, xo2, yo2;	/* wcs inverse matrix values */
   char label[1024];		        /* wcs file title */
   int  w_type;			        /* wcs scaling code */
   float low, high;	                /* wcs scaling limits */
   int chan;
   float rr;

   chan = iis_chan(frame);


   /* Send WCS read request */

   hdr[TRANSFER_ID] = -IREAD; 
   hdr[THING_COUNT] = 0;
   hdr[SUB_UNIT]   = WCS;
   hdr[CHECK_SUM]  = 0;
   hdr[X_REGISTER] = 0;
   hdr[Y_REGISTER] = 0;
   hdr[Z_REGISTER] = chan;
   hdr[T_REGISTER] = 0;
   iis_checksum(hdr);
   iis_write((char*)hdr, 8*sizeof(short));

   iis_read ((char*)wcsbuf, SZ_WCSTEXT); /* Get WCS data */

   sscanf(wcsbuf, "%[^\n]\n%f%f%f%f%f%f%f%f%d", label,
         &xx, &yx, &xy, &yy, &xo, &yo, &low, &high, &w_type);
   
   /* Invert transform (I don't care about non-square coord systems! */

   xcen2 = (xcen-xo)/xx;
   ycen2 = frameY - (ycen-yo)/yy - 1;

   /* Correct scale factor - OK for square images don't want to
      draw ellipses for non-square ones so take geometric mean  */

   rr =  radius / sqrt(iis_abs(xx*yy)); 

   /* Transfer limits (with buffer to allow for edge effects) */
   
   ymin = ycen2-rr-2; 
   if (ymin<0) 
      ymin=0;
   ymax = ycen2+rr+2; 
   if (ymax>=frameY) 
      ymax=frameY-1;
   
   /* Work out how many lines to transfer at a go */
   
   ntrans = RBUFFSZ/frameX;
   if (ntrans<1) 
      ntrans = 1;
 
   /* Allocate buffer for data transfers */

   data = (unsigned char*) calloc(ntrans*frameX, sizeof(unsigned char));
   if (data==NULL)
      iis_error("iis_drawcirc: out of memory for buffer","");

   /* Loop over blocks */

   for (y = ymin; y < ymax; y+=ntrans) {

      nlines = ntrans;  /* Number of lines to transfer */
      if (y+ntrans>ymax)
         nlines = ymax - y;

      /* Read data */

      hdr[TRANSFER_ID] = -IREAD | PACKED | BLOCKXFER;
      hdr[THING_COUNT] = -nlines*frameX;
      hdr[SUB_UNIT] = REFRESH;
      hdr[CHECK_SUM] = 0;
      hdr[X_REGISTER] = ADVXONTC;
      hdr[Y_REGISTER] = ADVYONXOV+frameY-y-nlines;
      hdr[Z_REGISTER] = chan;
      hdr[T_REGISTER] = ALLBITPL;
      iis_checksum(hdr);
      iis_write((char*)hdr, 8*sizeof(short));
      iis_read((char*)data, nlines*frameX*sizeof(char));
   
      /* Write data */

      hdr[TRANSFER_ID] = IWRITE | PACKED | BLOCKXFER;
      hdr[THING_COUNT] = -nlines*frameX;
      hdr[SUB_UNIT] = REFRESH;
      hdr[CHECK_SUM] = 0;
      hdr[X_REGISTER] = ADVXONTC;
      hdr[Y_REGISTER] = ADVYONXOV+frameY-y-nlines;
      hdr[Z_REGISTER] = chan;
      hdr[T_REGISTER] = ALLBITPL;
      iis_checksum(hdr);
      iis_write((char*)hdr, 8*sizeof(short));
   
      /* Change Data  - draw in i and j to fill circle gaps via symmetry */

      for (j=0; j<nlines; j++) {       
          dd = rr*rr - (y+j-ycen2)*(y+j-ycen2);
          if (dd>=0) {
             dd = sqrt(dd);
             i = iis_round( (float)xcen2 - dd  );
             if (i>=0 && i<frameX) 
                data[ (nlines-j-1)*frameX + i ] = colour;
             i = iis_round( (float)xcen2 + dd );
             if (i>=0 && i<frameX) 
                data[ (nlines-j-1)*frameX + i ] = colour;
          }
      }   

      for (i=0; i<frameX; i++) {       
          dd = rr*rr - (i-xcen2)*(i-xcen2);
          if (dd>=0) {
             dd = sqrt(dd);
             j = iis_round( (float)ycen2 - (float)y - dd ); 
             if (j>=0 && j<nlines)
                data[ (nlines-j-1)*frameX + i ] = colour;
             j = iis_round( (float)ycen2 - (float)y + dd );
             if (j>=0 && j<nlines) 
                data[ (nlines-j-1)*frameX + i ] = colour; 
          }
      }

      iis_write((char*)data, nlines*frameX*sizeof(char));

   }
   free(data);
}


/******************* iis_open ****************/

/*
   Open IIS connection - if inpipe or outpipe are "" default
   pipes are searched for in the environment variable $IMTDEV, 
   then in the directories (with the usual filenames) $HOME/iraf/dev, 
   $HOME/dev, and finally /dev.

   Note the frame buffer configuration number and dimensions
   must be suppled by hand - life is too short to write 
   imtoolrc parsing code in C!  If these don't match those in the
   appropriate imtoolrc file problems will occur.

*/

void iis_open(char* inpipe, char* outpipe, int fb, int fbx, int fby) {
    
   FILE *syspipe;
   char *home, *imtdev, *tok=NULL;
   char	iname[STRSIZE],oname[STRSIZE];
   int  i,j;

   home    = getenv("HOME"); 

   imtdev  = getenv("IMTDEV");
   if (imtdev != NULL)  /* Start parsing IMTDEV environment variable */
       tok = strtok(imtdev,":");
   if (tok!=NULL && strcmp(tok,"fifo")!=0) /* Ignore if not fifo */
      tok = NULL;
    
   /* Get input fifo name */

   if (strcmp(inpipe,"")==0) { 

      if (tok!=NULL) {  /* Check next bit of IMTDEV */
         tok = strtok(NULL,":");
         if (tok != NULL) {
            strncpy(iname,tok,STRSIZE);
            goto gotin;
         }
      }

      /* Else look in standard places */

      strncpy(iname,home,STRSIZE); strncat(iname,"/iraf/dev/imt1i",STRSIZE);
      if (!access(iname,F_OK))
         goto gotin;
      strncpy(iname,home,STRSIZE); strncat(iname,"/dev/imt1i",STRSIZE);
      if (!access(iname,F_OK))
         goto gotin;
      strncpy(iname,"/dev/imt1i",STRSIZE);
      if (!access(iname,F_OK))
         goto gotin;
   }
   else {
      strncpy(iname,inpipe,STRSIZE); /* Use supplied arg */
      goto gotin;
   }
   iis_error("Unable to locate input FIFO in any of $HOME/dev/imt1i or %s",
      "$HOME/dev/imt1i or /dev/imt1i\n");

   gotin:

   if (strcmp(outpipe,"")==0) { /* Get output fifo name */

      if (tok!=NULL) {  /* Check next bit of IMTDEV */
         tok = strtok(NULL,":");
         if (tok != NULL) {
            strncpy(oname,tok,STRSIZE);
            goto gotout;
         }
      }
      /* Else look in standard places */

      strncpy(oname,home,STRSIZE); strncat(oname,"/iraf/dev/imt1o",STRSIZE);
      if (!access(oname,F_OK))
         goto gotout;
      strncpy(oname,home,STRSIZE); strncat(oname,"/dev/imt1o",STRSIZE);
      if (!access(oname,F_OK))
         goto gotout;
      strncpy(oname,"/dev/imt1o",STRSIZE);
      if (!access(oname,F_OK))
         goto gotout;
   }
   else {
      strncpy(oname,outpipe,STRSIZE); /* Use supplied arg */
         goto gotout;
   }

   iis_error("Unable to locate output FIFO in any of $HOME/iraf/dev/imt1o or %s",
      "$HOME/dev/imt1o or /dev/imt1o\n");

   gotout:

   /*
      Open the output fifo.  We have to open it ourselves first as a client to
      get around the fifo open-no-client error.
   */


    if ((iispipe_i = open (oname, O_RDONLY | O_NONBLOCK)) != -1) {
	if ((iispipe_o = open (oname, O_WRONLY | O_NONBLOCK)) != -1) {
	    fcntl (iispipe_o, F_SETFL, O_WRONLY);
	} else
            iis_error("iis_open: cannot open IIS output pipe %s\n",oname);
	close (iispipe_i);
    } else 
       iis_error("iis_open: cannot open IIS output pipe %s\n",oname);

   /* Open the input fifo */

   if ((iispipe_i = open (iname, O_RDONLY | O_NONBLOCK)) != -1) {

      /* Clear input for reading. */
      fcntl (iispipe_i, F_SETFL, O_RDONLY);
   } else 
      iis_error("iis_open: cannot open IIS input pipe %s\n",iname);

   fbconfig = fb; frameX = fbx; frameY = fby; /* Frame buffer globals */
}

/******************* iis_close ****************/

/* Close the IIS connection */

void iis_close() {
  close(iispipe_o); 
  close(iispipe_i);
}  

/******************* Private routines ****************/

/* write to pipe */

void iis_write (char* buf, int size) {
    int n = 0;
    int total = 0;

    while (total < size) {
	n = write (iispipe_o, buf, size - total);
	if (n <= 0) 
            iis_error ("iis_write: can't write to pipe\n","");
	total += n;
    }
}

/* read from pipe */

void iis_read (char* buf, int size) {
    int n = 0;
    int total = 0;

    while (total < size) {
	n = read (iispipe_i, buf, size - total);
	if (n <= 0) 
            iis_error ("iis_read: can't read from pipe\n","");
	total += n;
    }
}

void iis_checksum ( unsigned short *hdr ) {
      int indx;
      int checksum = 0;
      for (indx = 0; indx < 8; indx++) {
         checksum += hdr[indx];
      }
      hdr[CHECK_SUM] = CHECKSUMVAL - (unsigned short) checksum;
}

/* Return the channel number associated with a display frame */

int iis_chan(int frame) { 
   int chan[5];

   chan[1]=CHAN1; chan[2]=CHAN2; chan[3]=CHAN3; chan[4]=CHAN4;
   if (frame>0 && frame<5) 
      return chan[frame];
   else
      iis_error("iis_display: invalid frame number, must be 1-4\n","");
}

/* Round to nearest int symmetrically about zero */

int iis_round ( float i ) {  
    if (i>=0) 
       return (int) (i+0.5);
    else
       return -( (int)(0.5-i) );
}

float iis_abs(float x) {
   if (x<0) 
      return (-x);
   else
      return x;
}