#!/bin/zsh
# All arguments are joined with spaces and inserted into the calendar
# file at the appropriate point.
#
# While the function compares the date of the new entry with dates in the
# existing calendar file, it does not do any sorting; it inserts the new
# entry before the first existing entry with a later date and time.

emulate -L zsh
setopt extendedglob

local context=":datetime:calendar_add:"

local calendar newfile REPLY lastline opt
local -a calendar_entries lockfiles reply
integer my_date done rstat nolock nobackup new_recurring old_recurring
local -A reply parse_new parse_old recurring_uids

autoload -U calendar_{parse,read,lockfiles}

while getopts "BL" opt; do
  case $opt in
    (B)
    nobackup=1
    ;;

    (L)
    nolock=1
    ;;

    (*)
    return 1
    ;;
  esac
done
shift $(( OPTIND - 1 ))

# Read the calendar file from the calendar-file style
zstyle -s $context calendar-file calendar ||
  calendar=~/calendar
newfile=$calendar.new.$HOST.$$

local addline="$*"
if ! calendar_parse $addline; then
  print "$0: failed to parse date/time" >&2
  return 1
fi
parse_new=("${(@kv)reply}")
(( my_date = $parse_new[time] ))
[[ -n $parse_new[rpttime] ]] && (( new_recurring = 1 ))
if zstyle -t $context reformat-date; then
  local datefmt
  zstyle -s $context date-format datefmt ||
    datefmt="%a %b %d %H:%M:%S %Z %Y"
  strftime -s REPLY $datefmt $parse_new[time]
  addline="$REPLY $parse_new[text1]"
fi

# $calendar doesn't necessarily exist yet.

local -a match mbegin mend
local my_uid their_uid

# Match a UID, a unique identifier for the entry inherited from
# text/calendar format.
local uidpat='(|*[[:space:]])UID[[:space:]]##(#b)([[:xdigit:]]##)(|[[:space:]]*)'
if [[ $addline = ${~uidpat} ]]; then
  my_uid=${(U)match[1]}
fi

# start of block for following always to clear up lockfiles.
{
  (( nolock )) || calendar_lockfiles $calendar || return 1

  if [[ -f $calendar ]]; then
    calendar_read $calendar

    if [[ -n $my_uid ]]; then
      # Pre-scan to find recurring events with a UID
      for line in $calendar_entries; do
	calendar_parse $line  ||  continue
	# Recurring with a UID?
	if [[ -n $reply[rpttime] && $line = ${~uidpat} ]]; then
	  # Yes, so record this as a recurring event.
	  their_uid=${(U)match[1]}
	  recurring_uids[$their_uid]=$reply[time]
	fi
      done
    fi

    {
      for line in $calendar_entries; do
	calendar_parse $line  ||  continue
	parse_old=("${(@kv)reply}")
	if (( ! done && ${parse_old[time]} > my_date )); then
	  print -r -- $addline
	  (( done = 1 ))
	fi
	if [[ -n $parse_old[rpttime] ]]; then
	  (( old_recurring = 1 ))
	else
	  (( old_recurring = 0 ))
	fi
	if [[ -n $my_uid && $line = ${~uidpat} ]]; then
	  their_uid=${(U)match[1]}
	  if [[ $my_uid = $their_uid ]]; then
	    # Deal with recurrences, being careful in case there
	    # are one-off variants that don't replace recurrences.
	    #
	    # Bug 1: "calendar" still doesn't know about one-off variants.
	    # Bug 2: neither do I; how do we know which occurrence
	    # it replaces?
	    # Bug 3: the code for calculating recurrences is awful anyway.

	    if (( old_recurring && new_recurring )); then
	      # Replacing a recurrence; there can be only one.
	      continue
	    elif (( ! new_recurring )); then
	      # Not recurring.  See if we have previously found
	      # a recurrent version
	      [[ -n $recurring_uids[$their_uid] ]] && (( old_recurring = 1 ))
	      # No, so assume this is a straightforward replacement
	      # of a non-recurring event.
	      (( ! old_recurring )) && continue
	      # It's recurring, but if this is a one-off at the
	      # same time as the previous one, replace anyway.
	      [[ -z $parse_old[$rpttime] ]] &&
	        (( ${parse_new[time]} == ${parse_old[time]} )) &&
		continue
	    fi
	  fi
	fi
	if [[ $parse_old[time] -eq $my_date && $line = $addline ]]; then
	  (( done )) && continue # paranoia: shouldn't happen
	  (( done = 1 ))
	fi
	print -r -- $line
      done
      (( done )) || print -r -- $addline
    } >$newfile
    if (( ! nobackup )); then
      if ! mv $calendar $calendar.old; then
	print "Couldn't back up $calendar to $calendar.old.
New calendar left in $newfile." >&2
	(( rstat = 1 ))
      fi
    fi
  else
    print -r -- $line >$newfile
  fi

  if (( !rstat )) && ! mv $newfile $calendar; then
    print "Failed to rename $newfile to $calendar.
Old calendar left in $calendar.old." >&2
    (( rstat = 1 ))
  fi
} always {
  (( ${#lockfiles} )) && rm -f $lockfiles
}

return $rstat
