# test that socket.connect() on a non-blocking socket raises EINPROGRESS
# and that an immediate write/send/read/recv does the right thing

import sys, time, socket, errno, ssl

isMP = sys.implementation.name == "micropython"


def dp(e):
    # uncomment next line for development and testing, to print the actual exceptions
    # print(repr(e))
    pass


# do_connect establishes the socket and wraps it if tls is True.
# If handshake is true, the initial connect (and TLS handshake) is
# allowed to be performed before returning.
def do_connect(peer_addr, tls, handshake):
    s = socket.socket()
    s.setblocking(False)
    try:
        # print("Connecting to", peer_addr)
        s.connect(peer_addr)
    except OSError as er:
        print("connect:", er.errno == errno.EINPROGRESS)
        if er.errno != errno.EINPROGRESS:
            print("  got", er.errno)
    # wrap with ssl/tls if desired
    if tls:
        ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
        if hasattr(ssl_context, "check_hostname"):
            ssl_context.check_hostname = False

        try:
            s = ssl_context.wrap_socket(s, do_handshake_on_connect=handshake)
            print("wrap: True")
        except Exception as e:
            dp(e)
            print("wrap:", e)
    elif handshake:
        # just sleep a little bit, this allows any connect() errors to happen
        time.sleep(0.2)
    return s


# test runs the test against a specific peer address.
def test(peer_addr, tls=False, handshake=False):
    # MicroPython plain sockets have read/write, but CPython's don't
    # MicroPython TLS sockets and CPython's have read/write
    # hasRW captures this wonderful state of affairs
    hasRW = isMP or tls

    # MicroPython plain sockets and CPython's have send/recv
    # MicroPython TLS sockets don't have send/recv, but CPython's do
    # hasSR captures this wonderful state of affairs
    hasSR = not (isMP and tls)

    # connect + send
    if hasSR:
        s = do_connect(peer_addr, tls, handshake)
        # send -> 4 or EAGAIN
        try:
            ret = s.send(b"1234")
            print("send:", handshake and ret == 4)
        except OSError as er:
            #
            dp(er)
            print("send:", er.errno in (errno.EAGAIN, errno.EINPROGRESS))
        s.close()
    else:  # fake it...
        print("connect:", True)
        if tls:
            print("wrap:", True)
        print("send:", True)

    # connect + write
    if hasRW:
        s = do_connect(peer_addr, tls, handshake)
        # write -> None
        try:
            ret = s.write(b"1234")
            print("write:", ret in (4, None))  # SSL may accept 4 into buffer
        except OSError as er:
            dp(er)
            print("write:", False)  # should not raise
        except ValueError as er:  # CPython
            dp(er)
            print("write:", er.args[0] == "Write on closed or unwrapped SSL socket.")
        s.close()
    else:  # fake it...
        print("connect:", True)
        if tls:
            print("wrap:", True)
        print("write:", True)

    if hasSR:
        # connect + recv
        s = do_connect(peer_addr, tls, handshake)
        # recv -> EAGAIN
        try:
            print("recv:", s.recv(10))
        except OSError as er:
            dp(er)
            print("recv:", er.errno == errno.EAGAIN)
        s.close()
    else:  # fake it...
        print("connect:", True)
        if tls:
            print("wrap:", True)
        print("recv:", True)

    # connect + read
    if hasRW:
        s = do_connect(peer_addr, tls, handshake)
        # read -> None
        try:
            ret = s.read(10)
            print("read:", ret is None)
        except OSError as er:
            dp(er)
            print("read:", False)  # should not raise
        except ValueError as er:  # CPython
            dp(er)
            print("read:", er.args[0] == "Read on closed or unwrapped SSL socket.")
        s.close()
    else:  # fake it...
        print("connect:", True)
        if tls:
            print("wrap:", True)
        print("read:", True)


if __name__ == "__main__":
    # these tests use a non-existent test IP address, this way the connect takes forever and
    # we can see EAGAIN/None (https://tools.ietf.org/html/rfc5737)
    print("--- Plain sockets to nowhere ---")
    test(socket.getaddrinfo("192.0.2.1", 80)[0][-1], False, False)
    print("--- SSL sockets to nowhere ---")
    # this test fails with AXTLS because do_handshake=False blocks on first read/write and
    # there it times out until the connect is aborted
    test(socket.getaddrinfo("192.0.2.1", 443)[0][-1], True, False)
    print("--- Plain sockets ---")
    test(socket.getaddrinfo("micropython.org", 80)[0][-1], False, True)
    print("--- SSL sockets ---")
    test(socket.getaddrinfo("micropython.org", 443)[0][-1], True, True)
