/*
 * popenr.c
 *
 * Attempt to provide popen for matlab, to allow reading from a process.
 * 
 * 2004-09-28 dpwe@ee.columbia.edu  after PlayOn
 * 
 * ToDo:
    - handle multidimensional reads i.e. 2nd arg = [2 1000]
    - at EOF, return matrix sized according to what was actually read
 *
 * $Header: /homes/drspeech/src/matlabpopen/RCS/popenr.c,v 1.2 2007/01/14 04:03:48 dpwe Exp dpwe $
 */
 
#include    <stdio.h>
#include    <math.h>
#include    <ctype.h>

#include    "mex.h"

#ifndef BIG_ENDIAN
#define BIG_ENDIAN 4321
#endif

#ifndef LITTLE_ENDIAN
#define LITTLE_ENDIAN 1234
#endif

#ifndef BYTE_ORDER
#ifdef  __DARWIN_UNIX03
#define BYTE_ORDER LITTLE_ENDIAN
#else
#define BYTE_ORDER BIG_ENDIAN
#endif
#endif

/* check if flags are working
#if BYTE_ORDER == BIG_ENDIAN
#error "byte order big endian"
#endif

#if BYTE_ORDER == LITTLE_ENDIAN
#error "byte order little endian"
#endif
*/


enum {
    MXPO_INT16BE,
    MXPO_INT16LE,
    MXPO_INT16N,
    MXPO_INT16R,
    MXPO_FLOAT,
    MXPO_DOUBLE,
    MXPO_UINT8,
    MXPO_CHAR,
};

#define FILETABSZ 16
static FILE *filetab[FILETABSZ];
static int filetabix = 0;

int findfreetab() {
    /* find an open slot in the file table */
    int i;
    for (i = 0; i < filetabix; ++i) {
	if ( filetab[i] == NULL ) {
	    /* NULL entries are currently unused */
	    return i;
	}
    }
    if (filetabix < FILETABSZ) {
	i = filetabix;
	/* initialize it */
	filetab[i] = NULL;
	++filetabix;
	return i;
    }
    /* out of space */
    return -1;
}

void
mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    int 	i, err, len;
    long   	pvl, pvb[16];


    if (nrhs < 1){
	mexPrintf("popenr     Y=popenr(X[,N[,F]])  Open and read an external process\n");
	mexPrintf("           When X is a string, that string is executed as a new process\n");
	mexPrintf("           and Y is returned as a handle (integer) to that stream.\n");
	mexPrintf("           Subsequent calls to popenr(Y,N) with that handle return\n");
	mexPrintf("           the next N values read from the standard ouptut of\n");
	mexPrintf("           converted to Matlab values according to the format\n");
	mexPrintf("           string F (default: char).  A call with N set to -1\n");
	mexPrintf("           means to close the stream.\n");
	return;
    }
    /* look at the data */
    /* Check to be sure input argument is a string. */
    if ((mxIsChar(prhs[0]))){
	/* first argument is string - opening a new command */
	FILE *f;
	char *cmd;
	int tabix = findfreetab();
	
	if (tabix < 0) {
	    mexErrMsgTxt("Out of file table slots.");
	} else {
	    cmd = mxArrayToString(prhs[0]);
	    /* fprintf(stderr, "cmd=%s\n", cmd); */
	    f = popen(cmd, "r");
	    mxFree(cmd);
	    if ( f == NULL ) {
		mexErrMsgTxt("Error running external command.");
		return;
	    }
	    /* else have a new command path - save the handle */
	    filetab[tabix] = f;
	    /* return the index */
	    if (nlhs > 0) {
		double *pd;
		mxArray *rslt = mxCreateDoubleMatrix(1,1, mxREAL);
		plhs[0] = rslt;
		pd = mxGetPr(rslt);
		*pd = (double)tabix;
	    }
	}
	return;
    }	

    if (nrhs < 2) {
	mexErrMsgTxt("apparently accessing handle, but no N argument");
	return;
    }
    
    /* get the handle */
    {
	int ix, rows, cols, npts, ngot; 
	int fmt = MXPO_INT16N;
	int sz = 2;
	FILE *f = NULL;
	double *pd;
	mxArray *rslt;

	if (mxGetN(prhs[0]) == 0) {
	    mexErrMsgTxt("handle argument is empty");
	    return;
	}

	pd = mxGetPr(prhs[0]);
	ix = (int)*pd;
	if (ix < filetabix) {
	    f = filetab[ix];
	}
	if (f == NULL) {
	    mexErrMsgTxt("invalid handle");
	    return;
	}
	/* how many items required? */
	if (mxGetN(prhs[1]) == 0) {
	    mexErrMsgTxt("length argument is empty");
	    return;
	} else if (mxGetN(prhs[1]) == 1) {
	    rows = (int)*mxGetPr(prhs[1]);
	    cols = 1;
	} else {
	    double *pd = mxGetPr(prhs[1]);
	    rows = (int)pd[0];
	    cols = (int)pd[1];
	}
	
	/* maybe close */
	if (rows < 0) {
	    pclose(f);
	    filetab[ix] = NULL;
	    return;
	}

	/* what is the format? */
	if ( nrhs > 2 ) {
	    char *fmtstr;

	    if (!mxIsChar(prhs[2])) {
		mexErrMsgTxt("format arg must be a string");
		return;
	    }
	    fmtstr = mxArrayToString(prhs[2]);
	    if (strcmp(fmtstr, "int16n")==0 || strcmp(fmtstr, "int16") == 0) {
		fmt = MXPO_INT16N;
	    } else if (strcmp(fmtstr, "int16r")==0) {
		fmt = MXPO_INT16R;
	    } else if (strcmp(fmtstr, "int16be")==0) {
#if BYTE_ORDER == BIG_ENDIAN
		fmt = MXPO_INT16N;
#else
		fmt = MXPO_INT16R;
#endif
	    } else if (strcmp(fmtstr, "int16le")==0) {
#if BYTE_ORDER == BIG_ENDIAN
		fmt = MXPO_INT16R;
#else
		fmt = MXPO_INT16N;
#endif
	    } else if (strcmp(fmtstr, "float")==0) {
		fmt = MXPO_FLOAT;
		sz = 4;
	    } else if (strcmp(fmtstr, "double")==0) {
		fmt = MXPO_DOUBLE;
		sz = 8;
	    } else if (strcmp(fmtstr, "uint8")==0) {
		fmt = MXPO_UINT8;
		sz = 1;
	    } else if (strcmp(fmtstr, "char")==0) {
		fmt = MXPO_CHAR;
		sz = 1;
	    } else {
		mexErrMsgTxt("unrecognized format");
	    }
	}

	/* do the read */
	rslt = mxCreateDoubleMatrix(rows, cols, mxREAL);
	npts = rows*cols;
	ngot = fread(mxGetPr(rslt), sz, npts, f);
	
	/* format conversion */
	if (sz == 4) {
	    int i;
	    double *pd = mxGetPr(rslt);
	    float *pf = (float *)pd;
	    pd = pd + npts - 1;
	    pf = pf + npts - 1;
	    for(i = npts-1; i >= 0; --i) {
		*pd-- = (double)*pf--;
	    }
	}
	if (sz == 1) {
	    int i;
	    double *pd = mxGetPr(rslt);
	    char *pc = (char *)pd;
	    pd = pd + npts - 1;
	    pc = pc + npts - 1;
	    for(i = npts-1; i >= 0; --i) {
		*pd-- = (double)*pc--;
	    }
	}
	if (sz == 2) {
	    int i;
	    double *pd = mxGetPr(rslt);
	    short *ps = (short *)pd;
	    pd = pd + npts - 1;
	    ps = ps + npts - 1;
	    if (fmt == MXPO_INT16N) {
		for(i = npts-1; i >= 0; --i) {
		    *pd-- = (double)*ps--;
		}
	    } else { /* MXPO_INT16R */
		short v;
		for(i = npts-1; i >= 0; --i) {
		    v = (short)*pd--;
		    *ps -- = (0xFF & (v >> 8)) + (0xFF00 & (v << 8));
		}
	    }
	}
	
	/* did we get all the points we asked for */
	if (ngot < npts) {
	    /* allocate a smaller array and copy to that */
	    /* but only chop down by whole columns */
	    int gotcols,gotrows;
	    int i;
	    double *pd, *ps;
	    mxArray *newarr;
	    if (cols == 1) {
		gotrows = ngot;
		gotcols = 1;
	    } else {
		gotrows = rows;
		gotcols = (ngot+rows-1)/rows;
	    }
	    newarr = mxCreateDoubleMatrix(gotrows, gotcols, mxREAL);
	    pd = mxGetPr(newarr);
	    ps = mxGetPr(rslt);
	    for (i = 0; i < ngot; ++i) {
		*pd++ = *ps++;
	    }
	    for (i = ngot; i < gotrows*gotcols; ++i) {
		*pd++ = 0;
	    }
	    mxDestroyArray(rslt);
	    rslt = newarr;
	}

	if (nlhs > 0) {
	    plhs[0] = rslt;
	} else {
	    mxDestroyArray(rslt);
	}
	return;
    }
}
