"""
Example showing for tkinter and ttk how to do:
  -- A GUI including a Canvas for drqwing objects
  -- while sending and receiving messages from other computers.

For the tkinter/ttk code, see the 22-TkinterAnimation code (but ignore the
part of that code that runs the animation by using root.after).

The novel part of this example is the message-passing to/from other computers.
The key ideas are:

  1. The   mqtt_for_csse120   module defines Sender and Receiver classes
       for sending and receiving messages from other computers.
       They use MQTT as the underlying communication protocol.

  2. PyCharm will not let you run the same module twice at the same time.
       So, to test your program on your own (single) computer, you run the
       main1  module  AND the  main2  module.  The former constructs a Game
       object with who_am_i set to 1, the latter sets it to 2.
       That way, the two runs will send to each other and NOT to themselves!

       When running on two computers (with a friend running one of them),
       decide who will run  main1  and the other should run  main2.

  3. The Game object constructs a Receiver and Sender:
        receiver = communicator.Receiver(self)
        self.sender = communicator.Sender(receiver, "something_unique",
                                          who_am_i)
     where  communicator  is an abbreviation for the  mqtt_for_csse120  module.

     The "something_unique" should be any string that only YOUR programs will
     be using (so you are not communicating with random other programs).
     We suggest you use your own username as that string, e.g.
        self.sender = communicator.Sender(receiver, "alangavr", who_am_i)

  4. To send a message, just use:
        self.sender.send_message(message)  where message is any STRING.

  5. The Receiver object that the Game object constructs causes a process
       to run **in the BACKGROUND** listening for messages.  That process
       has been coded to call the specially-name method of the Game class:
           act_on_message_received

       So the Game class MUST have a method like this:
           def act_on_message_received(self, message):
              # This is called when a message comes from another computer.
              # You do whatever you want with that message.

  6. The basic structure of the code below is very similar to the structure
       from the 22-TkinterAnimation code, but with the animation part removed.

SEE THE UML CLASS DIAGRAM include with this project.

Authors: David Mutchler and his colleagues
         at Rose-Hulman Institute of Technology.
"""
import tkinter
from tkinter import ttk

import old_mqtt_for_csse120 as communicator


def main(who_am_i):
    game = Game(who_am_i)
    game.start()


class Game(object):
    """ A Game played on two (or more) computers. """

    def __init__(self, who_am_i):
        # Construct the GUI, which constructs and stores a Canvas.
        # Store that Canvas in THIS object too, so that animated objects can
        # act upon it.  Here, our animated objects are a single Ball.
        # Each Ball needs to have the Canvas so that the Ball can change its
        # position (and anything else it might want to change).

        self.gui = GUI(self)
        canvas = self.gui.canvas

        # Make a Receiver and Sender for messages to/from other computers.
        receiver = communicator.Receiver(self)
        self.sender = communicator.Sender(receiver, "something_unique",
                                          who_am_i)

        # So each player's Ball looks different, we base the color on who_am_i.
        if who_am_i == 1:
            color = "blue"
        else:
            color = "red"
        self.ball = Ball(color, canvas)  # How each Ball gets the Canvas

    def start(self):
        # Called after the GUI, the Game, and all the animated objects
        # are constructed.  The GUI's  start  method starts the  mainloop
        # in which the program remains for the remainder of its run.
        self.gui.start()

    def act_on_message_received(self, message):
        """
        Called by the Receiver when a message is received.
        The MQTT code makes the Receiver **run in the background**
        and **automatically** call this method named  act_on_message_received.
        The method name must be exactly as it is here.
        """
        # The message always arrives as a STRING.
        # The  split  method returns a LIST of the words in that string
        # (where words are by definition separated by spaces, tabs or newlines).
        message_parts = message.split()
        x = int(message_parts[0])
        y = int(message_parts[1])

        self.ball.move_to(x, y)

    def send_xy(self, x, y):
        """ Combine the two numbers into a SINGLE, space-separated string. """
        self.sender.send_message("{} {}".format(x, y))


class GUI(object):
    def __init__(self, game):
        """
        Stores the given Game object in order to call the
        Game object's  send_xy  method when ready to send that message.
        Constructs all the GUI widgets, but does NOT (yet) call  root.mainloop.
          :type game: Game
        """
        self.game = game

        # The usual Tk and Frame objects, plus any other widgets you want.
        self.root = tkinter.Tk()
        self.frame = ttk.Frame(self.root, padding=10)
        self.frame.grid()

        # A Canvas for the Ball object.
        self.canvas = self.make_canvas()

        # A Chooser by which the user can enter an x and y to send to the
        # other computer/player, which moves ITS Ball to that x and y.
        self.make_chooser_for_xy()

    def make_canvas(self):
        canvas_width = 400
        canvas_height = 300
        canvas = tkinter.Canvas(self.frame, width=canvas_width,
                                height=canvas_height)
        canvas.width = canvas_width
        canvas.height = canvas_height
        canvas.grid()
        return canvas

    def make_chooser_for_xy(self):
        entry_for_x = ttk.Entry(self.frame)
        entry_for_y = ttk.Entry(self.frame)
        send_button = ttk.Button(self.frame, text="Send X and Y")
        send_button["command"] = lambda: self.send_xy(entry_for_x, entry_for_y)

        entry_for_x.grid()
        entry_for_y.grid()
        send_button.grid()

    def send_xy(self, entry_for_x, entry_for_y):
        # Get the data from the Entry boxes. Game sends them to other computer.
        x = int(entry_for_x.get())
        y = int(entry_for_y.get())
        self.game.send_xy(x, y)

    def start(self):
        # Called by the Game object when the program is ready to enter the
        # Tk object's mainloop and remain there for the remainder of the run.
        self.root.mainloop()


class Ball(object):
    def __init__(self, color, canvas):
        """
        The Ball needs the Canvas so that it can update its characteristics
        (position, fill color, etc) as the game runs.
          :type color   str
          :type canvas: tkinter.Canvas
        """
        self.canvas = canvas

        # Set the characteristics of the Ball: specific x, y and diameter.
        # It will use the given color.
        x = 200
        y = 200
        self.diameter = 20

        # Make the item on the Canvas for drawing the Ball, storing its ID
        # for making changes to the Ball (moving it, changing color, etc.).
        # Here, each Ball is a filled circle (actually an oval),
        # defined by its upper-left and lower-right corners.
        self.id = self.canvas.create_oval(x, y,
                                          x + self.diameter, y + self.diameter,
                                          fill=color)

    def move_to(self, x, y):
        self.canvas.coords(self.id, x, y, x + self.diameter, y + self.diameter)