# coding: latin-1
"""
  Solve Bankers bonus, aka Millionaires problem.
  to deduce the maximum value from a range of integer input.

  Demonstrate clients external to computing parties supplying input and receiving an authenticated result. See bankers-bonus-client.cpp for client (and setup instructions).
  
  Wait for MAX_NUM_CLIENTS to join the game or client finish flag to be sent
  before calculating the maximum.

  Note each client connects in a single thread and so is potentially blocked.
  
  Each round / game will reset and so this runs indefinitiely.
"""

from Compiler.types import sint, regint, Array, MemValue
from Compiler.library import print_ln, do_while, for_range
from Compiler.util import if_else

PORTNUM = 14000
MAX_NUM_CLIENTS = 8
n_rounds = 0

if len(program.args) > 1:
    n_rounds = int(program.args[1])

def accept_client():
    client_socket_id = accept_client_connection(PORTNUM)
    last = regint.read_from_socket(client_socket_id)
    return client_socket_id, last

def close_connections(number_clients):
    @for_range(number_clients)
    def _(i):
        closeclientconnection(i)

def client_input(t, client_socket_id):
    """
    Send share of random value, receive input and deduce share.
    """
    
    return t.receive_from_client(1, client_socket_id)[0]


def determine_winner(number_clients, client_values, client_ids):
    """Work out and return client_id which corresponds to max client_value"""
    max_value = Array(1, client_values.value_type)
    max_value[0] = client_values[0]
    win_client_id = Array(1, sint)
    win_client_id[0] = client_ids[0]

    @for_range(number_clients-1)
    def loop_body(i):
        # Is this client input a new maximum, will be sint(1) if true, else sint(0)
        is_new_max = max_value[0] < client_values[i+1]
        # Keep latest max_value
        max_value[0] = if_else(is_new_max, client_values[i+1], max_value[0])
        # Keep current winning client id
        win_client_id[0] = if_else(is_new_max, client_ids[i+1], win_client_id[0])

    print_ln('maximum: %s', max_value[0].reveal())
    return win_client_id[0]


def write_winner_to_clients(sockets, number_clients, winning_client_id):
    """Send share of winning client id to all clients who joined game."""

    # Setup authenticate result using share of random.
    # client can validate ∑ winning_client_id * ∑ rnd_from_triple = ∑ auth_result
    sint.reveal_to_clients(sockets.get_sub(number_clients), [winning_client_id])

def main():
    """Listen in while loop for players to join a game.
    Once maxiumum reached or have notified that round finished, run comparison and return result."""
    # Start listening for client socket connections
    listen_for_clients(PORTNUM)
    print_ln('Listening for client connections on base port %s', PORTNUM)

    def game_loop(_=None):
        print_ln('Starting a new round of the game.')

        # Clients socket id (integer).
        client_sockets = Array(MAX_NUM_CLIENTS, regint)
        # Number of clients
        number_clients = MemValue(regint(0))
        # Client ids to identity client
        client_ids = Array(MAX_NUM_CLIENTS, sint)
        # Keep track of received inputs
        seen = Array(MAX_NUM_CLIENTS, regint)
        seen.assign_all(0)

        # Loop round waiting for each client to connect
        @do_while
        def client_connections():
            client_id, last = accept_client()
            @if_(client_id >= MAX_NUM_CLIENTS)
            def _():
                print_ln('client id too high')
                crash()
            client_sockets[client_id] = client_id
            client_ids[client_id] = client_id
            seen[client_id] = 1
            @if_(last == 1)
            def _():
                number_clients.write(client_id + 1)

            return (sum(seen) < number_clients) + (number_clients == 0)

        def type_run(t):
            # Clients secret input.
            client_values = t.Array(MAX_NUM_CLIENTS)

            @for_range(number_clients)
            def _(client_id):
                client_values[client_id] = client_input(t, client_id)

            winning_client_id = determine_winner(number_clients, client_values,
                                                 client_ids)

            # print_ln('Found winner, index: %s.', winning_client_id.reveal())

            write_winner_to_clients(client_sockets, number_clients,
                                    winning_client_id)

        type_run(sint)
        type_run(sfix)

        close_connections(number_clients)

        return True

    if n_rounds > 0:
        print('run %d rounds' % n_rounds)
        for_range(n_rounds)(game_loop)
    else:
        print('run forever')
        do_while(game_loop)

main()
