# Copyright (C) 2009 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.

from PyQt4 import QtCore, QtGui

from bzrlib import bzrdir, errors, osutils, urlutils
from bzrlib.commands import get_cmd_object

from bzrlib.plugins.explorer.lib import (
    # This is needed to load the right icon when run standalone
    explorer_rc,
    kinds,
    options_panel,
    )
from bzrlib.plugins.explorer.lib.workspace_models import (
    workspace_model_registry,
    )
from bzrlib.plugins.explorer.lib.i18n import gettext
from bzrlib.plugins.qbzr.lib.init import fill_option_combo
try:
    # refactoring in QBzr 0.17 moved the class here
    from bzrlib.plugins.qbzr.lib.branch import QBzrBranchWindow
except ImportError:
    # try the old location
    from bzrlib.plugins.qbzr.lib.pull import QBzrBranchWindow
from bzrlib.plugins.qbzr.lib.subprocess import SubProcessDialog


class QInitWorkspaceDialog(SubProcessDialog):

    def exec_(self):
        result = SubProcessDialog.exec_(self)
        #print "exec result: %s" % (result,)
        #print "return code: %s" % (self.return_code,)
        # For some reason, QBzr is always returning Rejected
        # so fall back to checking the return code
        if self.return_code:
            return QtGui.QDialog.Rejected
        else:
            return QtGui.QDialog.Accepted

    def __init__(self, location, model=None, create_parent_dirs=False,
        dialog=True, ui_mode=True, parent=None, just_repo_models=False,
        model_params=None):
        super(QInitWorkspaceDialog, self).__init__(
                                  gettext("Initialize"),
                                  name="init-workspace",
                                  default_size=(400, 400),
                                  ui_mode=ui_mode,
                                  dialog=dialog,
                                  parent=parent,
                                  hide_progress=True,
                                  )
        if location == u'.':
            location = osutils.abspath(u'.')
        self._location = location
        self._create_parent_dirs = create_parent_dirs
        self._just_repo_models = just_repo_models
        self._model_params = model_params

        # Build the form
        layout = QtGui.QVBoxLayout(self)
        layout.addLayout(self._build_location_layout())
        model_group, self._model_params_panel = self._build_model_parts()
        layout.addWidget(model_group)
        layout.addWidget(self._build_options_group_box(
            self._model_params_panel))
        layout.addWidget(self._build_format_group_box())
        layout.addWidget(self.make_default_status_box())
        layout.addWidget(self.buttonbox)

        # Init the controls
        if location:
            self.location.setText(location)
        if model:
            self.set_model(model)
        if create_parent_dirs:
            self.create_parent_dirs.setCheckState(QtCore.Qt.Checked)
        if model_params:
            self._model_params_panel.currentWidget().set_values(model_params)
        self.location.setFocus()
 
    def _build_location_layout(self):
        label = QtGui.QLabel(gettext("Location:"))
        self.location = QtGui.QLineEdit()
        browse_button = QtGui.QPushButton(gettext("Browse"))
        QtCore.QObject.connect(browse_button, QtCore.SIGNAL("clicked(bool)"),
            self._do_browse_clicked)
        layout = QtGui.QHBoxLayout()
        layout.addWidget(label)
        layout.addWidget(self.location)
        layout.addWidget(browse_button)
        return layout

    def _do_browse_clicked(self):
        location = self.location.text()
        fileName = QtGui.QFileDialog.getExistingDirectory(self,
            gettext("Select location"), location);
        if fileName:
            self.location.setText(fileName)

    def _build_model_parts(self):
        """Build a group box and matching parameters panel for the models."""
        stack = QtGui.QStackedWidget()
        layout = QtGui.QVBoxLayout()
        button_group = QtGui.QButtonGroup()
        self.model_id_by_class = {}
        self.model_radios = {}
        for model_id, model in sorted(workspace_model_registry.iteritems()):
            self.model_id_by_class[model] = model_id
            ws_model = model()
            if self._just_repo_models and not ws_model.just_repository:
                continue
            help = workspace_model_registry.get_help(model_id)
            radio = self._model_radio(layout, button_group,
                gettext(ws_model.title), gettext(help))
            self.model_radios[model_id] = radio

            # Build the parameters panel for this model and wire up the
            # radio button to display it
            params_panel = options_panel.OptionsPanel(ws_model.parameters)
            def _radio_toggled(widget):
                return lambda b: stack.setCurrentWidget(widget)
            stack.addWidget(params_panel)
            self.connect(radio, QtCore.SIGNAL("toggled(bool)"),
                    _radio_toggled(params_panel))

        # Set the default model. For now, this is the first one, though it
        # might be configurable as a preference alter
        default_model_id = sorted(self.model_radios)[0]
        self.model_radios[default_model_id].setChecked(QtCore.Qt.Checked)

        # Explain how to get help on the models
        msg = kinds.html_status_message(kinds.GOOD_STATUS, gettext(
            "For more information about each model, see the tooltip."))
        tooltip_hint = QtGui.QLabel(msg)
        layout.addWidget(tooltip_hint)

        # Build the group box itself and return
        group_box = QtGui.QGroupBox(gettext("Workspace Model"))
        group_box.setLayout(layout)
        return group_box, stack

    def _model_radio(self, layout, button_group, label, tooltip):
        radio =  QtGui.QRadioButton(label)
        radio.setToolTip(tooltip)
        layout.addWidget(radio)
        button_group.addButton(radio)
        return radio

    def _build_format_group_box(self):
        # Build the radio buttons line
        # TODO: perhaps only add '2.0' if available so users running
        # on Bazaar 1.14 to 1.16 get a better UI?
        button_group = QtGui.QButtonGroup()
        self.format_2 = QtGui.QRadioButton(gettext("2.0 default"))
        button_group.addButton(self.format_2)
        self.format_1 = QtGui.QRadioButton(gettext("1.x default"))
        button_group.addButton(self.format_1)
        self.format_other = QtGui.QRadioButton(gettext("Other"))
        button_group.addButton(self.format_other)
        self.format_2.setChecked(True)
        layout = QtGui.QHBoxLayout()
        layout.addWidget(self.format_2)
        layout.addWidget(self.format_1)
        layout.addWidget(self.format_other)

        # Build the other format field and control when it get displayed
        self.format_name = QtGui.QComboBox()
        self.format_name.setVisible(False)
        cmd = get_cmd_object('init')
        opt = cmd.options()['format']
        fill_option_combo(self.format_name, opt, 'default')
        QtCore.QObject.connect(self.format_other,
            QtCore.SIGNAL("toggled(bool)"), self._do_other_toggled)

        # Put the group box together
        group_layout = QtGui.QVBoxLayout()
        group_layout.addLayout(layout)
        group_layout.addWidget(self.format_name)
        group_box = QtGui.QGroupBox(gettext("Format"))
        group_box.setLayout(group_layout)
        return group_box

    def _do_other_toggled(self, value):
        self.format_name.setVisible(value)

    def _build_options_group_box(self, model_params_panel):
        self.create_parent_dirs = QtGui.QCheckBox(
            gettext("Create parent directories of location, if required"))
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.create_parent_dirs)
        layout.addWidget(model_params_panel)
        group_box = QtGui.QGroupBox(gettext("Options"))
        group_box.setLayout(layout)
        return group_box

    def get_model(self):
        for model_id, radio in self.model_radios.items():
            if radio.isChecked():
                return model_id
        else:
            # XXX: assume the first one or raise an exception here?
            return sorted(self.model_radios)[0]

    def set_model(self, model):
        model_id = self.model_id_by_class[model]
        self.model_radios[model_id].setChecked(True)

    def get_location(self):
        # XXX: Should we insist on a location being set?
        return unicode(self.location.text()) or u'.'

    def get_format(self):
        if self.format_2.isChecked():
            return '2a'
        elif self.format_1.isChecked():
            return 'pack-0.92'
        else:
            return self.format_name.currentText()
 
    def get_model_parameters(self):
        panel = self._model_params_panel.currentWidget()
        return panel.get_values()

    def do_start(self):
        args = []
        model = self.get_model()
        if model:
            args.append('--' + model)
        if self.create_parent_dirs.isChecked():
            args.append('--create-prefix')
        format = self.get_format()
        if format:
            args.append('--format')
            args.append(format)
        parameters = self.get_model_parameters()
        #print "params: %s" % (parameters,)
        for k, v in parameters.items():
            if v in [False, '', None]:
                # Ignore false booleans and empty strings
                continue
            args.append('-P')
            if v is True:
                p_str = k
            else:
                # XXX: Do we need to quote v if it contains spaces?
                p_str = "%s=%s" % (k, v)
            args.append(p_str)
        args.append(self.get_location())
        self.process_widget.do_start(None, 'new', *args)


class QBranchExplorerStyleDialog(QBzrBranchWindow):
    """A custom branch dialog for Bazaar Explorer.

    Currently, this just provides Explorer with access to the destination
    location so it can open it on completion. In the future, we may
    get more ambitious and offer a 'bind' checkbox, etc.

    The implementation reuses the dialog used for "qbranch" so it's
    rather fragile.
    """

    def get_to_location(self):
        """The path the branch was created in."""
        # This is used by explorer to find the location to open on completion
        # TODO: return None if the command failed
        return unicode(self.ui.to_location.currentText())

    def validate(self):
        """If local, check that the destination is in a shared repo."""
        to_location = self.get_to_location()
        url = urlutils.normalize_url(to_location)
        if not url.startswith('file://'):
            # There's no technical reason for not doing this check on a
            # remote location (though it can be potentially slow). More
            # practically, casual users are unlikely to create branches
            # at non-local locations and users doing it typically know
            # what they are doing.
            return True
        try:
            repo = bzrdir.BzrDir.open_containing_tree_branch_or_repository(
                to_location)[2]
        except errors.NotBranchError:
            # We didn't find a shared repository
            pass
        else:
            # We found something - check it's a shared repo
            if repo is not None and repo.is_shared():
                return True
        return self._suggest_shared_repo(to_location)
    
    def _suggest_shared_repo(self, to_location):
        # We could be more fancy here and offer multiple workspace models
        # ala init. OTOH, a simple Yes/No is enough to effectively offer
        # 'feature branches' and 'plain/standalone branch' so it's only
        # 'shared tree' that we're not offering here. That model is
        # more complicated to deliver than the other models so we really
        # want it supported at the command line level before exposing it
        # here I think.
        msg = gettext("The destination is outside a shared repository. "
            "Would you like to initialize one now? "
            "This is generally recommended.")
        btn = QtGui.QMessageBox.warning(self, gettext("Branch"),
            msg, gettext("&Yes"), gettext("&No"), '', 0, 1)
        if btn == 0: # QtGui.QMessageBox.Yes:
            self._init_shared_repo(to_location,)
            return False
        return True

    def _init_shared_repo(self, path):
        """Create a shared repository at a given path."""
        # Unfortunately, we can't just make a repository here and
        # hope that the source branch will be format compatible.
        # So we pop up the init dialog and let the user choose
        # the model and format.
        window = QInitWorkspaceDialog(location=path, just_repo_models=True,
            parent=self)
        if window.exec_():
            model = window.get_model()
            new_destination = osutils.pathjoin(path, 'trunk')
            self.ui.to_location.lineEdit().setText(new_destination)
