#!/usr/bin/env ruby

require 'optparse'
require 'ostruct'
require 'abbrev'
require 'open-uri'
require 'rss/1.0'
require 'rss/2.0'
require 'rss/maker'
require 'haml'
# following are minimum needed for Time#yesterday
#require 'active_support/vendor'
#require 'active_support/core_ext/object/misc'
#require 'active_support/core_ext/date'
#require 'active_support/core_ext/time'

require 'rubygems'
require 'hpricot'

# constants and globals
MY_VERSION = %q($Revision: 1.16 $)
DAILY, WEEKLY = [1, 2]

###############################################################################
def main 
  filename = 'non-date-comics.index'
  info_for_non_date_comics = {}
  if File.exists?(filename)
    File.open(filename) {|f| info_for_non_date_comics = Marshal.restore(f)}
  end

  comics = [
    BasicInstructions.new(info_for_non_date_comics),
    CalvinAndHobbes.new,
    CyanideAndHappiness.new(info_for_non_date_comics),
    Dilbert.new,
    DoghouseDiaries.new(info_for_non_date_comics),
    FMinus.new,
    NonSequitur.new,
    PoochCafe.new,
    ShermansLagoon.new,
    SMBC.new(info_for_non_date_comics),
    UserFriendly.new,
    Xkcd.new(info_for_non_date_comics),
  ]

  ignore = [
    BloomCounty.new,
    DieselSweeties.new,
    DrFun.new,
    Opus.new,
    PennyArcade.new,
    PerryBibleFellowship.new,
    ThisModernWorld.new,
    TomTheDancingBug.new,
    WhiteNinja.new(info_for_non_date_comics),
  ]

  options = parse_cmdline(comics, ARGV)

  comics.each {|comic| comic.options = options}

  unless options.quiet
    p options 
    puts
  end

  if options.dumpindex
    info_for_non_date_comics.keys.sort.each do |comic|
      puts comic
      info_for_non_date_comics[comic].each_key do |id|
        print "  #{id}: "
        puts info_for_non_date_comics[comic][id].inspect
      end
      puts
    end
  end

  begin
    fetch_comics(comics, options)           if options.fetch
    generate_index(comics, ignore, options) if options.index or options.indexonly

  rescue Exception => e
    raise e

  ensure
    File.open(filename, 'w') do |f| 
      f.print(Marshal.dump(info_for_non_date_comics))
    end
  end

end

def fetch_comics(comics, options)
  puts "wanted comics: #{options.comics.inspect}" unless options.quiet
  for comic in comics
    comic.fetch if options.comics.include?(comic.name)
  end
end

def generate_index(comics, ignored, options)
  comics.each {|comic| comic.collect_local_image_info}
  datestamps = 7.times.collect {|i| (options.now - i*24*3600).strftime("%Y%m%d")}
  days = 7.times.collect {|i| (options.now - i*24*3600).strftime("%A")}

  write_index(comics, ignored, datestamps, days)
  write_rss(comics, datestamps, options) if not options.indexonly
end

###############################################################################
def parse_cmdline(comics, args)

  options = OpenStruct.new
  options.fetch = false
  options.index = false
  options.quiet = false
  options.now = Time.now
  options.day = options.now.day
  options.month = options.now.month
  options.year = options.now.year
  options.test = false
  options.dumpindex = false
  options.force = false
  options.comics = []
  options.comic_id = nil

  comic_names = comics.collect {|c| c.name}

  opts = OptionParser.new do |opt|
    opt.banner = "usage: #$0 [options]"
    opt.separator 'where:'

    opt.on("-f", "--fetch", "get some comics from the web") do |f| 
      options.fetch = f
    end
    opt.on("-i", "--index", "generate index.html and rss feed") do |i| 
      options.index = i
    end
    opt.on("-I", "--indexonly", "generate index.html but not rss feed") do |i| 
      options.indexonly = i
    end
    opt.on("-c", "--comics COMIC", 
            "specify a COMIC to fetch (may use any unique abbrev: #{comic_names.join(', ')})"
           ) {|c| options.comics << c}
    opt.on("-d", "--day DAY", "specify DAY for fetch") do |d| 
      options.day = d
    end
    opt.on("-m", "--month MONTH", "specify MONTH for fetch") do |m| 
      options.month = m
    end
    opt.on("-y", "--year YEAR", "specify YEAR for fetch") do |y| 
      options.year = y
    end
    opt.on("--id ID", "fetch a particular ID for non-date based comic") do |id|
      options.comic_id = id
    end

    opt.on_tail("--test", "don't fetch comics from internet") do |t| 
      options.test = t
    end
    opt.on_tail("--dumpindex", "dump the periodic comic db") do |d| 
      options.dumpindex = d
    end
    opt.on_tail("--force", "override what's in the periodic comic db") do |f| 
      options.force = f
    end

    opt.on_tail("-q", "--quiet", "suppress output messages") do |q| 
      options.quiet = q
    end
    opt.on_tail("-h", "--help", "show this help") do 
      puts opt
      exit
    end
  end
  opts.parse!(args)

  validate_comics(options, comic_names)
  validate_date(options)
  options
end

def validate_comics(options, comic_names)
  if options.comics.empty?
    options.comics = comic_names
  else
    good = []
    abbr = comic_names.abbrev
    for opt in options.comics
      if abbr[opt].nil?
        warn "*** omitting unknown or ambiguous -c option '#{opt}'"
      else
        good << abbr[opt]
      end
    end
    options.comics = good
  end
end

def validate_date(options)
  y, m, d = options.year.to_i, options.month.to_i, options.day.to_i
  begin
    date = Time.local(y, m, d)
  rescue ArgumentError
    warn "Invalid date: %s-%s-%s" % [options.year, options.month, options.day]
    exit
  end
  options.date, options.year, options.month, options.day = date, y, m, d
  options.datestamp = date.strftime("%Y%m%d")
end

###############################################################################

ImgInfo = Struct.new(:filename, :display, :url, :size, :title, :alt)

class Comic
  def initialize(name, title, home, info_for_non_date_comics=nil)
    @name = name
    @title = title
    @home = home
    @imgs = nil
    @date_url_tmpl = nil
    @freq = DAILY  #default
    @options = nil
    @info_for_non_date_comics = info_for_non_date_comics 
  end
  attr_reader :name, :title, :home, :imgs, :date_url_tmpl, :freq, :info_for_non_date_comics
  attr_accessor :options

  def retrieve_image(url, id=nil)
    url = home + url unless url.start_with?("http:")
    puts "... getting #{url}" if not options.quiet
    local_filename = url_to_filename(url, id)
    if options.test
      puts ".... .... retrieve #{url} to #{local_filename}"
      return false
    end

    begin
      out = File.open(local_filename, 'w').binmode
      open(url, :progress_proc => lambda {|s| print '.' unless options.quiet}) do |f|
        out.sync = true
        out.print(f.read)
      end
      print " done" unless options.quiet
    rescue Exception => e
      warn "Error retrieving #{url} to #{local_filename}: #{e}"
      return false
    ensure
      puts unless options.quiet
      out.close
    end
    File.utime(options.date.to_i, options.date.to_i, local_filename)
    true 
  end

  def url_to_filename(url, id)
    ext = File.extname(URI.split(url)[5])
    filename = "#{name}"
    filename << ".#{id}" unless id.nil?  
    filename << ".#{options.datestamp}#{ext}"
    puts "... local filename=" + filename if not options.quiet
    filename
  end

  def fetch
    warn "fetch not implemented for #{title}"
    false
  end

  def collect_local_image_info
    expiry_date = options.now - 7*24*3600
    @imgs = Dir.glob("#{name}*").sort.collect do |file| 
      get_img_info(file, expiry_date)
    end.compact
  end

  def get_img_info(file, expiry_date)
    parts = file.split('.')
    if size = test(?s, file)
      size = "%dK" % [size/1024]
    else
      puts "... deleting empty file #{file}" unless options.quiet
      File.delete(file)
      return nil
    end

    if File.mtime(file) < expiry_date
      puts "... #{file} is old .. deleting it" unless options.quiet
      File.delete(file)
      if parts.length == 4
        name, id = parts[0,2]
        info_for_non_date_comics[name].delete(id)
      end
      return nil
    end

    img = ImgInfo.new(file, file, web_url(file), size)

    if parts.length == 4
      name, id, date = parts[0,3]
      if info_for_non_date_comics[name][id].nil?
        # have the image file but not the data!
        p "attemtping to get the info for #{name} -> #{id}"
        if not fetch(true)
          warn "info_for_non_date_comics[#{name}][#{id}] is nil!"
          warn "and cannot fetch just the info!"
          die
        end
      end
      if info_for_non_date_comics[name][id][:title]
        img.title = info_for_non_date_comics[name][id][:title]
      end
      if info_for_non_date_comics[name][id][:alt]
        img.alt = info_for_non_date_comics[name][id][:alt]
      end
    end
    img 
  end

  def web_url(filename)
    y, m, d = filename.match(/(\d{4})(\d\d)(\d\d)/)[1,3]
    Time.local(y, m, d).strftime(date_url_tmpl)
  end
end

###############################################################################
# break this out of ParseHtmlComic so it can be mixed-in to other classes
#
module ParseHtmlModule
  def handle_html(url)
    if options.test
      puts ".... .... f = open_uri(#{url})"
      return false
    end
    begin
      doc = open(url) {|f| Hpricot(f)}
      parse_html(doc)
    rescue Exception => e
      warn "Error opening '#{url}': #{e}}'"
      return false
    end
  end
end

class ParseHtmlComic < Comic
  include ParseHtmlModule

  def initialize(name, title, home, date_url_tmpl)
    super(name, title, home)
    @date_url_tmpl = date_url_tmpl
  end

  def fetch
    url = options.date.strftime(date_url_tmpl)
    handle_html(url) 
  end
end

###############################################################################
class UserFriendly < ParseHtmlComic
  def initialize
    super 'userfriendly', 'User Friendly', 'http://www.userfriendly.org', \
          'http://ars.userfriendly.org/cartoons/?id=%Y%m%d'
    @date_url_tmpl = "http://ars.userfriendly.org/cartoons/?id=%Y%m%d"
  end

  def fetch
    @src_re = %r{(#{home}/cartoons/archives/#{options.date.strftime("%y%b")}/[^.]+\.gif)}i
    @alt_re = %r{^strip for #{options.date.strftime("%b %d, %Y")}}i
    super
  end

  # my argument is a doc object generated by Hpricot
  def parse_html(doc)
    doc.search("//img").each do |img|
      if (not img["src"].nil? and m=@src_re.match(img["src"])) and
         ( (not img["alt"].nil? and @alt_re.match(img["alt"])) or
           (not img["ALT"].nil? and @alt_re.match(img["ALT"])) or
           (not img["Alt"].nil? and @alt_re.match(img["Alt"])) )
        return retrieve_image(m[1])
      end
    end
    false
  end
end

###############################################################################
class KnowDivComic < ParseHtmlComic
  def initialize(name, title, home, date_url_tmpl, div_attr_type, div_attr_val)
    super(name, title, home, date_url_tmpl)
    @attribute = div_attr_type
    @attr_value = div_attr_val
  end

  # image is first img in div with @attribute=@attr_value
  def parse_html(doc)
    doc.search("//div[@#{@attribute}='#{@attr_value}']//img").each do |img|
      return retrieve_image(img['src'])
    end
    false
  end
end

class Dilbert < KnowDivComic
  def initialize
    home = 'http://www.dilbert.com'
    super 'dilbert', 'Dilbert', home, "#{home}/strips/%Y-%m-%d/", 'class', 'STR_Content'
    @date_url_tmpl = home + '/strips/%Y-%m-%d/'
  end
end

class FMinus < KnowDivComic
  def initialize
    home = 'http://www.comics.com/f_minus'
    super 'fminus', 'F Minus', home, "#{home}/%Y-%m-%d/", 'class', 'STR_Comic'
    @date_url_tmpl = home + '/%Y-%m-%d/'
  end
end

###############################################################################
class KnowImgurlComic < Comic
  def initialize(name, title, home)
    super
  end

  def fetch
    unless (freq == DAILY or @wday.to_s == options.date.strftime('%w'))
      puts "not today: #@name" unless options.quiet
      return false
    end
    imgurl = options.date.strftime(@imgurl_tmpl)
    for ext in @exts
      return true if retrieve_image(imgurl+ext)
    end
    false
  end
end

class ShermansLagoon < KnowImgurlComic
  def initialize
    super 'shermanslagoon', "Sherman's Lagoon", 'http://www.slagoon.com'

    @imgurl_tmpl = home + '/dailies/SL%y%m%d'
    @date_url_tmpl = home + '/cgi-bin/sviewer.pl?selectdate=%s'
    @exts = ['.gif']
  end

  def web_url(filename)
    y, m, d = filename.match(/(\d{4})(\d\d)(\d\d)/)[1,3]
    sherman_date = "%d/%d/%02d" % [m.to_i, d.to_i, y.to_i % 1000]
    date_url_tmpl % sherman_date
  end
end

###############################################################################
class GoComic < KnowImgurlComic
  def initialize(name, title, abbr)
    super(name, title, "http://www.gocomics.com/#{abbr}")
    @imgurl_tmpl = "http://images.ucomics.com/comics/#{abbr}/%Y/#{abbr}%y%m%d"
    @date_url_tmpl = home + '/%Y/%m/%d'
    @exts = ['.gif', '.jpg']
  end
end

class CalvinAndHobbes < GoComic
  def initialize
    super 'calvinandhobbes', 'Calvin and Hobbes', 'ch'
  end
end

class NonSequitur < GoComic
  def initialize
    super 'nonsequitur', 'Non Sequitur', 'nq'
  end
end

class PoochCafe < GoComic
  def initialize
    super 'poochcafe', 'Pooch Cafe', 'poc'
  end
end

###############################################################################
class SalonComic < KnowImgurlComic
  def initialize name, title, artist, wday
    super name, title, 'http://www.salon.com/comics/'
    @freq = WEEKLY

    @wday = wday
    @imgurl_tmpl = "http://images.salon.com/comics/#{artist}/%Y/%m/%d/#{artist}/story"
    @date_url_tmpl = @imgurl_tmpl.sub(/images/,'archive')
    @exts = ['.jpg', '.gif']
  end

  # it seems that the comic is posted today with yesterday's date.  ugh.
  def fetch
    today = options.date
    unless (freq == DAILY or @wday.to_s == today.strftime('%w'))
      puts "not today: #@name" unless options.quiet
      return false
    end

    imgurl = today.strftime(@imgurl_tmpl)
    result = false
    for ext in @exts
      result = retrieve_image(imgurl+ext)
      break if result
    end
    unless result
      imgurl = today.yesterday.strftime(@imgurl_tmpl)
      for ext in @exts
        result = retrieve_image(imgurl+ext)
        break if result
      end
    end
    false
  end
end

class ThisModernWorld < SalonComic
  def initialize
    super 'thismodernworld', 'This Modern World', 'tomo', 2
  end
end

class TomTheDancingBug < SalonComic
  def initialize
    super 'tomthedancingbug', 'Tom The Dancing Bug', 'boll', 4
  end
end

###############################################################################
# break this out of RssComic so it can be mixed-in to other classes
#
module RssModule
  def handle_rss
    begin
      xml = URI.parse(@feed).read
      rss = RSS::Parser.parse(xml, false)
    rescue Exception => e
      warn "Error handling RSS feed for #{name}: #{e}}"
      return false
    end
    # return the comic_id and url (and other stuff) from the feed
    parse_rss(rss)
  end
end

class RssComic < Comic
  include RssModule

  def initialize(name, title, home, url, info_for_non_date_comics)
    super(name, title, home, info_for_non_date_comics)
    @feed = url
    info_for_non_date_comics[@name] ||= {}
  end

  def fetch(info_only=false)
    begin
      comic_id, imgurl, extras = handle_rss
    rescue Exception => e
      $stderr.puts "exception reading rss feed for #{self.name}: #{e}"
      $stderr.puts e.backtrace
      return false
    end

    p [comic_id, imgurl, extras] unless options.quiet
    if imgurl.nil?
      warn "error: can't find comic in #@feed"
      false
    else
      if (not options.force) and 
         (not info_for_non_date_comics[@name][comic_id].nil?)
      then
        warn "already have this one: #@name -> #{comic_id} -> #{info_for_non_date_comics[@name][comic_id][:date]}" unless options.quiet
        false
      elsif info_only or retrieve_image(imgurl, comic_id)
        info_for_non_date_comics[@name][comic_id] = {:date => options.datestamp}
        info_for_non_date_comics[@name][comic_id].merge!(extras) if extras
        true
      end
    end
  end

  def web_url(filename)
    @date_url_tmpl % filename.split('.')[1]
  end
end

class BasicInstructions < RssComic
  def initialize(info_for_non_date_comics)
    bi = 'basicinstructions'
    home = "http://www.#{bi}.net"
    super bi, 'Basic Instructions', home, "#{home}/basic-instructions/rss.xml", info_for_non_date_comics
    @date_url_tmpl = home + '?p=%s'
  end

  # find the first 'comic' item with an image
  def parse_rss(rss)
    comic_id, imgurl, title, weburl = nil, nil, nil, nil
    rss.items.each do |item|
      next unless item.category.content == "comic"
      if m = item.description.match(/img src="(.+?)"/)
        comic_id = item.guid.content.gsub(/:/,'_')
        imgurl = m[1]
        title = item.title
        weburl = item.link
        p [options.comic_id, comic_id, title] unless options.quiet
        break if options.comic_id.nil? or comic_id == options.comic_id
      end
    end
    # note, if we have not matched the desired comic_id, we will still hold
    # the value of the *last* item in the feed.
    # Is this good or bad?
    [comic_id, imgurl, {:alt => title, :weburl => weburl}]
  end

  def web_url(filename)
    id = filename.split('.')[1]
    info_for_non_date_comics[@name][id][:weburl] || @home
  end
end

class SMBC < RssComic
  def initialize(info_for_non_date_comics)
    home = 'http://www.smbc-comics.com/'
    super 'smbc', 'Saturday Morning Breakfast Cereal', home, home+'rss.php', info_for_non_date_comics
    @date_url_tmpl = home + 'index.php?db=comics&id=%s'
  end

  # find the first item with an image
  def parse_rss(rss)
    comic_id, imgurl = nil, nil
    rss.items.each do |item|
      if m = item.description.match(/img src="(.+?)"/)
        comic_id = item.link.match(/id=(\w+)/)[1]
        imgurl = m[1]
        p [options.comic_id, comic_id] unless options.quiet
        break if options.comic_id.nil? or comic_id == options.comic_id
      end
    end
    [comic_id, imgurl]
  end
end

class DoghouseDiaries < RssComic
  def initialize(info_for_non_date_comics)
    super 'doghousediaries', 'DoghouseDiaries', 'http://www.thedoghousediaries.com/', 'http://feeds.feedburner.com/thedoghousediaries/feed?format=xml', info_for_non_date_comics
    @date_url_tmpl = home + '/?p=%s'
  end

  # find the first item with an image
  def parse_rss(rss)
    comic_id = imgurl = alt = title = nil
    rss.items.each do |item|
      if m = item.content_encoded.match(/img\b.*\bsrc="(.+?)".*\btitle="(.+?)"/)
        comic_id = item.guid.content.match(/p=(\d+)$/)[1]
        imgurl = m[1]
        alt = m[2]
        title = item.title
        p [options.comic_id, comic_id] unless options.quiet
        break if options.comic_id.nil? or comic_id == options.comic_id
      end
    end
    p [comic_id, imgurl, {:alt => alt, :title => title}] unless options.quiet
    [comic_id, imgurl, {:alt => alt, :title => title}]
  end
end

class Xkcd < RssComic
  def initialize(info_for_non_date_comics)
    super 'xkcd', 'Xkcd', 'http://www.xkcd.com', 'http://www.xkcd.com/rss.xml', info_for_non_date_comics
    @date_url_tmpl = home + '/%s/'
  end

  # find the first item with an image
  def parse_rss(rss)
    comic_id = imgurl = alt = title = nil
    rss.items.each do |item|
      if m = item.description.match(/img src="(.+?)" title="(.+?)"/)
        comic_id = item.link.match(/(\d+)\/$/)[1]
        imgurl = m[1]
        alt = m[2]
        title = item.title
        p [options.comic_id, comic_id] unless options.quiet
        break if options.comic_id.nil? or comic_id == options.comic_id
      end
    end
    p [comic_id, imgurl, {:alt => alt, :title => title}] unless options.quiet
    [comic_id, imgurl, {:alt => alt, :title => title}]
  end
end


###############################################################################
class NonDateComic < Comic
  def initialize(name, title, home, info_for_non_date_comics)
    super
    info_for_non_date_comics[@name] ||= {}
  end

  def web_url(filename)
    date = filename.match(/\d{8}/)[0]
    comicid = info_for_non_date_comics[@name].each do |id, hash|
           break id if hash[:date] = date
         end
    date_url_tmpl % comicid
  end

  def retrieve_image(url, comic_id, info_only, extras=nil)
    val = info_only || super(url, comic_id)
    if val
      info = {:date => options.datestamp}
      info.merge!(extras) if not extras.nil?
      info_for_non_date_comics[name][comic_id] = info
    end
    val
  end
end

class WhiteNinja < NonDateComic
  include RssModule

  def initialize(info_for_non_date_comics)
    super 'whiteninja', 'White Ninja', 'http://www.whiteninjacomics.com', info_for_non_date_comics
    @date_url_tmpl = home + '/comics/%s.shtml'
    @feed = 'http://www.whiteninjacomics.com/rss/z-latest.xml'
  end

  def fetch(info_only=false)
    comic_id = handle_rss
    return false if comic_id.nil?

    if (not options.force) and
       (not info_for_non_date_comics[name][comic_id].nil?)
    then
      warn "already have this one: #@name -> #{comic_id} -> #{info_for_non_date_comics[name][comic_id][:date]}" unless options.quiet
      return false
    end

    url = "#{home}/images/comics/#{comic_id}.gif"
    retrieve_image(url, comic_id, info_only, {:alt => @alt})
  end

  def parse_rss(rss)
    comic_id = nil
    rss.items.each do |item|
      comic_id = item.link.match(%r{([^/]+)\.shtml})[1]
      @alt = item.title.split(/ - /)[0]
      p [options.comic_id, comic_id] unless options.quiet
      break if options.comic_id.nil? or comic_id == options.comic_id
    end
    comic_id
  end
end


class CyanideAndHappiness < NonDateComic
  def initialize(info_for_non_date_comics)
    super 'cyanideandhappiness', 'Cyanide and Happiness', 'http://www.explosm.net/comics', info_for_non_date_comics
    @date_url_tmpl = home + '/%s/'
  end

  def fetch(info_only=false)
    if options.test
      puts ".... .... f = open_uri(#{url})"
      return false
    end
    url = options.comic_id.nil? ? home : date_url_tmpl % options.comic_id
    p url unless options.quiet

    begin
      f = open(url)
    rescue Errno::ETIMEDOUT, OpenURI::HTTPError => e
      warn "can't open '#{url}': #{e}'"
      return false
    end

    f.each_line do |line|
      if m = line.match(%r{\[URL="#{home}/(\d+).*\[IMG\]([^\[]+)})
        comic_id, imgurl = m[1..2]
        if (not options.force) and 
           (not info_for_non_date_comics[@name][comic_id].nil?)
        then
          warn "already have this one: #@name -> #{comic_id} -> #{info_for_non_date_comics[@name][comic_id][:date]}"  unless options.quiet
          return false
        elsif retrieve_image(imgurl, comic_id, info_only)
          break
        end
      end
    end
    f.close
    true
  end

  def web_url(filename)
    id = filename.split('.')[1]
    @date_url_tmpl % id
  end
end

###############################################################################
class IgnoredComic
  def initialize(name, title, home)
    @name = name
    @title = title
    @home = home
  end
  attr_reader :name, :title, :home
end

class BloomCounty < IgnoredComic
  def initialize
    super 'bloomcounty', 'Bloom County', 'http://www.gocomics.com/blm'
  end
end

class DieselSweeties < IgnoredComic
  def initialize
    super 'dieselsweeties', 'Diesel Sweeties', 'http://www.dieselsweeties.com'
  end
end

class DrFun < IgnoredComic
  def initialize
    super 'drfun', 'Dr Fun', 'http://www.ibiblio.org/Dave/'
  end
end

class PennyArcade < IgnoredComic
  def initialize
    super 'pennyarcade', 'Penny Arcade', 'http://www.penny-arcade.com/comic'
  end
end

class PerryBibleFellowship < IgnoredComic
  def initialize
    super 'pbf', 'The Perry Bible Fellowship', 'http://www.pbfcomics.com/'
  end
end

class Opus < SalonComic
  def initialize
    super 'opus', 'Opus', 'opus', 0
  end
end

###############################################################################
def write_index(comics, ignored, datestamps, days)
  links = {}
  comics.each do |comic|
    links[comic] = {}
    datestamps.each do |date| 
      links[comic][date] =
        if img = comic.imgs.find {|im| im.filename.include?(date)}
          attr = img.alt.nil? ? '' : %Q{ title="#{img.alt}"}
          pdate = img.title || date.match(/(....)(..)(..)/)[1,3].to_a.join('-')
          "<a href='%s'%s>%s</a>" % [img.filename, attr, pdate]
        else
          '&nbsp;'
        end
    end
  end

  haml = Haml::Engine.new(DATA.read, {:format => :html4})
  html = haml.render(binding)
  File.open("index.html", "w") {|f| f.write(html)}
end

###############################################################################
def write_rss(comics, datestamps, options)
  day_comics = {}

  comics.each do |comic|
    comic.imgs.each do |img|
      datestamp = img.filename.match(/\d{8}/)[0]
      day_comics[datestamp] ||= []
      if datestamps.include?(datestamp)
        day_comics[datestamp] << [comic, img]
      else
        puts ".... delete #{img.filename}"
      end
    end
  end

  link = "http://web.ncf.ca/xx087/comix/"
  content_tmpl1 = "<a href='%s'>%s</a><br /><img src='#{link}%s' /><div style='text-align: right; font-size: smaller'><a href='%s'>%s</a></div><hr />"
  content_tmpl2 = "<a href='%s'>%s</a><br /><div>%s</div><img src='#{link}%s' /><div style='text-align: right; font-size: smaller'><a href='%s'>%s</a></div><hr />"
  content_tmpl3 = "<a href='%s'>%s</a><br /><div>%s</div><img src='#{link}%s' /><div><i>%s</i></div><div style='text-align: right; font-size: smaller'><a href='%s'>%s</a></div><hr />"

  content = RSS::Maker.make("2.0") do |feed|
    feed.channel.title = "glenn's comix (new)"
    feed.channel.link = link
    feed.channel.description = "a daily collection of glenn's favourite web comics."
    feed.channel.lastBuildDate = options.now

    day_comics.keys.sort.reverse.each do |datestamp|
      d = datestamp.match(/(\d\d\d\d)(\d\d)(\d\d)/)[1,3].collect {|x| x.to_i}
      time = Time.local(*d)

      contents = []
      day_comics[datestamp].each do |comic, img|
        weburl = comic.web_url(img.filename)
        contents << if img.alt
          if img.title
            content_tmpl3 % [comic.home, comic.title, img.title, img.filename, img.alt, weburl, weburl]
          else
            content_tmpl2 % [comic.home, comic.title, img.alt, img.filename, weburl, weburl]
          end
        else
          content_tmpl1 % [comic.home, comic.title, img.filename, weburl, weburl]
        end
      end 

      item = feed.items.new_item
      item.title = time.strftime("%A, %B %d, %Y")
      item.pubDate = time
      item.link = link
      item.content_encoded = contents.join("\n")
    end
  end

  File.open("comix.xml","w") do |f|
    f.write(content)
  end
end

###############################################################################
###############################################################################

main

###############################################################################
###############################################################################
__END__
!!! Strict
%html
  %head
    %title comics
    %meta(http-equiv="Content-Type" content="text/html;charset=ISO-8859-1")
    %meta(http-equiv="CACHE-CONTROL" content="NO-CACHE")
    %meta(http-equiv="PRAGMA" content="NO-CACHE")
    %meta(http-equiv="EXPIRES" content="0")
    %link(rel="stylesheet" type="text/css" href="comix.css")
  %body
    %table
      %tr
        %th &nbsp;
        - days.each do |day| 
          %th= day
      - comics.each do |comic|
        %tr
          %td.title
            %a{:href => comic.home}= comic.title
          - datestamps.each do |date| 
            %td(align="center")= links[comic][date]
    .rightdiv
      %div
        %a.hidden{:href => File.basename($0)}= MY_VERSION
      %div
        %a{:href => 'comix.xml'}
          %img(src='feed-icon-14x14.png' style='border:0' alt='') 
          Feed me, Seymour
    %div Other interesting comics:
    %ul.ignore
      - ignored[0..-2].each do |comic| 
        %li
          %a{:href => comic.home}>= comic.title
      -# the last ignored comic needs to have the "class='last'" attribute
      %li.last
        %a{:href => ignored[-1].home}>= ignored[-1].title
    %p
      %a(href="http://www.ncf.ca/ncf/support/commentTaker.jsp") You talkin' to me?
      %a.hidden(href='http://www.msu.edu/user/svoboda1/taxi_driver/sounds/talk2me.wav') Well I'm the only one here.
