static char *ID = {"By:Kevin Peckover -- July 1995 Ottawa, Canada."};

#import <stdio.h>
#import <strings.h>
#import <bsd/libc.h>
#import <sys/types.h>
#import <servers/netname.h>

#import <mach/message.h>
#import <mach/mach_init.h>
#import <mach/mach_interface.h>
#import <mach/mach_error.h>
#import <mach/cthreads.h>

#import <dpsclient/dpsNeXT.h>
#import <dpsclient/dpsclient.h>
#import <windowserver/printmessage.h>

#import <appkit/NXBitmapImageRep.h>
#import <appkit/Application.h>
#import <appkit/tiff.h>


#define TIFF_SERVER_NAME "tiff_fd"

#define HEADDER_SIZE 			1024
#define MAX_DEVICE_NAME_LEN 		256
#define MAX_FILE_PATH 			256
#define TIFFSERVER_NAME 		"TIFFServer"
#define DEF_DEVICE_NAME 		"NeXTLaser-400"
#define DEF_DEVICE			""
#define TIME_TO_WAIT_FOR_ACTIVE_PORT	5  // seconds
#define TIME_OUTOF_RENDEREDPAGE_FROM_WINDOWSERVER  20  // seconds

#define NO_MORE_PAGES	666

#define ASCII_ESC	0x1b
#define ASCII_FF	0x0c

#define HPPCL_BITCOMPMODE_NONE		0
#define HPPCL_BITCOMPMODE_RLENENC	1
#define HPPCL_BITCOMPMODE_TIFFV4	2
#define HPPCL_BITCOMPMODE_DELROW	3

typedef struct ReplyMsg {
    msg_header_t	h;
    msg_type_t		t;
} replyMsg;

typedef struct _ChildParam {
    char *basename;
    int compression; 
    int res;
} ChildParam;


enum CompressType { none = 0, runLenEnc, TIFF_v40, DeltaRow };

// Global variables
char 	outName[MAX_DEVICE_NAME_LEN] ={DEF_DEVICE} ;

unsigned char* rleCompressForPCL(unsigned char* origData, 
    unsigned char* newData, int *length) 
{
  // Initialize for begining of line
  int origDataLength = *length,
      indToNewData = 0,
      indToOldData = 0;
  unsigned char baseByte = *origData;
  
  if (*length < 2) return origData; // essentally a NULL line case
  do {
    unsigned char byteCount = 0;
    while (baseByte == origData[1 + indToOldData + byteCount] 
	&& byteCount < 255 
	&& (1 + indToOldData + byteCount) < origDataLength ) 
      byteCount++;
    newData[indToNewData++] = (unsigned char)byteCount;
    newData[indToNewData++] = (unsigned char)baseByte;
    if ( (1+indToOldData + byteCount) >= origDataLength ) {
      // Were done! Set length and get out.
	*length = indToNewData+1;  // adding one makes it a count
	return newData;
    };
    indToOldData += byteCount; 
    baseByte = origData[indToOldData];
  } while (indToNewData < (origDataLength-1)); // will dropp out of loop if longer
  return origData; // don't use the compressed version, length did not change
}

void writeHPRast(FILE *stream, char *data, int pWidth, int pixelsHigh,
    int bytesWide, int res) {
    int i, bytesToSend;
    unsigned char *lineOfData = malloc(sizeof(unsigned char)*bytesWide),
       *lineToSend = 0;

#define COMPRESS_TYPE 	'1' /* run length encoding */
    fprintf(stream,"%c%s%c%s%d%s%c%s%c%s%d%s%c%s%d%s%c%s", 
	   ASCII_ESC,"*p0x0Y",	/* set postion */
	   ASCII_ESC,"*t", res, "R", /* set resoulution { 75,100,150,300 } */
	   ASCII_ESC,"*r0F",	/* write raster on logical page */
	   ASCII_ESC,"*r", pWidth,"S",	/* width */
	   ASCII_ESC,"*r", pixelsHigh,"T",	/* height */
	   ASCII_ESC,"*r1A"	/* start graphics mode */
    );
    for(i=0;i<pixelsHigh;i++) {
        bytesToSend = bytesWide;
	lineToSend = rleCompressForPCL(&data[bytesWide*i],lineOfData,
		&bytesToSend);
	fprintf(stream,"%c*b%cm%dW", ASCII_ESC, 
            ((unsigned char *)lineToSend == (unsigned char *)
		&data[bytesWide*i])?'0':COMPRESS_TYPE,
            bytesToSend);
	fwrite(lineToSend, 1, bytesToSend, stream);
    }
    fprintf(stream,"%c%s%c", 
	ASCII_ESC,"*rB", 
	ASCII_FF);	// stop graphics mode
    free(lineOfData);
} 


port_t	tiff_look_up()
// This function checks for an advertized Mach Port.
{
    port_t		port;
    kern_return_t 	ret;    
	
// Use "" instead of "*", so we look only on this machine.
    ret = netname_look_up(name_server_port, "", TIFF_SERVER_NAME, &port);
    if (ret != KERN_SUCCESS) return PORT_NULL;
    return port;
}



void server_loop(port_t port, char *baseFileName, int compression, int res)
{
    NXPrintPageMessage	 	msg;
    replyMsg	 		reply;
    kern_return_t 		ret;
    FILE			*out = 0;
    BOOL			getNextPage = TRUE;
    char			fname[MAX_FILE_PATH];
    int                         pagesPrinted=0;

// The reply message which does not change
    reply.h.msg_simple = TRUE;
    reply.h.msg_size = sizeof(replyMsg);
    reply.h.msg_type = MSG_TYPE_NORMAL ;
    reply.h.msg_id = NX_PRINTPAGEMSGID;
    reply.t.msg_type_name = MSG_TYPE_INTEGER_32 ;
    reply.t.msg_type_size = 32;
    reply.t.msg_type_number = 0;
    reply.t.msg_type_inline = TRUE;
    reply.t.msg_type_longform = FALSE;

    do { 
        msg.msgHeader.msg_local_port = port;
        msg.msgHeader.msg_size = sizeof(NXPrintPageMessage);
        ret = msg_receive(&msg.msgHeader
		, RCV_TIMEOUT, TIME_OUTOF_RENDEREDPAGE_FROM_WINDOWSERVER*1000);
	switch(ret) {
	case RCV_SUCCESS:
	    switch(msg.msgHeader.msg_id) {
	    case NO_MORE_PAGES:
	      getNextPage = NO;		
	      break;
	    case NX_PRINTPAGEMSGID:
		if(*fname) {
		    sprintf(fname,"%s", baseFileName);
		    out = fopen(fname,"wb");
		} else out=stdout;
		if(out) 
		{
		    writeHPRast(out, msg.printerData, msg.pixelsWide,
			    msg.pixelsHigh, msg.bytesPerRow, res);
		    if(*fname) fclose(out);
		} else fprintf(stderr,"** Could Not open file %s\n", fname);        
		vm_deallocate(task_self(),
		    (vm_address_t)msg.printerData,
		    (vm_size_t)(msg.samplesPerPixel*msg.pixelsWide*
			    msg.pixelsHigh*msg.bitsPerSample/8));
	// Send the reply to Window Server that we are ready for next page
		reply.h.msg_local_port = port ; //send to
		reply.h.msg_remote_port = msg.msgHeader.msg_remote_port; 
		reply.h.msg_id = NX_PRINTPAGEMSGID;
		ret = msg_send(&reply.h, MSG_OPTION_NONE, 0);
		switch(ret) {
		case SEND_SUCCESS: // Continue in loop for next page
		    pagesPrinted++;
		    break;
		case SEND_INVALID_PORT: // There is Probably no more pages
		    getNextPage = FALSE;
		    break;
		default:
		    mach_error("TIFFServer:** SEND to Window Server.\n", ret); 
		};
		break;
	    }
	    break;
	case RCV_TIMED_OUT:
	    getNextPage = FALSE;
	    return ; // exit receive loop completely with no error message
	default:
	    mach_error("TIFFServer:** RECV to TIFF routine.\n", ret);
	};
    } while (getNextPage) ;
    if(!*fname) fclose(out);
}

int TIFFReceptorMain(ChildParam *param) {
    port_t 		server_port;
    kern_return_t 	r;
    
    if(!strstr(ID, "Peckover")) exit(1);
    r = port_allocate(task_self(), &server_port); 
    if (r != KERN_SUCCESS) {
        mach_error("port_allocate failed",r);
	return 0;
    }
    r = netname_check_in(name_server_port, TIFF_SERVER_NAME, 
        PORT_NULL, server_port);
    if (r != KERN_SUCCESS) {
        mach_error("netname_check_in failed ",r);
	return 0;
    }   
    server_loop(server_port, param->basename, param->compression, param->res);
    return 1;
}
  
 
extern void DPSMyTextProc(
  DPSContext ctxt,
  const char *buf,
  long unsigned int count )
{
    fprintf(stderr,"Context:\t%s\n", ctxt->priv);
    fprintf(stderr,"Message:\t%s\n", buf);
};

extern void DPSMyErrorProc(
  DPSContext ctxt,
  DPSErrorCode errorCode,
  long unsigned int arg1,
  long unsigned int arg2 )
{
    char *string = (char *)arg1;
    string[arg2] = 0;
    fprintf(stderr,"PS_ERROR: Context:\t%s\t", ctxt->priv);
    fprintf(stderr,"ErrCode:\t%d\t", errorCode);
    fprintf(stderr,"A1:\t%s\n", string);
};

void usage(char *progName) {
    fprintf(stderr,"Usage: %s [Prf] <infn> <outfn>\n %s %s %s\n",
        progName,
	"[-r<0|1|2|3>]\t res. 0-75dpi, 1-100dpi, 2-150dpi, 3-300dpi\n",
	"[-f<outfn>]\t write each page to file(s)\n",
	"[<infn>]\t\t name of PostScript file to convert\n");
    exit(1);
};

void main(int argc, char *argv[])
{
    port_t	server;
    DPSContext	toWindowServer;
    time_t	checkInTime, currTime;
    char	line[BUFSIZ], c, 
		device_name[MAX_DEVICE_NAME_LEN] = {DEF_DEVICE_NAME},
		server_name[MAX_DEVICE_NAME_LEN] = {TIFF_SERVER_NAME};
    FILE	*psin = 0;
    int 	width = 0, height = 0, topMargin = 0,
		leftMargin = 0, vFaxDpi = 75, hFaxDpi ,
		compType = NX_TIFF_COMPRESSION_CCITTFAX3;

    cthread_t 	proc=0; 			
    ChildParam	param;	
    NXPrintPageMessage send;		
    kern_return_t 	ret;
    port_t	parentPort;
    
    while ((c = getopt(argc, argv, "r:f:?")) != -1)
	switch (c) {
	case 'r':	
	    switch(atoi(optarg)) {
	    case 1:
		vFaxDpi = 100;
		break;
	    case 2:
		vFaxDpi = 150;
		break;
	    case 3:
		vFaxDpi = 300;
		break;
	    default:
		vFaxDpi = 75;
	    }
	    break;
	case 'f':
	    strncpy(outName, optarg, MAX_DEVICE_NAME_LEN);
	    break;
	case '?':
	    usage(argv[0]);
	};

    width = (int)(8.5*vFaxDpi);
    height = (int)(11*vFaxDpi);
    hFaxDpi = vFaxDpi;
    	
    switch(argc - optind){
    case 0:
    	psin = stdin ;
	break;
    case 1:
	if (!(psin = fopen(argv[argc-1],"r"))) {
	    fprintf(stderr,"** Could not open \'%s\'.\n", argv[argc-1]);
	    exit(1);
	};
	break;
    default:
	usage(argv[0]);
    }
    
//	Run the child here.
    param.basename = outName;
    param.compression = compType;
    param.res = hFaxDpi ; // hFaxDpi == vFaxDpi 
    proc=cthread_fork((cthread_fn_t)TIFFReceptorMain,(any_t)&param);

// Check if the server is up
    time(&checkInTime);
    do {
	server = tiff_look_up();
	time(&currTime);
    } while (TIME_TO_WAIT_FOR_ACTIVE_PORT 
		> difftime(checkInTime, currTime) 
		&& server == PORT_NULL);
    if (server == PORT_NULL)
    {
	fprintf(stderr, "** Couldn't find the Tiff server.\n");
	fclose(psin);
	if((int)proc>0) {
	    thread_suspend((thread_t)proc);
	    cthread_abort(proc);
	    cthread_join(proc);				
	}
	exit(5);
    }	
//Communicating with Window Server	    
    toWindowServer = DPSCreateContext(NULL, NULL, 
	(DPSTextProc)DPSMyTextProc,
	(DPSErrorProc)DPSMyErrorProc) ;
    DPSSetContext(toWindowServer) ;
    DPSWaitContext(toWindowServer) ;

// Sifft through Comments and insert our headder to send image to port
// immediately after them.
    do {
	fgets(line, BUFSIZ, psin) ;
	DPSPrintf(toWindowServer,"%s", line) ;
    } while (strstr(line,"%%")||strstr(line,"%!"));
// This is the next line after the comments. (or mabe within the comments)

// put mach port description here.
    DPSPrintf(toWindowServer,"\n");
    DPSPrintf(toWindowServer,"/width %d def ", width);
    DPSPrintf(toWindowServer,"/height %d def ", height);
    DPSPrintf(toWindowServer,"/topdots %d def ", topMargin);
    DPSPrintf(toWindowServer,"/leftdots %d def ", leftMargin);
    DPSPrintf(toWindowServer,
"width height [ leftdots topdots width leftdots sub height topdots sub]");
    DPSPrintf(toWindowServer,
	"[%d 72 div 0 0 %d -72 div leftdots height topdots sub] () ",
	hFaxDpi, vFaxDpi);	    
    DPSPrintf(toWindowServer,"(%s) %s machportdevice \n\n",
	    server_name, device_name);	   

//Continue sending the rest of the postScript
    while(!feof(psin))
    { int count;
	count=fread((void *)line, sizeof(char), BUFSIZ, psin);
	DPSWriteData(toWindowServer, line, count);
    };


    DPSFlushContext(toWindowServer);
    DPSWaitContext(toWindowServer);
    DPSSendEOF(toWindowServer);

// Send a termination message for child to expect
    ret = port_allocate(task_self(), &parentPort); 
    if (ret == KERN_SUCCESS) {
        send.msgHeader.msg_id = NO_MORE_PAGES ;
	send.msgHeader.msg_simple = TRUE;	
	send.msgHeader.msg_size = sizeof(NXPrintPageMessage);
	send.msgHeader.msg_type = MSG_TYPE_NORMAL ;
	send.msgHeader.msg_local_port = parentPort ; 
	send.msgHeader.msg_remote_port = server;
	send.integerParams.msg_type_name = MSG_TYPE_INTEGER_32 ;
	send.integerParams.msg_type_size = 32;
	send.integerParams.msg_type_number = NX_PPMNUMINTS; 
	send.integerParams.msg_type_inline = TRUE;	
	send.integerParams.msg_type_longform = FALSE;	
	
	ret = msg_send(&send.msgHeader, MSG_OPTION_NONE, 0);
	switch(ret) {
	case SEND_SUCCESS: // Continue in loop for next page
	case SEND_INVALID_PORT: // printing has finished by time-out
	    break;
	default:
	    mach_error("TIFFServer:** SEND error to Window Server", ret); 
	};
	ret=port_deallocate(task_self(), parentPort);
	if (ret != KERN_SUCCESS) {
	    mach_error("port_deallocate", ret); 
	    exit(1);
	}
    } else mach_error("port_allocate",ret);
// Wait here for child to finish
    cthread_join(proc);				
    DPSDestroyContext(toWindowServer);
    fclose(psin); 
    exit(0);
};
