import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import datetime, os

class Monitor(QObject):
    def __init__(
            self,
            command, args,
            mainwindow, index, rindex,
            buttonMessage=None,
            actionMessage=None,
            createfiles=[],
            finishCallback=None,
            environ=None
    ):
        """
        the constructor
        @param command an executable
        @param args the command line args for the executable
        @param mainwindow a QWidget which should have signals endmonitor
        and closemonitor, and a QTabWidget in mainwindow.ui.tabWidget
        @param index the index of the wanted tab
        @param rindex the row index to access the table of devices
        @param buttonMessage the text for the button which appears at
        the end of the process
        @param actionMessage a short word to define the ongoing process
        @param createfiles a list of files which might be created: one can
        change their ownership if we are running as SUDO
        @param finishCallback a callback function which should be activated
        when the monitor finishes. This function takes no argument.
        @param environ a shell environment; it can be user to get variables
        SUDO_UID and SUDO_GID
        """
        QObject.__init__(self)
        self.index=index
        self.rindex=rindex
        self.command=command
        self.args=args
        if buttonMessage==None:
            buttonMessage=self.tr("Close this monitor")
        self.buttonMessage=buttonMessage
        if actionMessage==None:
            actionMessage=self.tr("Working")
        self.actionMessage=actionMessage
        self.createfiles=createfiles
        self.finishCallback=finishCallback
        self.environ=environ
        self.w=mainwindow.ui.tabWidget.widget(index)
        mainwindow.ui.tabWidget.setCurrentIndex(index)
        self.table=mainwindow.ui.tableWidget
        self.mainwindow=mainwindow
        self.process = QProcess(self.w)
        self.process.readyReadStandardOutput.connect(self.readOutput)
        self.process.readyReadStandardError.connect(self.readErr)
        self.process.finished.connect(self.finish)
        self.stdoutTerminal = QPlainTextEdit(self.w)
        self.stderrTerminal = QPlainTextEdit(self.w)
        self.cursor=QTextCursor(self.stdoutTerminal.document());
        self.err_cursor=QTextCursor(self.stderrTerminal.document());
        self.layout = QVBoxLayout(self.w)
        self.layout.addWidget(QLabel(self.tr("stdout")))
        self.layout.addWidget(self.stdoutTerminal, stretch=4)
        self.layout.addWidget(QLabel(self.tr("stderr")))
        self.layout.addWidget(self.stderrTerminal, stretch=1)
        return

    def start(self):
        self.process.start(self.command, self.args)
        # updates row rindex in self.table
        self.mainwindow.newmonitor.emit(self)
        return

    def downgrade_user(self):
        if self.environ and \
           "SUDO_UID" in self.environ and \
           "SUDO_GID" in self.environ:
            ## it is possible to downgrade the owner of the file
            for fname in self.createfiles:
                os.chown(
                    fname,
                    int(os.environ["SUDO_UID"]),
                    int(os.environ["SUDO_GID"])
                )
        self.createfiles=[]
        return

    def close(self):
        d=datetime.datetime.now()
        fname, _ =QFileDialog.getSaveFileName(
            self.mainwindow,
            self.tr("Save the messages?"),
            "{action}-log-{date}.log".format(
                action=self.actionMessage,
                date=d.strftime("%Y-%m-%d--%H-%M")
            ),
            self.tr("Log files (*.log);;All files (*)")
        )
        if fname:
            self.createfiles.append(fname)
            with open(fname, "w") as outfile:
                outfile.write("# STDOUT #\n")
                outfile.write(self.stdoutTerminal.toPlainText())
                outfile.write("\n# STDERR #\n")
                outfile.write(self.stderrTerminal.toPlainText())
        self.downgrade_user()
        self.mainwindow.closemonitor.emit(self)
        return True
        
    def finish(self):
        button=QPushButton(self.buttonMessage)
        button.clicked.connect(self.close)
        self.layout.addWidget(button)
        self.downgrade_user()
        if self.finishCallback:
            self.finishCallback()
        self.mainwindow.endmonitor.emit(self)

    def read(self, source, cursor, edit):
        """
        reads a QByteArray from source and writes it into a QPlainTextEdit
        widget
        @param source: either an instance of QProcess.readAllStandardOutput
        or an instance of QProcess.readAllStandardError
        @param cursor a QTextCursor instance
        @param edit a QPlainTextEdit instance
        """
        text= bytes(source()).decode("utf-8")
        if text.startswith("\r"):
            ## carriage return
            cursor.select(cursor.LineUnderCursor)
            cursor.removeSelectedText()
            cursor.insertText(text[1:])
        else:
            cursor.insertText(text)
        edit.verticalScrollBar().setValue(edit.verticalScrollBar().maximum())
        return
    
    def readOutput(self):
        self.read(self.process.readAllStandardOutput,
                  self.cursor,
                  self.stdoutTerminal)
        return
    
    def readErr(self):
        self.read(self.process.readAllStandardError,
                  self.err_cursor,
                  self.stderrTerminal)
        return
    
class CloneMonitor(Monitor):
    def __init__(self, command, args, mainwindow, index, rindex, environ=None):
        """
        the constructor
        @param command an executable
        @param args the command line args for the executable
        args[3] is the source to clone, args[4] is the targetted device
        @param mainwindow a QWidget which should have signals endmonitor
        and closemonitor, and a QTabWidget in mainwindow.ui.tabWidget
        @param index the index of the wanted tab
        @param rindex the row index to access the table of devices
        @param environ a shell environment; it can be user to get variables
        SUDO_UID and SUDO_GID
        """
        
        Monitor.__init__(self, command, args, mainwindow, index, rindex,
                         environ=environ
        )
        self.buttonMessage=self.tr("Cloning is over")
        self.actionMessage=self.tr("Cloning")
        self.source=args[3]
        self.device=args[4]
        return

    
if __name__ == "__main__":
    app = QApplication(sys.argv)
    main = Monitor('ls', ['-lR', '../..'])
    main.show()
    sys.exit(app.exec_())
