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

# Copyright (c) 2005 - 2015 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the base class of the VCS project helper.
"""

from __future__ import unicode_literals

import os
import shutil
import copy

from PyQt5.QtCore import pyqtSlot, QDir, QFileInfo, QObject
from PyQt5.QtWidgets import QDialog, QInputDialog

from E5Gui.E5Action import E5Action
from E5Gui import E5MessageBox
from E5Gui.E5Application import e5App

import Preferences


class VcsProjectHelper(QObject):
    """
    Class implementing the base class of the VCS project helper.
    """
    def __init__(self, vcsObject, projectObject, parent=None, name=None):
        """
        Constructor
        
        @param vcsObject reference to the vcs object
        @param projectObject reference to the project object
        @param parent parent widget (QWidget)
        @param name name of this object (string)
        """
        super(VcsProjectHelper, self).__init__(parent)
        if name:
            self.setObjectName(name)
        
        self.vcs = vcsObject
        self.project = projectObject
        
        self.actions = []
        
        self.vcsAddAct = None
        
        self.initActions()
        
    def setObjects(self, vcsObject, projectObject):
        """
        Public method to set references to the vcs and project objects.
        
        @param vcsObject reference to the vcs object
        @param projectObject reference to the project object
        """
        self.vcs = vcsObject
        self.project = projectObject
    
    def initActions(self):
        """
        Public method to generate the action objects.
        """
        self.vcsNewAct = E5Action(
            self.tr('New from repository'),
            self.tr('&New from repository...'),
            0, 0, self, 'vcs_new')
        self.vcsNewAct.setStatusTip(self.tr(
            'Create a new project from the VCS repository'
        ))
        self.vcsNewAct.setWhatsThis(self.tr(
            """<b>New from repository</b>"""
            """<p>This creates a new local project from the VCS"""
            """ repository.</p>"""
        ))
        self.vcsNewAct.triggered.connect(self._vcsCheckout)
        self.actions.append(self.vcsNewAct)
        
        self.vcsExportAct = E5Action(
            self.tr('Export from repository'),
            self.tr('&Export from repository...'),
            0, 0, self, 'vcs_export')
        self.vcsExportAct.setStatusTip(self.tr(
            'Export a project from the repository'
        ))
        self.vcsExportAct.setWhatsThis(self.tr(
            """<b>Export from repository</b>"""
            """<p>This exports a project from the repository.</p>"""
        ))
        self.vcsExportAct.triggered.connect(self._vcsExport)
        self.actions.append(self.vcsExportAct)
        
        self.vcsAddAct = E5Action(
            self.tr('Add to repository'),
            self.tr('&Add to repository...'),
            0, 0, self, 'vcs_add')
        self.vcsAddAct.setStatusTip(self.tr(
            'Add the local project to the VCS repository'
        ))
        self.vcsAddAct.setWhatsThis(self.tr(
            """<b>Add to repository</b>"""
            """<p>This adds (imports) the local project to the VCS"""
            """ repository.</p>"""
        ))
        self.vcsAddAct.triggered.connect(self._vcsImport)
        self.actions.append(self.vcsAddAct)
    
    def initMenu(self, menu):
        """
        Public method to generate the VCS menu.
        
        @param menu reference to the menu to be populated (QMenu)
        """
        menu.clear()
        
        menu.addAction(self.vcsNewAct)
        menu.addAction(self.vcsExportAct)
        menu.addSeparator()
        menu.addAction(self.vcsAddAct)
        menu.addSeparator()

    def showMenu(self):
        """
        Public slot called before the vcs menu is shown.
        """
        if self.vcsAddAct:
            self.vcsAddAct.setEnabled(self.project.isOpen())
    
    @pyqtSlot()
    def _vcsCheckout(self, export=False):
        """
        Protected slot used to create a local project from the repository.
        
        @param export flag indicating whether an export or a checkout
                should be performed
        """
        if not self.project.checkDirty():
            return
        
        vcsSystemsDict = e5App().getObject("PluginManager")\
            .getPluginDisplayStrings("version_control")
        if not vcsSystemsDict:
            # no version control system found
            return
        
        vcsSystemsDisplay = []
        keys = sorted(vcsSystemsDict.keys())
        for key in keys:
            vcsSystemsDisplay.append(vcsSystemsDict[key])
        vcsSelected, ok = QInputDialog.getItem(
            None,
            self.tr("New Project"),
            self.tr("Select version control system for the project"),
            vcsSystemsDisplay,
            0, False)
        if not ok:
            return
        for vcsSystem, vcsSystemDisplay in list(vcsSystemsDict.items()):
            if vcsSystemDisplay == vcsSelected:
                break
        
        if not self.project.closeProject():
            return
        
        self.project.pdata["VCS"] = [vcsSystem]
        self.project.vcs = self.project.initVCS(vcsSystem)
        if self.project.vcs is not None:
            vcsdlg = self.project.vcs.vcsNewProjectOptionsDialog()
            if vcsdlg.exec_() == QDialog.Accepted:
                projectdir, vcsDataDict = vcsdlg.getData()
                self.project.pdata["VCS"] = [vcsSystem]
                self.project.vcs = self.project.initVCS(vcsSystem)
                # edit VCS command options
                if self.project.vcs.vcsSupportCommandOptions():
                    vcores = E5MessageBox.yesNo(
                        self.parent(),
                        self.tr("New Project"),
                        self.tr(
                            """Would you like to edit the VCS command"""
                            """ options?"""))
                else:
                    vcores = False
                if vcores:
                    from .CommandOptionsDialog import VcsCommandOptionsDialog
                    codlg = VcsCommandOptionsDialog(self.project.vcs)
                    if codlg.exec_() == QDialog.Accepted:
                        self.project.vcs.vcsSetOptions(codlg.getOptions())
                
                # create the project directory if it doesn't exist already
                if not os.path.isdir(projectdir):
                    try:
                        os.makedirs(projectdir)
                    except EnvironmentError:
                        E5MessageBox.critical(
                            self.parent(),
                            self.tr("Create project directory"),
                            self.tr(
                                "<p>The project directory <b>{0}</b> could not"
                                " be created.</p>").format(projectdir))
                        self.project.pdata["VCS"] = ['None']
                        self.project.vcs = self.project.initVCS()
                        return
                
                # create the project from the VCS
                self.project.vcs.vcsSetDataFromDict(vcsDataDict)
                if export:
                    ok = self.project.vcs.vcsExport(vcsDataDict, projectdir)
                else:
                    ok = self.project.vcs.vcsCheckout(vcsDataDict, projectdir,
                                                      False)
                if ok:
                    projectdir = os.path.normpath(projectdir)
                    filters = ["*.e4p"]
                    d = QDir(projectdir)
                    plist = d.entryInfoList(filters)
                    if len(plist):
                        if len(plist) == 1:
                            self.project.openProject(
                                plist[0].absoluteFilePath())
                            self.project.newProject.emit()
                        else:
                            pfilenamelist = d.entryList(filters)
                            pfilename, ok = QInputDialog.getItem(
                                None,
                                self.tr("New project from repository"),
                                self.tr("Select a project file to open."),
                                pfilenamelist, 0, False)
                            if ok:
                                self.project.openProject(
                                    QFileInfo(d, pfilename).absoluteFilePath())
                                self.project.newProject.emit()
                        if export:
                            self.project.pdata["VCS"] = ['None']
                            self.project.vcs = self.project.initVCS()
                            self.project.setDirty(True)
                            self.project.saveProject()
                    else:
                        res = E5MessageBox.yesNo(
                            self.parent(),
                            self.tr("New project from repository"),
                            self.tr(
                                "The project retrieved from the repository"
                                " does not contain an eric project file"
                                " (*.e4p). Create it?"),
                            yesDefault=True)
                        if res:
                            self.project.ppath = projectdir
                            self.project.opened = True
                            
                            from Project.PropertiesDialog import \
                                PropertiesDialog
                            dlg = PropertiesDialog(self.project, False)
                            if dlg.exec_() == QDialog.Accepted:
                                dlg.storeData()
                                self.project.initFileTypes()
                                self.project.setDirty(True)
                                try:
                                    ms = os.path.join(
                                        self.project.ppath,
                                        self.project.pdata["MAINSCRIPT"][0])
                                    if os.path.exists(ms):
                                        self.project.appendFile(ms)
                                except IndexError:
                                    ms = ""
                                self.project.newProjectAddFiles(ms)
                                self.project.createProjectManagementDir()
                                self.project.saveProject()
                                self.project.openProject(self.project.pfile)
                                if not export:
                                    res = E5MessageBox.yesNo(
                                        self.parent(),
                                        self.tr(
                                            "New project from repository"),
                                        self.tr(
                                            "Shall the project file be added"
                                            " to the repository?"),
                                        yesDefault=True)
                                    if res:
                                        self.project.vcs.vcsAdd(
                                            self.project.pfile)
                else:
                    E5MessageBox.critical(
                        self.parent(),
                        self.tr("New project from repository"),
                        self.tr(
                            """The project could not be retrieved from"""
                            """ the repository."""))
                    self.project.pdata["VCS"] = ['None']
                    self.project.vcs = self.project.initVCS()
            else:
                self.project.pdata["VCS"] = ['None']
                self.project.vcs = self.project.initVCS()

    def _vcsExport(self):
        """
        Protected slot used to export a project from the repository.
        """
        self._vcsCheckout(True)

    def _vcsImport(self):
        """
        Protected slot used to import the local project into the repository.
        
        <b>NOTE</b>:
            This does not necessarily make the local project a vcs controlled
            project. You may have to checkout the project from the repository
            in order to accomplish that.
        """
        def revertChanges():
            """
            Local function to revert the changes made to the project object.
            """
            self.project.pdata["VCS"] = pdata_vcs[:]
            self.project.pdata["VCSOPTIONS"] = copy.deepcopy(pdata_vcsoptions)
            self.project.pdata["VCSOTHERDATA"] = copy.deepcopy(pdata_vcsother)
            self.project.vcs = vcs
            self.project.vcsProjectHelper = vcsHelper
            self.project.vcsBasicHelper = vcs is None
            self.initMenu(self.project.vcsMenu)
            self.project.setDirty(True)
            self.project.saveProject()
        
        pdata_vcs = self.project.pdata["VCS"][:]
        pdata_vcsoptions = copy.deepcopy(self.project.pdata["VCSOPTIONS"])
        pdata_vcsother = copy.deepcopy(self.project.pdata["VCSOTHERDATA"])
        vcs = self.project.vcs
        vcsHelper = self.project.vcsProjectHelper
        vcsSystemsDict = e5App().getObject("PluginManager")\
            .getPluginDisplayStrings("version_control")
        if not vcsSystemsDict:
            # no version control system found
            return
        
        vcsSystemsDisplay = []
        keys = sorted(list(vcsSystemsDict.keys()))
        for key in keys:
            vcsSystemsDisplay.append(vcsSystemsDict[key])
        vcsSelected, ok = QInputDialog.getItem(
            None,
            self.tr("Import Project"),
            self.tr("Select version control system for the project"),
            vcsSystemsDisplay,
            0, False)
        if not ok:
            return
        for vcsSystem, vcsSystemDisplay in list(vcsSystemsDict.items()):
            if vcsSystemDisplay == vcsSelected:
                break
        
        self.project.pdata["VCS"] = [vcsSystem]
        self.project.vcs = self.project.initVCS(vcsSystem)
        if self.project.vcs is not None:
            vcsdlg = self.project.vcs.vcsOptionsDialog(self.project,
                                                       self.project.name, 1)
            if vcsdlg.exec_() == QDialog.Accepted:
                vcsDataDict = vcsdlg.getData()
                # edit VCS command options
                if self.project.vcs.vcsSupportCommandOptions():
                    vcores = E5MessageBox.yesNo(
                        self.parent(),
                        self.tr("Import Project"),
                        self.tr(
                            """Would you like to edit the VCS command"""
                            """ options?"""))
                else:
                    vcores = False
                if vcores:
                    from .CommandOptionsDialog import VcsCommandOptionsDialog
                    codlg = VcsCommandOptionsDialog(self.project.vcs)
                    if codlg.exec_() == QDialog.Accepted:
                        self.project.vcs.vcsSetOptions(codlg.getOptions())
                self.project.setDirty(True)
                self.project.vcs.vcsSetDataFromDict(vcsDataDict)
                self.project.saveProject()
                isVcsControlled = self.project.vcs.vcsImport(
                    vcsDataDict, self.project.ppath)[0]
                if isVcsControlled:
                    # reopen the project
                    self.project.openProject(self.project.pfile)
                else:
                    # revert the changes to the local project
                    # because the project dir is not a VCS directory
                    revertChanges()
            else:
                # revert the changes because user cancelled
                revertChanges()

    def _vcsUpdate(self):
        """
        Protected slot used to update the local project from the repository.
        """
        shouldReopen = self.vcs.vcsUpdate(self.project.ppath)
        if shouldReopen:
            res = E5MessageBox.yesNo(
                self.parent(),
                self.tr("Update"),
                self.tr("""The project should be reread. Do this now?"""),
                yesDefault=True)
            if res:
                self.project.reopenProject()
        
    def _vcsCommit(self):
        """
        Protected slot used to commit changes to the local project to the
        repository.
        """
        if Preferences.getVCS("AutoSaveProject"):
            self.project.saveProject()
        if Preferences.getVCS("AutoSaveFiles"):
            self.project.saveAllScripts()
        self.vcs.vcsCommit(self.project.ppath, '')
        
    def _vcsRemove(self):
        """
        Protected slot used to remove the local project from the repository.
        
        Depending on the parameters set in the vcs object the project
        may be removed from the local disk as well.
        """
        res = E5MessageBox.yesNo(
            self.parent(),
            self.tr("Remove project from repository"),
            self.tr(
                "Dou you really want to remove this project from"
                " the repository (and disk)?"))
        if res:
            self.vcs.vcsRemove(self.project.ppath, True)
            self._vcsCommit()
            if not os.path.exists(self.project.pfile):
                ppath = self.project.ppath
                self.setDirty(False)
                self.project.closeProject()
                shutil.rmtree(ppath, True)
        
    def _vcsCommandOptions(self):
        """
        Protected slot to edit the VCS command options.
        """
        if self.vcs.vcsSupportCommandOptions():
            from .CommandOptionsDialog import VcsCommandOptionsDialog
            codlg = VcsCommandOptionsDialog(self.vcs)
            if codlg.exec_() == QDialog.Accepted:
                self.vcs.vcsSetOptions(codlg.getOptions())
                self.project.setDirty(True)
        
    def _vcsLog(self):
        """
        Protected slot used to show the log of the local project.
        """
        self.vcs.vcsLog(self.project.ppath)
        
    def _vcsLogBrowser(self):
        """
        Protected slot used to show the log of the local project with a
        log browser dialog.
        """
        self.vcs.vcsLogBrowser(self.project.ppath)
        
    def _vcsDiff(self):
        """
        Protected slot used to show the difference of the local project to
        the repository.
        """
        self.vcs.vcsDiff(self.project.ppath)
        
    def _vcsStatus(self):
        """
        Protected slot used to show the status of the local project.
        """
        self.vcs.vcsStatus(self.project.ppath)
        
    def _vcsTag(self):
        """
        Protected slot used to tag the local project in the repository.
        """
        self.vcs.vcsTag(self.project.ppath)
        
    def _vcsRevert(self):
        """
        Protected slot used to revert changes made to the local project.
        """
        self.vcs.vcsRevert(self.project.ppath)
        
    def _vcsSwitch(self):
        """
        Protected slot used to switch the local project to another tag/branch.
        """
        shouldReopen = self.vcs.vcsSwitch(self.project.ppath)
        if shouldReopen:
            res = E5MessageBox.yesNo(
                self.parent(),
                self.tr("Switch"),
                self.tr("""The project should be reread. Do this now?"""),
                yesDefault=True)
            if res:
                self.project.reopenProject()
        
    def _vcsMerge(self):
        """
        Protected slot used to merge changes of a tag/revision into the local
        project.
        """
        self.vcs.vcsMerge(self.project.ppath)
        
    def _vcsCleanup(self):
        """
        Protected slot used to cleanup the local project.
        """
        self.vcs.vcsCleanup(self.project.ppath)
        
    def _vcsCommand(self):
        """
        Protected slot used to execute an arbitrary vcs command.
        """
        self.vcs.vcsCommandLine(self.project.ppath)

    def _vcsInfoDisplay(self):
        """
        Protected slot called to show some vcs information.
        """
        from .RepositoryInfoDialog import VcsRepositoryInfoDialog
        info = self.vcs.vcsRepositoryInfos(self.project.ppath)
        dlg = VcsRepositoryInfoDialog(None, info)
        dlg.exec_()
