# -*- coding: utf-8 -*-
# Copyright © 2007 Lateef Alabi-Oki
#
# This file is part of Scribes.
#
# Scribes is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Scribes is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Scribes; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
# USA

"""
This module documents a class that implements paragraph operations.

@author: Lateef Alabi-Oki
@organization: The Scribes Project
@copyright: Copyright © 2007 Lateef Alabi-Oki
@license: GNU GPLv2 or Later
@contact: mystilleef@gmail.com
"""

class Manager(object):
	"""
	This class implements paragraph operations.
	"""

	def __init__(self, editor):
		"""
		Initialize object.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@param editor: Reference to the text editor.
		@type editor: An Editor object.
		"""
		self.__init_attributes(editor)
		from gobject import idle_add, PRIORITY_LOW
		idle_add(self.__precompile_methods, priority=PRIORITY_LOW)

	def __init_attributes(self, editor):
		"""
		Initialize object.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@param editor: Reference to the text editor.
		@type editor: An Editor object.
		"""
		self.__editor = editor
		self.__client = editor.gconf_client
		self.__buffer = editor.textbuffer
		self.__feedback = editor.feedback
		return

########################################################################
#
#							Public Methods
#
########################################################################

	def previous_paragraph(self):
		"""
		Move cursor to previous paragraph.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.
		"""
		cursor_iterator = self.__editor.get_cursor_iterator()
		current_line = cursor_iterator.get_line()
		from operator import eq, is_
		try:
			if eq(current_line, 0): raise RuntimeError
			line = self.__find_empty_line(cursor_iterator)
			if is_(line, None): raise ValueError
			self.__jump_to_line(line)
			from i18n import msg0003
			self.__feedback.update_status_message(msg0003, "yes", 5)
		except RuntimeError:
			from i18n import msg0004
			self.__feedback.update_status_message(msg0004, "warning", 5)
		except ValueError:
			self.__jump_to_line(0)
		return

	def next_paragraph(self):
		"""
		Move cursor to next paragraph.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.
		"""
		number_of_lines = self.__buffer.get_line_count()
		cursor_iterator = self.__editor.get_cursor_iterator()
		current_line = cursor_iterator.get_line()
		from operator import eq, is_
		try:
			if eq(current_line, number_of_lines-1): raise RuntimeError
			line = self.__find_empty_line(cursor_iterator, False)
			if is_(line, None): raise ValueError
			self.__jump_to_line(line)
			from i18n import msg0005
			self.__feedback.update_status_message(msg0005, "yes", 5)
		except RuntimeError:
			from i18n import msg0006
			self.__feedback.update_status_message(msg0006, "warning", 5)
		except ValueError:
			self.__jump_to_line(number_of_lines-1)
		return

	def select_paragraph(self):
		"""
		Select a paragraph.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.
		"""
		try:
			begin, end = self.__get_paragraph_position()
			self.__buffer.select_range(begin, end)
			from i18n import msg0007
			self.__feedback.update_status_message(msg0007, "yes", 5)
		except RuntimeError:
			from i18n import msg0001
			self.__feedback.update_status_message(msg0001, "warning", 5)
		return

	def reflow_paragraph(self):
		"""
		Reflow paragraph based on right margin position.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.
		"""
		if self.__is_readonly(): return
		try:
			start, end = self.__get_paragraph_position()
		except RuntimeError:
			from i18n import msg0001
			self.__feedback.update_status_message(msg0001, "warning", 5)
			return
		text = start.get_text(end)
		try:
			text = self.__reflow_text(text)
		except RuntimeError:
			from i18n import msg0002
			self.__feedback.update_status_message(msg0002, "warning", 5)
			return
		self.__buffer.begin_user_action()
		self.__buffer.delete(start, end)
		self.__buffer.insert_at_cursor(text)
		self.__buffer.end_user_action()
		from i18n import msg0009
		message = msg0009
		self.__feedback.update_status_message(message, "yes", 5)
		return

	def destroy(self):
		"""
		Destroy object.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.
		"""
		del self
		self = None
		return

################################################################################
#
#						Paragraph Helper Methods
#
################################################################################

	def __get_paragraph_position(self):
		"""
		Get start and end paragraph positions.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@return: The position of a paragraph.
		@rtype: A Tuple object.
		"""
		iterator = self.__get_current_line_iterator()
		if iterator.is_start() and iterator.is_end(): raise RuntimeError
		if self.__is_empty_line(iterator): raise RuntimeError
		first_paragraph_line = self.__find_empty_line(iterator)
		if first_paragraph_line is None:
			begin, end = self.__buffer.get_bounds()
		else:
			begin = self.__buffer.get_iter_at_line(first_paragraph_line)
			begin.forward_line()
		second_paragraph_line = self.__find_empty_line(iterator, False)
		if second_paragraph_line is None:
			start, end = self.__buffer.get_bounds()
		else:
			end = self.__buffer.get_iter_at_line(second_paragraph_line)
			end.backward_line()
			end.forward_to_line_end()
		return begin, end

	def __get_current_line_iterator(self):
		"""
		Get iterator at start of current line.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@return: Return the iterator at start of current line.
		@rtype: A gtk.TextIter object.
		"""
		iterator = self.__editor.get_cursor_iterator()
		if iterator.starts_line(): return iterator
		line = iterator.get_line()
		return self.__buffer.get_iter_at_line(line)

	def __find_empty_line(self, iterator, backwards=True):
		"""
		Search backwards for empty lines.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@param iterator: The cursor position
		@type iterator: A gtk.TextIter object.

		@return: Line number of empty line or None
		@rtype: An Integer or None object.
		"""
		from operator import not_
		while True:
			if backwards:
				if not_(iterator.backward_line()): return None
			else:
				if not_(iterator.forward_line()): return None
			if self.__is_empty_line(iterator): return iterator.get_line()
		return None

	def __is_empty_line(self, iterator):
		"""
		Whether or not a line is empty.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@param iterator: Reference a position in the buffer.
		@type iterator: A gtk.TextIter object.

		@return: True if line is empty.
		@rtype: A Boolean object.
		"""
		if iterator.ends_line(): return True
		temporary_iterator = iterator.copy()
		temporary_iterator.forward_to_line_end()
		text = iterator.get_text(temporary_iterator)
		from operator import not_
		if not_(text): return True
		if text.isspace(): return True
		return False

	def __jump_to_line(self, line):
		"""
		Move cursor to a specific line.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@param line: Line to jump to.
		@type line: An Integer object.
		"""
		iterator = self.__buffer.get_iter_at_line(line)
		self.__buffer.place_cursor(iterator)
		self.__editor.move_view_to_cursor()
		return

########################################################################
#
#					Paragraph Reflow Helper Methods
#
########################################################################

	def __reflow_text(self, text):
		"""
		Realign lines in text such that is line is not more than a
		specified character long.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@param text: Text to reflow.
		@type text: A String object.

		@return: Realigned text.
		@rtype: A String object.
		"""
		from operator import not_, gt, le
		if not_(text) or text.isspace(): raise RuntimeError
		text_lines = text.split("\n")
		indentation = self.__get_indentation(text_lines[0])
		wrap_width = self.__calculate_wrap_width(indentation)
		reflowed_lines = []
		remainder = ""
		line = " ".join(text_lines)
		line = line.replace("\t", " ")
		line = line.strip()
		line = self.__respace_line(line)
		if gt(len(line), wrap_width):
			while True:
				new_line, remainder = self.__shorten_line(line, wrap_width)
				reflowed_lines.append(new_line)
				if le(len(remainder), wrap_width): break
				line = remainder.strip()
		else:
			reflowed_lines.append(line)
		if remainder: reflowed_lines.append(remainder)
		indented_reflowed_lines = self.__indent_lines(reflowed_lines, indentation)
		return "\n".join(indented_reflowed_lines)

	def __shorten_line(self, line, wrap_width):
		"""
		Shorten a line to a specified wrap width.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@param line: Line to shorten.
		@type line: A String object.

		@param wrap_width: How long the line should be.
		@type wrap_width: An Integer object.

		@return: The shortened line and other strings that exceed the
			specified wrap width.
		@rtype: A Tuple object.
		"""
		from textwrap import wrap
		line = line.strip()
		new_lines = wrap(line, wrap_width, expand_tabs=False, replace_whitespace=False)
		new_line = new_lines[0]
		remainder = " ".join(new_lines[1:])
		return new_line.strip(), remainder.strip()

	def __indent_lines(self, reflowed_lines, indentation):
		"""
		Perform automatic indentation on each line, if necessary.

		Automatic indentation is based on the indentation of the first
		line.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@param reflowed_lines: Realigned lines for indentation.
		@type reflowed_lines: A List object.

		@return: A list of indented lines.
		@rtype: A List object.
		"""
		from operator import lt
		if lt(len(reflowed_lines), 2): return reflowed_lines
		indent_line = lambda x: indentation + x.strip()
		indented_lines = map(indent_line, reflowed_lines)
		return indented_lines

	def __get_indentation(self, line):
		"""
		Determine the indentation of a line.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@param line: A line to check for indentation.
		@type line: A String object.
		"""
		indentation_list = []
		from operator import contains, not_
		for char in line:
			if not_(contains([" ", "\t"], char)): break
			indentation_list.append(char)
		return "".join(indentation_list)

	def __respace_line(self, line):
		"""
		Remove duplicate spaces in a line.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@param line: A line to remove duplicate spaces from.
		@type line: A String object.

		@return: A new line with single spaces.
		@rtype: A String object.
		"""
		line = line.split(" ")
		while True:
			try:
				line.remove("")
			except ValueError:
				break
		return " ".join(line)

	def __calculate_wrap_width(self, indentation):
		"""
		Determine wrap width.

		This function calculates the wrap width paying attention to
		automatic indentation.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@param indentation: An indentation strings.
		@type indentation: A String object.

		@return: wrap width.
		@rtype: An Integer object.
		"""
		width = self.__get_right_margin_width()
		from operator import not_, ne
		if not_(indentation): return width
		tab_width = self.__get_tab_width()
		number_of_tab_chars = indentation.count("\t")
		number_of_space_chars = indentation.count(" ")
		width = width - (number_of_space_chars + (number_of_tab_chars * tab_width))
		return width

	def __get_tab_width(self):
		"""
		Get tab width from GConf.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@return: Tab width.
		@rtype: An Integer object.
		"""
		tab_width = 4
		if self.__client.get("/apps/scribes/tab"):
			tab_width = self.__client.get_int("/apps/scribes/tab")
		return tab_width

	def __is_readonly(self):
		"""
		Check if editor is in readonly mode.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@return: True if editor is in readonly mode.
		@rtype: A Boolean object.
		"""
		from operator import not_
		if not_(self.__editor.is_readonly): return False
		from i18n import msg0010
		self.__editor.feedback.update_status_message(msg0010, "warning", 7)
		return True

	def __get_right_margin_width(self):
		"""
		Get margin or wrap width from Gconf.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.

		@return: Margin/wrap width.
		@rtype: An Integer object.
		"""
		margin_position = 72
		if self.__client.get("/apps/scribes/margin_position"):
			margin_position = self.__client.get_int("/apps/scribes/margin_position")
		return margin_position

	def __precompile_methods(self):
		"""
		Optimize methods using Psyco.

		@param self: Reference to the Manager instance.
		@type self: A Manager object.
		"""
		try:
			from psyco import bind
			bind(self.reflow_paragraph)
			bind(self.next_paragraph)
			bind(self.previous_paragraph)
			bind(self.select_paragraph)
			bind(self.__reflow_text)
			bind(self.__shorten_line)
			bind(self.__respace_line)
			bind(self.__indent_lines)
			bind(self.__calculate_wrap_width)
			bind(self.__get_paragraph_position)
			bind(self.__get_current_line_iterator)
			bind(self.__find_empty_line)
			bind(self.__is_empty_line)
			bind(self.__jump_to_line)
		except ImportError:
			pass
		return False
