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

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

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

import os
import sys
import shutil
import copy

from PyQt4.QtCore import *
from PyQt4.QtGui import *

from KdeQt import KQFileDialog, KQMessageBox, KQInputDialog

import VCS
from Config import ConfigVcsSystemIndicators, ConfigVcsSystems, \
                       ConfigVcsSystemsDisplay
from CommandOptionsDialog import vcsCommandOptionsDialog
from RepositoryInfoDialog import VcsRepositoryInfoDialog

from E4Gui.E4Action import E4Action

import Utilities
import Preferences

class VcsProjectHelper(QObject):
    """
    Class implementing the base class of the VCS project helper.
    
    @signal vcsStatusMonitorData(QStringList) emitted to update the VCS status
    @signal vcsStatusMonitorStatus(QString, QString) emitted to signal the status of the
        monitoring thread (ok, nok, op, off) and a status message
    """
    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 or QString)
        """
        QObject.__init__(self, parent)
        if name:
            self.setObjectName(name)
        
        self.vcs = vcsObject
        self.project = projectObject
        
        self.statusMonitorThread = None
        self.actions = []
        
        self.initActions()
        
    def initActions(self):
        """
        Public method to generate the action objects.
        """
        self.vcsNewAct = E4Action(self.trUtf8('New from repository'),
                self.trUtf8('&New from repository...'), 0, 0, self, 'vcs_new')
        self.vcsNewAct.setStatusTip(self.trUtf8(
            'Create a new project from the VCS repository'
        ))
        self.vcsNewAct.setWhatsThis(self.trUtf8(
            """<b>New from repository</b>"""
            """<p>This creates a new local project from the VCS repository.</p>"""
        ))
        self.vcsNewAct.connectIt(SIGNAL('triggered()'),self._vcsCheckout)
        self.actions.append(self.vcsNewAct)
        
        self.vcsExportAct = E4Action(self.trUtf8('Export from repository'), 
                self.trUtf8('&Export from repository...'), 0, 0, self, 'vcs_export')
        self.vcsExportAct.setStatusTip(self.trUtf8(
            'Export a project from the repository'
        ))
        self.vcsExportAct.setWhatsThis(self.trUtf8(
            """<b>Export from repository</b>"""
            """<p>This exports a project from the repository.</p>"""
        ))
        self.vcsExportAct.connectIt(SIGNAL('triggered()'),self._vcsExport)
        self.actions.append(self.vcsExportAct)
        
        self.vcsAddAct = E4Action(self.trUtf8('Add to repository'),
                self.trUtf8('&Add to repository...'), 0, 0, self, 'vcs_add')
        self.vcsAddAct.setStatusTip(self.trUtf8(
            'Add the local project to the VCS repository'
        ))
        self.vcsAddAct.setWhatsThis(self.trUtf8(
            """<b>Add to repository</b>"""
            """<p>This adds (imports) the local project to the VCS repository.</p>"""
        ))
        self.vcsAddAct.connectIt(SIGNAL('triggered()'),self._vcsImport)
        self.actions.append(self.vcsAddAct)
        
        self.cvsLoginAct = E4Action(self.trUtf8('Login to repository'),
                self.trUtf8('Log&in to repository...'), 0, 0, self, 'vcs_login')
        self.cvsLoginAct.setStatusTip(self.trUtf8(
            'Login to repository'
        ))
        self.cvsLoginAct.setWhatsThis(self.trUtf8(
            """<b>Login to repository</b>"""
            """<p>This performs a login to the VCS repository.</p>"""
        ))
        self.cvsLoginAct.connectIt(SIGNAL('triggered()'),self.__cvsLogin)
        self.actions.append(self.cvsLoginAct)
        
        self.cvsLogoutAct = E4Action(self.trUtf8('Logout from repository'),
                self.trUtf8('Log&out from repository...'), 0, 0, self, 'vcs_logout')
        self.cvsLogoutAct.setStatusTip(self.trUtf8(
            'Logout from repository'
        ))
        self.cvsLogoutAct.setWhatsThis(self.trUtf8(
            """<b>Logout from repository</b>"""
            """<p>This performs a logout from the VCS repository.</p>"""
        ))
        self.cvsLogoutAct.connectIt(SIGNAL('triggered()'),self.__cvsLogout)
        self.actions.append(self.cvsLogoutAct)
    
    def initMenu(self, menu):
        """
        Public method to generate the VCS menu.
        
        @param menu reference to the menu to be populated (QMenu)
        """
        menu.clear()
        
        self.vcsNewAct.addTo(menu)
        self.vcsExportAct.addTo(menu)
        menu.addSeparator()
        self.vcsAddAct.addTo(menu)
        menu.addSeparator()
        # build CVS admin submenu
        cvsMenu = menu.addMenu(self.trUtf8("CVS Administration"))
        self.cvsLoginAct.addTo(cvsMenu)
        self.cvsLogoutAct.addTo(cvsMenu)

    def showMenu(self):
        """
        Public slot called before the vcs menu is shown.
        """
        self.vcsAddAct.setEnabled(self.project.isOpen())

    def __statusMonitorStatus(self, status, statusMsg):
        """
        Private method to receive the status monitor status.
        
        It simply reemits the received status.
        
        @param status status of the monitoring thread (QString, ok, nok or off)
        @param statusMsg explanotory text for the signaled status (QString)
        """
        self.emit(SIGNAL("vcsStatusMonitorStatus(QString, QString)"), status, statusMsg)

    def __statusMonitorData(self, statusList):
        """
        Private method to receive the status monitor status.
        
        It simply reemits the received status list.
        
        @param statusList list of status records(QStringList)
        """
        self.emit(SIGNAL("vcsStatusMonitorData(QStringList)"), statusList)

    def startStatusMonitor(self):
        """
        Public method to start the VCS status monitor thread.
        
        @return reference to the monitor thread (QThread)
        """
        if self.project.pudata["VCSSTATUSMONITORINTERVAL"]:
            vcsStatusMonitorInterval = self.project.pudata["VCSSTATUSMONITORINTERVAL"][0]
        else:
            vcsStatusMonitorInterval = Preferences.getVCS("StatusMonitorInterval")
        if vcsStatusMonitorInterval > 0:
            self.statusMonitorThread = \
                self._createStatusMonitorThread(vcsStatusMonitorInterval)
            if self.statusMonitorThread is not None:
                self.connect(self.statusMonitorThread, 
                             SIGNAL("vcsStatusMonitorData(QStringList)"),
                             self.__statusMonitorData, Qt.QueuedConnection)
                self.connect(self.statusMonitorThread, 
                             SIGNAL("vcsStatusMonitorStatus(QString, QString)"),
                             self.__statusMonitorStatus, Qt.QueuedConnection)
                self.statusMonitorThread.start()
        else:
            self.statusMonitorThread = None
        return self.statusMonitorThread
    
    def stopStatusMonitor(self):
        """
        Public method to stop the VCS status monitor thread.
        """
        if self.statusMonitorThread is not None:
            self.disconnect(self.statusMonitorThread, 
                SIGNAL("vcsStatusMonitorData(QStringList)"),
                self.__statusMonitorData)
            self.disconnect(self.statusMonitorThread, 
                SIGNAL("vcsStatusMonitorStatus(QString, QString)"),
                self.__statusMonitorStatus)
            self.statusMonitorThread.stop()
            self.statusMonitorThread.wait(10000)
            if not self.statusMonitorThread.isFinished():
                self.statusMonitorThread.terminate()
                self.statusMonitorThread.wait(10000)
            self.statusMonitorThread = None
            self.__statusMonitorStatus(QString("off"), 
                self.trUtf8("Repository status checking is switched off"))
    
    def setStatusMonitorInterval(self, interval):
        """
        Public method to change the monitor interval.
        
        @param interval new interval in seconds (integer)
        """
        if self.statusMonitorThread is not None:
            if interval == 0:
                self.stopStatusMonitor()
            else:
                self.statusMonitorThread.setInterval(interval)
        else:
            self.startStatusMonitor()
    
    def getStatusMonitorInterval(self):
        """
        Public method to get the monitor interval.
        
        @return interval in seconds (integer)
        """
        if self.statusMonitorThread is not None:
            return self.statusMonitorThread.getInterval()
        else:
            return 0
    
    def checkVCSStatus(self):
        """
        Public method to wake up the VCS status monitor thread.
        """
        if self.statusMonitorThread is not None:
            self.statusMonitorThread.checkStatus()
    
    def clearStatusMonitorCachedState(self, name):
        """
        Public method to clear the cached VCS state of a file/directory.
        
        @param name name of the entry to be cleared (QString or string)
        """
        if self.statusMonitorThread is not None:
            self.statusMonitorThread.clearCachedState(name)
        
    def _createStatusMonitorThread(self, interval):
        """
        Protected method to create an instance of the VCS status monitor thread.
        
        Note: This method should be overwritten in subclasses in order to support
        VCS status monitoring.
        
        @param interval check interval for the monitor thread in seconds (integer)
        @return reference to the monitor thread (QThread)
        """
        return None
    
    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
        
        vcsSelected, ok = KQInputDialog.getItem(\
            None,
            self.trUtf8("New Project"),
            self.trUtf8("Select version control system for the project"),
            ConfigVcsSystemsDisplay[1:],
            0, False)
        if not ok:
            return
        vcsSystem = ConfigVcsSystems[ConfigVcsSystemsDisplay.indexOf(vcsSelected)]
        
        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:
                self.project.closeProject()
                projectdir, vcsDataDict = vcsdlg.getData()
                self.project.pdata["VCS"] = [vcsSystem]
                self.project.vcs = self.project.initVCS(vcsSystem)
                # edit VCS command options
                vcores = KQMessageBox.question(None,
                    self.trUtf8("New Project"),
                    self.trUtf8("""Would you like to edit the VCS command options?"""),
                    QMessageBox.StandardButtons(\
                        QMessageBox.No | \
                        QMessageBox.Yes),
                    QMessageBox.No)
                if vcores == QMessageBox.Yes:
                    codlg = vcsCommandOptionsDialog(self.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:
                        KQMessageBox.critical(None,
                            self.trUtf8("Create project directory"),
                            self.trUtf8("<p>The project directory <b>%1</b> could not"
                                " be created.</p>").arg(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 = QStringList() << "*.e4p" << "*.e4pz" << "*.e3p" << "*.e3pz"
                    d = QDir(projectdir)
                    plist = d.entryInfoList(filters)
                    if len(plist):
                        if len(plist) == 1:
                            self.project.openProject(plist[0].absoluteFilePath())
                            self.project.emit(SIGNAL('newProject'))
                        else:
                            pfilenamelist = d.entryList(filters)
                            pfilename, ok = KQInputDialog.getItem(
                                None,
                                self.trUtf8("New project from repository"),
                                self.trUtf8("Select a project file to open."),
                                pfilenamelist, 0, False)
                            if ok:
                                self.project.openProject(\
                                    QFileInfo(d, pfilename).absoluteFilePath())
                                self.project.emit(SIGNAL('newProject'))
                        if export:
                            self.project.pdata["VCS"] = ['None']
                            self.project.vcs = self.project.initVCS()
                            self.project.setDirty(True)
                            self.project.saveProject()
                    else:
                        res = KQMessageBox.question(None,
                            self.trUtf8("New project from repository"),
                            self.trUtf8("The project retrieved from the repository"
                                " does not contain a project file"
                                " (*.e4p *.e4pz *.e3p *.e3pz)."
                                " Create it automatically?"),
                            QMessageBox.StandardButtons(\
                                QMessageBox.No | \
                                QMessageBox.Yes),
                            QMessageBox.Yes)
                        if res == QMessageBox.Yes:
                            self.project.ppath = projectdir
                            self.project.opened = True
                            
                            # search the project directory for files with extensions
                            # of 'py', 'ptl', 'idl', 'ui' and 'ts'
                            for filespec in ['*.py', '*.ptl', '.pyw', '*.idl', 
                                             '*.ui', '*.ui.h', '*.qrc']:
                                files = Utilities.direntries(projectdir, 1, filespec)
                                for file in files:
                                    self.project.appendFile(file)
                                    
                            # special handling for translation files (*.ts and *.qm)
                            tslist = d.entryList(QStringList() << '*.ts' << '*.qm')
                            if len(tslist) and tslist[0].contains('_'):
                                # the first entry determines the filename pattern
                                mainscriptname = tslist[0].section('_', 0, 0)
                                for ts in tslist:
                                    if ts.startsWith(mainscriptname):
                                        self.project.addLanguageFile(unicode(ts))
                                if self.project.pdata["PROGLANGUAGE"][0] == "Python":
                                    self.project.pdata["MAINSCRIPT"] = \
                                        ['%s.py' % unicode(mainscriptname)]
                                elif self.project.pdata["PROGLANGUAGE"][0] == "Ruby":
                                    self.project.pdata["MAINSCRIPT"] = \
                                        ['%s.rb' % unicode(mainscriptname)]
                                
                            # now save the new project file and reload it
                            self.project.saveProjectAs()
                            self.project.openProject(self.project.pfile)
                            if not export:
                                res = KQMessageBox.question(None,
                                    self.trUtf8("New project from repository"),
                                    self.trUtf8("Shall the project file be added to"
                                        " the repository?"),
                                    QMessageBox.StandardButtons(\
                                        QMessageBox.No | \
                                        QMessageBox.Yes),
                                    QMessageBox.Yes)
                                if res == QMessageBox.Yes:
                                    self.project.vcs.vcsAdd(self.project.pfile)
                else:
                    KQMessageBox.critical(None,
                        self.trUtf8("New project from repository"),
                        self.trUtf8("""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 necvessarily make the local project a vcs controlled
            project. You may have to checkout the project from the repository in 
            order to accomplish that.
        """
        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
        vcsSelected, ok = KQInputDialog.getItem(\
            None,
            self.trUtf8("Import Project"),
            self.trUtf8("Select version control system for the project"),
            ConfigVcsSystemsDisplay[1:],
            0, False)
        if not ok:
            return
        vcsSystem = ConfigVcsSystems[ConfigVcsSystemsDisplay.indexOf(vcsSelected)]
        
        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
                vcores = KQMessageBox.question(None,
                    self.trUtf8("Import Project"),
                    self.trUtf8("""Would you like to edit the VCS command options?"""),
                    QMessageBox.StandardButtons(\
                        QMessageBox.No | \
                        QMessageBox.Yes),
                    QMessageBox.No)
                if vcores == QMessageBox.Yes:
                    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)[1]
                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
                    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.initMenu(self.project.vcsMenu)
                    self.project.setDirty(True)
                    self.project.saveProject()

    def _vcsUpdate(self):
        """
        Protected slot used to update the local project from the repository.
        """
        self.vcs.vcsUpdate(self.project.ppath)
        
    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 = KQMessageBox.warning(None,
            self.trUtf8("Remove project from repository"),
            self.trUtf8("Dou you really want to remove this project from"
                " the repository (and disk)?"),
            QMessageBox.StandardButtons(\
                QMessageBox.No | \
                QMessageBox.Yes),
            QMessageBox.No)
        if res == QMessageBox.Yes:
            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.
        """
        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 _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.
        """
        self.vcs.vcsSwitch(self.project.ppath)
        
    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.
        """
        info = self.vcs.vcsRepositoryInfos(self.project.ppath)
        dlg = VcsRepositoryInfoDialog(None, info)
        dlg.exec_()

    ################################################################################
    ## Some cvs specific methods below
    ################################################################################

    def __cvsLogin(self):
        """
        Private slot used to login to a remote repository.
        """
        try:
            vcs = VCS.factory("CVS")
        except ImportError:
            return
        
        if not vcs.vcsExists():
            KQMessageBox.critical(None,
                self.trUtf8("Version Control System"),
                self.trUtf8("<p><b>CVS</b> is not available.</p>"))
        else:
            vcs.cvsLogin()
        
    def __cvsLogout(self):
        """
        Private slot used to logout from a remote repository.
        """
        try:
            vcs = VCS.factory("CVS")
        except ImportError:
            return
        
        if not vcs.vcsExists():
            KQMessageBox.critical(None,
                self.trUtf8("Version Control System"),
                self.trUtf8("<p><b>CVS</b> is not available.</p>"))
        else:
            vcs.cvsLogout()
