#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#The MIT License (MIT)
#
#Copyright (c) <2013-2014> <Colin Duquesnoy and others, see AUTHORS.txt>
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in
#all copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
#
"""
This module contains interactive widgets:
    - interactive console: a text edit made to run subprocesses interactively
"""
import logging
import os
import sys

# needed to build doc on readthedocs; should be fixed with the next version
# of pyqode.core
try:
    from pyqode.qt.QtCore import Qt, Signal, Property, QProcess
    from pyqode.qt.QtGui import QTextEdit, QApplication, QTextCursor, QColor
except:
    class Property(object):
        def __init__(self, *args, **kwargs):
            pass

    class QProcess(object):
        pass


    class Foo(object):
        pass


    class QTextEdit(object):
        ExtraSelection = Foo


    class QColor(object):
        def __init__(self, *args, **kwargs):
            pass


class QInteractiveConsole(QTextEdit):
    """
    An interactive console is a QTextEdit specialised to run an interactive
    process. The user will see the process outputs and will be able to
    interact with the process by typing inputs.

    You can customize the colors using the following attributes:
        - processOutputColor:
        - userInputColor: color of the user inputs. Green by default
        - appMessageColor: a color for custom application message (
                           process started, process finished)
        - backgroundColor: the console background color
    """
    #: Signal emitted when the process has finished.
    processFinished = Signal(int)

    @property
    def mergeStdErrWithStdOut(self):
        """
        Merge stderr with stdout. Default is False. If set to true, stderr and
        stdin won't have distinctive colors, i.e. stderr output will be display
        with the same color as stdout.
        """
        return self._mergeOutput

    @mergeStdErrWithStdOut.setter
    def mergeStdErrWithStdOut(self, value):
        self._mergeOutput = value
        if value:
            self.process.setProcessChannelMode(QProcess.MergedChannels)
        else:
            self.process.setProcessChannelMode(QProcess.SeparateChannels)

    def __init__(self, parent=None,
                 writer=None,
                 mergeStdErrWithStdOut=False,
                 clearOnStart=True,
                 errorColor=QColor("#FF0000"),
                 processOutputColor=QColor("#404040"),
                 userInputColor=QColor("#22AA22"),
                 appMessageColor=QColor("#4040FF")):
        QTextEdit.__init__(self, parent)
        self._processOutputColor = processOutputColor
        self._appMessageColor = appMessageColor
        self._userInputColor = userInputColor
        self._errorColor = errorColor
        self._usrBuffer = ""
        self._clearOnStart = clearOnStart
        self.process = QProcess()
        self._mergeOutput = None
        self.mergeStdErrWithStdOut = mergeStdErrWithStdOut
        self.process.finished.connect(self._writeProcessFinished)
        #self.process.started.connect(self._writeProcessStarted)
        self.process.error.connect(self._writeProcessErrored)
        self.process.readyReadStandardError.connect(self._onOutputReady)
        self.process.readyReadStandardOutput.connect(self._onErrorReady)
        self._running = False
        if not writer:
            self._writer = self.write
        else:
            self._writer = writer

    def setWriter(self, writer):
        """
        Changes the write function. A writer function must have the following
        prototype:

        .. code-block:: python

            def write(textEdit, text, color)
        """
        if self._writer != writer and self._writer:
            self._writer = None
        if writer:
            self._writer = writer

    def _onOutputReady(self):
        raw = self.process.readAllStandardError()
        if sys.version_info[0] == 2:
            self._writer(self, unicode(raw, "utf-8"), self._errorColor)
        else:
            self._writer(self, str(raw, "utf-8"), self._errorColor)

    def _onErrorReady(self):
        raw = self.process.readAllStandardOutput()
        if sys.version_info[0] == 2:
            self._writer(self, unicode(raw, "utf-8"), self._processOutputColor)
        else:
            self._writer(self, str(raw, "utf-8"), self._processOutputColor)

    def __getBackgroundColor(self):
        p = self.palette()
        return p.color(p.Base)

    def __setBackgroundColor(self, color):
        p = self.palette()
        p.setColor(p.Base, color)
        p.setColor(p.Text, self.processOutputColor)
        self.setPalette(p)

    #: The console background color. Default is white.
    backgroundColor = Property(
        QColor, __getBackgroundColor, __setBackgroundColor,
        "The console background color")

    def __getProcessOutputColor(self):
        return self._processOutputColor

    def __setProcessOutputColor(self, color):
        self._processOutputColor = color
        p = self.palette()
        p.setColor(p.Text, self.processOutputColor)
        self.setPalette(p)

    #: Color of the process output. Default is black.
    processOutputColor = Property(
        QColor, __getProcessOutputColor, __setProcessOutputColor,
        doc="The color of the process output (stdout)")

    def __getErrorColor(self):
        return self._errorColor

    def __setErrorColor(self, color):
        self._errorColor = color

    #: Color for stderr output if
    # :attr:`pyqode.widgets.QInteractiveConsole.mergeStderrWithStdout`is False.
    errorColor = Property(
        QColor, __getProcessOutputColor, __setProcessOutputColor,
        doc="The color of the error messages (stderr)")

    def __getUserInputOutputColor(self):
        return self._userInputColor

    def __setUserInputOutputColor(self, color):
        self._userInputColor = color

    #: Color for user inputs. Default is green.
    usrInputColor = Property(
        QColor, __getUserInputOutputColor, __setUserInputOutputColor,
        doc="The color of the user inputs")

    def __getAppMessageColor(self):
        return self._appMessageColor

    def __setAppMessageColor(self, color):
        self._appMessageColor = color

    #: Application messages color, e.g. process started/finished messages.
    #: Default is blue.
    appMessageColor = Property(
        QColor, __getAppMessageColor, __setAppMessageColor,
        doc="The color of the application messages (such as process "
            "informations)")

    def closeEvent(self, *args, **kwargs):
        if self.process.state() == QProcess.Running:
            self.process.terminate()

    def runProcess(self, process, args=[], cwd=None):
        """
        Run a process interactively.

        E.g:

        .. code-block:: python

            ic = pyqode.python.QInteractiveConsole()
            ic.runProcess(sys.executable, ["setup.py", "install"],
                          cwd="/home/myUserName/myProj")

        :param process: Process to run
        :type process: str

        :param args: List of arguments (list of str)
        :type args: list

        :param cwd: Working directory
        :type cwd: str
        """
        if not self._running:
            if cwd:
                self.process.setWorkingDirectory(cwd)
            self._running = True
            self._process = process
            self._args = args
            if self._clearOnStart:
                self.clear()
            self.process.start(process, args)
            self._writeProcessStarted()
        else:
            logging.getLogger("pyqode").warning(
                "QInteractiveConsole: Process already running")

    def keyPressEvent(self, QKeyEvent):
        if QKeyEvent.key() == Qt.Key_Return or QKeyEvent.key() == Qt.Key_Enter:
            # send the user input to the child process
            self._usrBuffer += "\n"
            if sys.version_info[0] == 3:
                self.process.write(bytes(self._usrBuffer, "utf-8"))
            self.process.write(self._usrBuffer.encode("utf-8"))
            self._usrBuffer = ""
        else:
            if QKeyEvent.key() != Qt.Key_Backspace:
                txt = QKeyEvent.text()
                self._usrBuffer += txt
                self.setTextColor(self._userInputColor)
            else:
                self._usrBuffer = self._usrBuffer[0:len(self._usrBuffer)-1]
        # text is inserted here, the text color must be defined before this line
        QTextEdit.keyPressEvent(self, QKeyEvent)

    def _writeProcessFinished(self, exitCode, exitStatus):
        self._writer(self, "\nProcess finished with exit code %d" % exitCode,
                     self._appMessageColor)
        self._running = False
        self.processFinished.emit(exitCode)

    def _writeProcessStarted(self):
        self._writer(self, "{0} {1}\n".format(self._process, " ".join(self._args)),
                     self._appMessageColor)
        self._running = True

    def _writeProcessErrored(self):
        self._writer(self, "Failed to start {0} {1}\n".format(
            self._process, " ".join(self._args)), self._errorColor)
        self._running = False

    @staticmethod
    def write(textEdit, text, color):
        """
        Default write function. Move the cursor to the cursor to the end and
        insert text with the specified color.

        :param textEdit: QInteractiveConsole instance
        :type textEdit: pyqode.widgets.QInteractiveConsole

        :param text: Text to write
        :type text: str

        :param color: Desired text color
        :type color: QColor
        """
        textEdit.moveCursor(QTextCursor.End)
        textEdit.setTextColor(color)
        textEdit.insertPlainText(text)
        textEdit.moveCursor(QTextCursor.End)


if __name__ == "__main__":
    if len(sys.argv) <= 1:
        app = QApplication(sys.argv)
        t = QInteractiveConsole(mergeStdErrWithStdOut=False)
        t.resize(800, 600)
        t.show()
        t.runProcess(sys.executable, args=[__file__, "subprocess"], cwd=os.getcwd())
        #t.runProcess("sh", ["-i"])
        app.exec_()
    else:
        print("Hello from a subprocess!")
        if sys.version_info[0] >= 3:
            d = input("Enter something:\n>>> ")
        else:
            d = raw_input("Enter something\n>>> ")
        sys.stderr.write('This message has been written to stderr\n')
        print("You've just typed '%s' in the QTextEdit" % d)
