"""Support code for gtp_controller_tests.

This is also used by gtp_engine_fixtures (and so by gtp proxy tests).

"""

from gomill import gtp_controller
from gomill.gtp_controller import (
    GtpChannelError, GtpProtocolError, GtpTransportError, GtpChannelClosed,
    BadGtpResponse)

from gomill_tests import test_support
from gomill_tests.test_framework import SupporterError


class Preprogrammed_gtp_channel(gtp_controller.Subprocess_gtp_channel):
    """A Linebased_gtp_channel with hardwired response stream.

    Instantiate with a string containing the complete response stream.

    This sends the contents of the response stream, irrespective of what
    commands are received.

    Pass hangs_before_eof True to simulate an engine that doesn't close its
    response pipe when the preprogrammed response data runs out.

    The command stream is available from get_command_stream().

    """
    def __init__(self, response, hangs_before_eof=False):
        gtp_controller.Linebased_gtp_channel.__init__(self)
        self.command_pipe = test_support.Mock_writing_pipe()
        self.response_pipe = test_support.Mock_reading_pipe(response)
        self.response_pipe.hangs_before_eof = hangs_before_eof

    def close(self):
        self.command_pipe.close()
        self.response_pipe.close()

    def get_command_stream(self):
        """Return the complete contents of the command stream sent so far."""
        return self.command_pipe.getvalue()

    def break_command_stream(self):
        """Break the simulated pipe for the command stream."""
        self.command_pipe.simulate_broken_pipe()

    def break_response_stream(self):
        """Break the simulated pipe for the response stream."""
        self.response_pipe.simulate_broken_pipe()


class Testing_gtp_channel(gtp_controller.Linebased_gtp_channel):
    """Linebased GTP channel that runs an internal Gtp_engine.

    Instantiate with a Gtp_engine_protocol object.

    This is used for testing how controllers handle GtpChannelError.

    Public attributes:
      engine    -- the engine it was instantiated with
      is_closed -- bool (closed() has been called without a forced error)

    This raises an error if sent two commands without requesting a response in
    between, or if asked for a response when no command was sent since the last
    response. (GTP permits stacking up commands, but Gtp_controller should never
    do it, so we want to report it).

    Unlike Internal_gtp_channel, this runs the command as the point when it is
    sent.

    If you send a command after the engine has exited, this raises
    GtpChannelClosed. Set the attribute engine_exit_breaks_commands to False to
    disable this behaviour (it will ignore the command and respond with EOF
    instead).

    You can force errors by setting the following attributes:
      fail_next_command   -- bool (send_command_line raises GtpTransportError)
      fail_command        -- string (like fail_next_command, if command line
                             starts with this string)
      fail_next_response  -- bool (get_response_line raises GtpTransportError)
      force_next_response -- string (get_response_line uses this string)
      fail_close          -- bool (close raises GtpTransportError)

    """
    def __init__(self, engine):
        gtp_controller.Linebased_gtp_channel.__init__(self)
        self.engine = engine
        self.stored_response = ""
        self.session_is_ended = False
        self.is_closed = False
        self.engine_exit_breaks_commands = True
        self.fail_next_command = False
        self.fail_next_response = False
        self.force_next_response = None
        self.fail_close = False
        self.fail_command = None

    def send_command_line(self, command):
        if self.is_closed:
            raise SupporterError("channel is closed")
        if self.stored_response != "":
            raise SupporterError("two commands in a row")
        if self.session_is_ended:
            if self.engine_exit_breaks_commands:
                raise GtpChannelClosed("engine has closed the command channel")
            return
        if self.fail_next_command:
            self.fail_next_command = False
            raise GtpTransportError("forced failure for send_command_line")
        if self.fail_command and command.startswith(self.fail_command):
            self.fail_command = None
            raise GtpTransportError("forced failure for send_command_line")
        cmd_list = command.strip().split(" ")
        is_error, response, end_session = \
            self.engine.run_command(cmd_list[0], cmd_list[1:])
        if end_session:
            self.session_is_ended = True
        self.stored_response = ("? " if is_error else "= ") + response + "\n\n"

    def get_response_line(self):
        if self.is_closed:
            raise SupporterError("channel is closed")
        if self.stored_response == "":
            if self.session_is_ended:
                return ""
            raise SupporterError("response request without command")
        if self.fail_next_response:
            self.fail_next_response = False
            raise GtpTransportError("forced failure for get_response_line")
        if self.force_next_response is not None:
            self.stored_response = self.force_next_response
            self.force_next_response = None
        line, self.stored_response = self.stored_response.split("\n", 1)
        return line + "\n"

    def close(self):
        if self.fail_close:
            raise GtpTransportError("forced failure for close")
        self.is_closed = True