# Copyright (C) 2009, 2010, 2011  Roman Zimbelmann <romanz@lavabit.com>
#
# This program 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 3 of the License, or
# (at your option) any later version.
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

"""The BrowserView manages a set of BrowserColumns."""
import curses
from ranger.ext.signals import Signal
from ranger.ext.keybinding_parser import key_to_string
from . import Widget
from .browsercolumn import BrowserColumn
from .pager import Pager
from ..displayable import DisplayableContainer

class BrowserView(Widget, DisplayableContainer):
	ratios = None
	preview = True
	is_collapsed = False
	draw_bookmarks = False
	stretch_ratios = None
	need_clear = False
	old_collapse = False
	draw_hints = False

	def __init__(self, win, ratios, preview = True):
		DisplayableContainer.__init__(self, win)
		self.preview = preview
		self.columns = []

		self.pager = Pager(self.win, embedded=True)
		self.pager.visible = False
		self.add_child(self.pager)

		self.change_ratios(ratios)

		for option in ('preview_directories', 'preview_files'):
			self.settings.signal_bind('setopt.' + option,
					self._request_clear_if_has_borders, weak=True)

		self.fm.env.signal_bind('move', self.request_clear)
		self.settings.signal_bind('setopt.column_ratios', self.request_clear)

	def change_ratios(self, ratios):
		if isinstance(ratios, Signal):
			ratios = ratios.value

		for column in self.columns:
			column.destroy()
			self.remove_child(column)
		self.columns = []

		ratio_sum = float(sum(ratios))
		self.ratios = tuple(x / ratio_sum for x in ratios)

		last = 0.1 if self.settings.padding_right else 0
		if len(self.ratios) >= 2:
			self.stretch_ratios = self.ratios[:-2] + \
					((self.ratios[-2] + self.ratios[-1] * 1.0 - last),
					(self.ratios[-1] * last))

		offset = 1 - len(ratios)
		if self.preview: offset += 1

		for level in range(len(ratios)):
			fl = BrowserColumn(self.win, level + offset)
			self.add_child(fl)
			self.columns.append(fl)

		try:
			self.main_column = self.columns[self.preview and -2 or -1]
		except IndexError:
			self.main_column = None
		else:
			self.main_column.display_infostring = True
			self.main_column.main_column = True

		self.resize(self.y, self.x, self.hei, self.wid)

	def _request_clear_if_has_borders(self):
		if self.settings.draw_borders:
			self.request_clear()

	def request_clear(self):
		self.need_clear = True

	def draw(self):
		if self.need_clear:
			self.win.erase()
			self.need_redraw = True
			self.need_clear = False
		for path in self.fm.tabs.values():
			if path is not None:
				directory = self.env.get_directory(path)
				directory.load_content_if_outdated()
				directory.use()
		DisplayableContainer.draw(self)
		if self.settings.draw_borders:
			self._draw_borders()
		if self.draw_bookmarks:
			self._draw_bookmarks()
		elif self.draw_hints:
			self._draw_hints()

	def finalize(self):
		if self.pager.visible:
			try:
				self.fm.ui.win.move(self.main_column.y, self.main_column.x)
			except:
				pass
		else:
			try:
				x = self.main_column.x
				y = self.main_column.y + self.main_column.target.pointer\
						- self.main_column.scroll_begin
				self.fm.ui.win.move(y, x)
			except:
				pass

	def _draw_borders(self):
		win = self.win
		self.color('in_browser', 'border')

		left_start = 0
		right_end = self.wid - 1

		for child in self.columns:
			if not child.has_preview():
				left_start = child.x + child.wid
			else:
				break
		if not self.pager.visible:
			for child in reversed(self.columns):
				if not child.has_preview():
					right_end = child.x - 1
				else:
					break
			if right_end < left_start:
				right_end = self.wid - 1

		win.hline(0, left_start, curses.ACS_HLINE, right_end - left_start)
		win.hline(self.hei - 1, left_start, curses.ACS_HLINE,
				right_end - left_start)
		win.vline(1, left_start, curses.ACS_VLINE, self.hei - 2)

		for child in self.columns:
			if not child.has_preview():
				continue
			if child.main_column and self.pager.visible:
				win.vline(1, right_end, curses.ACS_VLINE, self.hei - 2)
				break
			x = child.x + child.wid
			y = self.hei - 1
			try:
				win.vline(1, x, curses.ACS_VLINE, y - 1)
				win.addch(0, x, curses.ACS_TTEE, 0)
				win.addch(y, x, curses.ACS_BTEE, 0)
			except:
				# in case it's off the boundaries
				pass

		self.addch(0, left_start, curses.ACS_ULCORNER)
		self.addch(self.hei - 1, left_start, curses.ACS_LLCORNER)
		self.addch(0, right_end, curses.ACS_URCORNER)
		self.addch(self.hei - 1, right_end, curses.ACS_LRCORNER)

	def _draw_bookmarks(self):
		self.fm.bookmarks.update_if_outdated()
		self.color_reset()
		self.need_clear = True

		sorted_bookmarks = sorted((item for item in self.fm.bookmarks \
			if self.fm.settings.show_hidden_bookmarks or \
			'/.' not in item[1].path), key=lambda t: t[0].lower())

		hei = min(self.hei - 1, len(sorted_bookmarks))
		ystart = self.hei - hei

		maxlen = self.wid
		self.addnstr(ystart - 1, 0, "mark  path".ljust(self.wid), self.wid)

		whitespace = " " * maxlen
		for line, items in zip(range(self.hei-1), sorted_bookmarks):
			key, mark = items
			string = " " + key + "   " + mark.path
			self.addstr(ystart + line, 0, whitespace)
			self.addnstr(ystart + line, 0, string, self.wid)

		self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE)

	def _draw_hints(self):
		self.need_clear = True
		hints = []
		for k, v in self.fm.env.keybuffer.pointer.items():
			k = key_to_string(k)
			if isinstance(v, dict):
				text = '...'
			else:
				text = v
			if text.startswith('hint') or text.startswith('chain hint'):
				continue
			hints.append((k, text))
		hints.sort(key=lambda t: t[1])

		hei = min(self.hei - 1, len(hints))
		ystart = self.hei - hei
		self.addnstr(ystart - 1, 0, "key          command".ljust(self.wid),
				self.wid)
		try:
			self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE)
		except:
			pass
		whitespace = " " * self.wid
		i = ystart
		for key, cmd in hints:
			string = " " + key.ljust(11) + " " + cmd
			self.addstr(i, 0, whitespace)
			self.addnstr(i, 0, string, self.wid)
			i += 1

	def _collapse(self):
		# Should the last column be cut off? (Because there is no preview)
		if not self.settings.collapse_preview or not self.preview \
				or not self.stretch_ratios:
			return False
		result = not self.columns[-1].has_preview()
		target = self.columns[-1].target
		if not result and target and target.is_file and \
			self.fm.settings.preview_script and \
			self.fm.settings.use_preview_script:
			try:
				result = not self.fm.previews[target.realpath]['foundpreview']
			except:
				return self.old_collapse
		self.old_collapse = result
		return result

	def resize(self, y, x, hei, wid):
		"""Resize all the columns according to the given ratio"""
		DisplayableContainer.resize(self, y, x, hei, wid)
		borders = self.settings.draw_borders
		pad = 1 if borders else 0
		left = pad

		self.is_collapsed = self._collapse()
		if self.is_collapsed:
			generator = enumerate(self.stretch_ratios)
		else:
			generator = enumerate(self.ratios)

		last_i = len(self.ratios) - 1

		for i, ratio in generator:
			wid = int(ratio * self.wid)

			cut_off = self.is_collapsed and not self.settings.padding_right
			if i == last_i:
				if not cut_off:
					wid = int(self.wid - left + 1 - pad)
				else:
					self.columns[i].resize(pad, left - 1, hei - pad * 2, 1)
					self.columns[i].visible = False
					continue

			if i == last_i - 1:
				self.pager.resize(pad, left, hei - pad * 2, \
						max(1, self.wid - left - pad))

				if cut_off:
					self.columns[i].resize(pad, left, hei - pad * 2, \
							max(1, self.wid - left - pad))
					continue

			try:
				self.columns[i].resize(pad, left, hei - pad * 2, \
						max(1, wid - 1))
			except KeyError:
				pass

			left += wid

	def click(self, event):
		if DisplayableContainer.click(self, event):
			return True
		direction = event.mouse_wheel_direction()
		if direction:
			self.main_column.scroll(direction)
		return False

	def open_pager(self):
		self.pager.visible = True
		self.pager.focused = True
		self.need_clear = True
		self.pager.open()
		try:
			self.columns[-1].visible = False
			self.columns[-2].visible = False
		except IndexError:
			pass

	def close_pager(self):
		self.pager.visible = False
		self.pager.focused = False
		self.need_clear = True
		self.pager.close()
		try:
			self.columns[-1].visible = True
			self.columns[-2].visible = True
		except IndexError:
			pass

	def poke(self):
		DisplayableContainer.poke(self)

		# Show the preview column when it has a preview but has
		# been hidden (e.g. because of padding_right = False)
		if not self.pager.visible and not self.columns[-1].visible and \
		self.columns[-1].target and self.columns[-1].target.is_directory \
		or self.columns[-1].has_preview() and not self.pager.visible:
			self.columns[-1].visible = True

		if self.preview and self.is_collapsed != self._collapse():
			self.resize(self.y, self.x, self.hei, self.wid)
