# By Dan Hagon (@axiomsofchoice)
# 05/06/2022 at a rainy EMFCamp
# See: https://2022.badge.emfcamp.org/projects/its_raining_at_emfcamp2022
# 24/07/2022 at a fast drying MCH2022 ¯\_(ツ)_/¯
# See: https://mch2022.badge.team/projects/maycontainh2o
# MIT License

import mch22
import buttons
import display
#import virtualtimers as vt
import audio
from machine import Timer


from math import sqrt,pow,fabs
from random import randint
from time import ticks_ms,ticks_diff

from .bno055 import init as acc_init
from .bno055 import get_accel

class Hole():
    _x = 0
    _y = 0
    _r = 0

    def set_pos(self,tuple_pos):
        self._x,self._y = tuple_pos

    def __init__(self, r=20, x=100,y=100):
        self.set_pos((x,y))
        if (r<4):
            self._r = 4
        else:
            self._r = r
         
    def draw(self, activated=False):
        if activated:
            display.drawCircle(self._x, self._y, self._r, 0, 360, True, 0x00FF00)
        else:
            display.drawCircle(self._x, self._y, self._r, 0, 360, True, 0x009900)
        display.drawCircle(self._x, self._y, self._r-4, 0, 360, True, 0x000000) 

class AnimatedHole(Hole):
    _ticks_at_creation = 0
    _drawable = False
    _delay = 1000
    def __init__(self,r,x,y,delay=500):
        super().__init__(r,x,y)
        self._delay = delay
        self._ticks_at_creation = ticks_ms()
    
    def draw(self):
        if self.drawable():
            #display.translate(self._x, self._y)
            #display.pushMatrix()
            scalefactor = 1-(ticks_diff(ticks_ms(),self._ticks_at_creation) / (self._delay))
            display.drawCircle(self._x, self._y, self._r*scalefactor, 0, 360, False, 0x00FF00)
            #display.scale(scalefactor,scalefactor)
            #display.translate(-self._x, -self._y)
            #display.popMatrix()     

    def reset_time(self):
        self._ticks_at_creation = ticks_ms()


    def drawable(self):
        if ticks_diff(ticks_ms(),self._ticks_at_creation) > self._delay:
            self._drawable = False
        else: 
            self._drawable = True
        return self._drawable

class Ball():
    class Axis():
        vel = 0;
        pos = 0;
        pos_min = 0;
        pos_max = 0;
        bounce_status = False
        #used to detect rolling on axis, which is not bouncing
        was_on_axis = False

    # ball radius
    _r = 10
    # timekeeping
    _tick_period=5

    bounce_damping = 0.8
    
    def __init__(self,r=5, x_min=0, x_max=100, y_min=0, y_max = 100, tick_period=5):
        self._r = r
        self._x = self.Axis()
        self._y = self.Axis()
        self._x.pos_min = x_min
        self._x.pos_max = x_max
        self._y.pos_min = y_min
        self._y.pos_max = y_max
        self._tick_period = tick_period

    def set_pos(self,tuple_pos):
        self._x.pos,self._y.pos = tuple_pos

    def get_pos(self):
        return(self._x.pos,self._y.pos)

    def draw(self):
        display.drawCircle(self._x.pos, self._y.pos, self._r, 0, 360, True, 0xEEEEEE) 

    def update_position(self,acc):
        self._x = self.update(acc[1], self._x)
        self._y = self.update(-acc[0], self._y)

    def update(self, acc, axis):
        #scaling by amplification by arbitrary factor
        scaled_acc = float(acc)*10
        #print(scaled_acc_x))
        axis.vel = axis.vel + scaled_acc / self._tick_period
        new_axis_pos = axis.pos + axis.vel / self._tick_period
        #if at window border min
        if (new_axis_pos < axis.pos_min ):
            #truncate ball position
            axis.pos = axis.pos_min
            if axis.was_on_axis == False:
                axis.bounce_status = True
                axis.vel = -axis.vel * self.bounce_damping
            else:
                axis.bounce_status = False
                axis.vel = 0
            axis.was_on_axis = True
            
            #print(str(display.width()) + " " + str(self.ball_x))
        #if at window border max
        elif (new_axis_pos > axis.pos_max):
            axis.pos = axis.pos_max          
            if axis.was_on_axis == False:
                axis.bounce_status = True
                axis.vel = -axis.vel * self.bounce_damping
            else:
                axis.bounce_status = False
                axis.vel = 0
            axis.was_on_axis = True
            #print(str(display.width()) + " " + str(self.ball_x))
            
        else:
            axis.pos = new_axis_pos
            axis.was_on_axis = False
            axis.bounce_status = False

        return axis

class CircleApp():
    def __init__(self, tick_period = 20):
        ball_radius = 10
        self.ball = Ball(ball_radius,
                    ball_radius,
                    display.width()-ball_radius,
                    ball_radius,
                    display.height()-ball_radius,
                    tick_period)
        self.tick_period = tick_period
        self.hole=Hole(r=20)
        self.animated_hole = AnimatedHole(self.hole._r,self.hole._r,self.hole._r)
    
    def distance_2D(self,x1,y1,x2,y2):
        return sqrt(pow(x1-x2,2) + pow(y1-y2,2))

    def ball_in_hole(self,tuple_ball,tuple_hole):
        ball_x,ball_y,ball_r = tuple_ball
        hole_x,hole_y,hole_r = tuple_hole
        #Pythagorian distance calculation
        dist = self.distance_2D(ball_x,ball_y,hole_x, hole_y)
        # if ball *in* hole
        if dist < hole_r-ball_r:
            return True
        else:
            return False

    def ball_on_hole(self,tuple_ball,tuple_hole):
        ball_x,ball_y,ball_r = tuple_ball
        hole_x,hole_y,hole_r = tuple_hole
        #Pythagorian distance calculation
        dist = self.distance_2D(ball_x,ball_y,hole_x, hole_y)
        # if ball *on* hole
        if dist < hole_r+ball_r:
            return True
        else:
            return False

    def bounced(self):
        # Return True if bounced and not rolling along one axis.
        #bounce_vel_threshold = 200
        if ( self.ball._x.bounce_status == True) or ( self.ball._y.bounce_status == True):
            return True
        return False

    def animation_tick(self):
        # MCH2022 black gray
        display.drawFill(0x333333)
        raw_acc = get_accel()
        self.ball.update_position(raw_acc)
        # Play sound when bouncing with some speed from the border
        if(self.bounced()):
            audio.play('/apps/python/bouncyball/sfx_footsteps.mp3', volume=20)
        #if animation needs to be played (status checked in draw function)
        self.animated_hole.draw()
        # Lighten up hole edge if ball is going over the edge of the hole
        if(self.ball_on_hole(
                                (self.ball._x.pos,self.ball._y.pos,self.ball._r),
                                (self.hole._x,self.hole._y,self.hole._r))): 
            self.hole.draw(activated=True)
        else:
            self.hole.draw()
        
        self.ball.draw()
        display.flush()
        # if ball goes into hole
        if(self.ball_in_hole(
                                (self.ball._x.pos,self.ball._y.pos,self.ball._r),
                                (self.hole._x,self.hole._y,self.hole._r))):
            audio.play('/apps/python/bouncyball/sfx_powerup.mp3', volume=20);
            #start animation
            self.animated_hole.set_pos((self.hole._x,self.hole._y))
            self.animated_hole.reset_time()
            #do while loop to find new hole location that's some distance away from ball
            while True:
                new_hole_x = randint(self.hole._r,display.width()-self.hole._r)
                new_hole_y = randint(self.hole._r,display.height()-self.hole._r)
                if(self.distance_2D(new_hole_x,new_hole_y, self.ball._x.pos, self.ball._y.pos) > 20):
                    self.hole.set_pos((new_hole_x,new_hole_y))
                    break;
        return self.tick_period

    def reboot(self, pressed):
        if pressed:
            #vt.delete(self.animation_tick)
            #vt.stop()

            display.windowRemove(self.window_name)
            mch22.exit_python()

    def next_frame(self,tim0):
        self.call_animation = True

    def on_activate(self):
        self.call_animation = False
        self.window_name = "bouncyball"

        self.w = display.width()
        self.h = display.height()
        self.ball.set_pos((display.width()/2,display.height()/2))
        self.hole.set_pos((display.width()/2,display.height()/2))

        display.windowCreate(self.window_name,self.w, self.h)
        acc_init()

        buttons.attach(buttons.BTN_A, self.reboot)
        #vt.new(self.tick_period, self.animation_tick)
        #vt.begin()
        tim0 = Timer(0)
        tim0.init(period=self.tick_period,mode=Timer.PERIODIC,callback=self.next_frame)
        while(True):
            if self.call_animation:
                self.animation_tick()

app = CircleApp(tick_period=20)
app.on_activate()