/* clip.c
 * Written by David N. Junod - August 28, 1990.
 * Main routines for CF (ClipFile).
 *
 * CF was written so that I could quickly, an easily, access (test) the
 * clipboard while writing the clipboard routines for rexxutil.library
 * (and the clipboard example for AmigaMail).
 *
 * BTW---This program isn't written in my usual style.  This time around I
 * went for using as many of the SAS/C functions as possible.
 *
 */

#include <exec/types.h>
#include <exec/libraries.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <dos/dos.h>
#include <dos/notify.h>
#include <devices/clipboard.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <error.h>

#include "cf.h"
#include "cf_protos.h"

/* So that we can check OS versions... */
extern struct Library *SysBase;

/* IO file name buffer */
char name[256];

/* File notification file name buffer */
char notify[256];

/* Disable Lattice/SASC CTRL-C handling */
int CXBRK (void)
{
    return (0);
}

int chkabort (void)
{
    return (0);
}

void main (int argc, char **argv)
{
    /* Make sure we were called from the Shell */
    if (argc > 0)
    {
	LONG unit = PRIMARY_CLIP;
	UBYTE idbuff[6] = {NULL};
	int fh = -1;		/* File handle */
	int mode = 0;		/* File access mode */
	int prot = 0;		/* File protection mode */
	int acs = 0;		/* Access mode */
	int next = 1;		/* Option counter */
	unsigned int count;	/* Bytes read/written */
	unsigned int length;	/* Bytes to transfer */
	char option;		/* Option pointer */
	char *odata;		/* Option data pointer */
	BOOL cook = TRUE;	/* Do we process the buffer? */
	struct MsgPort *mp;	/* Notification message port */
	char opts[] = "urwn";	/* Options that require data */

	for (next = 1; (odata = argopt (argc, argv, opts, &next, &option)) != NULL;)
	{
	    switch (toupper(option))
	    {
		case '?':
		case 'H':
		    help ();
		    break;

		case 'F':
		    cook = FALSE;
		    break;

		case 'Q':
		    cook = FALSE;
		    acs = CF_QUERY;
		    break;

		case 'C':
		    cook = FALSE;
		    acs = CF_CLEAR;
		    break;

		case 'N':
		    if (SysBase->lib_Version < 36)
			fprintf (stderr, "-n option requires AmigaOS 2.0\n");
		    else
		    {
			if (*odata)
			{
			    /* Remember the notification name */
			    strcpy (notify, odata);

			    /* Set the access mode */
			    acs = CF_NOTIFY;
			}
			else
			{
			    fprintf (stderr, "-n option requires a file name\n");
			    exit (5);
			}
		    }
		    break;

		case 'U':
		    if (*odata)
		    {
			unit = (LONG) atoi (odata);

			if (unit < 0L || unit > 255L)
			{
			    fprintf (stderr, "Clipboard unit must range from 0 to 255\n");
			    exit (5);
			}
		    }
		    else
		    {
			fprintf (stderr, "-u option requires a number from 0 to 255\n");
			exit (5);
		    }
		    break;

		case 'R':
		    if (*odata)
		    {
			strcpy (name, odata);

			if (access (name, 4) == -1)
			{
			    fprintf (stderr, "Can't read file %s\n", name);
			    exit (5);
			}

			/* Set the access mode */
			mode = O_RDONLY;
			acs = CF_READ;
		    }
		    else
		    {
			fprintf (stderr, "-r option requires a name\n");
			exit (5);
		    }
		    break;

		case 'W':
		    if (*odata)
		    {
			strcpy (name, odata);

			if ((access (name, 0) == 0) &&
			    (access (name, 2) == -1))
			{
			    fprintf (stderr, "Can't write to file %s\n", name);
			    exit (5);
			}

			/* Set the access mode */
			mode = O_WRONLY | O_CREAT | O_TRUNC;
			acs = CF_WRITE;
		    }
		    else
		    {
			fprintf (stderr, "-r option requires a name\n");
			exit (5);
		    }
		    break;

		default:
		    fprintf (stderr, "%c is not a supported option\n", option);
		    exit (5);
		    break;
	    }
	}

	/* See if help is needed */
	if ((argc == 2 && (strcmp (argv[1], "?") == 0)) || next < argc)
	    help ();

	/* Do we need to open/create a file? */
	if (acs == CF_READ || acs == CF_WRITE)
	{
	    /* Open/create the file */
	    if ((fh = open (name, mode, prot)) == -1)
	    {
		/* Display why we failed */
		perror ("CF; open");

		/* Clear the access flag */
		acs = 0;
	    }
	}

	/* Attempt to open the designated clipboard unit */
	if (CBOpen (unit) == 0L)
	{
	    struct Buffer *buff = NULL;

	    switch (acs)
	    {
		case 0:	/* Just browsing, thanks */
		    if (buff = CBRead (cook))
		    {
			if (cook && (buff->b_Type == 0))
			    printf ("%s\n", (STRPTR) buff->b_Buff);
			else
			    printf ("Contents are %s\n",
				    PrintID (buff->b_Type, idbuff));
		    }
		    else
			printf ("clipboard is empty\n");
		    break;

		case CF_QUERY:
		    if (buff = CBRead (cook))
			printf ("%s, %ld\n",
			        PrintID (buff->b_Type, idbuff), buff->b_ClipID);
		    else
			printf ("empty\n");
		    break;

		case CF_READ:
		    if (buff = fillbuffer (fh))
			CBWrite (buff);
		    break;

		case CF_WRITE:
		    if (buff = CBRead (cook))
		    {
			/* Compute number of bytes to write */
			length = buff->b_Size - sizeof (struct Buffer);

			/* Write the buffer */
			count = write (fh, (char *) buff->b_Buff, length);
		    }
		    break;

		case CF_NOTIFY:
		    /* Create a message port */
		    if (mp = CreatePort (NULL, NULL))
		    {
			struct NotifyRequest *nr;
			LONG msize;

			/* Calculate size of allocation */
			msize = sizeof (struct NotifyRequest);

			/* Allocate the notification request record */
			if (nr = (struct NotifyRequest *) AllocMem (msize, MEMF_CLEAR))
			{
			    /* Fill in the notification request */
			    nr->nr_Name = notify;
			    nr->nr_stuff.nr_Msg.nr_Port = mp;
			    nr->nr_Flags = NRF_SEND_MESSAGE | NRF_WAIT_REPLY;

			    /* Start the notification */
			    StartNotify (nr);

			    /* Handle notification messages */
			    HandleNotify (mp);

			    /* Say that we're not interested any longer */
			    EndNotify (nr);

			    /* Free the notification request */
			    FreeMem ((APTR) nr, msize);
			}
			else
			    fprintf (stderr, "Couldn't allocate notify request\n");

			/* Delete our message port */
			DeletePort (mp);
		    }
		    else
			fprintf (stderr, "Couldn't create message port\n");

		    break;

		case CF_CLEAR:
		    CBClear();
		    break;
	    }

	    /* Free the temporary buffer */
	    freebuffer (buff);

	    /* Close the clipboard */
	    CBClose ();
	}

	/* See if we opened a file */
	if (fh != -1)
	{
	    /* Close the file, and change the file mode if we created the file */
	    if ((close (fh) == 0) && (mode == O_WRONLY))
	    {
		if (chmod (name, S_IWRITE | S_IREAD))
		    perror ("PF; chmod");
	    }
	}
    }
}

VOID HandleNotify (struct MsgPort * mp)
{
    LONG sigbits;
    BOOL going = TRUE;

    /* Build the signal bits that we're going to wait on */
    sigbits = (1L << mp->mp_SigBit) | SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_F;

    /* Loop until we get a signal or error */
    while (going)
    {
	struct NotifyMessage *nmsg;
	LONG sigsr;

	/* Wait for a message to come in */
	sigsr = Wait (sigbits);

	/* See if we got a ^C signal */
	if (sigsr & SIGBREAKF_CTRL_C)
	{
	    printf ("process watching %s aborted\n", notify);

	    /* Signal the end... */
	    going = FALSE;
	}

	/* See if we got a ^F signal */
	if (sigsr & SIGBREAKF_CTRL_F)
	    printf ("watching %s\n", notify);

	/* See if we have a file notification message */
	if (sigsr & (1L << mp->mp_SigBit))
	{
	    int fh, i, error;

	    /* Get each notification message and reply to it */
	    while (nmsg = (struct NotifyMessage *) GetMsg (mp))
		ReplyMsg ((struct Message *) nmsg);

	    /* For some reason, DPaint does something with the file twice,
	     * if it already exists, so wait around until we have read
	     * access to the file.  So, when getting notification on a
	     * DPaint brush, we'll actually get two messages. */
	    for (i = 0, error = (-1); (error == (-1) && i < 100); i++)
		if ((error = access (notify, 4)) == (-1))
		    Delay (10);

	    /* Open the file */
	    if ((fh = open (notify, O_RDONLY, 0)) == -1)
	    {
		/* Build the error string */
		sprintf (name, "CF;  %s", notify);

		/* Display why we failed */
		perror (name);

		/* Don't continue */
		going = FALSE;
	    }
	    else
	    {
		struct Buffer *buff;

		/* Get contents of the file */
		if (buff = fillbuffer (fh))
		{
		    /* Write the buffer to the clipboard */
		    CBWrite (buff);

		    /* Free the temporary buffer */
		    freebuffer (buff);
		}

		/* Close the file */
		close (fh);
	    }
	}
    }
}

void help (void)
{

    printf ("CF (ClipFile) version 1.0\n");
    printf ("Written by David N. Junod\n");
    printf ("\n");
    printf ("-h		Display this message\n");
    printf ("-u <unit>	Clipboard unit, ranging from 0 to 255.\n");
    printf ("-r <file>	Place the contents of <file> into the clipboard.\n");
    printf ("-w <file>	Place the contents of the clipboard into <file>.\n");
    printf ("-f		Don't convert FTXT form to ASCII\n");
    printf ("-q		Query clipboard unit contents type\n");
    printf ("-c		Clear the clipboard unit\n");
    printf ("-n <file>	Watch the named file and place it into the clipboard\n");
    printf ("		whenever it changes.  Must send a break signal to shutdown\n");
    printf ("		the process\n");

    exit (0);
}

struct Buffer *fillbuffer (int fh)
{
    struct Buffer *buff = NULL;
    long msize = NULL;
    long count, length;

    /* Make sure we have a file handle */
    if (fh >= 0)
    {
	/* Find out how big the file is */
	if ((length = lseek (fh, 0L, 2)) >= 0L)
	{
	    /* Seek back to the beginning */
	    lseek (fh, 0L, 0);

	    /* Calculate the size of the buffer */
	    msize = sizeof (struct Buffer) + length;

	    /* Allocate the buffer */
	    if (buff = (struct Buffer *) AllocMem (msize, MEMF_CLEAR))
	    {
		buff->b_Size = msize;
		buff->b_Type = 0;

		/* Point the buffer at the right place */
		buff->b_Buff = (VOID *) ((buff) + 1);

		/* Fill in the buffer */
		if ((count = read (fh, (char *) buff->b_Buff, length)) == -1)
		{
		    /* Display error message */
		    perror ("CF; read");

		    /* Discard buffer */
		    FreeMem ((APTR) buff, msize);
		    buff = NULL;
		}
		else
		{
		    LONG *buffer;

		    buffer = (LONG *) buff->b_Buff;
		    if (buffer[0] == ID_FORM)
			buff->b_Type = buffer[2];
		    else if (count == 0L)
			buff->b_Type = ID_FTXT;
		}
	    }
	}
    }

    return (buff);
}
