"""Data structures for History manipulation
"""

from Tkinter import *
import tktools
import string
import regex
import os
import sys
import time
from grailutil import *
from urlparse import urldefrag


class PageInfo:
    """This is the data structure used to represent a page in the
    History stack.  Each Browser object has it's own unique History
    stack which maintains volatile informatio about the page, to be
    restored when the page is re-visited via the history mechanism.
    For example, the state of any forms on the page, the entered and
    relocated URLs, or the scroll position are information that might
    be kept.

    A page can actually have 3 URL's associated with it.  First, the
    typed or clicked URL as it appears in the entry field or anchor,
    after relative URL resolution but before any server relocation
    errors.

    Second, the URL after relocation, possibly many steps of
    relocation.  Often this is to redirect a browser to the new
    location of a page, but more often it is to add a trailing slash
    on a page that lacks one.  In fact, there can be a relocation
    path, and you must watch for loops, but only the final resolved
    URL is of any significance.

    Third, if the pages contains a BASE tag, this URL is used in
    resolution of relative urls on this page.  Currently the base URL
    information is kept with the context object.
    """
    def __init__(self, url='', title='', scrollpos=1.0, formdata=None):
	self._url = url
	self._title = title
	self._scrollpos = scrollpos
	if formdata is None: formdata = []
	self._formdata = formdata

    def set_url(self, url): self._url = url
    def set_title(self, title): self._title = title
    def set_scrollpos(self, scrollpos): self._scrollpos = scrollpos
    def set_formdata(self, formdata): self._formdata = formdata

    def url(self): return self._url
    def title(self): return self._title
    def scrollpos(self): return self._scrollpos
    def formdata(self): return self._formdata

    def clone(self):
	return self.__class__(self._url, self._title,
			      self._scrollpos, self._formdata[:])



class DummyHistoryDialog:
    """Dummy so History can avoid testing for self._dialog != None."""
    def refresh(self): pass
    def select(self, index): pass

class History:
    def __init__(self):
	self._history = []
	self._dialog = DummyHistoryDialog()
	self._current = 0

    def clone(self):
	newhist = History()
	list = []
	for page in self._history:
	    list.append(page.clone())
	newhist._history = list
	newhist._current = self._current
	return newhist

    def set_dialog(self, dialog):
	self._dialog = dialog

    def append_page(self, pageinfo):
	# Discard alternative futures.  Someday we might have `Cactus
	# History' or we might expose the Global History to the user.
	del self._history[self._current+1:]
	# We no longer check to see if the page has the same URL as
	# the previous page, because this introduced problems with
	# CGI scripts that produced different output for the same URL
	self._history.append(pageinfo)
	self._current = len(self._history)-1
	self._dialog.refresh()

    def page(self, index=None):
	if index is None: index = self._current
	if 0 <= index < len(self._history):
	    self._current = index
	    self._dialog.select(self._current)
	    return self._history[self._current]
	else: return None

    def peek(self, offset=0, pos=None):
	if pos is None:
	    pos = self._current
	i = pos + offset
	if 0 <= i < len(self._history):
	    return i, self._history[i]
	else:
	    return -1, None

    def current(self): return self._current
    def forward(self): return self.page(self._current+1)
    def back(self): return self.page(self._current-1)
    def pages(self): return self._history
    def refresh(self): self._dialog.refresh()



HISTORY_PREFGROUP = 'history'
VIEW_BY_PREF = 'view-by'
VIEW_BY_TITLES = 'titles'
VIEW_BY_URLS = 'urls'


class HistoryDialog:
    def __init__(self, context, historyobj=None):
	if not historyobj:
	    # XXX I guess this is here for testing?  (It's used nowhere.)
	    self._history = History()
	else:
	    self._history = historyobj
	# 
	self._context = context
	self._history.set_dialog(self)
	self._frame = tktools.make_toplevel(self._context.viewer.frame,
					    title="History Dialog")
	# get preferences
	self._viewby = StringVar()
	from __main__ import app
	self._prefs = prefs = app.prefs
	prefs.AddGroupCallback(HISTORY_PREFGROUP, self._notify)
	try:
	    viewby = prefs.Get(HISTORY_PREFGROUP, VIEW_BY_PREF)
	    if viewby not in [VIEW_BY_TITLES, VIEW_BY_URLS]:
		raise TypeError
	except (KeyError, TypeError):
	    viewby = VIEW_BY_TITLES
	self._viewby.set(viewby)
	# add a couple of buttons
	btnbar = Frame(self._frame)
	btnbar.pack(fill=BOTH, side=BOTTOM)
	gotobtn = Button(btnbar, text='Go To', command=self._goto)
	gotobtn.pack(side=LEFT)
	closebtn = Button(btnbar, text='Close', command=self._close)
	closebtn.pack(side=LEFT)
	# radio button for view option
	rbframe = Frame(btnbar)
	rbframe.pack()
	rb1 = Radiobutton(rbframe, text='View by titles',
			  command=self._viewby_command,
			  variable=self._viewby,
			  value=VIEW_BY_TITLES)
	rb2 = Radiobutton(rbframe, text='View by URIs',
			  command=self._viewby_command,
			  variable=self._viewby,
			  value=VIEW_BY_URLS)
	rb1.pack(anchor='w')
	rb2.pack(anchor='w')
	# create listbox
	self._listbox, frame = tktools.make_list_box(self._frame, 40, 24, 1, 1)
	self.refresh()
	self._listbox.config(takefocus=0, exportselection=0)
	self._listbox.bind('<Double-Button-1>', self._goto)
	self._listbox.bind('<Double-Button-2>', self._goto_new)
	self._listbox.bind('<ButtonPress-2>', self._highlight)
	# yes, yes, the mapping seems inverted, but it has to do with
	# the way history elements are displayed in reverse order in
	# the listbox...
	self._frame.bind("<Up>", self.next_cmd)
	self._frame.bind("p", self.next_cmd)
	self._frame.bind("P", self.next_cmd)
	self._frame.bind("<Down>", self.previous_cmd)
	self._frame.bind("n", self.previous_cmd)
	self._frame.bind("N", self.previous_cmd)
	self._frame.bind('<Alt-W>', self._close)
	self._frame.bind('<Alt-w>', self._close)
	tktools.set_transient(self._frame, self._context.root)
	    
    def history(self): return self._history

    def _notify(self):
	viewby = self._prefs.Get(HISTORY_PREFGROUP, VIEW_BY_PREF)
	self._viewby.set(viewby)
	self.refresh()

    def refresh(self):
	# populate listbox
	self._listbox.delete(0, END)
	viewby = self._viewby.get()
	# view in reverse order
	pages = self._history.pages()[:]
	pages.reverse()
	for page in pages:
	    url = page.url()
	    title = page.title() or url
	    if viewby == VIEW_BY_TITLES:
		docurl, frag = urldefrag(url)
		if frag and title <> url:
		    title = title + ' [%s]' % frag
		self._listbox.insert(END, title)
	    elif viewby == VIEW_BY_URLS:
		self._listbox.insert(END, url)
	self.select(self._history.current())

    def previous_cmd(self, event=None):
	if self._history.back(): self._goto()
	else: self._frame.bell()
    def next_cmd(self, event=None):
	if self._history.forward(): self._goto()
	else: self._frame.bell()

    def _load_url(self, which, context):
	selection = string.atoi(which)
	last = self._listbox.index(END)
	pos = last - selection - 1
	context.load_from_history(self._history.peek(pos=pos))

    def _goto(self, event=None):
	list = self._listbox.curselection()
	if len(list) > 0:
	    self._load_url(list[0], self._context)

    def _goto_new(self, event=None):
	list = self._listbox.curselection()
	if len(list) > 0:
	    from Browser import Browser
	    browser = Browser(self._context.app.root, self._context.app)
	    self._load_url(list[0], browser.context)

    def _highlight(self, event):
	self._listbox.select_clear(0, END)
	self._listbox.select_set('@%d,%d' % (event.x, event.y))

    def _close(self, event=None):
	self._frame.withdraw()

    def _viewby_command(self, event=None):
	self.refresh()

    def select(self, index):
	last = self._listbox.index(END)
	self._listbox.select_clear(0, END)
	self._listbox.select_set(last-index-1)
	self._listbox.activate(last-index-1)

    def show(self):
	self._frame.deiconify()
	self._frame.tkraise()
