// stand-alone appointment creation for ical
//
// compile as
//
//   cc -g -o ~/bin/irix/addappt addappt.c
//
// seth teller, march 2003

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>

/* 

this stand-alone program can be invoked from the shell to
add a notice or appointment that will be incorporated into
ical's gui and appointment-listing functions.  this program
appends its appointment listings to the file

    $HOME/.auxcalendar

where $HOME is your home directory as reported by getenv().

to make ical aware of this file, you need to 
incorporated in your main ical calendar file, add the line e.g.

Include [23 [/home/seth/.auxcalendar]]

to the file (where 23 is the length of the included filename)

the format for a one-shot appt is:

Appt [				# appointment header
Start [900]             	# number of minutes since midnight
Length [120]            	# appt duration in minutes
Uid [larch.lcs.mit.edu_0_716_a] # hostname, unique integer(s)
Owner [user] 			# creator of appointment
Text [16 [Description Text]]    # description of appointment
Remind [1]			# enable gui reminders
Hilite [always]			# enable gui display
Dates [Single 20/9/1994 End 	# date format is dd/[m]m/yyyy
]
]

format for a notice is similar (header is Note; Start field is omitted)

see usage() function below for several examples of valid usage

BUGS/ENHANCEMENTS:

  doesn't know how to create repeating appointments
  only understands meeting lengths in multiples of 5 minutes
  doesn't know how many days are in each month (so 30 feb is OK)

*/

/* allow durations specified in minutes, multiples of 5 up to two hours */
int interpretduration ( const int val )
{
  if ( val % 5 == 0 && 5 <= val && val <= 115 )
    return ( val );
  else
    return -1;
}

/* returns decimal duration (e.g. 30 minutes) or -1 if failure */
int parseduration ( const char *s, int verbose )
{
int len;
int val;

  // format is 15m, 30m, 90m, 1h etc.
  len = strlen ( s );
   
  if ( len >= 2 && s[len-1] == 'm' ) {
    if ( ( sscanf ( s, "%d", &val ) == 1 ) && ( interpretduration ( val ) > 0 ) )
        return ( interpretduration( val ) );
    }
  else if ( len >= 2 && s[len-1] == 'h' ) {
    if ( ( sscanf ( s, "%d", &val ) == 1 ) && ( 1 <= val && val <= 12 ) )
        return ( val * 60 );
    }

  if ( verbose )
    fprintf ( stderr, "Error in duration spec '%s':  use format 15m, 30m, 45m, 75m, 90m, 1h, 2h etc.\n", s );

  return -1;
}

/* convert a decimal time specification into number of minutes since midnight */
int interprettime ( const int val )
{
  if ( (1 <= val) && (val <= 12) )
    return ( 60 * (val % 12) );
  else if ( (100 <= val) && (val < 1200) && (val % 100 <= 45) )
    return 60 * (val / 100) + (val % 100);
  else if ( (1200 <= val) && (val <= 1245) && (val % 100 <= 45) )
    return ( val % 100 );
  else
    return -1;
}

/* returns decimal time (e.g. 1330 for 130pm) or -1 if failure */
int parsetime ( const char *s, int verbose )
{
int len;
int val;

  // format is 1pm, 130pm, or same with am at end
  len = strlen ( s );
   
  if ( len >= 3 && s[len-2] == 'a' && s[len-1] == 'm' ) {
    if ( ( sscanf ( s, "%d", &val ) == 1 ) && ( interprettime (val) >= 0 ) )
        return ( interprettime(val) );
    }
  else if ( len >= 3 && s[len-2] == 'p' && s[len-1] == 'm' ) {
    if ( ( sscanf ( s, "%d", &val ) == 1 ) && ( interprettime (val) >= 0 ) )
	  return ( 720 + interprettime(val) );  // shift 12 hours
    }

  if ( verbose )
    fprintf ( stderr, "Error in time spec `%s':  use format 115pm, 1pm, 430am, 1245pm, etc.\n", s );

  return -1;
}

/* returns day of month, 1..31, or -1 if failure */
int parseday ( const char *s, int verbose )
{
int val;

  if ( ( sscanf ( s, "%d", &val ) == 1 ) && 1 <= val && val <= 31 )
    return val;

  if (verbose)
    fprintf ( stderr, "Error in day spec `%s':  specify day between 1 and 31\n", s );

  return -1;
}

/* array of month names, note indexing is one-relative */
char monthnames[13][10] = {
	"null",
	"January", 
	"February",
 	"March",
	"April",
	"May",
	"June",
	"July",
	"August",
	"September",
	"October",
	"November",
	"December" };

/* returns month number, 1..12, or -1 if failure */
int parsemonth ( const char *s, int verbose )
{
int k;

  for ( k = 1; k <= 12; k++ )
    if ( !strncasecmp ( s, monthnames[k], 3 ) ) 
      return k;

  if (verbose)
    fprintf ( stderr, "Error in month spec `%s':  specify jan, feb, mar etc.\n", s );

  return -1;
}

/* returns year as decimal, 20xx, or -1 if failure */
int parseyear ( const char *s, int verbose )
{
int val;

  if ( ( sscanf ( s, "%d", &val ) == 1 ) && 2000 <= val && val <= 2099 )
    return val;

  if (verbose)
    fprintf ( stderr, "Error in year spec `%s':  specify year between 2000 and 2099\n", s );

  return -1;
}

/* emit a usage message with examples */
void
usage ( char *progname )
{
  fprintf ( stderr, "\nusage: %s [[duration: 15m, 1h etc.] [330pm] 17 feb [year] description description ... ]\n",
            progname );

  fprintf ( stderr, "examples of valid usage:\n" );

  fprintf ( stderr, "  %s -\n", progname );

  fprintf ( stderr, "  %s 17 feb description description ...\n", progname );

  fprintf ( stderr, "  %s 17 feb 2004 description description ...\n", progname );

  fprintf ( stderr, "  %s 330pm 17 feb description description ...\n", progname );

  fprintf ( stderr, "  %s 45m 330pm 17 feb description description ...\n", progname );

  fprintf ( stderr, "  %s 2h 915am 17 feb 2004 description description ...\n", progname );

  fprintf ( stderr, "  %s 90m 330pm 17 feb 2004 description description ...\n", progname );

  fprintf ( stderr, "\n" );

  exit ( -1 );
}

/* collect all description arguments into one string, return its length */
int
collectdesc ( int arg, int argc, char *argv[], char *desc )
{
int len = 0;

  while ( arg < argc ) {
    if ( len == 0 ) {
      sprintf ( &desc[len], "%s", argv[arg] );
	  len += (strlen (argv[arg]));
	}
    else {
      sprintf ( &desc[len], " %s", argv[arg] );
	  len += (1 + strlen (argv[arg]));
	}
    arg++;
  }

  return len;
}

/* emit an appointment descriptor */
void emitappt ( FILE *af, char *user, char *host, 
                int duration, int time, int day, int month, int year, int dlen, char *desc )
{
  fprintf ( af, "Appt [\n" );
  fprintf ( af, "Start [%d]\n", time );
  fprintf ( af, "Length [%d]\n", duration );
  fprintf ( af, "Uid [%s_%d_%x]\n", host, getpid(), (srand(getpid()), rand()) );
  fprintf ( af, "Owner [%s]\n", user );
  fprintf ( af, "Text [%d [%s]]\n", dlen, desc );
  fprintf ( af, "Remind [1]\n" );
  fprintf ( af, "Hilite [always]\n" );
  fprintf ( af, "Dates [Single %d/%d/%d End\n", day, month, year );
  fprintf ( af, "]\n");
  fprintf ( af, "]\n");
}

/* emit a notice descriptor */
void emitnote ( FILE *af, char *user, char *host,
                int day, int month, int year, int dlen, char *desc )
{
  fprintf ( af, "Note [\n");
  fprintf ( af, "Length [30]\n" ); // dummy line, but ical needs it
  fprintf ( af, "Uid [%s_%d_%x]\n", host, getpid(), (srand(getpid()), rand()) );
  fprintf ( af, "Owner [%s]\n", user );
  fprintf ( af, "Text [%d [%s]]\n", dlen, desc );
  fprintf ( af, "Remind [1]\n" );
  fprintf ( af, "Hilite [always]\n" );
  fprintf ( af, "Dates [Single %d/%d/%d End\n", day, month, year );
  fprintf ( af, "]\n");
  fprintf ( af, "]\n");
}

/* determine current month and year */
void
getmonthyear ( int *thismonth, int *thisyear )
{
  time_t t = time( NULL );
  struct tm *ltime = localtime( &t );
  *thismonth = ltime->tm_mon + 1;
  *thisyear = 1900 + ltime->tm_year;
}

main( int argc, char *argv[] )
{
int arg = 1, thismonth, thisyear;
int duration, time, day, month, year, dlen;
char desc[1024], cmd[1024];
char *user, *host, *home, calfile[1024];
FILE *af;

  // special case:  no arguments, just check this week's appts
  if ( argc == 1 ) {
    sprintf ( cmd, "/usr/local/bin/ical -show +7" );
    printf ("invoking `%s':\n", cmd );
    system ( cmd );
    printf ("\n");
    usage ( argv[0] );
    }

  // special case:  1 argument "-", just emit usage message
  if ( argc == 2 && !strcmp ( argv[1], "-" ) ) {
    usage ( argv[0] );
    }

  // see if duration string is present
  duration = parseduration ( argv[arg], 0 );
  if ( duration > 0 )
    arg++;

  // see if time string is present
  time = parsetime ( argv[arg], 0 );
  if ( time >= 0 )
    arg++;
  else if ( duration > 0 ) // duration specified, but no time:  error
	usage ( argv[0] );

  if ( duration <= 0 )
    duration = 30;
 
  day = parseday ( argv[arg], 1 );
  if ( day < 0 ) usage( argv[0] );
  arg++;

  month = parsemonth ( argv[arg], 1 );
  if ( month < 0 ) usage( argv[0] );
  arg++;

  year = parseyear ( argv[arg], 0 );
  if ( year > 0 )
    arg++;
  else { // no year supplied, infer it
    getmonthyear ( &thismonth, &thisyear );
    if ( thismonth <= month )
      year = 2003;
    else
      year = 2004;

    fprintf ( stderr, "%s: no year supplied, assuming %d!\n", argv[0], year );
    }

  dlen = collectdesc ( arg, argc, argv, desc );
  if ( dlen < 1 ) usage( argv[0] );

  home = getenv ( "HOME" );
  sprintf ( calfile, "%s/.auxcalendar", home ? home : "" );

  // if cal file does not exist, create and initialize it
  af = fopen ( calfile, "r" );
  if ( !af ) {
    af = fopen ( calfile, "w" );
    if ( !af ) {
      fprintf ( stderr, "cannot open calendar file %s for write\n", calfile );
      exit ( -1 );
      }
    fprintf ( af, "Calendar [v1.7]\n" );
    fprintf ( stderr, "%s: Created auxiliary calendar file %s\n", argv[0], calfile );
    fprintf ( stderr, "to make ical aware of it, add the line:\n" );
    fprintf ( stderr, "Include [%d [%s]]\n", strlen(calfile), calfile );
    fprintf ( stderr, "to your main ical file (probably ~/.calendar)\n" );
    }
  fclose ( af );

  af = fopen ( calfile, "a" );
  if ( !af ) {
    fprintf ( stderr, "cannot open calendar file %s for append\n", calfile );
    exit ( -1 );
    }

  user = getenv ( "USER" );
  host = getenv ( "HOST" );
  if ( time < 0 )
    emitnote ( af, user, host, day, month, year, dlen, desc );
  else
    emitappt ( af, user, host, duration, time, day, month, year, dlen, desc );

  fclose ( af );

  sprintf ( cmd, "ical -calendar %s -date \"%s %d, %d\" -show +0",
	    calfile, monthnames[month], day, year );
  printf ("invoking `%s':\n\n", cmd );
  system ( cmd );
  printf ("\n\n");
}

