#!/usr/bin/env ruby

# A script to continously check the parcel tracking system of the Swedish mail
# service (Posten) for updates.
# Copyright (C) 2008 Joakim Andersson
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# The GNU General Public License can be found at
# <http://www.gnu.org/licenses/>.

require 'net/http'
require 'net/smtp'
require 'rexml/document'
require 'time'
require 'optparse'

# Default options
$smtp_server = 'localhost'
$smtp_port = 25
$mailto = nil
help = false

# Parse command line arguments
opts = OptionParser.new do |opts|
	opts.banner = "Användning: #{opts.program_name} [alternativ] KOLLI-ID"

	opts.separator ""
	opts.separator "Alternativ:"

	opts.on("-m", "--mailto=MAIL", "Skicka statusändringar till MAIL också, förutom stdout.") do |m|
		raise OptionParser::InvalidArgument if ! m.match(/^[a-zA-Z0-9_\.-]+@[a-zA-Z0-9\.-]+\.[a-zA-Z]{2,4}$/) 
		$mailto = m
	end

	opts.on("-s", "--server=HOST[:PORT]", "Skicka mail med SMTP-servern HOST[:PORT] (default är #{$smtp_server}:#{$smtp_port}).") do |s|
		s = s.split(":");
		raise OptionParser::InvalidArgument if s.length > 2
		$smtp_server = s[0]
		$smtp_port = s[1] if s.length == 2
	end

	opts.on_tail("-h", "--help", "Skriv ut den här hjälpen.") do |h|
		help = true
		puts opts
	end
end

begin
	opts.parse!(ARGV)
rescue Exception => e
	puts e, "Skriv `#{File.basename($0, '.*')} --help' för mer information."
	exit 1
end

if help
	exit 0
end

$parcel_id = $*[0].to_s
if $parcel_id.length < 9
	puts "KOLLI-ID måste vara minst 9 tecken långt!","",opts
	exit 1
end

# Exit without error when killed
trap ("INT") { exit 0 }
trap ("TERM") { exit 0 }

# Get events for a specified parcel id
def get_events(prev_parcels)
	# Parcel search URL
	url = "http://server.logistik.posten.se/servlet/PacTrack?lang=SE&kolliid=#{$parcel_id}"

	# Get the XML data as a string
	begin
		xml_data = Net::HTTP.get_response(URI.parse(url)).body
	rescue => e
		STDERR.puts("Error while fetching data: #{e}\n")
	end

	# Extract event information
	parcels = Hash.new
	begin
		doc = REXML::Document.new(xml_data)
		doc.elements.each('pactrack/body/parcel') do |parcel|
			id = parcel.attributes['id']

			event_dates = Array.new
			event_times = Array.new
			event_locs = Array.new
			event_descs = Array.new
			parcel.elements.each('event/date') do |date|
				date_string = date.text
				event_dates << "#{date_string[0..3]}-#{date_string[4..5]}-#{date_string[6..7]}"
			end
			parcel.elements.each('event/time') do |time|
				time_string = time.text
				event_times << "#{time_string[0..1]}:#{time_string[2..3]}"
			end
			parcel.elements.each('event/location') do |loc|
				event_locs << loc.text
			end
			parcel.elements.each('event/description') do |desc|
				event_descs << desc.text
			end

			events = Array.new
			event_dates.each_with_index do |date, i|
				time = Time.parse("#{date} #{event_times[i]}")
				events << [time, "#{event_locs[i]}: #{event_descs[i]}"]
			end
			events.sort! { |a,b| b[0]<=>a[0] }

			parcels[id] = events
		end

		return parcels

	rescue => e
		STDERR.puts "Got erroneous data from server! Assuming problem is temporary.\n"

		return prev_parcels
	end

end

# Send mail about changed status
def send_mail(report)
	Net::SMTP.start($smtp_server, $smtp_port) do |smtp|
		smtp.open_message_stream($mailto, $mailto) do |f|
			f.puts "From: #{$mailto}"
			f.puts "To: #{$mailto}"
			f.puts "Subject:  Statusändring för kollisökning \"#{$parcel_id}\""
			f.puts
			f.puts report
		end
	end
end



# Check for new events every 5 minutes
old_parcels = Hash.new
while true do
	parcels = get_events(old_parcels)
	report = Array.new
	parcels.each_key do |parcel|
		if old_parcels[parcel]
			parcel_diff = parcels[parcel] - old_parcels[parcel]
		else
			parcel_diff = parcels[parcel]
		end
		if parcel_diff.length > 0
			report << "#{parcel}:"
			parcel_diff.each do |time,event|
				report << "\t#{time.strftime("%A, %B %d %Y, %H:%M")} - #{event}"
			end
			report << ''
		end
	end
	
	if report.length > 0
		report = report.join("\n")
		if $mailto
			send_mail(report)
		end
		puts report
	end
	 	
	old_parcels = parcels
	
	sleep 300
end
