from _pydevd_bundle.pydevd_io import IORedirector
from _pydevd_bundle.pydevd_net_command_factory_xml import NetCommandFactory
import pytest
import sys


def test_io_redirector():
    class MyRedirection1(object):
        encoding = "foo"

    class MyRedirection2(object):
        pass

    my_redirector = IORedirector(MyRedirection1(), MyRedirection2(), wrap_buffer=True)
    none_redirector = IORedirector(None, None, wrap_buffer=True)

    assert my_redirector.encoding == "foo"
    with pytest.raises(AttributeError):
        none_redirector.encoding

    # Check that we don't fail creating the IORedirector if the original
    # doesn't have a 'buffer'.
    for redirector in (
        my_redirector,
        none_redirector,
    ):
        redirector.write("test")
        redirector.flush()

    assert not redirector.isatty()


class _DummyWriter(object):
    __slots__ = ["commands", "command_meanings"]

    def __init__(self):
        self.commands = []
        self.command_meanings = []

    def add_command(self, cmd):
        from _pydevd_bundle.pydevd_comm import ID_TO_MEANING

        meaning = ID_TO_MEANING[str(cmd.id)]
        self.command_meanings.append(meaning)
        self.commands.append(cmd)


class _DummyPyDb(object):
    def __init__(self):
        self.cmd_factory = NetCommandFactory()
        self.writer = _DummyWriter()


def test_patch_stdin():
    from pydevd import _internal_patch_stdin

    py_db = _DummyPyDb()

    class _Stub(object):
        pass

    actions = []

    class OriginalStdin(object):
        def readline(self):
            # On a readline we keep the patched version.
            assert sys_mod.stdin is not original_stdin
            actions.append("readline")
            return "read"

    def getpass_stub(*args, **kwargs):
        # On getpass we need to revert to the original version.
        actions.append("getpass")
        assert sys_mod.stdin is original_stdin
        return "pass"

    sys_mod = _Stub()
    original_stdin = sys_mod.stdin = OriginalStdin()

    getpass_mod = _Stub()
    getpass_mod.getpass = getpass_stub

    _internal_patch_stdin(py_db, sys_mod, getpass_mod)

    assert sys_mod.stdin.readline() == "read"

    assert py_db.writer.command_meanings == ["CMD_INPUT_REQUESTED", "CMD_INPUT_REQUESTED"]
    del py_db.writer.command_meanings[:]
    assert actions == ["readline"]
    del actions[:]

    assert getpass_mod.getpass() == "pass"
    assert py_db.writer.command_meanings == ["CMD_INPUT_REQUESTED", "CMD_INPUT_REQUESTED"]
    del py_db.writer.command_meanings[:]


def test_debug_console():
    from _pydev_bundle.pydev_console_utils import DebugConsoleStdIn

    class OriginalStdin(object):
        def readline(self):
            return "read"

    original_stdin = OriginalStdin()

    py_db = _DummyPyDb()
    debug_console_std_in = DebugConsoleStdIn(py_db, original_stdin)
    assert debug_console_std_in.readline() == "read"

    assert py_db.writer.command_meanings == ["CMD_INPUT_REQUESTED", "CMD_INPUT_REQUESTED"]
    del py_db.writer.command_meanings[:]

    with debug_console_std_in.notify_input_requested():
        with debug_console_std_in.notify_input_requested():
            pass
    assert py_db.writer.command_meanings == ["CMD_INPUT_REQUESTED", "CMD_INPUT_REQUESTED"]


@pytest.yield_fixture
def _redirect_context():
    from _pydevd_bundle.pydevd_io import RedirectToPyDBIoMessages
    from _pydevd_bundle.pydevd_io import _RedirectionsHolder

    py_db = _DummyPyDb()

    _original_get_pydb = RedirectToPyDBIoMessages.get_pydb
    _original_stack_stdout = _RedirectionsHolder._stack_stdout
    _original_stack_stderr = _RedirectionsHolder._stack_stderr
    _original_stdout_redirect = _RedirectionsHolder._pydevd_stdout_redirect_
    _original_stderr_redirect = _RedirectionsHolder._pydevd_stderr_redirect_

    RedirectToPyDBIoMessages.get_pydb = lambda *args, **kwargs: py_db
    _RedirectionsHolder._stack_stdout = []
    _RedirectionsHolder._stack_stderr = []
    _RedirectionsHolder._pydevd_stdout_redirect_ = None
    _RedirectionsHolder._pydevd_stderr_redirect_ = None

    original_stdout = sys.stdout
    original_stderr = sys.stderr

    yield {"py_db": py_db}

    sys.stdout = original_stdout
    sys.stderr = original_stderr

    RedirectToPyDBIoMessages.get_pydb = _original_get_pydb
    _RedirectionsHolder._stack_stdout = _original_stack_stdout
    _RedirectionsHolder._stack_stderr = _original_stack_stderr
    _RedirectionsHolder._pydevd_stdout_redirect_ = _original_stdout_redirect
    _RedirectionsHolder._pydevd_stderr_redirect_ = _original_stderr_redirect


def test_redirect_to_pyd_io_messages_basic(_redirect_context):
    from _pydevd_bundle.pydevd_io import redirect_stream_to_pydb_io_messages
    from _pydevd_bundle.pydevd_io import redirect_stream_to_pydb_io_messages_context
    from _pydevd_bundle.pydevd_io import stop_redirect_stream_to_pydb_io_messages
    from _pydevd_bundle.pydevd_io import _RedirectionsHolder

    py_db = _redirect_context["py_db"]

    redirect_stream_to_pydb_io_messages(std="stdout")
    assert len(_RedirectionsHolder._stack_stdout) == 1
    assert _RedirectionsHolder._pydevd_stdout_redirect_ is not None
    sys.stdout.write("aaa")
    assert py_db.writer.command_meanings == ["CMD_WRITE_TO_CONSOLE"]

    with redirect_stream_to_pydb_io_messages_context():
        assert len(_RedirectionsHolder._stack_stdout) == 1
        assert _RedirectionsHolder._pydevd_stdout_redirect_ is not None
        sys.stdout.write("bbb")

        assert py_db.writer.command_meanings == ["CMD_WRITE_TO_CONSOLE", "CMD_WRITE_TO_CONSOLE"]

    assert len(_RedirectionsHolder._stack_stdout) == 1
    assert _RedirectionsHolder._pydevd_stdout_redirect_ is not None
    sys.stdout.write("ccc")
    assert py_db.writer.command_meanings == ["CMD_WRITE_TO_CONSOLE", "CMD_WRITE_TO_CONSOLE", "CMD_WRITE_TO_CONSOLE"]

    stop_redirect_stream_to_pydb_io_messages(std="stdout")
    assert len(_RedirectionsHolder._stack_stdout) == 0
    assert _RedirectionsHolder._pydevd_stdout_redirect_ is None
    sys.stdout.write("ddd")
    assert py_db.writer.command_meanings == ["CMD_WRITE_TO_CONSOLE", "CMD_WRITE_TO_CONSOLE", "CMD_WRITE_TO_CONSOLE"]


@pytest.mark.parametrize("std", ["stderr", "stdout"])
def test_redirect_to_pyd_io_messages_user_change_stdout(_redirect_context, std):
    from _pydevd_bundle.pydevd_io import redirect_stream_to_pydb_io_messages
    from _pydevd_bundle.pydevd_io import stop_redirect_stream_to_pydb_io_messages
    from _pydevd_bundle.pydevd_io import _RedirectionsHolder

    py_db = _redirect_context["py_db"]
    stack = getattr(_RedirectionsHolder, "_stack_%s" % (std,))

    def get_redirect():
        return getattr(_RedirectionsHolder, "_pydevd_%s_redirect_" % (std,))

    def write(s):
        getattr(sys, std).write(s)

    redirect_stream_to_pydb_io_messages(std=std)
    assert len(stack) == 1
    assert get_redirect() is not None
    write("aaa")
    assert py_db.writer.command_meanings == ["CMD_WRITE_TO_CONSOLE"]

    from io import StringIO

    stream = StringIO()
    setattr(sys, std, stream)

    write("bbb")
    assert py_db.writer.command_meanings == ["CMD_WRITE_TO_CONSOLE"]
    assert stream.getvalue() == "bbb"

    # i.e.: because the user changed the sys.stdout, we cannot change it to our previous version.
    stop_redirect_stream_to_pydb_io_messages(std=std)
    assert len(stack) == 0
    assert get_redirect() is None
    write("ccc")
    assert py_db.writer.command_meanings == ["CMD_WRITE_TO_CONSOLE"]
    assert stream.getvalue() == "bbbccc"
