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/cthreads.h>
#import <mach/mach_error.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>

#import "PSfaxHeadder.h"

#import "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 TIME_TO_WAIT_FOR_ACTIVE_PORT	5  // seconds
#define TIME_OUTOF_RENDEREDPAGE_FROM_WINDOWSERVER  20  // seconds

#define PS_PAGE_SEARCH_FORMAT                "\%\%Page:"

#define NO_MORE_PAGES	666

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

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

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;
}

long rawTIFFData(NXStream *tiffFile,int imageNum)
// Function sets file pointer at begining of data and returns the number
// of bytes in that data block.
{
    TIFFHeader		TIFFhead;
//    TIFFDirEntry	seledtedDir;
//    unsigned short	itemSize = 0;

//Get TIFF Headder
    NXSeek(tiffFile, 0, NX_FROMSTART);
    NXRead(tiffFile, &TIFFhead.tiff_magic, sizeof(TIFFhead.tiff_magic));
    NXRead(tiffFile, &TIFFhead.tiff_version, sizeof(TIFFhead.tiff_version));
    NXRead(tiffFile, &TIFFhead.tiff_diroff, sizeof(TIFFhead.tiff_diroff));

//WARNING: This is a total hack! It works for G3, Probably G4, and probably
// 	any TIFF with one bitmap in it that has been generated by the 
//	NXBitmapImageRep class.  This algorithm has only been tested with the
//	G3 output from the NXBitmapImageRep.
//
//	Image data will follow immediately after the TIFFhead.  The image
//	directory is located at the end if the bitmap.  File structure 
//	for one TIFF only is:
//
//		2 bytes - TIFFhead.tiff_magic
//		2 bytes - TIFFhead.tiff_version
//		4 bytes - TIFFhead.tiff_diroff <- to first directoy
//		n bytes - image data
//		1 bytes  - number of directories (or is it two?)
//		2 bytes - seledtedDir.tdir_tag
//		2 bytes - seledtedDir.tdir_type
//		m bytes - not exactly sure from here...
//
//		n = TIFFhead.tiff_diroff - 2 bytes
//
//	NOTE: 	TIFFhead.tiff_diroff is offset from the first byte of the
//		offset number. NOT the byte following that number.
//
//	A correct treatment of this issue of stripping bitmap data out of a 
//	TIFF file would be MUCH appreciated.
//
    // I would think that the 3 should be a one or a two but...it isn't
    return (TIFFhead.tiff_diroff-sizeof(TIFFhead.tiff_diroff)-3);
};


void server_loop(port_t port, char *baseFileName, 
	int compression)
{
    id 				renderingImage;
    int				pagesPrinted=0;
    NXPrintPageMessage	 	msg;
    replyMsg	 		reply;
    kern_return_t 		ret;
    NXStream 			*stream ;
    FILE			*out;
    BOOL			getNextPage = TRUE;
    char			fname[MAX_FILE_PATH];
    
// 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:
		sprintf(fname,"%s.%.3d", baseFileName, msg.pageNum);
		if((out=fopen(fname,"wb")))
		{
		    NXStream *tmpFile;
	    
		    tmpFile = NXOpenMemory(0, 0, NX_READWRITE);
		    stream = NXOpenFile(fileno(out), NX_WRITEONLY);			    
	
		    renderingImage = [[NXBitmapImageRep alloc] 
				initData:msg.printerData 
				pixelsWide:(int)msg.pixelsWide
				pixelsHigh:(int)msg.pixelsHigh 
				bitsPerSample:msg.bitsPerSample 
				samplesPerPixel:msg.samplesPerPixel 
				hasAlpha:NO 
				isPlanar:msg.isPlanar
				colorSpace:msg.colorSpace 
				bytesPerRow:0  
				bitsPerPixel:0
				];
		    [renderingImage writeTIFF:tmpFile 
			usingCompression:compression];
		    [renderingImage free];	    
		    NXWrite(stream, tmpFile->buf_ptr, rawTIFFData(tmpFile,1));
		    NXClose(stream);
		    NXCloseMemory(tmpFile, NX_FREEBUFFER);
		    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; // sent from
		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 error 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 error.\n", ret);
	};
    } while (getNextPage) ;
}

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);
    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 [Pwhrgudc] <infn> <outfn>\n %s %s %s %s %s %s %s %s %s\n",
        progName,
	"[-w<0|1|2>]\t width of 0-215 mm, 1-255 mm, 2-303 mm (def=0)\n",
	"[-h<0|1|2>]\t hight of 0-281 mm, 1-297 mm, 2-364 mm (def=0)\n", 
	"[-r<0|1>]\t vert.res.of 0-98 lpi or 1-196 lpi (def=0)\n",
	"[-g<0|1>]\t comp.type 0-G3 Fax, 1-G4 Fax (def=0)\n",
	"[-u<string>]\t user name for headder\n",
	"[-d<string>]\t date and/or time for headder\n",
	"[-c<string>]\t csi identification for headder \n",
	"<infn>\t\t name of PostScript file to convert\n",
	"<outfn>\t base name for G3/4 files\n");
    exit(1);
};
int insertHeadder(DPSContext context, char *line, int count, char *name, char *time, char *csi)
{
  static int pageCount = 0; /* should only initialize the first time.*/
  char *ptr = 0;
  int subCount = 0;

  if((ptr=strstr(line,PS_PAGE_SEARCH_FORMAT))) {
    char page[MAX_DEVICE_NAME_LEN] = {""};
    pageCount++;
    sprintf(page,"Page %d",pageCount);
    DPSWriteData(context, line,(subCount=((int)ptr-(int)line)));
    PSfaxHeadder(name,time,csi,page);
    DPSWriteData(context, ptr, count-subCount);
    return 1; /* made insert and printed line */
  } else { /* line could be broken between data blocks.  Account for it. */
    return 0;
  }
}

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},
		user[MAX_DEVICE_NAME_LEN] = {""},
		date[MAX_DEVICE_NAME_LEN] = {""},
		csi[MAX_DEVICE_NAME_LEN] = {""},
		server_name[MAX_DEVICE_NAME_LEN] = {TIFF_SERVER_NAME} ;
    FILE	*psin = 0;
    int		addheadder = 0;
    int 	width = 1728, height = 2166, topMargin = 0,
		leftMargin = 0, vFaxDpi = 98, 
		compType = NX_TIFF_COMPRESSION_CCITTFAX3;
    const float	hFaxDpi = 204.1451;

    cthread_t 	proc=0; 			
    ChildParam	param;	
    NXPrintPageMessage send;		
    kern_return_t 	ret;
    port_t	parentPort;
        
    while ((c = getopt(argc, argv, "w:h:r:g:u:d:c:")) != -1)
	switch (c) {
	case 'w':			
	    width = (atoi(optarg)==1)?2048:(atoi(optarg)==2)?2432:1728  ;
	    break;
	case 'h':			
	    height = (atoi(optarg)==1)?2292:(atoi(optarg)==2)?2810:2166  ;
	    break;
	case 'r':			
	    vFaxDpi = (atoi(optarg)==0)?98:196 ;
	    break;
	case 'g':			
	    compType = (atoi(optarg)==1)
	    	?NX_TIFF_COMPRESSION_CCITTFAX4
		:NX_TIFF_COMPRESSION_CCITTFAX3  ;
	    break;
	case 'u':
	    strncpy(user,optarg,MAX_DEVICE_NAME_LEN);
	    addheadder = 1;
	    break;
	case 'd':
	    strncpy(date,optarg,MAX_DEVICE_NAME_LEN);
            addheadder = 1;
	    break;
	case 'c':
	    strncpy(csi,optarg,MAX_DEVICE_NAME_LEN);
            addheadder = 1;
	    break;
	case '?':
	    usage(argv[0]);
	};
    if (vFaxDpi == 98)	height /= 2;
    
    switch(argc - optind){
    case 1:
	psin = stdin ;
	break;
    case 2:
	if (!(psin = fopen(argv[argc-2],"r"))) {
	    fprintf(stderr,"** Could not open \'%s\'.\n", argv[argc-2]);
	    exit(1);
	};
	break;
    default:
	usage(argv[0]);
    }
    
//	Run the child here.
    param.basename = argv[argc-1];
    param.compression = compType;
    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) ;
	if(!insertHeadder(toWindowServer,line, strlen(line), user, date, csi)
		&& addheadder )
	  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,
	"[%f 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);
      if(!insertHeadder(toWindowServer,line, count, user, date, csi)
		&& addheadder)
			DPSWriteData(toWindowServer, line, count);
    };
    DPSFlushContext(toWindowServer);
    DPSWaitContext(toWindowServer);
    DPSSendEOF(toWindowServer);

// Send the number of pages 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);
};



