# Arup Guha
# 6/16/2025
# Edit to Bouncing Ball to illustrate methods from OOP Python Quiz 1

import random
import math
import time
import pygame, sys
from pygame.locals import *

# Token Class we'll use for drawing objects in pyGame
class token:

    # Constructor.
    def __init__(self,myx,myy,mydx,mydy,myr,mycolor):
        self.x = myx
        self.y = myy
        self.dx = mydx
        self.dy = mydy
        self.r = myr
        self.color = mycolor

    # Call each frame.
    def move(self):
        self.x += self.dx
        self.y += self.dy

    # Executes updating dx as necessary for bouncing off the left wall.
    def bounceLeft(self):
        if self.x + self.dx < self.r:
            self.dx = -self.dx

    # Executes updating dx as necessary for bouncing off the right wall.
    def bounceRight(self, SCREEN_W):
        if self.x + self.dx > SCREEN_W-self.r:
            self.dx = -self.dx

    # Executes updating y as necessary for bouncing off the top wall.
    def bounceUp(self):
        if self.y + self.dy < self.r:
            self.dy = -self.dy

    # Executes updating y as necessary for bouncing off the bottom wall.
    def bounceDown(self, SCREEN_H):
        if self.y + self.dy > SCREEN_H-self.r:
            self.dy = -self.dy

    # Adds addDX to the the change in x per frame, and adds addDY to
    # the change in y per frame.
    def changeVelocity(self,addDX,addDY):
        self.dx += addDX
        self.dy += addDY

    # Draws this object on the display surface as a circle.
    # Likely to be overridden most of the time.
    def draw(self, DISPLAYSURF):
        pygame.draw.circle(DISPLAYSURF, self.color,(self.x,self.y),self.r, 0)

    # Update for a single frame. Maybe this will typically be overridden.
    def updateFrame(self, DISPLAYSURF):
        self.move()
        self.bounceLeft()
        self.bounceRight(DISPLAYSURF.get_width())
        self.bounceUp()
        self.bounceDown(DISPLAYSURF.get_height())
        self.draw(DISPLAYSURF)
        
    # Returns true iff the two circles represented by self and other intersect.
    def collide(self, other):
        center_dx = self.x - other.x
        center_dy = self.y - other.y
        dist_sq = center_dx*center_dx + center_dy*center_dy
        dist_sq_radii = (self.r + other.r)*(self.r + other.r)
        return dist_sq <= dist_sq_radii

    # Returns true iff the position pos is within the circle.
    def inCircle(self, pos):
        # Calculate the distance squared between pos and circle center.
        dist_sq = (pos[0]-self.x)*(pos[0]-self.x) + (pos[1]-self.y)*(pos[1]-self.y)
        return dist_sq <= self.r*self.r

    # FILL THIS IN!!!
    def distToTop(self):
        return self.y-self.r

    # FILL THIS IN!!!
    def distToLeft(self):
        return self.x-self.r

    # FILL THIS IN!!! SCREEN_H is height of the screen.
    def distToBottom(self, SCREEN_H):
        return SCREEN_H -(self.y+self.r)

    # FILL THIS IN!!! SCREEN_W is the width of the screen.
    def distToRight(self, SCREEN_W):
        return SCREEN_W - (self.x+self.r)

    def toEdge(self, SCREEN_W, SCREEN_H):
        ans = min(self.distToTop(), self.distToLeft())
        ans = min(ans, self.distToBottom(SCREEN_H))
        return min(ans, self.distToRight(SCREEN_W))

def draw(text, font, color, surface, x, y):
    # Draw text on a new Surface. Title, antialias and color are used.
    text = font.render(text, 1, color)

    # Returns a new rectangle covering the entire surface.
    # This rectangle will always start at (0, 0) with a width and height the same size as the image.
    textrect = text.get_rect()

    # Sets location of text.
    textrect.topleft = (x, y)

    # Go ahead and draw it to the surface.
    surface.blit(text, textrect)
    
# Test is here.
def main():

    # Basic Set Up
    pygame.init()
    SCREEN_W = 1000
    SCREEN_H = 600
    BALL_R = 30
    DISPLAYSURF = pygame.display.set_mode((SCREEN_W, SCREEN_H))
    pygame.display.set_caption("Object Oriented Bouncing")
    WHITE = pygame.Color(255,255,255)
    BLUE = pygame.Color(0,0,255)
    
    clock = pygame.time.Clock()
    font = pygame.font.SysFont("Arial", 24)

    curT = time.time()

    # Somewhere on the screen.
    x = random.randint(1, SCREEN_W-BALL_R)
    y = random.randint(1, SCREEN_H-BALL_R)

    # Random non-zero movement in both directions.
    dx = random.randint(-2,2)
    while dx == 0:
        dx = random.randint(-2,2)
    dy = random.randint(-2,2)
    while dy == 0:
        dy = random.randint(-2,2)

    # This is the starting spot of the ball.
    mytoken = token(x,y,dx,dy,BALL_R,BLUE)
        
    # Game loop.
    while True:

        # Look for events - we aren't using this right now.
        for event in pygame.event.get():
            
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

        # White background.
        DISPLAYSURF.fill(WHITE)

        mytoken.updateFrame(DISPLAYSURF)
        draw("Distance to Edge: "+str(mytoken.toEdge(SCREEN_W, SCREEN_H)), font, BLUE, DISPLAYSURF, 10, 10)
            
        # Update what we put on the canvas.
        pygame.display.update()

        # Wait a bit!
        clock.tick(100)

# Run it
main()
