#!/packages/bin/perl

require 5.004;

# Set DEBUG equal to 1 to print debugging information
local ($DEBUG) = 0;
$| = 1 if ($DEBUG == 1);                        # Force prints when debugging

local ($VERSION) = "1.1.0";			# Current version
local ($COPYRIGHT) = "
Copyright (c) 2000 Xerox Corporation.  All Rights Reserved.

Permission to use, copy, modify  and  distribute  without  charge
this  software,  documentation, images, etc. is granted, provided
that this copyright and the author's name is retained.
    
A fee may be charged for this program ONLY to recover  costs  for
distribution  (i.e.  media costs).  No profit can be made on this
program.
        
The author assumes no responsibility for  disasters  (natural  or
otherwise) as a consequence of use of this software.
            
Adam Stein (adam\@scan.mc.xerox.com)
";

use Net::ICal;
use Calendar::CSA;
use Time::ParseDate;
use Time::Timezone;
use Date::Calc qw(Add_Delta_DHMS);

# Global variables
local ($program);				# Name of current program

# For date/time parsing operation
local(%options) = ("WHOLE" => 1, "DATE_REQUIRED" => 1, "TIME_REQUIRED" => 1,
		   "NO_RELATIVE" => 1);

MAIN:
{
  my ($calendar),				# Calendar to connect to
  my ($cluster),				# Vcalendar cluster
  my ($handle);					# Handle to CDE calendar

  # Set name of current program
  ($program = $0) =~ s#.*/##;

  # We can have an optional argument to specify the calendar to connect to
  if ($ARGV[0] eq "-c") {
    $calendar = $ARGV[1];
    shift; shift;
  }

  # It's bad to have more than 1 command line argument at this point
  (scalar(@ARGV) <= 1) || die "usage: $program [-c name\@machine] [file]\n";

  # If we don't get the calendar from the command line, get it from
  # the environment variable "VCAL".  If we can't get it from there, complain
  $calendar = $ENV{"VCAL"} if (!defined($calendar));
  die "$program: can't figure out which calendar to connect to\n"
	if (!defined($calendar));

  # Open a connection to the CDE calendar
  $handle = &OpenCalendar($calendar);

  # Read in vcalendar file
  &ReadVCalendar(\@ARGV, \$cluster);

  foreach ($cluster->components) {
    SWITCH: {
      &AddEvent($handle, $_), next if ($_->type eq "VEVENT");

      print "$program: can't handle <", $_->type, "> types yet\n";
    }
  }

  # Close the connection to the calendar
  &CloseCalendar($handle);
}

# Read a vcalendar entry and create a vcalendar object from it
sub ReadVCalendar {
  my ($argv) = shift,				# Command line arguments
  my ($cluster) = shift,			# VCalendar object
  my ($text);					# Text from file

  # To make sure we get the whole think in one gulp
  undef $/;

  # If we don't have a filename, then read from STDIN
  if (scalar(@ARGV) == 0) {
    $text = <>;
  } else {
    open(FILE, $$argv[0]) || die "$program: can't open <$$argv[0]>\n";
    $text = <FILE>;
    close(FILE);
  }

  # Change back to normal
  $/ = "\n";

  # Create vcalendar object
  $$cluster = new Net::ICal::Component(\$text);

  # Make sure it is a vcalendar object
  die "$program: can't find vcalender component\n"
	if ($$cluster->type ne "VCALENDAR");
}

# Add an event to the CDE calendar
sub AddEvent {
  my ($handle) = shift,				# Handle to CDE calendar
  my ($event) = shift,				# Event to add
  my ($end),					# End of event
  my ($loc),					# Location of event
  my ($start),					# Start of event
  my ($summary);				# Summary of event

  # Get starting/ending times for event
  $start = &GetField($event, "DTSTART");
  $end = &GetField($event, "DTEND");

  # Get event location
  $loc = &GetField($event, "LOCATION");
  $loc = "($loc)" if (substr($loc, 0, 1) ne "(");

  # Get event summary
  $summary = &GetField($event, "SUMMARY");

  # Add appointment to CDE calendar
  &CreateAppt($handle, $start, $end, $summary, $loc);
}

# Get fields
sub GetField {
  my ($event) = shift,				# Event to get field from
  my ($field) = shift,				# Field to get
  my ($value);					# Field value

  $value = ($event->properties($field))[0]->as_ical_string;
  $value =~ s/^[^:]*://s;			# Remove everything before :
  $value =~ s/\015//g;				# Remove Windows ^Ms
  chomp($value);

  return($value);
}

# Open a connection to a CDE calendar
sub OpenCalendar {
  my ($calendar) = shift,			# Calendar to connect to
  my ($handle);					# Handle to CDE calendar

  eval {
    $handle = Calendar::CSA::logon("",
				   {
				     'user_name' => "$calendar",
				     'user_type' => 'INDIVIDUAL',
				     'calendar_address' => "$calendar",
				   })
  };

  # Croak if we can't connect to the calendar
  die "$program: can't connect to calendar\n" if ($@ ne "");

  if ($handle) {
    $handle->short_entry_names(1);
    $handle->unix_times(1);
  }

  return($handle);
}

# Close a connection to a CDE calendar
sub CloseCalendar {
  my ($handle) = shift;                        # Handle to CDE calendar

  eval { $handle->logoff; };

  die "$program: couldn't close connection to calendar\n" if ($@);
}

# Add appointment to a CDE calendar
sub CreateAppt {
  my ($handle) = shift,				# Handle to CDE calendar
  my ($start) = shift,				# Start of appointment
  my ($end) = shift,				# End of appointment
  my ($summary) = shift,			# Appointment
  my ($loc) = shift,				# Location of appointment
  my (%appt),					# Appointment structure
  my ($etime),					# Ending time
  my ($stime);					# Starting time

  # Get starting/ending times in the format CSA needs it
  $stime = parsedate(&ParseDate($start), %options);
  $etime = parsedate(&ParseDate($end), %options);

  $appt{"Type"} = &data("UINT32", 0);
  $appt{"Subtype"} = &data("STRING", "Subtype Appointment");

  $appt{"Start Date"} = &data("DATE TIME", $stime);
  $appt{"End Date"} = &data("DATE TIME", $etime);
  $appt{"Show Time"} = &data("SINT32", 1);

  # Add location info to the appt description if we have it
  if (defined($loc)) {
    $appt{"Summary"} = &data("STRING", "$summary\n$loc");
  } else {
    $appt{"Summary"} = &data("STRING", $summary);
  }

  eval { $new = $handle->add_entry(%appt); };

  if ($@) {
    print "Error: $@\n";
    exit(1);
  }
}

# This comes from PilotManager
sub data
{
    my ($type, $value) = @_;

    # Make sure that the integer values aren't represented
    # as a string.  If they go to the Calendar::CSA layer as
    # strings, CSA assumes that they are in CSA's ISO format
    # and won't convert them.
    #
    if ($type =~ /^(DATE TIME|SINT32|UINT32|)$/)
    {
        $value += 0;
    }

    return ({
              'type' => $type,
              'value' => $value,
            });
}

# Parse a date to return the form "mm/dd/yy hh:mm:ss"
sub ParseDate {
  my ($date) = shift,				# Date to parse
  my (@date);					# Timezone corrected date

  $date =~ /(....)(..)(..)T(..)(..)(..)(Z?)/;

  # Need to offset the date/time by our timezone (if given UTC)
  @date = Add_Delta_DHMS($1, $2, $3, $4, $5, $6, 0, 0, 0,
			 ($7) ? tz_local_offset() : 0);

  return(sprintf("%02d/%02d/%4d %02d:%02d:%02d", $date[1], $date[2], $date[0],
		 $date[3], $date[4], $date[5]));
}

