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

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

"""
Module implementing a dialog to show the output of a conda execution.
"""


import json

from PyQt5.QtCore import pyqtSlot, QProcess, QTimer
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton

from E5Gui import E5MessageBox

from .Ui_CondaExecDialog import Ui_CondaExecDialog

import Preferences
import Globals


class CondaExecDialog(QDialog, Ui_CondaExecDialog):
    """
    Class implementing a dialog to show the output of a conda execution.
    """
    def __init__(self, command, parent=None):
        """
        Constructor
        
        @param command conda command executed
        @type str
        @param parent reference to the parent widget
        @type QWidget
        """
        super(CondaExecDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.__condaCommand = command
        
        self.__process = None
        self.__condaExe = Preferences.getConda("CondaExecutable")
        if not self.__condaExe:
            self.__condaExe = "conda"
    
    @pyqtSlot(QAbstractButton)
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked
        @type QAbstractButton
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.accept()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            self.__finish(1, 0)
    
    def start(self, arguments):
        """
        Public slot to start the conda command.
        
        @param arguments commandline arguments for conda program
        @type list of str
        """
        self.errorGroup.hide()
        self.progressLabel.hide()
        self.progressBar.hide()
        
        self.contents.clear()
        self.errors.clear()
        self.progressLabel.clear()
        self.progressBar.setValue(0)
        
        self.__bufferedStdout = None
        self.__json = "--json" in arguments
        self.__firstProgress = True
        self.__lastFetchFile = ""
        
        self.__statusOk = False
        self.__result = None
        
        self.__logOutput(self.__condaExe + " " + " ".join(arguments) + "\n\n")
        
        self.__process = QProcess()
        self.__process.readyReadStandardOutput.connect(self.__readStdout)
        self.__process.readyReadStandardError.connect(self.__readStderr)
        self.__process.finished.connect(self.__finish)
        
        self.__process.start(self.__condaExe, arguments)
        procStarted = self.__process.waitForStarted(5000)
        if not procStarted:
            E5MessageBox.critical(
                self,
                self.tr("Conda Execution"),
                self.tr("""The conda executable could not be started. Is it"""
                        """ configured correctly?"""))
            self.__finish(1, 0)
        else:
            self.__logOutput(self.tr("Operation started.\n"))
    
    def __finish(self, exitCode, exitStatus, giveUp=False):
        """
        Private slot called when the process finished.
        
        It is called when the process finished or
        the user pressed the button.
        
        @param exitCode exit code of the process
        @type int
        @param exitStatus exit status of the process
        @type QProcess.ExitStatus
        @param giveUp flag indicating to not start another attempt
        @type bool
        """
        if (self.__process is not None and
           self.__process.state() != QProcess.NotRunning):
            self.__process.terminate()
            QTimer.singleShot(2000, self.__process.kill)
            self.__process.waitForFinished(3000)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        
        self.progressLabel.hide()
        self.progressBar.hide()
        
        self.__statusOk = exitCode == 0
        
        self.__logOutput(self.tr("Operation finished.\n"))
        if not self.__json and self.__bufferedStdout:
            self.__logOutput(self.__bufferedStdout)
        
        if self.__json and self.__bufferedStdout:
            index = self.__bufferedStdout.find("{")
            rindex = self.__bufferedStdout.rfind("}")
            self.__bufferedStdout = self.__bufferedStdout[index:rindex + 1]
            try:
                self.__result = json.loads(self.__bufferedStdout)
            except Exception as error:
                self.__result = {}
                self.__logError(str(error))
                return
            
            if "error" in self.__result:
                self.__logError(self.__result["error"])
                self.__statusOk = False
            elif ("success" in self.__result and
                    not self.__result["success"]):
                self.__logError(
                    self.tr("Conda command '{0}' did not return success.")
                    .format(self.__condaCommand))
                if "message" in self.__result:
                    self.__logError("\n")
                    self.__logError(
                        self.tr("\nConda Message: {0}").format(
                            self.__result["message"]))
                self.__statusOk = False
            elif "message" in self.__result:
                self.__logOutput(
                    self.tr("\nConda Message: {0}").format(
                        self.__result["message"]))
    
    def getResult(self):
        """
        Public method to the result of the command execution.
        
        @return tuple containing a flag indicating success and the result data.
        @rtype tuple of (bool, dict)
        """
        return self.__statusOk, self.__result
    
    def __setProgressValues(self, jsonDict, progressType):
        """
        Private method to set the value of the progress bar.
        
        @param jsonDict dictionary containing the progress info
        @type dict
        @param progressType action type to check for
        @type str
        @return flag indicating success
        @rtype bool
        """
        if progressType in jsonDict and "progress" in jsonDict:
            if jsonDict["maxval"] == 1:
                self.progressBar.setMaximum(100)
                # percent values
                self.progressBar.setValue(
                    int(jsonDict["progress"] * 100))
                parts = jsonDict["fetch"].split("|")
                filename = parts[0].strip()
                filesize = parts[1].strip()
            else:
                self.progressBar.setMaximum(jsonDict["maxval"])
                self.progressBar.setValue(jsonDict["progress"])
                filename = jsonDict["fetch"].strip()
                filesize = Globals.dataString(int(jsonDict["maxval"]))
            
            self.progressLabel.setText(
                self.tr("{0} (Size: {1})").format(filename, filesize))
            
            if progressType == "fetch":
                if filename != self.__lastFetchFile:
                    self.__logOutput(
                        self.tr("Fetching {0} ...").format(filename))
                    self.__lastFetchFile = filename
                elif jsonDict["finished"]:
                    self.__logOutput(self.tr(" Done.\n"))
            
            if self.__firstProgress:
                self.progressLabel.show()
                self.progressBar.show()
                self.__firstProgress = False
            
            return True
        
        return False
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStandardOutput signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        all_stdout = str(self.__process.readAllStandardOutput(),
                         Preferences.getSystem("IOEncoding"),
                         'replace')
        all_stdout = all_stdout.replace("\x00", "")
        if self.__json:
            for stdout in all_stdout.splitlines():
                try:
                    jsonDict = json.loads(stdout.replace("\x00", "").strip())
                    if self.__setProgressValues(jsonDict, "fetch"):
                        # nothing to do anymore
                        pass
                    elif "progress" not in jsonDict:
                        if self.__bufferedStdout is None:
                            self.__bufferedStdout = stdout
                        else:
                            self.__bufferedStdout += stdout
                except (TypeError, ValueError):
                    if self.__bufferedStdout is None:
                        self.__bufferedStdout = stdout
                    else:
                        self.__bufferedStdout += stdout
        else:
            self.__logOutput(all_stdout)
    
    def __readStderr(self):
        """
        Private slot to handle the readyReadStandardError signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        self.__process.setReadChannel(QProcess.StandardError)
        
        while self.__process.canReadLine():
            stderr = str(self.__process.readLine(),
                         Preferences.getSystem("IOEncoding"),
                         'replace')
            self.__logError(stderr)
    
    def __logOutput(self, stdout):
        """
        Private method to log some output.
        
        @param stdout output string to log
        @type str
        """
        self.contents.insertPlainText(stdout)
        self.contents.ensureCursorVisible()
    
    def __logError(self, stderr):
        """
        Private method to log an error.
        
        @param stderr error string to log
        @type str
        """
        self.errorGroup.show()
        self.errors.insertPlainText(stderr)
        self.errors.ensureCursorVisible()
