import errno
import os

import gtk
import gtk.glade
import inspect

import lodjusetup
import ThumbnailArea
import MetaData
import Filesel
import Alert


def deduce_title(filename):
    if filename == u"":
	name = u"unnamed"
    else:
    	name = os.path.basename(filename)
	if name in [u"", u"lodju.xml"]:
	    name = os.path.basename(os.path.dirname(filename))
	    if name == u"":
	    	name = filename
    return name


class Model:

    def __init__(self, doc):
    	assert doc == None or doc.type == u"lodju-document"
	self.xml = gtk.glade.XML(lodjusetup.LODJU_GLADE)
	self.cutcopypaste_target = None
	self.set_document(doc)

    def set_document(self, doc):
    	self.doc = doc

    def get_target(self):
    	return self.cutcopypaste_target

    def set_target(self, target):
    	self.cutcopypaste_target = target

    def get_widget(self, name):
    	return self.xml.get_widget(name)


class View:

    def __init__(self, model, controller, folder_tree, app_model):
    	self.model = model
	self.controller = controller
	self.folder_tree = folder_tree
	self.app_model = app_model
	
	self.thumb = ThumbnailArea.View(self, self.model.doc, self.model.xml)
	self.folder_tree.set_thumb_area(self.thumb)
	
    	self.focused_widget = self.thumb

    	# This bit of code constructs a list of methods for binding to
	# Gtk+ signals. This way, we don't have to maintain a list
	# manually, saving editing effort. It's enough to add a method
	# to the suitable *Controller class and give the same name in the 
	# .glade file.
    	dict = {}
	members = inspect.getmembers(self.controller) + \
	    	  inspect.getmembers(self.folder_tree.controller) + \
		  inspect.getmembers(self.thumb.controller)
    	for name, member in members:
	    dict[name] = member
	self.model.xml.signal_autoconnect(dict)

	self.main_window = self.model.get_widget("main_window")
	width, height = app_model.get_main_window_default_size()
    	if width == 0 or height == 0:
	    width = gtk.gdk.screen_width() - 100
	    height = gtk.gdk.screen_height() - 100
	    if width < 0:
		width = 0
	    if height < 0:
		height = 0
	if width > 0 and height > 0:
	    if self.main_window.window:
		self.main_window.window.resize(width, height)
	    else:
	    	self.main_window.set_default_size(width, height)

	hpaned = self.model.get_widget("work_area_hpaned")
    	pos = app_model.get_hpaned_default_position()
    	if pos == 0:
	    size = hpaned.get_toplevel().get_size()
	    if size:
	    	pos = size[0] / 3
	if pos > 0:
	    hpaned.set_position(pos)
	hpaned.connect("notify::position", self.controller.hpaned_changed)

    	vpaned = self.model.get_widget("left_vpaned")
	pos = app_model.get_vpaned_default_position()
	if pos == 0:
	    # vpaned is not yet realized, make guess based on toplevel size
	    size = vpaned.get_toplevel().get_size()
	    if size:
	    	pos = size[1] / 3
	if pos > 0:
	    vpaned.set_position(pos)
	vpaned.connect("notify::position", self.controller.vpaned_changed)

	# For some reason, the values set in gnome.init are not copied to the
	# About dialog. Thus, we set them here explicitly
	self.about = self.model.get_widget("about")
	self.about.set_property("name", lodjusetup.LODJU_NAME)
	self.about.set_property("version", lodjusetup.LODJU_VERSION)
	
	# Disable menu entries that don't yet do anything useful.
	for name in ["cut", "copy", "paste", "clear", "properties", 
	    	     "preferences", "photo_properties"]:
	    item = self.model.get_widget(name)
	    item.set_sensitive(gtk.FALSE)

	children = self.model.xml.get_widget("appbar").get_children()
	self.progressbar = children[0]
	self.status = children[1].get_children()[0]
	
    	self.set_document(self.model.doc)

    def drag_source_is_within_window(self, context):
    	src = context.get_source_widget()
	return src in [self.folder_tree.widget, self.thumb.widget]

    def set_progress(self, fraction):
	self.progressbar.set_fraction(fraction)

    def set_status_text(self, text):
    	self.status.set_text(text)

    def set_document(self, doc):
    	self.model.set_document(doc)
    	self.set_title(doc, u"filename")
	if doc:
	    doc.listen(self.set_title)
	self.thumb.set_document(doc)
	self.folder_tree.set_thumb_area(self.thumb)
	self.folder_tree.model.set_document(doc)

    def set_title(self, doc, key):
    	if doc and key == u"filename":
	    self.main_window.set_title(deduce_title(doc[u"filename"]))

    def show(self):
	self.app_model.add_window(self)
    	self.main_window.show()

    def close(self):
	self.model.doc.storage.discard()
    	self.app_model.remove_window(self)
	self.main_window.destroy()

    def show_about(self):
    	self.about.show()


class Controller:

    def __init__(self, model, view):
    	self.model = model
	self.view = view
	self.filesel_location = u""

    def set_filesel_location(self, new_location):
	self.filesel_location = os.path.dirname(new_location)

    def on_main_window_set_focus(self, *args):
	if args[1] == self.view.thumb.widget:
	    self.view.focused_widget = self.view.thumb
	elif args[1] == self.view.folder_tree.widget:
	    self.view.focused_widget = self.view.folder_tree
	else:
	    self.view.focused_widget = None

    def on_main_window_configure_event(self, *args):
	e = args[1]
	self.view.app_model.save_main_window_default_size(e.width, e.height)
    
    def hpaned_changed(self, *args):
    	pos = args[0].get_position()
	self.view.app_model.save_hpaned_default_position(pos)

    def vpaned_changed(self, *args):
    	pos = args[0].get_position()
	self.view.app_model.save_vpaned_default_position(pos)

    def close_window(self):
    	if self.model.doc.is_dirty():
	    filename = self.model.doc[u"filename"]
	    if filename:
	    	primary = u"Save changes to document \"" + filename + \
		    	  u"\" before closing?"
		secondary = u"If you do not save, all changes will be " + \
		    	    u"discarded."
    	    else:
	    	primary = u"Save changes to unnamed document before closing?"
		secondary = u"If you do not save, all changes will be " + \
		    	    u"discarded. If you choose to save, you will " + \
			    u"be asked for a filename."
	    Alert.Alert(gtk.STOCK_DIALOG_WARNING,
	    	    	[(u"Close without saving", self.close_without_saving),
			 (gtk.STOCK_CANCEL,),
			 (gtk.STOCK_SAVE, self.save_and_close)],
			primary,
			secondary)
    	else:
	    self.view.close()

    def on_main_window_delete_event(self, *args):
    	self.close_window()
	return gtk.TRUE
    
    def on_close_activate(self, *args):
    	self.close_window()

    def close_without_saving(self):
    	self.model.doc.storage.discard()
    	self.view.close()
	
    def quit_without_saving(self):
    	for win in self.view.app_model.windows:
	    win.close()
    	self.view.app_model.quit()

    def save_and_close(self):
	filename = self.model.doc[u"filename"]
	if filename:
	    if self.save(filename):
		self.view.close()
	else:
	    Filesel.Filesel(u"Save as", self.filesel_location, 
	    	    	    gtk.TRUE, gtk.FALSE, 
			    self.save_as_ok_and_quit)

    def on_new_activate(self, *args):
    	self.view.app_model.new_window()

    def on_quit_activate(self, *args):
    	docs = self.view.app_model.unsaved_changes()
    	if len(docs) == 0:
	    for win in self.view.app_model.windows:
	    	win.close()
	    self.view.app_model.quit()
	    return

	unnamed = filter(lambda doc: doc[u"filename"] == u"", docs)

    	close_without_saving = (u"Close without saving", 
	    	    	    	self.quit_without_saving)
	cancel = (gtk.STOCK_CANCEL,)
	save = (gtk.STOCK_SAVE, self.save_all_and_quit)

	if len(unnamed) > 1:
    	    buttons = [close_without_saving, cancel]
    	    primary = u"Several documents with unsaved changes, and " + \
		      u"more than one of them has no name."
    	    secondary = u"If you want to save your changes, choose " + \
			u"'Cancel' and save them one by one."
    	elif len(unnamed) == 1 and len(docs) == 1:
    	    buttons = [close_without_saving, cancel, save]
	    primary = u"Save changes to unnamed document before closing?"
	    secondary = u"If you close without saving, changes will be " + \
			u"discarded. If you want to save, you will be " + \
			u"asked for a name."
    	elif len(docs) > 1:
	    buttons = [close_without_saving, cancel, save]
	    primary = u"Save changes to several documents before closing?"
	    secondary = u"You have unsaved changes in several documents, " + \
			u"all of which have a name. If you close without " + \
			u"saving, changes will be discarded."
    	else:
	    buttons = [close_without_saving, cancel, save]
    	    primary = u"Save changes to document \"%s\" before closing?" % \
			docs[0][u"filename"]
	    secondary = u"If you close without saving, changes will be " + \
    	    	    	u"discarded."

    	Alert.Alert(gtk.STOCK_DIALOG_WARNING, buttons, primary, secondary)

    def save_all_and_quit(self):
	for window in self.view.app_model.windows:
	    if window.model.doc.is_dirty():
	    	filename = window.model.doc[u"filename"]
	    	if filename and not window.controller.save(filename):
		    return
	for window in self.view.app_model.windows:
	    if window.model.doc.is_dirty():
	    	filename = window.model.doc[u"filename"]
		if not filename:
		    Filesel.Filesel(u"Save as", self.filesel_location, 
		    	    	    gtk.TRUE, gtk.FALSE, 
		    	    	    self.save_as_ok_and_quit)
		    return
	self.view.app_model.quit()

    def save_as_ok_and_quit(self, filenames):
	assert len(filenames) == 1
	if self.save(unicode(filenames[0])):
	    self.view.app_model.quit()

    def on_show_trashcan_activate(self, *args):
	self.view.app_model.trash.show()

    def on_about_activate(self, *args):
	self.view.show_about()

    def on_copy_activate(self, *args):
    	if self.view.focused_widget == self.view.folder_tree:
	    iters = self.view.folder_tree.controller.get_selection_iters()
	    folders = MetaData.Folders()
	    for iter in iters:
	    	folders.add(self.view.folder_tree.model.get_folder(iter))
	    self.view.app_model.set_clipboard(folders)
    	elif self.view.focused_widget == self.view.thumb:
	    self.view.app_model.set_clipboard(
		self.view.thumb.model.copy_selected())
	
    def on_cut_activate(self, *args):
    	if self.view.focused_widget == self.view.folder_tree:
	    iters = self.view.folder_tree.controller.get_selection_iters()
	    folders = MetaData.Folders()
	    for iter in iters:
	    	folder.add(self.view.folder_tree.model.get_folder(iter))
		self.view.folder_tree.model.store.remove(iter)
	    self.view.app_model.set_clipboard(folders)
    	elif self.view.focused_widget == self.view.thumb:
	    thumbs = self.view.thumb.model.get_selection()
	    photos = map(lambda t: t.photo, thumbs)
	    self.view.app_model.set_clipboard(photos)
    	    self.view.thumb.model.remove_selection_from_storage()
	    self.view.thumb.recompute_positions()
	    self.view.thumb.update_scrollbar()
	    self.view.thumb.draw()

    def on_paste_activate(self, *args):
    	contents = self.view.app_model.get_clipboard()
	if type(contents) == type([]):
    	    copy = map(lambda p: p.copy(), contents)
	    self.view.thumb.model.paste(copy)
	    self.view.thumb.recompute_positions()
	    self.view.thumb.update_scrollbar()
	    self.view.thumb.scroll_to_focused()
	    self.view.thumb.draw()
	else:
	    selected_iters = \
	    	self.view.folder_tree.controller.get_selection_iters()
    	    if selected_iters:
	    	selected_iter = selected_iters[0]
	    else:
	    	selected_iter = None
	    iter = self.view.folder_tree.model.paste_folder(selected_iter, 
				    	    	    	    contents)
	    path = self.view.folder_tree.model.store.get_path(iter)
	    self.view.folder_tree.widget.set_cursor(path, None, gtk.FALSE)
	    self.view.folder_tree.selection.select_iter(iter)

    def on_empty_trash_activate(self, *args):
    	self.view.app_model.trash.folder_tree.model.empty_trash()

    def on_delete_photo_activate(self, *args):
	thumbs = self.view.thumb.model.get_selection()
	photos = map(lambda t: t.photo, thumbs)

	self.view.app_model.trash.folder_tree.model.copy_photos(
	    self.model.doc, photos)
	self.view.app_model.trash.thumb.recompute_positions()
	self.view.app_model.trash.thumb.update_scrollbar()
	self.view.app_model.trash.thumb.draw()

	self.view.thumb.model.remove_selection_from_storage()
	self.view.thumb.recompute_positions()
	self.view.thumb.update_scrollbar()
	self.view.thumb.draw()

    def on_save_activate(self, *args):
	if self.model.doc[u"filename"] == u"":
	    self.on_save_as_activate()
	else:
	    self.save(self.model.doc[u"filename"])

    def on_save_as_activate(self, *args):
	Filesel.Filesel(u"Save as", self.filesel_location, 
	    	    	gtk.TRUE, gtk.FALSE, self.save_as_ok)

    def save_as_ok(self, filenames):
	assert len(filenames) == 1
	self.set_filesel_location(filenames[0])
	self.save(unicode(filenames[0]))

    def save(self, filename):
	self.view.folder_tree.model.build_folder_tree()
	self.model.doc[u"filename"] = filename
	try:
	    self.model.doc.save()
	except IOError, (code, txt):
	    self.save_alert(code, txt, filename)
	    return 0
    	except OSError, (code, txt):
	    self.save_alert(code, txt, filename)
	    return 0
	else:
	    self.view.set_status_text("Saved document")
	    return 1

    def save_alert(self, code, txt, filename):
	if code == errno.EACCES:
	    sec = u"You do not have write permission."
	elif code == errno.ENOENT:
	    sec = u"The directory doesn't exist."
	elif code == errno.ENOSPC:	
	    sec = u"Disk is full. Choose another place to save or " + \
		  u"make room."
	else:
	    sec = u"Error %d: %s" % (code, txt)
    	Alert.Alert(gtk.STOCK_DIALOG_ERROR,
		    [(gtk.STOCK_OK,)],
		    u"Could not save document \"%s\"." % filename,
		    sec)
