# problem4.py
#
# ICS H32 Fall 2025
# Exercise Set 2
# INSTRUCTOR SOLUTION
#
# This is one possible solution to Problem 4.  There were two basic
# design goals here:
#
# (1) Use only Python techniques that we've seen in class.  It's
#     certainly true that as we learn more about Python, our solution
#     to this problem would become gradually more simplified.
# (2) Keep separate things separate, so write functions that do one
#     job, rather than combining everything into large, complex functions.
#
# The one new thing here is the use of the "random" module from the Python
# standard library to generate random numbers.
#
# Of course, the program won't run without updating the SHAKESPEARE_HOST
# and SHAKESPEARE_PORT constants, so they correctly indicate where
# the Shakespeare server is running.

import random
import socket


SHAKESPEARE_HOST = 'REPLACE_WITH_ACTUAL_HOST'
SHAKESPEARE_PORT = 1564


def connect(host: str, port: int) -> 'connection':
    'Connects to the given host and port, returning a connection object'
    shake_socket = socket.socket()
    shake_socket.connect((host, port))

    shake_in = shake_socket.makefile('r')
    shake_out = shake_socket.makefile('w')

    return shake_socket, shake_in, shake_out


def close(connection: 'connection') -> None:
    'Closes the given connection'
    shake_socket, shake_in, shake_out = connection

    shake_in.close()
    shake_out.close()
    shake_socket.close()


def send_message(connection: 'connection', message: str) -> None:
    'Sends a line of text on the given connection, including a newline sequence'
    shake_socket, shake_in, shake_out = connection

    shake_out.write(message + '\r\n')
    shake_out.flush()


def receive_message(connection: 'connection') -> str:
    '''
    Receives a line of text from the given connection, which is returned
    without any kind of newline character/sequence in it
    '''
    shake_socket, shake_in, shake_out = connection

    return shake_in.readline()[:-1]


def read_counts(connection: 'connection') -> list[int]:
    'Reads the Shakespeare counts from the server'
    counts_message = receive_message(connection)

    counts = []

    for count in counts_message.split():
        counts.append(int(count))

    return counts


def choose_insult(counts: list[int]) -> list[int]:
    '''
    Chooses the words for a Shakespearean insult at random, given the number
    of words available in each position
    '''
    words = []

    for count in counts:
        words.append(random.randint(1, count))

    return words


def format_chosen_insult(words: list[int]) -> str:
    '''
    Formats the chosen insult words in a way that they can be sent to the
    Shakespeare server and understood by it
    '''
    formatted = ''

    for word in words:
        if len(formatted) > 0:
            formatted += ' '

        formatted += str(word)

    return formatted


def run() -> None:
    '''
    Runs the Shakespeare client, generating one random insult and printing
    it (along with the server's goodbye message)
    '''
    connection = None

    try:
        connection = connect(SHAKESPEARE_HOST, SHAKESPEARE_PORT)

        send_message(connection, 'SHAKESPEARE_COUNTS')
        counts = read_counts(connection)
        chosen_insult = format_chosen_insult(choose_insult(counts))

        send_message(connection, f'SHAKESPEARE_INSULT {chosen_insult}')
        insult = receive_message(connection)
        print(insult)

        send_message(connection, 'SHAKESPEARE_GOODBYE')
        goodbye = receive_message(connection)
        print(goodbye)

    finally:
        close(connection)


if __name__ == '__main__':
    run()
