#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <math.h>
#include <errno.h>
#include <Xm/Xm.h>
#include <Xm/MwmUtil.h>
#include <Xm/PushB.h>
#include <Xm/Form.h>
#include <Xm/RowColumn.h>
#include <Xm/Label.h>
#include <Xm/TextF.h>
#include <Xm/List.h>
#include <Xm/DrawingA.h>
#include <Xm/Protocols.h>
#include <X11/Shell.h>

#include "config.h"
#include "gps.h"
#include "display.h"

/* 
 * Widget and window sizes.
 */
#define MAX_FONTSIZE	18		/* maximum fontsize we handle*/
/* height of satellite-data display */
#define SATDATA_HEIGHT	MAX_FONTSIZE*(MAXCHANNELS+1)
#define LEFTSIDE_WIDTH	205		/* width of data-display side */
#define SATDIAG_SIZE	400		/* size of satellite diagram */

static Widget toplevel, form, left, right;
#ifdef EXPLICIT_QUIT
static Widget quitbutton;
#endif /* EXPLICIT_QUIT */
static Widget satellite_list, satellite_diagram, status;
static Widget rowColumn_11, rowColumn_12, rowColumn_13, rowColumn_14;
static Widget rowColumn_15, rowColumn_16, rowColumn_17, rowColumn_18;
static Widget rowColumn_19, rowColumn_20;
static Widget text_1, text_2, text_3, text_4, text_5;
static Widget text_6, text_7, text_8, text_9, text_10;
static Widget label_1, label_2, label_3, label_4, label_5;
static Widget label_6, label_7, label_8, label_9, label_10;
static GC gc;

/*@ -nullassign @*/
static XrmOptionDescRec options[] = {
{"-altunits",  "*altunits",            XrmoptionSepArg,        NULL},
{"-speedunits","*speedunits",          XrmoptionSepArg,        NULL},
};
String fallback_resources[] = {NULL};
/*@ +nullassign @*/

struct unit_t {
    char *legend;
    double factor;
};
static struct unit_t speedtable[] = {
    {"knots",		1/KNOTS_TO_KPH},
    {"mph",		MPS_TO_MPH},
    {"kph",		MPS_TO_KPH},
}, *speedunits = speedtable;
static struct unit_t  alttable[] = {
    {"feet",		METERS_TO_FEET},
    {"meters",		1},
}, *altunits = alttable;

static void quit_cb(void)
{
    exit(0);	/* closes the GPS along with other fds */
}

/*@ -mustfreefresh -compdef +ignoresigns @*/
static Pixel get_pixel(Widget w, char *resource_value)
{
    Colormap colormap;
    Boolean cstatus;
    XColor exact, color;

    colormap = DefaultColormapOfScreen(DefaultScreenOfDisplay(XtDisplay(w)));
    /*@i@*/cstatus = XAllocNamedColor(XtDisplay(w), colormap, resource_value, &color, &exact);
    if (cstatus == (Boolean)False) {
	(void)fprintf(stderr, "Unknown color: %s", resource_value);
	color.pixel = BlackPixelOfScreen(DefaultScreenOfDisplay(XtDisplay(w)));
    };
    /*@i1@*/return (color.pixel);
}

static void build_gui(Widget toplevel)
{
    Arg args[100];
    XGCValues gcv;
    Atom delw;
    int i;
    XmString string;

    /*@ -immediatetrans -usedef @*/
    /* the root application window */
    XtSetArg(args[0], XmNwidth, LEFTSIDE_WIDTH + SATDIAG_SIZE + 26);
    XtSetArg(args[1], XmNheight, SATDATA_HEIGHT + 14*MAX_FONTSIZE + 12);
#ifdef __UNUSED__
    XtSetArg(args[2], XmNresizePolicy, XmRESIZE_NONE);
    XtSetArg(args[3], XmNallowShellResize, False);
    XtSetArg(args[4], XmNdeleteResponse, XmDO_NOTHING);
    XtSetArg(args[5], XmNmwmFunctions,
	     MWM_FUNC_RESIZE | MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE | MWM_FUNC_MAXIMIZE);
    /*@ +immediatetrans +usedef @*/
    XtSetValues(toplevel, args, 6);
#else
    /*@ +immediatetrans +usedef @*/
    XtSetValues(toplevel, args, 2);
#endif /* __UNUSED__ */

    /*@ -onlytrans @*/
    /* a form to assist with geometry negotiation */
    form = XtVaCreateManagedWidget("form", xmFormWidgetClass, toplevel, NULL);
    /* the left half of the screen */
    left = XtVaCreateManagedWidget("left", xmRowColumnWidgetClass, form,
				   XmNleftAttachment, XmATTACH_FORM,
				   XmNtopAttachment, XmATTACH_FORM,
				   NULL);
    /* the right half of the screen */
    right = XtVaCreateManagedWidget("right", xmRowColumnWidgetClass, form,
				    XmNleftAttachment, XmATTACH_WIDGET,
				    XmNleftWidget, left,
				    XmNtopAttachment, XmATTACH_FORM,
				    NULL);
    /* the application status bar */
    status = XtVaCreateManagedWidget("status", xmTextFieldWidgetClass, form,
				     XmNcursorPositionVisible, False,
				     XmNeditable, False,
				     XmNmarginHeight, 1,
				     XmNhighlightThickness, 0,
				     XmNshadowThickness, 2,
				     XmNleftAttachment, XmATTACH_FORM,
				     XmNrightAttachment, XmATTACH_FORM,
				     XmNtopAttachment, XmATTACH_WIDGET,
				     XmNtopWidget, left,
				     NULL);
    /* satellite location and SNR data panel */
    satellite_list =
      XtVaCreateManagedWidget("satellite_list", xmListWidgetClass, left,
			      XmNbackground, get_pixel(toplevel, "snow"),
			      XmNheight, SATDATA_HEIGHT,
			      XmNwidth, LEFTSIDE_WIDTH,
			      XmNlistSizePolicy, XmCONSTANT,
			      XmNhighlightThickness, 0,
			      XmNlistSpacing, 4,
			      NULL);
    /* the satellite diagram */
    satellite_diagram = 
      XtVaCreateManagedWidget("satellite_diagram",
			      xmDrawingAreaWidgetClass, right, 
			      XmNbackground, get_pixel(toplevel, "snow"),
			      XmNheight, SATDIAG_SIZE+24, XmNwidth, SATDIAG_SIZE,
			      NULL);
    gcv.foreground = BlackPixelOfScreen(XtScreen(satellite_diagram));
    gc = XCreateGC(XtDisplay(satellite_diagram),
	RootWindowOfScreen(XtScreen(satellite_diagram)), GCForeground, &gcv);
    register_canvas(satellite_diagram, gc);
    /*@i@*/XtAddCallback(satellite_diagram, XmNexposeCallback, (XtPointer)redraw, NULL);
    /* the data display */
    /*@ -immediatetrans @*/
    XtSetArg(args[0], XmNorientation, XmHORIZONTAL);
    /*@ +immediatetrans @*/
    rowColumn_11 = XtCreateManagedWidget("time     ", xmRowColumnWidgetClass, left, args, 1);

    rowColumn_12 = XtCreateManagedWidget("latitude ", xmRowColumnWidgetClass, left, args, 1);
    rowColumn_13 = XtCreateManagedWidget("longitude", xmRowColumnWidgetClass, left, args, 1);
    rowColumn_14 = XtCreateManagedWidget("altitude ", xmRowColumnWidgetClass, left, args, 1);
    rowColumn_15 = XtCreateManagedWidget("speed    ", xmRowColumnWidgetClass, left, args, 1);
    rowColumn_16 = XtCreateManagedWidget("track    ", xmRowColumnWidgetClass, left, args, 1);
    rowColumn_17 = XtCreateManagedWidget("eph      ", xmRowColumnWidgetClass, left, args, 1);
    rowColumn_18 = XtCreateManagedWidget("epv      ", xmRowColumnWidgetClass, left, args, 1);
    rowColumn_19 = XtCreateManagedWidget("climb    ", xmRowColumnWidgetClass, left, args, 1);
    rowColumn_20 = XtCreateManagedWidget("status   ", xmRowColumnWidgetClass, right, args, 1);

    label_1 = XtCreateManagedWidget("Time     ", xmLabelWidgetClass, rowColumn_11, args, 0);
    label_2 = XtCreateManagedWidget("Latitude ", xmLabelWidgetClass, rowColumn_12, args, 0);
    label_3 = XtCreateManagedWidget("Longitude", xmLabelWidgetClass, rowColumn_13, args, 0);
    label_4 = XtCreateManagedWidget("Altitude ", xmLabelWidgetClass, rowColumn_14, args, 0);
    label_5 = XtCreateManagedWidget("Speed    ", xmLabelWidgetClass, rowColumn_15, args, 0);
    label_6 = XtCreateManagedWidget("Course   ", xmLabelWidgetClass, rowColumn_16, args, 0);
    label_7 = XtCreateManagedWidget("EPH      ", xmLabelWidgetClass, rowColumn_17, args, 0);
    label_8 = XtCreateManagedWidget("EPV      ", xmLabelWidgetClass, rowColumn_18, args, 0);
    label_9 = XtCreateManagedWidget("Climb    ", xmLabelWidgetClass, rowColumn_19, args, 0);
    label_10= XtCreateManagedWidget("Status   ", xmLabelWidgetClass, rowColumn_20, args, 0);

    /*@ -immediatetrans @*/
    XtSetArg(args[0], XmNcursorPositionVisible, False);
    XtSetArg(args[1], XmNeditable, False);
    XtSetArg(args[2], XmNmarginHeight, 2);
    XtSetArg(args[3], XmNhighlightThickness, 0);
    XtSetArg(args[4], XmNshadowThickness, 1);
    XtSetArg(args[5], XmNcolumns, 23);
    /*@ +immediatetrans @*/
    text_1 = XtCreateManagedWidget("text_1", xmTextFieldWidgetClass,
				   rowColumn_11, args, 6);
    text_2 = XtCreateManagedWidget("text_2", xmTextFieldWidgetClass,
				   rowColumn_12, args, 6);
    text_3 = XtCreateManagedWidget("text_3", xmTextFieldWidgetClass,
				   rowColumn_13, args, 6);
    text_4 = XtCreateManagedWidget("text_4", xmTextFieldWidgetClass,
				   rowColumn_14, args, 6);
    text_5 = XtCreateManagedWidget("text_5", xmTextFieldWidgetClass,
				   rowColumn_15, args, 6);
    text_6 = XtCreateManagedWidget("text_6", xmTextFieldWidgetClass,
				   rowColumn_16, args, 6);
    text_7 = XtCreateManagedWidget("text_7", xmTextFieldWidgetClass,
				   rowColumn_17, args, 6);
    text_8 = XtCreateManagedWidget("text_8", xmTextFieldWidgetClass,
				   rowColumn_18, args, 6);
    text_9 = XtCreateManagedWidget("text_9", xmTextFieldWidgetClass,
				   rowColumn_19, args, 6);
    text_10 = XtCreateManagedWidget("text_10", xmTextFieldWidgetClass,
				   rowColumn_20, args, 6);

#ifdef EXPLICIT_QUIT
    quitbutton = XtCreateManagedWidget("            Quit            ",
			 xmPushButtonWidgetClass, rowColumn_20, args, 0);
    /*@i@*/XtAddCallback(quitbutton, XmNactivateCallback, (XtPointer)quit_cb, NULL);
#endif /* EXPLICIT_QUIT */

    XtRealizeWidget(toplevel);
    delw = XmInternAtom(XtDisplay(toplevel), "WM_DELETE_WINDOW", False);
    /*@ -nullpass @*//* splint 3.1.1 lacks annotated prototype... */
    /*@i@*/XmAddWMProtocolCallback(toplevel, delw,
			    (XtCallbackProc)quit_cb, (XtPointer)NULL);
    /*@ +onlytrans +nullpass @*/

    /* create empty list items to be replaced on update */
    string = XmStringCreateSimple(" ");
    for (i = 0; i <= MAXCHANNELS; i++)
	XmListAddItem(satellite_list, string, 0);
    XmStringFree(string);
}
/*@ +mustfreefresh -ignoresigns +immediatetrans @*/

static void handle_time_out(XtPointer client_data UNUSED,
			    XtIntervalId *ignored UNUSED)
/* runs when there is no data for a while */
{
    XmTextFieldSetString(status, "no data arriving");
    XmTextFieldSetString(text_10, "UNKNOWN");
}

/*
 * No dependencies on the session structure above this point.
 */

static struct gps_data_t *gpsdata;
static time_t timer;	/* time of last state change */
static int state = 0;	/* or MODE_NO_FIX=1, MODE_2D=2, MODE_3D=3 */
static int smoothing = 4;	/* # of transmitted sentences to smooth across */
static XtAppContext app;
static XtIntervalId timeout;
static enum deg_str_type deg_type = deg_dd;

static void handle_input(XtPointer client_data UNUSED, int *source UNUSED,
			 XtInputId *id UNUSED)
{
    if (gps_poll(gpsdata) < 0) {
	(void)fprintf(stderr, "Read error on server socket.");
	exit(1);
    }
}

static void update_panel(struct gps_data_t *gpsdata, 
			 char *message, 
			 size_t len UNUSED, int level UNUSED)
/* runs on each sentence */
{
    static int lfok = 0;
    unsigned int i;
    int newstate, newtxt;
    XmString string[MAXCHANNELS+1];
    char s[128], *latlon, *sp;

    if (message[0] != '\0')
	while (isspace(*(sp = message + strlen(message) - 1)))
	    *sp = '\0';
    XmTextFieldSetString(status, message);
    /* This is for the satellite status display */
    if (gpsdata->satellites) {
	string[0] = XmStringCreateSimple("PRN:   Elev:  Azim:  SNR:  Used:");
	for (i = 0; i < MAXCHANNELS; i++) {
	    if (i < (unsigned int)gpsdata->satellites) {
		(void)snprintf(s, sizeof(s),  
			       " %3d    %02d    %03d    %02d      %c", 
			       gpsdata->PRN[i],
			       gpsdata->elevation[i], gpsdata->azimuth[i], 
			       gpsdata->ss[i],	gpsdata->used[i] ? 'Y' : 'N');
	    } else
		(void)strcpy(s, "                  ");
	    string[i+1] = XmStringCreateSimple(s);
	}
	XmListReplaceItemsPos(satellite_list, string, (int)sizeof(string), 1);
	for (i = 0; i < (unsigned int)(sizeof(string)/sizeof(string[0])); i++)
	    XmStringFree(string[i]);
    }
    /* here are the value fields */
    if (isnan(gpsdata->fix.time)==0) {
	    (void)unix_to_iso8601(gpsdata->fix.time, s, (int)sizeof(s));
	    newtxt = 1;
	} else {
	    newtxt = (lfok>0) ? 0 : ((void)strcpy(s, "n/a"), 1);
	}
	if (newtxt != 0) XmTextFieldSetString(text_1, s);
	if (gpsdata->fix.mode >= MODE_2D) {
	    latlon = deg_to_str(deg_type,  fabs(gpsdata->fix.latitude));
	    newtxt = snprintf(s, sizeof(s), "%s %c", latlon, (gpsdata->fix.latitude < 0) ? 'S' : 'N');
	} else {
	    newtxt = (lfok>0) ? 0 : ((void)strcpy(s, "n/a"), 1);
	}
	if (newtxt != 0) XmTextFieldSetString(text_2, s);
	if (gpsdata->fix.mode >= MODE_2D) {
	    latlon = deg_to_str(deg_type,  fabs(gpsdata->fix.longitude));
	    newtxt = snprintf(s, sizeof(s), "%s %c", latlon, (gpsdata->fix.longitude < 0) ? 'W' : 'E');
	} else {
	    newtxt = (lfok>0) ? 0 : ((void)strcpy(s, "n/a"), 1);
	}
	if (newtxt != 0) XmTextFieldSetString(text_3, s);
	if (gpsdata->fix.mode == MODE_3D) {
	    newtxt = snprintf(s, sizeof(s), "%f %s",gpsdata->fix.altitude*altunits->factor, altunits->legend);
	} else {
	    newtxt = (lfok>0) ? 0 : ((void)strcpy(s, "n/a"), 1);
	}
	if (newtxt != 0) XmTextFieldSetString(text_4, s);
	if (gpsdata->fix.mode >= MODE_2D && isnan(gpsdata->fix.track)==0) {
	    newtxt = snprintf(s, sizeof(s), "%f %s", gpsdata->fix.speed*speedunits->factor, speedunits->legend);
	} else {
	    newtxt = (lfok>0) ? 0 : ((void)strcpy(s, "n/a"), 1);
	}
	if (newtxt != 0) XmTextFieldSetString(text_5, s);
	if (gpsdata->fix.mode >= MODE_2D && isnan(gpsdata->fix.track)==0) {
	    newtxt = snprintf(s, sizeof(s), "%f degrees", gpsdata->fix.track);
	} else {
	    newtxt = (lfok>0) ? 0 : ((void)strcpy(s, "n/a"), 1);
	}
	if (newtxt != 0) XmTextFieldSetString(text_6, s);
	if (isnan(gpsdata->fix.eph)==0) {
	    newtxt = snprintf(s, sizeof(s), "%f %s", gpsdata->fix.eph * altunits->factor, altunits->legend);
	} else {
	    newtxt = (lfok>0) ? 0 : ((void)strcpy(s, "n/a"), 1);
	}
	if (newtxt != 0) XmTextFieldSetString(text_7, s);
	if (isnan(gpsdata->fix.epv)==0) {
	    newtxt = snprintf(s, sizeof(s), "%f %s", gpsdata->fix.epv * altunits->factor, altunits->legend);
	} else {
	    newtxt = (lfok>0) ? 0 : ((void)strcpy(s, "n/a"), 1);
	}
	if (newtxt != 0) XmTextFieldSetString(text_8, s);
	if (gpsdata->fix.mode == MODE_3D && isnan(gpsdata->fix.climb)==0) {
	    newtxt = snprintf(s, sizeof(s), "%f %s/sec", gpsdata->fix.climb * altunits->factor, altunits->legend);
	} else {
	    newtxt = (lfok>0) ? 0 : ((void)strcpy(s, "n/a"), 1);
	}
	if (newtxt != 0) XmTextFieldSetString(text_9, s);

	if (gpsdata->online == 0) {
	    newstate = 0;
	    (void)snprintf(s, sizeof(s), "OFFLINE");
	    lfok = 0;
	} else {
	    newstate = gpsdata->fix.mode;
	    switch (gpsdata->fix.mode) {
	    case MODE_2D:
		(void)snprintf(s, sizeof(s), "2D %sFIX",(gpsdata->status==STATUS_DGPS_FIX)?"DIFF ":"");
		if (lfok>0) lfok--;
		break;
	    case MODE_3D:
		(void)snprintf(s, sizeof(s), "3D %sFIX",(gpsdata->status==STATUS_DGPS_FIX)?"DIFF ":"");
		lfok = smoothing;
		break;
	    default:
		(void)snprintf(s, sizeof(s), "NO FIX");
		lfok = 0;
		break;
	    }
	}
	if (newstate != state) {
	    timer = time(NULL);
	    state = newstate;
	}
	(void)snprintf(s+strlen(s), sizeof(s)-strlen(s),
		       " (%d secs)", (int) (time(NULL) - timer));
	XmTextFieldSetString(text_10, s);
	draw_graphics(gpsdata);

	XtRemoveTimeOut(timeout);
	timeout = XtAppAddTimeOut(app, 2000, handle_time_out, NULL);
    }

    static char *get_resource(Widget w, char *name, char *default_value)
    {
      XtResource xtr;
      char *value = NULL;

      /*@ -observertrans -statictrans -immediatetrans -compdestroy @*/
      xtr.resource_name = name;
      xtr.resource_class = "AnyClass";
      xtr.resource_type = XmRString;
      xtr.resource_size = (Cardinal)sizeof(String);
      xtr.resource_offset = 0;
      xtr.default_type = XmRImmediate;
      xtr.default_addr = default_value;
      XtGetApplicationResources(w, &value, &xtr, 1, NULL, 0);
      if (value) return value;
      /*@ +observertrans +statictrans +immediatetrans +compdestroy @*/
      /*@i@*/return default_value;
    }

    /*@ -mustfreefresh @*/
    int main(int argc, char *argv[])
    {
	int option;
	char *arg = NULL, *colon1, *colon2, *device = NULL, *server = NULL, *port = DEFAULT_GPSD_PORT;
	char *su, *au;
	char *err_str = NULL;

	/*@ -onlytrans */
	toplevel = XtVaAppInitialize(&app, "xgps", 
				   options, XtNumber(options), 
				   &argc,argv, fallback_resources,NULL);

	/*@ +onlytrans */
	su = get_resource(toplevel, "speedunits", "mph");
	for (speedunits = speedtable; 
	     speedunits < speedtable + sizeof(speedtable)/sizeof(speedtable[0]);
	     speedunits++)
	    if (strcmp(speedunits->legend, su) == 0)
		goto speedunits_ok;
	speedunits = speedtable;
	(void)fprintf(stderr, "xgps: unknown speed unit, defaulting to %s\n", speedunits->legend);
    speedunits_ok:;

	au = get_resource(toplevel, "altunits",   "feet");
	for (altunits = alttable; 
	     altunits < alttable + sizeof(alttable)/sizeof(alttable[0]);
	     altunits++)
	    if (strcmp(altunits->legend, au) == 0)
		goto altunits_ok;
	altunits = alttable;
	(void)fprintf(stderr, "xgps: unknown altitude unit, defaulting to %s\n", altunits->legend);
    altunits_ok:;

	while ((option = getopt(argc, argv, "hl:s:V")) != -1) {
	    switch (option) {
	    case 'V':
		(void)printf("xgps %s\n", VERSION);
		exit(0);
	    case 'l':
		switch ( optarg[0] ) {
		case 'd':
		    deg_type = deg_dd;
		    continue;
		case 'm':
		    deg_type = deg_ddmm;
		    continue;
		case 's':
		    deg_type = deg_ddmmss;
		    continue;
		default:
		    (void)fprintf(stderr, "Unknown -l argument: %s\n", optarg);
		    /*@ -casebreak @*/
		}
	case 's':
	    smoothing = atoi(optarg);
	    break;
	case 'h': default:
	    (void)fputs("usage:  xgps [-hv] [-speedunits {mph,kph,knots}] [-altunits {ft,meters}] [-l {d|m|s}] [-s smoothing] [server[:port:[device]]]\n", stderr);
	    exit(1);
	}
    }
    /*@ -branchstate @*/
    if (optind < argc) {
	arg = strdup(argv[optind]);
	/*@i@*/colon1 = strchr(arg, ':');
	server = arg;
	if (colon1 != NULL) {
	    if (colon1 == arg)
		server = NULL;
	    else
		*colon1 = '\0';
	    port = colon1 + 1;
	    colon2 = strchr(port, ':');
	    if (colon2 != NULL) {
		if (colon2 == port)
		    port = NULL;
	        else
		    *colon2 = '\0';
		device = colon2 + 1;
	    }
	}
	colon1 = colon2 = NULL;
    }
    /*@ +branchstate @*/

    /*@i@*/gpsdata = gps_open(server, port);
    if (!gpsdata) {
	switch ( errno ) {
	case NL_NOSERVICE: 	err_str = "can't get service entry"; break;
	case NL_NOHOST: 	err_str = "can't get host entry"; break;
	case NL_NOPROTO: 	err_str = "can't get protocol entry"; break;
	case NL_NOSOCK: 	err_str = "can't create socket"; break;
	case NL_NOSOCKOPT: 	err_str = "error SETSOCKOPT SO_REUSEADDR"; break;
	case NL_NOCONNECT: 	err_str = "can't connect to host"; break;
	default:             	err_str = "Unknown"; break;
	}
	(void)fprintf( stderr, 
		       "xgps: no gpsd running or network error: %d, %s\n", 
		       errno, err_str);
	exit(2);
    }

    build_gui(toplevel);

    timeout = XtAppAddTimeOut(app, 2000, handle_time_out, app);
    timer = time(NULL);

    gps_set_raw_hook(gpsdata, update_panel);

    if (device) {
	char *channelcmd = (char *)malloc(strlen(device)+3);

	if (channelcmd) {
	    /*@i1@*/(void)strcpy(channelcmd, "F=");
	    (void)strcpy(channelcmd+2, device);
	    (void)gps_query(gpsdata, channelcmd);
	    (void)free(channelcmd);
	}
    }
	
    (void)gps_query(gpsdata, "w+x\n");

    (void)XtAppAddInput(app, gpsdata->gps_fd, 
		  (XtPointer)XtInputReadMask, handle_input, NULL);
    (void)XtAppMainLoop(app);

    (void)gps_close(gpsdata);
    if (arg != NULL)
	(void)free(arg);
    return 0;
}
/*@ +mustfreefresh @*/


syntax highlighted by Code2HTML, v. 0.9.1