#    Licensed to the Apache Software Foundation (ASF) under one
#    or more contributor license agreements.  See the NOTICE file
#    distributed with this work for additional information
#    regarding copyright ownership.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

__all__ = [
    "read_socket_input",
    "write_socket_output"
]

import errno
import logging
import socket

from connection import Connection

LOG = logging.getLogger(__name__)

"""helper methods that provide boilerplate socket I/O and Connection
    processing.
"""


def read_socket_input(connection, socket_obj):
    """Read from the network layer and processes all data read.  Can
    support both blocking and non-blocking sockets.
    Returns the number of input bytes processed, or EOS if input processing
    is done.  Any exceptions raised by the socket are re-raised.
    """
    count = connection.needs_input
    if count <= 0:
        return count  # 0 or EOS

    try:
        sock_data = socket_obj.recv(count)
    except socket.timeout as e:
        LOG.debug("Socket timeout exception %s", str(e))
        raise  # caller must handle
    except socket.error as e:
        LOG.debug("Socket error exception %s", str(e))
        err = e.args[0]
        # ignore non-fatal errors
        if (err != errno.EAGAIN and
                err != errno.EWOULDBLOCK and
                err != errno.EINTR):
            # otherwise, unrecoverable:
            connection.close_input()
        raise  # caller must handle
    except Exception as e:  # beats me... assume fatal
        LOG.debug("unknown socket exception %s", str(e))
        connection.close_input()
        raise  # caller must handle

    if sock_data:
        count = connection.process_input(sock_data)
    else:
        LOG.debug("Socket closed")
        count = Connection.EOS
        connection.close_input()
    return count


def write_socket_output(connection, socket_obj):
    """Write data to the network layer.  Can support both blocking and
    non-blocking sockets.
    Returns the number of output bytes sent, or EOS if output processing
    is done.  Any exceptions raised by the socket are re-raised.
    """
    count = connection.has_output
    if count <= 0:
        return count  # 0 or EOS

    data = connection.output_data()
    if not data:
        # error - has_output > 0, but no data?
        return Connection.EOS
    try:
        count = socket_obj.send(data)
    except socket.timeout as e:
        LOG.debug("Socket timeout exception %s", str(e))
        raise  # caller must handle
    except socket.error as e:
        LOG.debug("Socket error exception %s", str(e))
        err = e.args[0]
        # ignore non-fatal errors
        if (err != errno.EAGAIN and
                err != errno.EWOULDBLOCK and
                err != errno.EINTR):
            # otherwise, unrecoverable
            connection.close_output()
        raise
    except Exception as e:  # beats me... assume fatal
        LOG.debug("unknown socket exception %s", str(e))
        connection.close_output()
        raise

    if count > 0:
        connection.output_written(count)
    elif data:
        LOG.debug("Socket closed")
        count = Connection.EOS
        connection.close_output()
    return count