"""tcprocd client."""
from __future__ import unicode_literals, print_function, absolute_import
from tcprocd.protocol import Protocol
import socket
import select
import sys


if sys.version_info[0] < 3:
    str_types = (str, unicode)  # noqa
else:
    str_types = (str, bytes)  # noqa


class SocketShell(object):
    """
    A class to connect to a process's thread.

    Adds a line buffer for the socket and passes received messages to ``on_receive``.
    Messages by the user are passed to ``on_stdin``.

    :param client: :class:`tcprocd.client.Client` - The client to use for the connection.
    """

    def __init__(self, client):
        """Initialize shell."""
        self.client = client
        self.sockets = [sys.stdin, client.socket]
        self._do_stop = False

    def on_stdin_ready(self):
        """Called when some input is ready."""
        line = sys.stdin.readline().strip()

        if line == 'exit':
            return True

        self.client.protocol.sendline(line + '\n')

    def on_socket_ready(self):
        """Called when receiving some process output."""
        line = self.client.protocol.readline()

        if line == 'exit':
            return True

        sys.stdout.write(line + '\n')
        sys.stdout.flush()

    def run(self):
        """Start waiting for input/output."""
        try:
            while not self._do_stop:
                ready = select.select(self.sockets, [], [])[0]

                for s in ready:
                    if s == self.client.socket:  # message by server
                        if self.on_socket_ready():
                            self._do_stop = True

                    else:  # message by user
                        

        finally:
            self.client.socket.close()


class AuthenticationError(Exception):
    """Exception raised when authentication fails."""

    pass


class ServerError(Exception):
    """Exception raised when the server answers with an error."""

    pass


class Client(object):
    """
    A class to connect to a tcprocd server.

    :param server_address: tuple of host and port or the path to the socket file
    """

    def __init__(self, server_address):
        """Initialize client."""
        self.server_address = server_address

        if isinstance(server_address, str_types):
            self.is_unix_domain = True
            sock_type = socket.AF_UNIX
        else:
            self.is_unix_domain = False
            sock_type = socket.AF_INET

        self.socket = socket.socket(sock_type, socket.SOCK_STREAM)
        self.protocol = Protocol(self.socket)
        self.server_version = None
        self.attached_to = None

    def connect(self, username=None, password=None, username_callback=None, password_callback=None):
        """Connect to the server."""
        if self.is_unix_domain:
            try:
                SO_PASSCRED = socket.SO_PASSCRED
            except AttributeError:
                SO_PASSCRED = 16

            self.socket.setsockopt(socket.SOL_SOCKET, SO_PASSCRED, 1)

        self.socket.connect(self.server_address)
        self.server_version = self.protocol.recv_part(3)

        answer = self.protocol.recv_part(2)

        # TCP connections always require username and password.
        # A unix domain socket does not accept an username and
        # only requires a password if the connecting user has one.
        # TODO: return 'authentication required' and let the caller authenticate on its own
        if answer == self.protocol.AUTHENTICATION_REQUIRED:
            if not self.is_unix_domain:

                if username is None:
                    username = username_callback()
                self.protocol.send_part(2, username)

            if password is None:
                password = password_callback()
            self.protocol.send_part(2, password)

            answer = self.protocol.recv_part(2)

            if answer != self.protocol.OK:
                raise AuthenticationError()

        elif answer != self.protocol.OK:
            raise ServerError(answer)

    def close(self):
        """Close the connection."""
        self.socket.close()

    def __enter__(self):
        """Connect when used as context manager."""
        self.connect()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Disconnect afterwards, when used as context manager."""
        self.close()

    def list(self):
        """List servers."""
        self.protocol.send_part(2, 'list')
        answer = self.protocol.recv_part(2)

        data = []
        if answer == self.protocol.OK:
            data = self.protocol.recv_part(6).split('\n')

        return answer, data

    def cat(self, name, start=0):
        """Get output of given process.

        :param name: :class:`str` - Name of the process.
        :param start: :class:`int` - Start at this line. (Default: ``0``)
        :return: :class:`str` - Multi-line output of the process.
        """
        self.protocol.send_part(2, 'cat')
        self.protocol.send_part(2, name)
        self.protocol.send_part(1, str(start))
        answer = self.protocol.recv_part(2)
        data = []
        if answer == self.protocol.OK:
            data = self.protocol.recv_part(6).split('\n')
        return answer, data

    def start(self, name, command, path=''):
        """Create a new process with the given ``name`` and ``command``.

        :param name: :class:`str` - Name of the process.
        :param command: :class:`str` - The command to run the process.
        :param path: :class:`str` - The (remote) path to execute the
            command in. (Default: ``None``)
        :return: :class:`str` - Status message
        """
        self.protocol.send_part(2, 'start')
        self.protocol.send_part(2, name)
        self.protocol.send_part(3, command)
        self.protocol.send_part(3, path)
        return self.protocol.recv_part(2)

    def kill(self, name):
        """Kill the given process.

        :param name: :class:`str` - Name of the process.
        :return: :class:`str` - Status message
        """
        self.protocol.send_part(2, 'kill')
        self.protocol.send_part(2, name)
        return self.protocol.recv_part(2)

    def command(self, name, command):
        """Write the given command to the given process's stdin.

        .. Note: Use ``cat`` to see the process's stdout!

        :param name: :class:`str` - Name of the process.
        :param command: :class:`str` - The command to send to the process.
        :return: :class:`str` - Status message
        """
        self.protocol.send_part(2, 'command')
        self.protocol.send_part(2, name)
        self.protocol.send_part(3, command)
        return self.protocol.recv_part(2)

    def attach(self, name):
        """
        Attach to the given process's shell.

        :param name: :class:`str` - Name of the process.
        :return: :class:`str` - Status message
        """
        self.protocol.send_part(2, 'attach')
        self.protocol.send_part(2, name)
        return self.protocol.recv_part(2)

if self.on_stdin_ready():
                            self._do_stop = True