#!/usr/bin/env ruby
require 'backpack_calendar'
BACKPACK_ICON = File.join(
  ENV['HOME'],
  'Library', 'Application Support',
  %w{Quicksilver PlugIns},
  'Backpack Module.qsplugin',
  %w{Contents Resources Backpack.png}
)
APPLICATION = "Backpack Calendar"
KEY = "Backpack API"

def notify(message, opts = {})
  notifications = %q{{"Confirmation", "Error"}}
  opts = {
    :type   => :confirmation,
    :title  => APPLICATION,
    :sticky => false,
  }.merge(opts)
  message.gsub!(/\"/, '\"') # escape double quotes for AppleScript
  image = File.exist?(BACKPACK_ICON) ? 
    %{image from location "#{BACKPACK_ICON}"} : ""
  cmd = <<-END.gsub(/^\s+/m, '')
    /usr/bin/osascript <<AS
      tell application "GrowlHelperApp"
        register as application "#{APPLICATION}" \
          all notifications #{notifications} \
          default notifications #{notifications}
        notify with \
          name "#{opts[:type].to_s.capitalize}" \
          title "#{opts[:title]}" \
          description "#{message}" \
          application name "#{APPLICATION}" \
          #{image} \
          sticky #{opts[:sticky]}
      end tell
    AS
  END
  system(cmd)
end

begin
  password = %x{security find-generic-password -s '#{KEY}' -g 2>&1}
  token = password.grep(/password:/).first.match(/: "([^"]+)"/)[1]
  username = password.grep(/"acct"[^=]*=/).first.match(/="([^"]+)"/)[1]
rescue
  msg = "No key found in keychain. Add a key called '#{KEY}' " +
    "with your Backpack user name as the account name " +
    "and your API token as the password."
  notify(msg, :type => :error, :sticky => true)
end

# event text
post = ARGV.join(" ")

# calendar list
session = Backpack::Calendar.new("http://#{username}.backpackit.com", token)
begin
  calendars = session.calendars
rescue
  notify("Login failed. Check your API token.", :type => :error)
  exit
end

# reminder?
post.sub!(/(\s+\+rem.*)\b/, '')
remind = !$1.nil?

# pick calendar
post.sub!(/\s*\[([^\]]+)\]/, '')
calendar = if $1 
  calendars.find do |calendar|
    calendar.name.downcase == $1.downcase
  end or calendars.first
else
  calendars.first
end

# post it
response = session.post_calendar_event(
  calendar.id, :title => post, :remind => remind
)

if response
  event_id = response.match(%r{/(\d+).xml}).captures.first
  event = session.calendar_event(event_id)
  event_time = event.all_day ? event.occurs_at : event.occurs_at.localtime
  now = Time.now
  date_format =
    if event_time.to_a[3..5] == now.to_a[3..5]
      nil         # today
    elsif (event_time - 86400).to_a[3..5] == now.to_a[3..5]
      "tomorrow"
    elsif (event_time - now) < 604800  # one week
      "%A"        # day of the week
    elsif now.year != event_time.year
      "%e %B %Y"  # 14 April 2007
    else
      "%e %B"     # 14 April
    end
  date = date_format && event_time.strftime(date_format)
  notify(
    [date, event.title, event.remind ? "reminder" : nil].compact.join("\n")
  )
else
  notify("Event post failed", :type => :error)
end
