#!/usr/bin/env python

from Tkinter import *
import os, time, urllib
from pygale import *

DEBUG = 0

THUMBS = {}
THUMB_CBS = {}
# URL for thumbnails
THUMB_URL = 'http://www.ugcs.caltech.edu/~jtr/gale/mug/index.cgi?id=%s&ret=thumb&nopiggy=1'
#THUMB_URL = 'http://apathy.cs.washington.edu:8000/cgi-bin/getthumb.py?id=%s'
# Don't fetch thumbnails more often than 5 minutes [in secs]
THUMB_RETRY_INTERVAL = 5 * 60
# Refetch thumbnails we've already fetched successfully after this interval
# (2 hours) [in seconds]
THUMB_REFETCH_INTERVAL = 2 * 3600
# 1 min for testing
#THUMB_REFETCH_INTERVAL = 60
# How big the largest thumbnail is
THUMB_MAX_SIZE = 10 * 1024
# Max number of pixels in the image height (image will be subsampled down
# until less than this height)
THUMB_IMAGE_SIZE = 40

# These should get set by caller (ui.py)
FUGUDIR = '.'
Config = None

# Cache of prior thumbnail images (so they don't blank out immediately)
OLDTHUMBS = []

class Thumb:
	def __init__(self):
		self.url = None
		self.img = None
		self.datahash = None
		self.time = None
		self.path = None

def GC(imagelist):
	global OLDTHUMBS
	dellist = []
	for url in THUMBS.keys():
		if THUMBS[url].img is not None:
			imgname = THUMBS[url].img.name
		else:
			continue
		if imgname not in imagelist:
			# Delete it!  it's no longer referenced
			dellist.append(url)
	imgname = None
	for d in dellist:
		if DEBUG: print 'Deleting thumb', d
		del THUMBS[d]
	
	# now do oldthumbs
	keep = []
	for img in OLDTHUMBS:
		if img.name in imagelist:
			keep.append(img)
	OLDTHUMBS = keep

def id_to_url(id):
	return THUMB_URL % id

def getthumb(url, callback, fromcache=0):
	now = time.time()

	# Set up path
	THUMBDIR = os.path.join(FUGUDIR, 'thumbs')
	if not os.path.exists(THUMBDIR):
		try:
			os.mkdir(THUMBDIR)
		except OSError, e:
			pygale.call_error_handler(str(e))
			callback(None)
			return
	thumbimgpath = os.path.join(THUMBDIR, urllib.quote(url, ''))

	if fromcache:
		# Special handling if a cache hit was requested
		if THUMBS.has_key(url):
			if DEBUG: print 'fromcache: serving from memory cache', url
			t = THUMBS[url]
			callback(t.img)
		elif os.path.exists(thumbimgpath):
			if DEBUG: print 'fromcache: serving from disk cache', url
			THUMB_CBS[url] = [(now, callback)]
			t = Thumb()
			t.url = url
			t.path = thumbimgpath
			t.time = time.time()
			getthumb3(t)
		else:
			if DEBUG: print 'fromcache: not in cache', url
			callback(None)
		return

	WEB_FETCH = 0
	if THUMBS.has_key(url):
		t = THUMBS[url]
		if now - t.time <= THUMB_REFETCH_INTERVAL:
			# If it's been less than the refetch interval, return the
			# cached image
			if DEBUG:
				print 'in cache and still fresh (%i secs);'%(now-t.time),
				print 'returning', url
			callback(t.img)
			return
		# Otherwise, try refetching it.
		else:
			if DEBUG: print 'enabling web fetch for', url
			t.url = url
			WEB_FETCH = 1
	else:
		t = Thumb()
		t.url = url
		t.path = thumbimgpath
		t.time = time.time()

	if THUMB_CBS.has_key(url):
		# If we have tried to fetch this in the past, and not completed
		if DEBUG: print 'incomplete web fetch for', url
		firstreqtime = THUMB_CBS[url][0][0]
		if now - firstreqtime <= THUMB_RETRY_INTERVAL:
			# Don't try again just yet
			pygale.call_error_handler(
				'Thumbnail fetch for %s started %i seconds ago'
				% (url, now - firstreqtime))
			if DEBUG: print 'Thumbnail fetch for %s started %i seconds ago'\
				% (url, now - firstreqtime)
			THUMB_CBS[url].append((time.time(), callback))
			return
		else:
			# It's been too long; give up on the others and start afresh
			pygale.call_error_handler(
				'Thumbnail fetch for %s timed out; starting over'
				% url)
			if DEBUG: print ('Thumbnail fetch for %s timed out; ' %url)+\
				'starting over'
			for (tm, cb) in THUMB_CBS[url]:
				cb(None)
			del THUMB_CBS[url]

	# If we get here, we're the first
	THUMB_CBS[url] = [(now, callback)]
	if WEB_FETCH or not os.path.exists(t.path):
		# if we need to fetch it from the web
		if DEBUG: print 'starting web fetch for', url
		pygale.call_error_handler('Fetching thumbnail %s' % url)
		asyncurl.fetch_url(url, lambda data, t=t:
			getthumb2(data, t), maxsize=THUMB_MAX_SIZE)
	else:
		# Already exists on disk
		if DEBUG: print 'going straight to disk cache for', url
		getthumb3(t)

def getthumb2(data, t):
	t.time = time.time()
	if data is None:
		# Couldn't retrieve it from the web
		# Try local cache
		if DEBUG: print 'received None from web fetch', t.url
		if not os.path.exists(t.path):
			pygale.call_error_handler(
				'Unable to retrieve thumbnail %s' % t.url)
			if DEBUG: print 'fetch failed, no disk cache', t.url
			t.img = None
			THUMBS[t.url] = t
			if THUMB_CBS.has_key(t.url):
				for (tm, cb) in THUMB_CBS[t.url]:
					cb(None)
				del THUMB_CBS[t.url]
			return
		else:
			pygale.call_error_handler(
				'Thumbnail fetch failed; using cache for %s' %
				t.url)
			if THUMBS.has_key(t.url):
				img = THUMBS[t.url].img
				# Use in-memory cache
				# BUG: There may be no cbs yet
				if THUMB_CBS.has_key(t.url):
					for (tm, cb) in THUMB_CBS[t.url]:
						cb(img)
					del THUMB_CBS[t.url]
				else:
					if DEBUG: print 'ERROR: no callbacks for', t.url
				return
			# Otherwise, go to disk
			if DEBUG: print 'fetch failed; using disk cache for', t.url
	else:
		if t.datahash == hash(data):
			# It's the same as what we already have
			if DEBUG: print 'fetched image is same as ours'
			if THUMB_CBS.has_key(t.url):
				for (tm, cb) in THUMB_CBS[t.url]:
					cb(t.img)
				del THUMB_CBS[t.url]
			return
		else:
			if DEBUG: print 'fetched image is different', t.url

		# Otherwise, save it to disk
		if DEBUG: print 'saving image to disk', t.path
		t.datahash = hash(data)
		try:
			g = open(t.path, 'wb')
			g.write(data)
			g.close()
		except IOError, e:
			if DEBUG: print 'error saving image to disk:', e
			pygale.call_error_handler(
				'Unable to cache thumbnail %s' % t.url)
			THUMBS[t.url] = t
			for (tm, cb) in THUMB_CBS[t.url]:
				cb(None)
			del THUMB_CBS[t.url]
			callback(t.img)
			return
	if DEBUG: print 'going to getthumb3 after saving to disk'
	getthumb3(t)
		
def getthumb3(t):
	if DEBUG: print 'loading thumbnail from disk', t.url
	assert os.path.exists(t.path)
	t.datahash = hash(open(t.path).read())
	try:
		# No more BIG_THUMBS; all thumbnails are half-size
		# Sample them down to <= THUMB_IMAGE_SIZE pixels
		bigimg = PhotoImage(file=t.path)
	except:
		pygale.call_error_handler("No thumbnail for %s" % t.url)
		if os.path.exists(t.path):
			os.unlink(t.path)
		img = None
	else:
		while bigimg.height() > THUMB_IMAGE_SIZE:
			img = bigimg.subsample(2)
			bigimg = img
		img = bigimg

	if t.img is not None:
		# Need to back up old image
		if DEBUG: print 'Adding image to OLDTHUMBS', t.url 
		OLDTHUMBS.append(t.img)
	t.img = img
	t.time = time.time()

	if THUMB_CBS.has_key(t.url):
		if DEBUG: print 'calling callbacks for', t.url
		for (tm, cb) in THUMB_CBS[t.url]:
			cb(t.img)
		del THUMB_CBS[t.url]
		THUMBS[t.url] = t
	else:
		pygale.call_error_handler(
			'Got thumbnail, but no callbacks! (%s)' % t.url)
		if DEBUG: print 'got thumbnail, but no callbacks', t.url
	return


