import socket
import numpy as np


def open_server(server_address: tuple = ('localhost', 10000), ack: bool = True, tabs: int = 1) -> socket:
    """
    :param server_address:
    :param ack:
    :param tabs:
    see open_server_test()
    """
    if ack:
        print('{}Opening server on IP,PORT {}'.format(tabs * '\t', server_address))
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(server_address)
    sock.listen(1)
    return sock


def get_host_name() -> str:
    """
    :return: hostname
    see get_host_name_test()
    """
    hostname = socket.gethostname()
    return hostname


def get_ipv4() -> str:
    """
     :return ipv4 address of this computer
     see get_ipv4_test()
     """
    ipv4 = socket.gethostbyname(get_host_name())
    return ipv4


def send_msg(connection: socket, buflen: int, data: str, msg_end: str) -> None:
    """
    :param connection: the connection of this client\server to the server\client
    :param buflen: needed to split the message if it is longer than 'buflen'
    :param data: string to send
    :param msg_end: special string that notifies when the msg is over(faster than try catch)
        MUST BE A STRING THAT CANT APPEAR ON MESSAGES - e.g. "$#$#"
    see open_server_test()
    """
    data_e = str.encode(data + msg_end)
    data_e_len = len(data_e)
    for i in range(0, data_e_len, buflen):
        chunk_i = data_e[i:i + buflen]
        connection.send(chunk_i)
    return


def receive_msg(connection: socket, buflen: int, msg_end: str) -> str:
    """
    :param connection: the connection of this client\server to the server\client
    :param buflen: needed to receive the message in chunks
    :param msg_end: special string that notifies when the msg is over(faster than try catch)
        MUST BE A STRING THAT CANT APPEAR ON MESSAGES - e.g. "$#$#"
    :return: string of the received data
    see open_server_test()
    """
    data_in = ''
    saw_end_delimiter = False
    while not saw_end_delimiter:
        data_in += connection.recv(buflen).decode('utf-8')
        if not data_in:
            break  # empty transmission
        if data_in.endswith(msg_end):
            data_in = data_in.replace('$#$#', '')
            saw_end_delimiter = True

    return data_in


def buffer_to_str(data: str, prefix: str, tabs: int = 1, max_chars: int = 100) -> str:
    """
    :param data: data as string
    :param prefix: string prefix e.g. 'in', 'out', 'server', 'client'
    :param tabs:
    :param max_chars:
    :return: pretty print of the buffer
    see buffer_to_str_test()
    """
    data_len = len(data)
    data_chars = data_len + 1 if data_len <= max_chars else max_chars

    msg = '{}{}: {} (bytes sent={})'.format(tabs * '\t', prefix, data[:data_chars], data_len)
    if data_len > max_chars:
        msg += ' ... message is too long'
    return msg


def rounds_summary(times: list, tabs: int = 0) -> None:
    """
    :param times:
    :param tabs:
    """
    if len(times) > 0:  # try print time avg before exit
        print('\n{}Rounds Summary:'.format(tabs * '\t'))
        print('{}\tTotal rounds = {}'.format(tabs * '\t', len(times)))
        print('{}\tTotal run time = {:.3f} seconds'.format(tabs * '\t', np.sum(times)))
        print('{}\tAvg   run time = {:.3f} seconds (std = {:.3f})'.format(tabs * '\t', np.mean(times), np.std(times)))
        if np.mean(times) > 0.0001:
            print('{}\t{:.2f} FPS'.format(tabs * '\t', 1 / np.mean(times)))
    return
