# L-36 MCS 260 Mon 11 Apr 2016 : flying_birds.py
"""
The script simulates flying birds via threads.

The class diagram is
+------------+
|   Bird     |
+------------+
| position   |   coordinates (x,y) for its location
| direction  |   tuple (dx,dy) defines flying direction
| fly range  |   how many times a bird flies and lands
| land time  |   time spent on land between air trips
| eat radius |   distance for closest frog to be eaten
+------------+
|    fly     |   fly in some direction and land
|    eat     |   eat closest frog within a certain radius
|    run     |   do fly() and eat() a fixed number of times
+------------+
"""
from time import sleep
from random import randint
from threading import Thread

from class_threadfrog import ThreadFrog
from frog_pond import FrogPond

print('swamp with four frogs')
ANN = ThreadFrog('ann', +10, +10, 2, 5, 20)
BOB = ThreadFrog('bob', +10, -10, 2, 5, 20)
CINDY = ThreadFrog('cindy', -10, +10, 2, 5, 20)
DAVE = ThreadFrog('dave', -10, -10, 2, 5, 20)
SWAMP = FrogPond([ANN, BOB, CINDY, DAVE])

class Bird(Thread):
    """
    Exports flying birds as threads.
    """
    def __init__(self, n, p, d, m, s, r):
        """
        A bird with name n at position p
        flies m times in the direction d,
        spending at most s seconds on land,
        eating frogs within radius r.
        """
        Thread.__init__(self, name=n)
        self.position = p
        self.direction = d
        self.fly_range = m
        self.land_time = s
        self.eat_radius = r

    def fly(self):
        """
        The bird waits a bit before flying.
        """
        name = self.getName()
        pos = self.position
        tsec = randint(0, self.land_time)
        print(name + ' at ' + str(pos) \
            + ' lands ' + str(tsec) + ' seconds')
        sleep(tsec/2)
        self.eat()
        sleep(tsec/2)
        (xps, yps) = self.position
        nxps = xps + self.direction[0]
        nyps = yps + self.direction[1]
        self.position = (nxps, nyps)
        print(name + ' flies to ' + str((nxps, nyps)))

    def eat(self):
        """
        Finds closest frog to the bird
        and eats it if within its radius.
        """
        name = self.getName()
        prey = SWAMP.nearest_frog(self.position)
        if prey[0] != -1:
            prn = prey[1].getName()
            spr = 'nearest frog to ' + name \
                + ' is ' + prn \
                + ' at distance ' + ('%.2f' % prey[0])
            if prey[0] > self.eat_radius:
                print(spr)
            else:
                print(spr + ' => ' + prn + ' gobbled up')
                prey[1].hop_limit = 0
                del SWAMP.frogs[prey[2]]

    def run(self):
        """
        The bird flies as many times
        as the value set by self.fly_range.
        """
        while self.fly_range > 0:
            self.fly()
            self.fly_range = self.fly_range - 1

def main():
    """
    Runs a simple test on flying birds.
    """
    print('creating two birds: A and B')
    (apos, adir) = ((-15, -15), (+1, +1))
    (bpos, bdir) = ((+15, -15), (-1, +1))
    apred = Bird('A', apos, adir, 30, 4, 5)
    bpred = Bird('B', bpos, bdir, 30, 4, 5)
    print('frogs start life in swamp ...')
    for frog in SWAMP.frogs:
        frog.start()
    print('birds start to fly ...')
    apred.start()
    bpred.start()
    apred.join()
    bpred.join()

if __name__ == "__main__":
    main()
