/*
 * Copyright (C) 1990 Commodore-Amiga, Inc. All rights reserved
 */

/*
 * WatchMan. Watches files for changes. Example Appshell application
 */

#include <libraries/appshell.h>
#include <clib/appshell_protos.h>

#include "fn_handler.h"
#include "wm.h"

extern LONG     _stack = 4000;
extern UBYTE   *_procname = APPNAME;
extern LONG     _priority = 0;
extern LONG     _BackGroundIO = 0;
extern LONG     _Backstdout;

struct FilePart {
    struct PoolHeader *ph;
    struct List    *filelist;
    struct List    *flaglist;
    LONG            Retries;
    LONG            Interval;
    LONG            Flags;
    UBYTE           FlagDesc[12];
    UBYTE           Filename[512];
};

UBYTE          *flagdescr[] =
{
    "FN_CREATED",
    "FN_DELETED",
    "FN_SMALLER",
    "FN_BIGGER",
    "FN_NEWER",
    "FN_MUSTEXIST",
};

extern struct WBStartup *WBenchMsg;

VOID 
main(int argc, char **argv)
{

    if (_Backstdout) {
	Close(_Backstdout);
	_Backstdout = 0;
    }
    HandleApp(argc, argv, WBenchMsg, WM);
}

/* Custom application init. Setup local lists etc. */
VOID 
WMInitFunc(struct AppInfo * ai, STRPTR str, struct TagItem * tl)
{
    struct FilePart *fp;
    struct Node    *node;
    LONG            w, g;
    int             i;

    if (fp = AllocVec(sizeof(struct FilePart), MEMF_CLEAR)) {
	if (fp->ph = CreatePrivatePool(MEMF_CLEAR, 2048, 128)) {
	    fp->filelist = AllocPooled(sizeof(struct List), fp->ph);
	    fp->flaglist = AllocPooled(sizeof(struct List), fp->ph);
	    NewList(fp->filelist);
	    NewList(fp->flaglist);

	    /* Fill in the list for available flags */
	    for (i = 0; i < 6; i++) {
		node = AllocPooled(sizeof(struct Node), fp->ph);
		node->ln_Name = AllocPooled(strlen(flagdescr[i]) + 1, fp->ph);
		strcpy(node->ln_Name, flagdescr[i]);
		AddTail(fp->flaglist, node);
	    }
	    /* And update the listview gadget */
	    if (APSHGetGadgetInfo(ai, "MAIN", "FLAGLIST", &w, &g)) {
		GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
				  GTLV_Labels, fp->flaglist,
				  GTLV_Top, 0,
				  TAG_DONE);

	    }
	    /* Use ai->ai_UserData to pass structure around */
	    ai->ai_UserData = fp;
	} else {
	    FreeVec(fp);
	}
    }
}

/* Custom application exit, free resources */
VOID 
WMExitFunc(struct AppInfo * ai, STRPTR str, struct TagItem * tl)
{

    struct FilePart *fp;
    struct Node    *wnode, *nnode;

    if (ai->ai_UserData) {
	/* Get pointer to application structure */
	fp = (struct FilePart *) ai->ai_UserData;
	if (fp->ph) {
	    /* Free the lists and free the pool. */
	    wnode = (struct Node *) fp->filelist->lh_Head;
	    while (nnode = (struct Node *) (wnode->ln_Succ)) {
		FreePooled(wnode->ln_Name, fp->ph);
		Remove(wnode);
		FreePooled(wnode, fp->ph);
		wnode = nnode;
	    }
	    FreePooled(fp->filelist, fp->ph);
	    wnode = (struct Node *) fp->flaglist->lh_Head;
	    while (nnode = (struct Node *) (wnode->ln_Succ)) {
		FreePooled(wnode->ln_Name, fp->ph);
		Remove(wnode);
		FreePooled(wnode, fp->ph);
		wnode = nnode;
	    }
	    FreePooled(fp->flaglist, fp->ph);
	    DeletePrivatePool(fp->ph);
	}
	FreeVec(fp);
    }
}

VOID 
WMAboutFunc(struct AppInfo * ai, STRPTR str, struct TagItem * tl)
{
    struct FilePart *fp = ai->ai_UserData;
    STRPTR          buffer;

    /*
     * Simple fill a buffer and call NotifyUser() to put up a nice requester.
     */
    if (buffer = AllocPooled(512, fp->ph)) {
	sprintf(buffer, "%s\n\n%s\n\n%s",
		ai->ai_AppVersion,
		ai->ai_AppCopyright,
		ai->ai_AppAuthor);

	NotifyUser(ai, buffer, NULL);
	FreePooled(buffer, fp->ph);
    }
}

/* Application function called by fn_handler */
VOID 
FileNotification(struct AppInfo * ai, STRPTR str, struct TagItem * tl)
{
    struct FNData  *fndata;
    struct FilePart *fp = (struct FilePart *) ai->ai_UserData;
    struct Node    *node;
    ULONG           w, g;
    UBYTE          *buffer;

    /*
     * When the handler calls us, it passes the pointer to the relevant data
     * as tag data
     */

    fndata = (struct FNData *) GetTagData(APSH_CmdData, NULL, tl);

    /* Fill in a buffer and put up a cool requester */
    buffer = AllocPooled(300, fp->ph);
    sprintf(buffer, "File Notification!\n\nTarget: %s\nStatus: %s%s%s%s%s%s",
	    fndata->fnd_Filename,
	    (fndata->fnd_Status & FN_DONE) ? "DONE " : NULL,
	    (fndata->fnd_Status & FN_CREATED) ? "CREATED " : NULL,
	    (fndata->fnd_Status & FN_DELETED) ? "DELETED " : NULL,
	    (fndata->fnd_Status & FN_SMALLER) ? "SMALLER " : NULL,
	    (fndata->fnd_Status & FN_BIGGER) ? "BIGGER " : NULL,
	    (fndata->fnd_Status & FN_NEWER) ? "NEWER" : NULL);

    NotifyUser(ai, buffer, NULL);
    FreePooled(buffer, fp->ph);

    /*
     * If the handler tells us the number of retries for the target has been
     * reached, try to remove the target from the list and listview gadget.
     * If the window is hidden, we cannot touch the gadget, so we'll leave a
     * false entry. That will be dealt with when the user tries to re-delete
     * the target
     */

    if (fndata->fnd_Status & FN_DONE) {
	if (APSHGetGadgetInfo(ai, "MAIN", "FILELIST", &w, &g)) {
	    /*
	     * Detach the list from the listview gadget, so we can update the
	     * list
	     */
	    GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
			      GTLV_Labels, ~0,
			      TAG_DONE);

	    /* If it's there, remove it */
	    if (node = FindName(fp->filelist, fp->Filename)) {
		FreePooled(node->ln_Name, fp->ph);
		Remove(node);
		FreePooled(node, fp->ph);
	    }
	    /* And attach the list to the listview gadget again */
	    GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
			      GTLV_Labels, fp->filelist,
			      GTLV_Top, 0,
			      TAG_DONE);
	}
    }
}


void 
SetFilenameFunc(struct AppInfo * ai, STRPTR str, struct TagItem * tl)
{
    struct FilePart *fp = ai->ai_UserData;
    struct Gadget  *gadget;
    struct WBArg   *wbarg;
    STRPTR          buffer = NULL;
    STRPTR          entry;
    STRPTR          parsedline;
    STRPTR          argv[MAXARG];
    ULONG           argc = 0;
    ULONG           w, g;

    if (str) {
	parsedline = BuildParseLine(str, &argc, argv);
    }
    if (argc >= 2) {
	/* We're called with a string argument, via AREXX or cmdshell */
	entry = argv[1];
	if (APSHGetGadgetInfo(ai, "MAIN", "FSTRING", &w, &g)) {
	    GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
			      GTST_String, entry,
			      TAG_DONE);
	}
    } else {
	gadget = (struct Gadget *) GetTagData(APSH_MsgIAddress, NULL, tl);
	if (gadget)
	    entry = ((STRPTR) ((struct StringInfo *) (gadget)->SpecialInfo)->Buffer);
	else {
	    /* WB drop support */
	    wbarg = (struct WBArg *) GetTagData(APSH_WBArg, NULL, tl);
	    buffer = AllocPooled(512, fp->ph);
	    /*
	     * WB passes a lock to the directory and a pointer to the
	     * filename which was dropped.
	     */
	    if (NameFromLock(wbarg->wa_Lock, buffer, 512)) {
		entry = wbarg->wa_Name;
		if (AddPart(buffer, entry, 512))
		    entry = buffer;
	    } else
		entry = NULL;
	    if (APSHGetGadgetInfo(ai, "MAIN", "FSTRING", &w, &g)) {
		GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
				  GTST_String, entry,
				  TAG_DONE);
	    }
	}

	/* Activate next gadget, it seems logical */
	if (APSHGetGadgetInfo(ai, "MAIN", "ISTRING", &w, &g))
	    ActivateGadget((struct Gadget *) g, (struct Window *) w, NULL);
    }
    strcpy(fp->Filename, entry);
    if (buffer)
	FreePooled(buffer, fp->ph);
    if (str)
	FreeParseLine(parsedline);
}


void 
SetRetriesFunc(struct AppInfo * ai, STRPTR str, struct TagItem * tl)
{
    struct FilePart *fp = ai->ai_UserData;
    struct Gadget  *gadget;
    LONG            retries;
    STRPTR          parsedline;
    STRPTR          argv[MAXARG];
    ULONG           argc;
    ULONG           w, g;

    if (str) {
	parsedline = BuildParseLine(str, &argc, argv);
    }
    if (argc >= 2) {
	stcd_l(argv[1], &retries);
	if (APSHGetGadgetInfo(ai, "MAIN", "RSTRING", &w, &g)) {
	    GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
			      GTIN_Number, retries,
			      TAG_DONE);
	}
    } else {
	gadget = (struct Gadget *) GetTagData(APSH_MsgIAddress, NULL, tl);
	retries = ((LONG) ((struct StringInfo *) (gadget)->SpecialInfo)->LongInt);
    }
    fp->Retries = retries;
    if (str)
	FreeParseLine(parsedline);
}


void 
SetIntervalFunc(struct AppInfo * ai, STRPTR str, struct TagItem * tl)
{
    struct FilePart *fp = ai->ai_UserData;
    struct Gadget  *gadget;
    LONG            interval;
    STRPTR          argv[MAXARG];
    STRPTR          parsedline;
    ULONG           argc = 0;
    ULONG           w, g;

    if (str) {
	parsedline = BuildParseLine(str, &argc, argv);
    }
    if (argc >= 2) {
	stcd_l(argv[1], &interval);
	if (APSHGetGadgetInfo(ai, "MAIN", "ISTRING", &w, &g)) {
	    /*
	     * Currently GadTools doesn't allow signed numbers, so negatives
	     * come out 'weird'.
	     */
	    GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
			      GTIN_Number, interval,
			      TAG_DONE);
	}
    } else {
	gadget = (struct Gadget *) GetTagData(APSH_MsgIAddress, NULL, tl);
	interval = ((LONG) ((struct StringInfo *) (gadget)->SpecialInfo)->LongInt);
	/* Activate next gadget, it seems logical */
	if (APSHGetGadgetInfo(ai, "MAIN", "RSTRING", &w, &g))
	    ActivateGadget((struct Gadget *) g, (struct Window *) w, NULL);
    }
    fp->Interval = interval;
    if (str)
	FreeParseLine(parsedline);
}


void 
SetFlagsFunc(struct AppInfo * ai, STRPTR str, struct TagItem * tl)
{

    struct FilePart *fp = ai->ai_UserData;
    USHORT          code;
    STRPTR          parsedline;
    ULONG           argc;
    STRPTR          argv[MAXARG];
    ULONG           w, g;

    if (str)
	parsedline = BuildParseLine(str, &argc, argv);
    if (argc >= 2) {
        /* We're called with a string argment, like 
         * "setflags fn_newer|fn_mustexist"
         */
	if (MatchValue(argv[1], "FN_MUSTEXIST"))
	    fp->Flags ^= FN_MUSTEXIST;
	if (MatchValue(argv[1], "FN_CREATED"))
	    fp->Flags ^= FN_CREATED;
	if (MatchValue(argv[1], "FN_DELETED"))
	    fp->Flags ^= FN_DELETED;
	if (MatchValue(argv[1], "FN_SMALLER"))
	    fp->Flags ^= FN_SMALLER;
	if (MatchValue(argv[1], "FN_BIGGER"))
	    fp->Flags ^= FN_BIGGER;
	if (MatchValue(argv[1], "FN_NEWER"))
	    fp->Flags ^= FN_NEWER;
    } else {
        /* Tags then? */
	code = (USHORT) GetTagData(APSH_MsgCode, 10, tl);

	switch (code) {
	case 0:
	    fp->Flags ^= FN_CREATED;
	    break;
	case 1:
	    fp->Flags ^= FN_DELETED;
	    break;
	case 2:
	    fp->Flags ^= FN_SMALLER;
	    break;
	case 3:
	    fp->Flags ^= FN_BIGGER;
	    break;
	case 4:
	    fp->Flags ^= FN_NEWER;
	    break;
	case 5:
	    fp->Flags ^= FN_MUSTEXIST;
	    break;
	default:
	    break;
	}
    }

    /* Put the flag letters in a fixed buffer and show it, if possible */
    sprintf(fp->FlagDesc, "%s%s%s%s%s%s",
	    (fp->Flags & FN_CREATED) ? "C " : NULL,
	    (fp->Flags & FN_DELETED) ? "D " : NULL,
	    (fp->Flags & FN_SMALLER) ? "S " : NULL,
	    (fp->Flags & FN_BIGGER) ? "B " : NULL,
	    (fp->Flags & FN_NEWER) ? "N " : NULL,
	    (fp->Flags & FN_MUSTEXIST) ? "M" : NULL);

    if (APSHGetGadgetInfo(ai, "MAIN", "SHOWTEXT", &w, &g)) {
	GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
			  GTTX_Text, fp->FlagDesc,
			  TAG_DONE);
    }
    if (str)
	FreeParseLine(parsedline);
}

/* Accept the choice in the 'target' listview as general filename */
void 
GetFileFromListFunc(struct AppInfo * ai, STRPTR str, struct TagItem * tl)
{
    struct FilePart *fp = ai->ai_UserData;
    struct Node    *node;
    USHORT          code;
    ULONG           w, g;
    int             i;

    code = GetTagData(APSH_MsgCode, 0, tl);

    if (code != 32) {
        /* Actually selected a target */
	node = fp->filelist->lh_Head;
	for (i = 0; i < code; i++)
	    node = node->ln_Succ;

        /* Make choice general target */
	strcpy(fp->Filename, node->ln_Name);

        /* Update the filename string gadget, if possible */
	if (APSHGetGadgetInfo(ai, "MAIN", "FSTRING", &w, &g)) {
	    GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
			      GTST_String, fp->Filename,
			      TAG_DONE);
	}
    }
}

/* Put in a request to the handler to add a target */
VOID 
AddTargetFunc(struct AppInfo * ai, STRPTR str, struct TagItem * tl)
{
    struct FilePart *fp = ai->ai_UserData;
    struct Node    *node;
    ULONG           w, g;
    BOOL            error = 0;

    /* Call the handler function, because it's called with all
     * tags (even if they're NULL) we don't actually use the
     * handlers defaults.
     */

    FileNotify(ai, NULL,
	       FN_Command, FN_ADDTARGET,
	       FN_Filename, fp->Filename,
	       FN_Interval, fp->Interval,
	       FN_Retries, fp->Retries,
	       FN_Flags, fp->Flags,
	       FN_Error, &error,
	       TAG_DONE);

    if (error == 0) {
        /* Handler accepted the request, try to make it visible */
	if (APSHGetGadgetInfo(ai, "MAIN", "FILELIST", &w, &g)) {
            /* Detach the list from the listview gadget, so it can
             * be updated.
             */
	    GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
			      GTLV_Labels, ~0,
			      TAG_DONE);

            /* If it's not there already, add a new node */
	    if (!(node = FindName(fp->filelist, fp->Filename))) {
		node = AllocPooled(sizeof(struct Node), fp->ph);
		node->ln_Name = AllocPooled(strlen(fp->Filename) + 1, fp->ph);
		strcpy(node->ln_Name, fp->Filename);
		AddTail(fp->filelist, node);
	    }
            /* And attach the list to the listview gadget again */
	    GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
			      GTLV_Labels, fp->filelist,
			      GTLV_Top, 0,
			      TAG_DONE);
	}
    }
}


/* Put in a request to the handler to stop watching a target */
VOID 
DelTargetFunc(struct AppInfo * ai, STRPTR str, struct TagItem * tl)
{
    struct FilePart *fp = ai->ai_UserData;
    struct Node    *node;
    ULONG           w, g;
    BOOL            error;

    /* Call handler function with the target name which should be 
     * removed from the list */
    FileNotify(ai, NULL,
	       FN_Command, FN_DELTARGET,
	       FN_Filename, fp->Filename,
	       FN_Error, &error,
	       TAG_DONE);

    /* Even if the handler doesn't know the target and complains,
     * remove it from the list, the user doesn't want it anymore.
     */

    /* Detach the list from the listview gadget, so it can be updated */
    if (APSHGetGadgetInfo(ai, "MAIN", "FILELIST", &w, &g)) {
	GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
			  GTLV_Labels, ~0,
			  TAG_DONE);

        /* Remove the node from the list if it's found */
	if (node = FindName(fp->filelist, fp->Filename)) {
	    FreePooled(node->ln_Name, fp->ph);
	    Remove(node);
	    FreePooled(node, fp->ph);
	}
        /* Attach the list to the listview gadget again */
	GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
			  GTLV_Labels, fp->filelist,
			  GTLV_Top, 0,
			  TAG_DONE);
    }
}


/* Put in a request to sync a target */
VOID 
SyncTargetFunc(struct AppInfo * ai, STRPTR str, struct TagItem * tl)
{

    struct FilePart *fp = ai->ai_UserData;
    struct Node    *node;
    ULONG           w, g;
    BOOL            error;

    /* Call the handler function */
    FileNotify(ai, NULL,
	       FN_Command, FN_SYNC,
	       FN_Filename, fp->Filename,
	       FN_Error, &error,
	       TAG_DONE);

    /*
     * If sync failed, try to remove it from the list. It's probably removed
     * thru the shell or arexx.
     */

    if (error) {
        /* Detach list from the listview gadget so it can be updated */
	if (APSHGetGadgetInfo(ai, "MAIN", "FILELIST", &w, &g)) {
	    GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
			      GTLV_Labels, ~0,
			      TAG_DONE);

            /* Remove node from the list if found */
	    if (node = FindName(fp->filelist, fp->Filename)) {
		FreePooled(node->ln_Name, fp->ph);
		Remove(node);
		FreePooled(node, fp->ph);
	    }
            /* Attach list to the listview gadget again */
	    GT_SetGadgetAttrs((struct Gadget *) g, (struct Window *) w, NULL,
			      GTLV_Labels, fp->filelist,
			      GTLV_Top, 0,
			      TAG_DONE);
	}
    }
}

VOID 
InquiryTargetFunc(struct AppInfo * ai, STRPTR str, struct TagItem * tl)
{
    struct FilePart *fp = ai->ai_UserData;
    struct FNData  *fndata;
    BOOL            error;
    STRPTR          buffer;

    fndata = AllocPooled(sizeof(struct FNData), fp->ph);

    FileNotify(ai, NULL,
	       FN_Command, FN_INQUIRY,
	       FN_Filename, fp->Filename,
	       FN_Error, &error,
	       FN_Buffer, fndata,
	       TAG_DONE);

    if (!(error)) {
	buffer = AllocPooled(300, fp->ph);
	sprintf(buffer, "Target: %s\nInterval: %ld\nRetries: %ld\nFlags: %s%s%s%s%s%s",
		fndata->fnd_Filename,
		fndata->fnd_Interval,
		fndata->fnd_Retries,
		(fndata->fnd_Flags & FN_CREATED) ? "FN_CREATED " : NULL,
		(fndata->fnd_Flags & FN_DELETED) ? "FN_DELETED " : NULL,
		(fndata->fnd_Flags & FN_SMALLER) ? "FN_SMALLER " : NULL,
		(fndata->fnd_Flags & FN_BIGGER) ? "FN_BIGGER " : NULL,
		(fndata->fnd_Flags & FN_NEWER) ? "FN_NEWER " : NULL,
		(fndata->fnd_Flags & FN_MUSTEXIST) ? "FN_MUSTEXIST " : NULL);

	NotifyUser(ai, buffer, NULL);
	FreePooled(buffer, fp->ph);
    }
    FreePooled(fndata, fp->ph);
}
