# Copyright (C) 2010 Canonical Ltd
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA


from PyQt4 import QtCore, QtGui

from bzrlib import urlutils
from bzrlib.plugins.explorer.lib import (
    experimental_mode,
    location_selector,
    )


class LocationViewer(QtGui.QWidget):
    """A widget for viewing and navigating between views."""
    #emitted when a new location has been added
    locationAdded = QtCore.pyqtSignal()

    def __init__(self, action_map, selector_key, history_manager, *args):
        QtGui.QWidget.__init__(self, *args)
        self._action_map = action_map
        self._selector_key = selector_key
        self._history_manager = history_manager
        self._key_model_by_view = {}
        self._reopen_required_next_refresh = set()
        self.setLayout(self._layout_ui())

    def _layout_ui(self):
        # Build the view stack and selector
        self._stack = QtGui.QStackedWidget()
        self._selector = self._build_selector()
        north_ui, east_ui, west_ui, south_ui = self._selector.ui_decorators()
        north_resize, east_resize, west_resize, south_resize = \
                self._selector.ui_resizable()
        # Put them together
        inner = self._build_ui_strip(north_ui, self._stack, south_ui,
                                     north_resize, south_resize, True)
        outer = self._build_ui_strip(west_ui, inner, east_ui,
                                     west_resize, east_resize, False)
        layout = QtGui.QVBoxLayout()
        layout.addWidget(outer, 1)
        return layout
    
    def _build_selector(self):
        selector_factory = location_selector.location_selector_registry.get(
            self._selector_key)
        selector = selector_factory(self._action_map)
        self.connect(selector, QtCore.SIGNAL("currentChanged"),
            self._select_new_view)
        self.connect(selector, QtCore.SIGNAL("locationDeleted"),
                     self._delete_view)
        return selector

    def _build_ui_strip(self, first, center, last, first_resize, last_resize,
                        vertical):
        if vertical:
            splitter_orientation = QtCore.Qt.Vertical
            box_layout = QtGui.QVBoxLayout
        else:
            splitter_orientation = QtCore.Qt.Horizontal
            box_layout = QtGui.QHBoxLayout
        if first_resize and last_resize:
            strip = QtGui.QSplitter(splitter_orientation)
            strip.setContentsMargins(0, 0, 0, 0)
            if first:
                strip.addWidget(first)
            strip.addWidget(center)
            strip.setStretchFactor(strip.count()-1, 1)
            if last:
                strip.addWidget(last)
        elif first_resize or last_resize:
            strip = QtGui.QSplitter(splitter_orientation)
            strip.setContentsMargins(0, 0, 0, 0)
            if first_resize:
                if first:
                    strip.addWidget(first)
                fixed = QtGui.QWidget()
                fixed_layout = box_layout(fixed)
                fixed_layout.setContentsMargins(0, 0, 0, 0)
                fixed_layout.addWidget(center, 1)
                if last:
                    fixed_layout.addWidget(last)
                strip.addWidget(fixed)
                strip.setStretchFactor(strip.count()-1, 1)
            elif last_resize:
                fixed = QtGui.QWidget()
                fixed_layout = box_layout(fixed)
                fixed_layout.setContentsMargins(0, 0, 0, 0)
                if first:
                    fixed_layout.addWidget(first)
                fixed_layout.addWidget(center, 1)
                strip.addWidget(fixed)
                strip.setStretchFactor(strip.count()-1, 1)
                if last:
                    strip.addWidget(last)
        else:
            strip = QtGui.QWidget()
            strip_layout = box_layout(strip)
            strip_layout.setContentsMargins(0, 0, 0, 0)
            if first:
                strip_layout.addWidget(first)
            strip_layout.addWidget(center, 1)
            if last:
                strip_layout.addWidget(last)
        return strip

    def _select_new_view(self, key, model):
        if not key:
            # We've closed the last page. We could emit a key of None
            # here but doing nothing seems equally sensible.
            #self.emit(QtCore.SIGNAL("currentChanged"), key)
            return
        view_opened = False
        for i in range(0, self._stack.count()):
            widget = self._stack.widget(i)
            widget_key, widget_model = self._key_model_by_view[widget]
            if key == widget_key:
                self._stack.setCurrentIndex(i)
                view_opened = True
                break
        if not view_opened:
            #print "opening view for %s" % (key,)
            # The selector has a location we're yet to open.
            # Open it now.
            view = model.view()
            self._key_model_by_view[view] = (key, model)
            self._stack.addWidget(view)
            index = self._stack.count() - 1
            self._stack.setCurrentIndex(index)
            self._record_history(key, model)
        self.emit(QtCore.SIGNAL("currentChanged"), key)
        
    def _delete_view(self, key):
        for i in range(0, self._stack.count()):
            widget = self._stack.widget(i)
            widget_key, widget_model = self._key_model_by_view[widget]
            if key == widget_key:
                self._stack.removeWidget(widget)
                del self._key_model_by_view[widget]
                break

    def add_location(self, key, model):
        """Add a tab for a location.

        :param key: a unique key for this location
        :param model: an instance of AbstractLocationModel
        """
        key = self._normalise_key(key)
        # Get the view & remember the mapping from view to model
        view = model.view()
        self._key_model_by_view[view] = (key, model)

        # Add the view and make it active
        self._stack.addWidget(view)
        self._selector.add_location(key, model)
        self._record_history(key, model)
        #notify history browser
        self.locationAdded.emit()

    def _record_history(self, key, model):
        """Record this location as recently opened."""
        if self._history_manager:
            self._history_manager.add_recent(key, model.kind(),
                model.display_name())

    def _normalise_key(self, key):
        # If the key is a file URL, use the path instead
        if key.startswith("file://"):
            return urlutils.local_path_from_url(key)
        return key

    def select_view_for_key(self, key):
        """Change the selected view to be the one for key.
        
        :return: True if succeeded, False if key not found.
        """
        key = self._normalise_key(key)
        try:
            self._selector.select_key(key)
            return True
        except KeyError:
            return False

    def current_key(self):
        """What's the current key?
        
        :return: None if no locations exist.
        """
        widget = self._stack.currentWidget()
        if widget:
            key, model = self._key_model_by_view.get(widget)
            return key
        else:
            return None

    def current_model(self):
        """What's the current location model?
        
        :return: None if no locations exist.
        """
        widget = self._stack.currentWidget()
        if widget:
            key, model = self._key_model_by_view.get(widget)
            return model
        else:
            return None

    def close_current(self):
        """Close the current location."""
        self._selector.close_current()

    def refresh_current(self, reopen=False):
        """Refresh the current location."""
        key, model = self._key_model_by_view.get(self._stack.currentWidget())
        if reopen or key in self._reopen_required_next_refresh:
            self._reopen_required_next_refresh.discard(key)
            model.reopen_location()
            self._selector.update_current_label()
        model.refresh_view()

    def set_reopen_on_next_refresh(self):
        """Remember to reopen the current location next time it is refreshed."""
        key, model = self._key_model_by_view.get(self._stack.currentWidget())
        self._reopen_required_next_refresh.add(key)

    def refresh_view_for_key(self, key):
        """Refresh the location for key if any.
        
        :return: True if succeeded, False if key not found.
        """
        key = self._normalise_key(key)
        for i in range(0, self._stack.count()):
            widget = self._stack.widget(i)
            widget_key, model = self._key_model_by_view[widget]
            if key == widget_key:
                model.refresh_view()
                return True
        return False
