"""Base reader class -- read from a URL in the background."""

import sys
import string
from Tkinter import *
import urlparse
import grailutil


# Default tuning parameters
# BUFSIZE = 8*1024			# Buffer size for api.getdata()
BUFSIZE = 512				# Smaller size for better response
SLEEPTIME = 100				# Milliseconds between regular checks

class BaseReader:

    """Base reader class -- read from a URL in the background.

    Given an API object, poll it until it completes or an
    unrecoverable error occurs.

    Derived classes are supposed to override the handle_*() methods to
    do something meaningful.

    The sequence of calls made to the stop and handle_* functions can
    be expressed by a regular expression:

    (meta data* stop (eof | error) | stop handle_error)

    """

    # Tuning parameters
    sleeptime = SLEEPTIME

    def __init__(self, context, api):
	self.context = context
	self.api = api
	self.callback = self.checkmeta
	self.poller = self.api.pollmeta
	self.bufsize = BUFSIZE
	
	# Stuff for status reporting
	self.nbytes = 0
	self.maxbytes = 0
	self.shorturl = ""
	self.message = "waiting for socket"

	self.context.addreader(self)

	self.fno = None   # will be assigned by start
	self.killed = None

	### only http_access has this delayed startup property
	try:
	    self.api.register_reader(self.start)
	except AttributeError:
	    # if the protocol doesn't do that
	    self.start()

    def start(self):
	# when the protocol API is ready to go, it tells the reader
	# to get busy
	self.message = "awaiting server response"
	if self.killed:
	    print "start() called after a kill"
	    return
	self.fno = self.api.fileno()
	if TkVersion == 4.0 and sys.platform == 'irix5':
	    if self.fno >= 20: self.fno = -1 # XXX for SGI Tk OPEN_MAX bug

	if self.fno >= 0:
	    tkinter.createfilehandler(
		self.fno, tkinter.READABLE, self.checkapi)
	else:
            # No fileno() -- check every 100 ms
	    self.checkapi_regularly()


    def __str__(self):
	if self.maxbytes:
	    percent = self.nbytes*100/self.maxbytes
	    status = "%d%% of %s read" % (percent,
					  grailutil.nicebytes(self.maxbytes))
	elif not self.nbytes:
	    status = self.message
	else:
	    status = "%s read" % grailutil.nicebytes(self.nbytes)
	if self.api and self.api.iscached():
	    status = status + " (cached)"
	if self.api and not self.shorturl:
	    tuple = urlparse.urlparse(self.api._url_)
	    path = tuple[2]
	    i = string.rfind(path[:-1], '/')
	    if i >= 0:
		path = path[i+1:]
	    self.shorturl = path or self.api._url_
	return "%s: %s" % (self.shorturl, status)

    def __repr__(self):
	return "%s(...%s)" % (self.__class__.__name__, self.api)

    def update_status(self):
	self.context.new_reader_status() # Will call our __str__() method

    def update_maxbytes(self, headers):
	self.maxbytes = 0
	if headers.has_key('content-length'):
	    try:
		self.maxbytes = string.atoi(headers['content-length'])
	    except string.atoi_error:
		pass
	self.update_status()

    def update_nbytes(self, data):
	self.nbytes = self.nbytes + len(data)
	self.update_status()

    def kill(self):
	self.killed = 1
	self.stop()
	self.handle_error(-1, "Killed", {})

    def stop(self):
	if self.fno >= 0:
	    fno = self.fno
	    self.fno = -1
	    tkinter.deletefilehandler(fno)

	self.callback = None
	self.poller = None

	if self.api:
	    self.api.close()
	    self.api = None

	if self.context:
	    self.context.rmreader(self)
	    self.context = None

    def checkapi_regularly(self):
	if not self.callback:
##	    print "*** checkapi_regularly -- too late ***"
	    return
	self.callback()
	if self.callback:
	    sleeptime = self.sleeptime
	    if self.poller and self.poller()[1]: sleeptime = 0
	    self.context.root.after(sleeptime, self.checkapi_regularly)

    def checkapi(self, *args):
	if not self.callback:
	    print "*** checkapi -- too late ***"
	    if self.fno >= 0:
		fno = self.fno
		self.fno = -1
		tkinter.deletefilehandler(fno)
	    return
	try:
	    self.callback()			# Call via function pointer
	except:
	    if self.context and self.context.app:
		app = self.context.app
	    else:
		from __main__ import app
	    app.exception_dialog("in BaseReader")
	    self.kill()

    def checkmeta(self):
	self.message, ready = self.api.pollmeta()
	if ready:
	    self.getapimeta()

    def checkdata(self):
	self.message, ready = self.api.polldata()
	if ready:
	    self.getapidata()

    def getapimeta(self):
	errcode, errmsg, headers = self.api.getmeta()
	self.callback = self.checkdata
	self.poller = self.api.polldata
	if headers.has_key('content-type'):
	    content_type = headers['content-type']
	else:
	    content_type = None
	if headers.has_key('content-encoding'):
	    content_encoding = headers['content-encoding']
	else:
	    content_encoding = None
	self.content_type = content_type
	self.content_encoding = content_encoding
	self.update_maxbytes(headers)
	self.handle_meta(errcode, errmsg, headers)
	if self.callback:
	    self.callback()		# XXX Handle httpAPI readahead

    def getapidata(self):
	data = self.api.getdata(self.bufsize)
	if not data:
	    self.handle_eof()
	    self.stop()
	    return
	self.update_nbytes(data)
	self.handle_data(data)

    def geteverything(self):
	if self.api:
	    if self.callback == self.checkmeta:
		self.getapimeta()
	    while self.api:
		self.getapidata()

    # Derived classes are expected to override the following methods

    def handle_meta(self, errcode, errmsg, headers):
	# May call self.stop()
	self.update_maxbytes(headers)
	if errcode != 200:
	    self.stop()
	    self.handle_error(errcode, errmsg, headers)

    def handle_data(self, data):
	# May call self.stop()
	pass

    def handle_error(self, errcode, errmsg, headers):
	# Called after self.stop() has been called
	pass

    def handle_eof(self):
	# Called after self.stop() has been called
	pass
