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

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

"""
Module implementing a dialog to configure the PyLint process
"""

import os
import sys
import copy

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

from KdeQt import KQFileDialog, KQMessageBox
from KdeQt.KQApplication import e4App

from E4Gui.E4Completers import E4FileCompleter

from Ui_PyLintConfigDialog import Ui_PyLintConfigDialog

import Preferences
import Utilities

try:
    from pylint.__pkginfo__ import numversion
except ImportError:
    numversion = (0, 12, 0)     # play it save
    
class PyLintConfigDialog(QDialog, Ui_PyLintConfigDialog):
    """
    Class implementing a dialog to configure the PyLint process
    """
    def __init__(self, ppath, parms = None, parent = None):
        """
        Constructor
        
        @param ppath project path (string or QString)
            Used to set the default path for the rcfile selection dialog
        @param parms parameters to set in the dialog
        @param parent reference to the parent widget (QWidget)
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)
        
        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
        
        self.pylintProc = None
        self.lint = 'pylint'
        if sys.platform == "win32":
            self.lint = os.path.join(sys.exec_prefix, "Scripts", self.lint + '.bat')
        
        self.__initializeDefaults()
        
        # get a copy of the defaults to store the user settings
        self.parameters = copy.deepcopy(self.defaults)
        
        # combine it with the values of parms
        if parms is not None:
            for key, value in parms.items():
                self.parameters[key] = parms[key]
        
        self.configfileCompleter = E4FileCompleter(self.configfileEdit)
        self.reportfileCompleter = E4FileCompleter(self.reportfileEdit)
        
        # initialize general tab
        self.configfileEdit.setText(self.parameters['configFile'])
        self.txtOutputButton.setChecked(self.parameters['txtReport'])
        self.htmlOutputButton.setChecked(self.parameters['htmlReport'])
        self.dialogOutputButton.setChecked(self.parameters['dialogReport'])
        self.reportfileEdit.setText(self.parameters['reportFile'])
        
        # initialize checkers tab
        self.basicCheckBox.setChecked(self.parameters['enableBasic'])
        self.classesCheckBox.setChecked(self.parameters['enableClasses'])
        self.designCheckBox.setChecked(self.parameters['enableDesign'])
        self.exceptionsCheckBox.setChecked(self.parameters['enableExceptions'])
        self.formatCheckBox.setChecked(self.parameters['enableFormat'])
        self.importsCheckBox.setChecked(self.parameters['enableImports'])
        self.metricsCheckBox.setChecked(self.parameters['enableMetrics'])
        self.miscellaneousCheckBox.setChecked(self.parameters['enableMiscellaneous'])
        self.newstyleCheckBox.setChecked(self.parameters['enableNewstyle'])
        self.similaritiesCheckBox.setChecked(self.parameters['enableSimilarities'])
        self.typecheckCheckBox.setChecked(self.parameters['enableTypecheck'])
        self.variablesCheckBox.setChecked(self.parameters['enableVariables'])
        if numversion >= (0, 13, 0):
            self.rpythonCheckBox.setChecked(self.parameters['enableRPython'])
        else:
            self.rpythonCheckBox.setChecked(False)
            self.rpythonCheckBox.setEnabled(False)
        
        self.ppath = ppath

    def __initializeDefaults(self):
        """
        Private method to set the default values. 
        
        These are needed later on to generate the commandline
        parameters.
        """
        self.defaults = {
            # general options
            'configFile' : '',
            'reportFile' : '',
            'txtReport' : False,
            'htmlReport' : True,
            'dialogReport' : False,
            
            # enabled checkers
            'enableBasic'         : False,
            'enableClasses'       : False,
            'enableDesign'        : False,
            'enableExceptions'    : False,
            'enableFormat'        : False,
            'enableImports'       : False,
            'enableMetrics'       : True,
            'enableMiscellaneous' : False,
            'enableNewstyle'      : False,
            'enableSimilarities'  : False,
            'enableTypecheck'     : False,
            'enableVariables'     : False,
            # >= 0.13.0
            'enableRPython'       : False, 
        }

    def generateParameters(self):
        """
        Public method that generates the commandline parameters.
        
        It generates a QStringList to be used
        to set the QProcess arguments for the pylint call and
        a list containing the non default parameters. The second
        list can be passed back upon object generation to overwrite
        the default settings.
        
        <b>Note</b>: The arguments list contains the name of the pylint executable as
        the first parameter.
        
        @return a tuple of the commandline parameters and non default parameters
            (QStringList, dictionary)
        """
        parms = {}
        args = QStringList()
        
        # 1. the program name
        args.append(self.lint)
        
        # 2. the commandline options
        # 2.1 general options
        if self.parameters['configFile'] != self.defaults['configFile']:
            parms['configFile'] = self.parameters['configFile']
            args.append('--rcfile=%s' % self.parameters['configFile'])
        parms['txtReport'] = self.parameters['txtReport']
        parms['htmlReport'] = self.parameters['htmlReport']
        parms['dialogReport'] = self.parameters['dialogReport']
        if self.parameters['htmlReport']:
            args.append('--output-format=html')
        elif self.parameters['dialogReport']:
            args.append('--output-format=parseable')
            args.append('--report=n')
        else:
            args.append('--output-format=text')
        if self.parameters['reportFile'] != self.defaults['reportFile']:
            parms['reportFile'] = self.parameters['reportFile']
        
        # 2.2 checkers options
        parms['enableBasic'] = self.parameters['enableBasic']
        parms['enableClasses'] = self.parameters['enableClasses']
        parms['enableDesign'] = self.parameters['enableDesign']
        parms['enableExceptions'] = self.parameters['enableExceptions']
        parms['enableFormat'] = self.parameters['enableFormat']
        parms['enableImports'] = self.parameters['enableImports']
        parms['enableMetrics'] = self.parameters['enableMetrics']
        parms['enableMiscellaneous'] = self.parameters['enableMiscellaneous']
        parms['enableNewstyle'] = self.parameters['enableNewstyle']
        parms['enableSimilarities'] = self.parameters['enableSimilarities']
        parms['enableTypecheck'] = self.parameters['enableTypecheck']
        parms['enableVariables'] = self.parameters['enableVariables']
        # >= 0.13.0
        parms['enableRPython'] = self.parameters['enableRPython']
        
        checkers = []
        if self.parameters['enableBasic']:
            checkers.append('basic')
        if self.parameters['enableClasses']:
            checkers.append('classes')
        if self.parameters['enableDesign']:
            checkers.append('design')
        if self.parameters['enableExceptions']:
            checkers.append('exceptions')
        if self.parameters['enableFormat']:
            checkers.append('format')
        if self.parameters['enableImports']:
            checkers.append('imports')
        if self.parameters['enableMetrics']:
            checkers.append('metrics')
        if self.parameters['enableMiscellaneous']:
            checkers.append('miscellaneous')
        if self.parameters['enableNewstyle']:
            checkers.append('newstyle')
        if self.parameters['enableSimilarities']:
            checkers.append('similarities')
        if self.parameters['enableTypecheck']:
            checkers.append('typecheck')
        if self.parameters['enableVariables']:
            checkers.append('variables')
        # >= 0.13.0
        if self.parameters['enableRPython']:
            checkers.append('rpython')
        args.append('--enable-checker=%s' % ','.join(checkers))
        
        return (args, parms)

    @pyqtSignature("")
    def on_configfileButton_clicked(self):
        """
        Private slot to select the configuration file.
        
        It displays a file selection dialog to select the configuration file.
        """
        startWith = self.configfileEdit.text()
        if startWith.isEmpty():
            startWith = self.ppath
        config = KQFileDialog.getOpenFileName(\
            self,
            self.trUtf8("Select configuration file"),
            startWith,
            self.trUtf8("Configuration Files (*.cfg *.cnf *.rc);;All Files (*)"))
        if not config.isEmpty():
            self.configfileEdit.setText(Utilities.toNativeSeparators(config))
    
    @pyqtSignature("")
    def on_reportfileButton_clicked(self):
        """
        Private slot to select the report file.
        
        It displays a file selection dialog to select the report file.
        """
        report = KQFileDialog.getSaveFileName(\
            self,
            self.trUtf8("Select report file"),
            self.reportfileEdit.text(),
            QString(),
            None,
            QFileDialog.Options(QFileDialog.DontConfirmOverwrite))
        
        if not report.isEmpty():
            self.reportfileEdit.setText(Utilities.toNativeSeparators(report))
    
    def on_configfileEdit_textChanged(self, txt):
        """
        Private slot to handle the TextChanged signal of configfileEdit.
        
        @param txt contents of configfileEdit (QString)
        """
        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(not txt.isEmpty())
        
    def accept(self):
        """
        Protected slot called by the Ok button. 
        
        It saves the values in the parameters dictionary.
        """
        # get data of general tab
        self.parameters['configFile'] = unicode(self.configfileEdit.text())
        self.parameters['txtReport'] = self.txtOutputButton.isChecked()
        self.parameters['htmlReport'] = self.htmlOutputButton.isChecked()
        self.parameters['dialogReport'] = self.dialogOutputButton.isChecked()
        self.parameters['reportFile'] = unicode(self.reportfileEdit.text())
        
        # get data of checkers tab
        self.parameters['enableBasic'] = self.basicCheckBox.isChecked()
        self.parameters['enableClasses'] = self.classesCheckBox.isChecked()
        self.parameters['enableDesign'] = self.designCheckBox.isChecked()
        self.parameters['enableExceptions'] = self.exceptionsCheckBox.isChecked()
        self.parameters['enableFormat'] = self.formatCheckBox.isChecked()
        self.parameters['enableImports'] = self.importsCheckBox.isChecked()
        self.parameters['enableMetrics'] = self.metricsCheckBox.isChecked()
        self.parameters['enableMiscellaneous'] = self.miscellaneousCheckBox.isChecked()
        self.parameters['enableNewstyle'] = self.newstyleCheckBox.isChecked()
        self.parameters['enableSimilarities'] = self.similaritiesCheckBox.isChecked()
        self.parameters['enableTypecheck'] = self.typecheckCheckBox.isChecked()
        self.parameters['enableVariables'] = self.variablesCheckBox.isChecked()
        # >= 0.13.0
        self.parameters['enableRPython'] = self.rpythonCheckBox.isChecked()
        
        # call the accept slot of the base class
        QDialog.accept(self)

    ############################################################################
    ## Methods below are needed to generate a configuration file template
    ############################################################################

    @pyqtSignature("")
    def on_configButton_clicked(self):
        """
        Public slot to handle the generation of a sample configuration.
        """
        self.buf = QString("")
        self.pylintProc = QProcess()
        args = QStringList()
        
        self.__ioEncoding = str(Preferences.getSystem("IOEncoding"))
        
        args.append('--generate-rcfile')
        
        self.connect(self.pylintProc, SIGNAL('readyReadStandardOutput()'),
            self.__readStdout)
        self.connect(self.pylintProc, SIGNAL('readyReadStandardError()'),
            self.__readStderr)
        self.connect(self.pylintProc, SIGNAL('finished(int, QProcess::ExitStatus)'),
            self.__createConfigDone)
        
        self.pylintProc.start(self.lint, args)
        procStarted = self.pylintProc.waitForStarted()
        if procStarted:
            e4App().getObject("ViewManager").enableEditorsCheckFocusIn(False)
        else:
            KQMessageBox.critical(self,
                self.trUtf8('Process Generation Error'),
                self.trUtf8(
                    'Could not start %1.<br>'
                    'Ensure that it is in the search path.'
                ).arg(self.lint))
    
    def __createConfigDone(self, exitCode, exitStatus):
        """
        Private slot to handle the the finished signal of the pylint process.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        vm = e4App().getObject("ViewManager")
        vm.enableEditorsCheckFocusIn(True)
        if exitStatus == QProcess.NormalExit and exitCode == 0:
            vm.newEditor()
            aw = vm.activeWindow()
            aw.insertAt(self.buf, 0, 0)
            aw.setLanguage('dummy.rc')
        self.reject()
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal of the pylint process.
        """
        if self.pylintProc is None:
            return
        self.pylintProc.setReadChannel(QProcess.StandardOutput)
        
        while self.pylintProc and self.pylintProc.canReadLine():
            line = unicode(self.pylintProc.readLine(), self.__ioEncoding).rstrip()
            self.buf.append(line)
            self.buf.append(os.linesep)
        
    def __readStderr(self):
        """
        Private slot to handle the readyReadStandardError signal of the pylint process.
        """
        if self.pylintProc is None:
            return
        self.pylintProc.setReadChannel(QProcess.StandardError)
        while self.pylintProc and self.pylintProc.canReadLine():
            s = QString('pylint: ')
            s.append(self.pylintProc.readLine())
            e4App().getObject("UserInterface").emit(SIGNAL('appendStderr'), s)
