# -*- coding: utf-8 -*-

# Copyright (C) 2003-2006 by Igor E. Novikov
# Copyright (C) 1997, 1998, 1999, 2000, 2001, 2003 by Bernhard Herzog
#
# This library is covered by GNU Library General Public License.
# For more info see COPYRIGHTS file in sK1 root directory.



# This file contains various classes for reading a drawing from (ASCII)
# files.
#
# Classes:
#
# LoaderWithComposites
# GenericLoader(LoaderWithComposites)
#
#	Two classes that provide common functions for the format
#	specific classes.
#
# Functions
#
# load_drawing(filename)
#
#	Determines the type of the file (by reading a few lines) and
#	invokes the appropriate import filter. Return the newly created
#	document.
#

from types import StringType, TupleType
import os, string
import time

from app import config
from app.plugins import plugins
from app.utils import locale_utils
from app.events.warn import warn, INTERNAL, pdebug
from app.conf.const import ArcPieSlice

from app.Graphics import document, layer, group, text
from app import _, PolyBezier, Rectangle, Ellipse, Trafo, Translation, \
		Style, PropertyStack, EmptyPattern, Document, SketchLoadError, \
		ImageData, Image


doc_class = Document


class EmptyCompositeError(SketchLoadError):
	pass


# The loaders usually intercept exceptions raised while reading and
# raise a SketchLoadError instead. Setting the following variable to
# true will prohibit this. (useful only for debugging a loader)
#
# XXX this is better done by using warn_tb(INTERNAL,...) before
# reraising the exception as a SketchLoadError.
_dont_handle_exceptions = 0


class LoaderWithComposites:

	guess_continuity = 0

	def __init__(self):
		self.composite_stack = None
		self.composite_items = []
		self.composite_class = None
		self.composite_args = None
		self.object = None

	def __push(self):
		self.composite_stack = (self.composite_class,
								self.composite_args,
								self.composite_items,
								self.composite_stack)

	def __pop(self):
		(self.composite_class, self.composite_args,
			self.composite_items, self.composite_stack) = self.composite_stack

	def begin_composite(self, composite_class, args = (), kw = None):
		self.__push()
		self.composite_class = composite_class
		self.composite_args = (args, kw)
		self.composite_items = []
		self.object = None

	def end_composite(self):
		if self.composite_class:
			args, kw = self.composite_args
			if not kw:
				kw = {}
			composite = apply(self.composite_class, args, kw)
			# We treat Plugins specially here and in check_object so
			# that we can be a bit more lenient with plugin objects.
			# They should not be empty (after all they'd be invisible
			# then) but they might. If they're empty they're simply
			# ignored
			if self.composite_items or composite.can_be_empty \
					or composite.is_Plugin:
				append = composite.load_AppendObject
				for item in self.composite_items:
					if self.check_object(item):
						append(item)
				composite.load_Done()
				self.__pop()
				self.append_object(composite)
			else:
				self.__pop()
				raise EmptyCompositeError
		else:
			raise SketchLoadError('no composite to end')

	def end_all(self):
		while self.composite_stack:
			self.end_composite()

	def check_object(self, object):
		# Return true if object is OK. Currently this just checks the
		# following things:
		#
		# - whether a bezier object has at least one path and all paths
		#   have at least length 1.
		# - for bezier objects, guess the continuity if
		#   self.guess_continuity is true.
		# - Whether a plugin object is not empty
		result = 1
		if object.is_Bezier:
			paths = object.Paths()
			if len(paths) >= 1:
				for path in paths:
					if path.len == 0:
						result = 0
						break
			else:
				result = 0
			if result and self.guess_continuity:
				object.guess_continuity()
		elif object.is_Plugin:
			result = len(object.GetObjects())
		return result

	def append_object(self, object):
		self.composite_items.append(object)
		self.object = object

	def pop_last(self):
		# remove the last object in self.composite_items and return it
		object = None
		if self.composite_items:
			object = self.composite_items[-1]
			del self.composite_items[-1]
			if self.composite_items:
				self.object = self.composite_items[-1]
			else:
				self.object = None
		return object


class GenericLoader(LoaderWithComposites):

	format_name = ''

	base_style = None

	def __init__(self, file, filename, match):
		LoaderWithComposites.__init__(self)
		self.file = file
		self.filename = filename
		self.match = match
		self.style = Style()
		if self.base_style is not None:
			self.prop_stack = PropertyStack(base=self.base_style.Duplicate())
		else:
			self.prop_stack = PropertyStack()
		self.messages = {}

	def get_prop_stack(self):
		stack = self.prop_stack
		if not self.style.IsEmpty():
			stack.load_AddStyle(self.style)
		stack.condense()
		if self.base_style is not None:
			self.prop_stack = PropertyStack(base =self.base_style.Duplicate())
		else:
			self.prop_stack = PropertyStack()
		self.style = Style()
		return stack

	def set_prop_stack(self, stack):
		self.prop_stack = stack

	def document(self, *args, **kw):
		self.begin_composite(doc_class, args, kw)

	def layer(self, *args, **kw):
		self.begin_layer_class(layer.Layer, args, kw)

	def end_layer(self):
		self.end_composite()

	def begin_layer_class(self, layer_class, args, kw = None):
		if issubclass(self.composite_class, layer.Layer):
			self.end_composite()
		if issubclass(self.composite_class, doc_class):
			self.begin_composite(layer_class, args, kw)
		else:
			raise SketchLoadError('self.composite_class is %s, not a document',
									self.composite_class)

	def bezier(self, paths = None):
		self.append_object(PolyBezier(paths = paths,
										properties = self.get_prop_stack()))

	def rectangle(self, m11, m21, m12, m22, v1, v2, radius1 = 0, radius2 = 0):
		trafo = Trafo(m11, m21, m12, m22, v1, v2)
		self.append_object(Rectangle(trafo, radius1 = radius1,
										radius2 = radius2,
										properties = self.get_prop_stack()))

	def ellipse(self, m11, m21, m12, m22, v1, v2, start_angle = 0.0,
				end_angle = 0.0, arc_type = ArcPieSlice):
		self.append_object(Ellipse(Trafo(m11, m21, m12, m22, v1, v2),
									start_angle, end_angle, arc_type,
									properties = self.get_prop_stack()))
	def simple_text(self, str, trafo = None, valign = text.ALIGN_BASE,
					halign = text.ALIGN_LEFT):
		if type(trafo) == TupleType:
			if len(trafo) == 2:
				trafo = apply(Translation, trafo)
			else:
				raise TypeError, "trafo must be a Trafo-object or a 2-tuple"
		self.append_object(text.SimpleText(text = str, trafo = trafo,
											valign = valign, halign = halign,
											properties = self.get_prop_stack()))

	def image(self, image, trafo):
		if type(trafo) == TupleType:
			if len(trafo) == 2:
				trafo = apply(Translation, trafo)
			else:
				raise TypeError, "trafo must be a Trafo-object or a 2-tuple"
		image = ImageData(image)
		self.append_object(Image(image, trafo = trafo))

	def begin_group(self, *args, **kw):
		self.begin_composite(group.Group, args, kw)

	def end_group(self):
		self.end_composite()

	def guess_cont(self):
		self.guess_continuity = 1

	def end_composite(self):
		isdoc = self.composite_class is doc_class
		LoaderWithComposites.end_composite(self)
		if isdoc:
			self.add_meta(self.object)

	def add_meta(self, doc):
		doc.meta.fullpathname = self.filename
		dir, name = os.path.split(self.filename)
		doc.meta.directory = dir
		doc.meta.filename = name
		doc.meta.native_format = 0
		doc.meta.format_name = self.format_name

	def add_message(self, message):
		pdebug(('load', 'echo_messages'), message)
		self.messages[message] = self.messages.get(message, 0) + 1

	def Messages(self):
		messages = self.messages.items()
		list = []
		for message, count in messages:
			if count > 1:
				list.append(_("%(message)s (%(count)d times)") % locals())
			else:
				list.append(message)
		list.sort()
		return string.join(list, '\n')


class SimplifiedLoader(GenericLoader):

	def __init__(self, file, filename, match):
		GenericLoader.__init__(self, file, filename, match)
		self.lineno = 1 # first line has been read

	def readline(self):
		line = self.file.readline()
		self.lineno = self.lineno + 1
		return line

	def set_properties(self, **kw):
		style = self.style
		for key, value in kw.items():
			setattr(style, key, value)

	def empty_line(self):
		self.style.line_pattern = EmptyPattern

	def empty_fill(self):
		self.style.fill_pattern = EmptyPattern



do_profile = 0
def load_drawing_from_file(file, filename = '', doc_class = None):
	# Note: the doc_class argument is only here for plugin interface
	# compatibility with 0.7 (especiall e.g. gziploader)
	line = file.readline()
	# XXX ugly hack for riff-based files, e.g. Corel's CMX. The length
	# might contain newline characters.
	if line[:4] == 'RIFF' and len(line) < 12:
		line = line + file.read(12 - len(line))
	#print line
	for info in plugins.import_plugins:
		match = info.rx_magic.match(line)
		if match:
			loader = info(file, filename, match)
			try:
				if do_profile:
					import profile
					warn(INTERNAL, 'profiling...')
					prof = profile.Profile()
					prof.runctx('loader.Load()', globals(), locals())
					prof.dump_stats(os.path.join(info.dir, info.module_name + '.prof'))
					warn(INTERNAL, 'profiling... (done)')
					doc = loader.object
				else:
					#t = time.clock()
					doc = loader.Load()
					#print 'load in', time.clock() - t, 'sec.'
				messages = loader.Messages()
				if messages:
					doc.meta.load_messages = messages
				return doc
			finally:
				info.UnloadPlugin()
	else:
		raise SketchLoadError(_("unrecognised file type"))


def load_drawing(filename):
	if type(filename) == StringType:
		name=locale_utils.utf_to_locale(filename)
		#name=locale_utils.strip_line(name)
		try:
			file = open(name, 'rb')
		except IOError, value:
			message = value.strerror
			raise SketchLoadError(_("Cannot open %(filename)s:\n%(message)s")
									% locals())
	else:
		# assume a file object. This does not happen at the moment and
		# SKLoder requires the filename for external objects.
		file = filename
		filename = ''
	return load_drawing_from_file(file, filename)
