import ugfx, badge, utime, random
try:
    import wifi
except Exception as e:
    print("[LEDGAME] Emulator I suppose? ;)")
# import gc

''' BenV's LedGame

The concept is simple, we light up one (or more later) random LED which fades over time.
The player has to press the corresponding button the keyboard before the led is off to score point.
Wrong button presses cost points, the sooner you press the correct button the more points you score.
Missing a light (i.e. not pressing the right button in the given time) costs you a live, whenever you're out out lives it's game over.

Later we introduce levels/rounds, after X lights the speed goes up and we start flipping button<->led 'wires'.
(so instead of pressing LEFT for the 1st button you might have to press LEFT for the 4th button and START for the 1st)

The whole input disaster (that is: lack of documentation on the wiki) of the badge had me reshuffle the code a few times.
Seems like this setup is working for now.
The game loop keeps track of which leds were actived when (ledsactive[led] = utime.ticks_ms()) and fades the led over $ledtime.
When the user presses an invalid button we have (ledsfail[led] = utime.ticks_ms()) to blink it red.
When the user presses the right button we have (ledsscore[led] = utime.ticks_ms()) to blink it green.

NOTE: maybe the ticks_ms isn't the best, but the fine documentation advised against time() due to precision. We'll debug that later.

TODO:
- countdown before we start
'''

class LedGame:
    def action_up(self, pressed):
        if pressed:
            self.button_pressed = "UP"
    
    def action_down(self, pressed):
        if pressed:
            self.button_pressed = "DOWN"
    
    def action_left(self, pressed):
        if pressed:
            self.button_pressed = "LEFT"
    
    def action_right(self, pressed):
        if pressed:
            self.button_pressed = "RIGHT"
    
    def action_select(self, pressed):
        if pressed:
            self.button_pressed = "SELECT"
    
    def action_start(self, pressed):
        if pressed:
            self.button_pressed = "START"
    
    def action_b(self, pressed):
        if pressed:
            self.button_pressed = "B"
    
    def action_a(self, pressed):
        if pressed:
            self.button_pressed = "A"
    
    def instructions(self):
        print("[LEDGAME] instructions.")
        ugfx.clear(ugfx.BLACK)
        ugfx.string_box(0,0,300,20,"Instructions","Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        ugfx.string_box(0,20,280,20,"A random LED will light up","Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        ugfx.string_box(0,30,280,45,"The objective is to press the corresponding button before it dies out","Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        ugfx.string_box(0,65,280,50,"Have fun! Press A/B to start, SELECT to quit.","Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        ugfx.flush()
    
    def __init__(self):
        print("[LEDGAME] init.")
        ugfx.init()
        badge.init()
        ugfx.input_init()
        badge.leds_init()
        badge.vibrator_init()
        try:
            wifi.init()
        except Exception as e:
           print("[LEDGAME] Still emulator.... (no wifi)")
        self.leddata = bytearray([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
        self.ledschanged = False
    
    def leds_off(self):
        badge.leds_send_data(bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 24)
    
    def leds_on(self, brightness = 255):
        if brightness == 255:
            brightness = self.maxbright
        brightness = int(brightness)
        badge.leds_send_data(bytes([brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness, brightness]), 24)
    
    def set_led(self, led, r, g, b, w):
        if led > 5:
            print("[LEDGAME] BUG - NO SUCH LED " + str(led))
            return
        if len(self.leddata) != 24:
            self.leddata = bytearray([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
        print("[LEDGAME] Led " + str(led) + " to RGBW %d/%d/%d/%d" % (r, g, b, w))
        # Detect changes
        oldleddata = self.leddata
        # GRBW
        self.leddata[led * 4 + 0] = g
        self.leddata[led * 4 + 1] = r
        self.leddata[led * 4 + 2] = b
        self.leddata[led * 4 + 3] = w
        if self.leddata != oldleddata:
            self.ledschanged = True

    def update_leds(self):
        badge.leds_send_data(bytes(self.leddata), len(self.leddata))
        self.ledschanged = False
    
    def checkLedHit(self):
        # Check button map and if led was active
        if self.button_pressed == None:
            return
        print("[LEDGAME] checkLedHit() called, button pressed: " + str(self.button_pressed))
        if self.lives <= 0:
            print("[LEDGAME] Lives 0 -- no score check.")
            return
        if self.button_pressed not in self.ledsmap:
            print("[LEDGAME] Button not mapped!")
            self.button_pressed == None
            return
        lednumber = self.ledsmap[self.button_pressed]
        if self.ledsactive[lednumber] > 0:
            points = int(utime.ticks_diff(utime.ticks_ms(), self.ledsactive[lednumber]) / float(self.ledtime) * 10.0)
            print("[LEDGAME] SCORE! Button " + self.button_pressed + " mapped to led " + str(lednumber) + " which was active for " + str(points) + " points!")
            self.score = self.score + points
            self.ledsactive[lednumber] = 0
            self.ledsscore[lednumber] = utime.ticks_ms()
            if self.buzzer:
                badge.vibrator_activate(3)
            self.add_led()
            # TODO: Bonus system / combo? Light more leds?
        else:
            print("[LEDGAME] MISS! Button " + self.button_pressed + " mapped to led " + str(lednumber) + " which was NOT active! Lost 1 point! Active was: " + str(self.ledsactive))
            if self.buzzer:
                badge.vibrator_activate(1)
            self.ledsfailed[lednumber] = utime.ticks_ms()
            self.score = self.score - 1
        # Detect last button press
        self.lastbuttontime = utime.ticks_ms()
        self.button_pressed = None
    
    def game_start(self):
        print("[LEDGAME] Game start.")
        # Bookkeeping
        self.score = 0
        self.lastbuttontime = 0
        self.lives = 3
        self.level = 1
        self.ledtime = 2000
        self.lastadded = None
        self.ledsactive = [0] * 6
        self.ledsscore  = [0] * 6
        self.ledsfailed = [0] * 6
        self.ledsmap = {
            "LEFT"  : 5,
            "UP"    : 5,
            "RIGHT" : 4,
            "DOWN"  : 4,
            "SELECT": 3,
            "START" : 2,
            "B"     : 1,
            "A"     : 0
        }
        self.maxbright = 255 - self.nightmode
    
        # Draw button connections
        ugfx.clear(ugfx.BLACK)
        self.draw_connections()
        ugfx.flush()
        self.add_led()
    
        # Main loop
        while self.lives > 0:
            if not self.wifi:
                try:
                    self.wifi = wifi.sta_if.isconnected()
                except Exception as e:
                   pass
                if self.wifi:
                    print("[LEDGAME] Wifi connected.")
            # Update all LEDs, first the active ones
            self.checkLedHit()
            # Prevent WDT from killing the badge, hopefully?
            ugfx.clear(ugfx.BLACK)
            self.draw_connections()
            ugfx.string_box(0,0,280,50,"Score: " + str(self.score), "Roboto_Regular12",ugfx.WHITE,ugfx.justifyRight)
            ugfx.string_box(0,90,280,50,"Lives left: " + str(self.lives), "Roboto_Regular12",ugfx.WHITE,ugfx.justifyRight)
            ugfx.string_box(0,90,280,50,"Level: " + str(self.level), "Roboto_Regular12",ugfx.WHITE,ugfx.justifyLeft)
            ugfx.flush()
            newcount = 0
            # print("[LEDGAME] Free memory: " + str(gc.mem_free()))
            for led in range(0,6):
                if self.ledsactive[led] > 0:
                    # Fade it based on the current time, if it's 0 the user takes a hit.
                    timeleft = self.ledtime - utime.ticks_diff(utime.ticks_ms(), self.ledsactive[led])
                    if timeleft > 0:
                        # Scale new RGB value based on time left.
                        val = int(float(timeleft / self.ledtime) * self.maxbright)
                        print("[LEDGAME] Active Led " + str(led) + " timer has timeleft[" + str(timeleft) + "] -> val(" + str(val) +")")
                        # print("[LEDGAME] Free memory: " + str(gc.mem_free()))
                        self.set_led(led, val, val, val, val)
                    else:
                        self.lives = self.lives - 1
                        print("[LEDGAME] Life lost! Too late, active led expired. Lifes left: " + str(self.lives))
                        # print("[LEDGAME] Free memory: " + str(gc.mem_free()))
                        self.ledsactive[led] = 0
                        if self.lives > 0:
                            newcount = newcount + 1
                            self.set_led(led, 0, 0, 0, 0)
                if self.ledsfailed[led] > 0:
                    timeleft = self.ledtime - utime.ticks_diff(utime.ticks_ms(), self.ledsfailed[led])
                    if timeleft > 0:
                        # Scale new RGB value based on time left.
                        val = int(float(timeleft / self.ledtime) * self.maxbright)
                        print("[LEDGAME] Failed Led " + str(led) + " timer has timeleft[" + str(timeleft) + "] -> val(" + str(val) +")")
                        # print("[LEDGAME] Free memory: " + str(gc.mem_free()))
                        self.set_led(led, val, 0, 0, 0)
                    else:
                        self.set_led(led, 0, 0, 0, 0)
                if self.ledsscore[led] > 0:
                    timeleft = self.ledtime - utime.ticks_diff(utime.ticks_ms(), self.ledsscore[led])
                    if timeleft > 0:
                        # Scale new RGB value based on time left.
                        val = int(float(timeleft / self.ledtime) * self.maxbright)
                        print("[LEDGAME] Score Led " + str(led) + " timer has timeleft[" + str(timeleft) + "] -> val(" + str(val) +")")
                        # print("[LEDGAME] Free memory: " + str(gc.mem_free()))
                        # do some color cycling, why not.
                        rgb = random.randint(0,3)
                        if rgb == 0:
                            self.set_led(led, 0, val, 0, 0)
                        elif rgb == 1:
                            self.set_led(led, 0, val, 0, val)
                        elif rgb == 2:
                            self.set_led(led, 0, val, val, 0)
                        elif rgb == 3:
                            self.set_led(led, val, val, val, 0)
                    else:
                        self.set_led(led, 0, 0, 0, 0)

            # Check score for level increase
            if (self.score > (self.level*100)):
                 self.level = self.level + 1
                 self.ledtime = int(self.ledtime / 1.5)
                 if self.ledtime <= 0:
                     self.ledtime = 1
                 print("[LEDGAME] LEVEL UP! Level is now: " + str(self.level) + " with " + str(self.ledtime) + " time per led.")
                 if self.level > 3:
                     print("[LEDGAME] Now you are fscked, extra LED for this level ;)")
                     self.add_led()

            # Light random new LEDs for newcount, but not an already active one
            for num in range(0, newcount):
                self.add_led()

            # Finally update leds.
            self.update_leds()
        self.game_over()
    
    def draw_connections(self):
        # We'll assume 296 for now. 296 / 6 =~ 49
        ugfx.line(03, 0, 03, ugfx.height(), ugfx.WHITE)
        ugfx.line(50, 0, 50, ugfx.height(), ugfx.WHITE)
        ugfx.line(111, 0, 115, ugfx.height(), ugfx.WHITE)
        ugfx.line(163, 0, 170, ugfx.height(), ugfx.WHITE)
        ugfx.line(219, 0, 229, ugfx.height(), ugfx.WHITE)
        ugfx.line(280, 0, 296, ugfx.height(), ugfx.WHITE)

    def add_led(self):
        led = random.randint(0,5)
        while self.ledsactive[led] > 0 or self.lastadded == led:
            led = random.randint(0,5)
        self.ledsactive[led] = utime.ticks_ms()
        self.lastadded = led
        self.ledsscore[led] = 0
        self.ledsfailed[led] = 0
        self.set_led(led, self.maxbright, self.maxbright, self.maxbright, self.maxbright)

    def game_over(self):
        print("[LEDGAME] main menu.")
        self.button_pressed = None
        ugfx.clear(ugfx.BLACK)
        ugfx.string_box(0,0,300,20,"GAME OVER","Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        ugfx.string_box(0,20,280,20,"Start : instructions","Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        ugfx.string_box(0,30,280,45,"Select: Quit game ","Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        ugfx.string_box(0,40,280,50,"A/B   : Start new game","Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        if self.score <= 0:
            ugfx.string_box(0,70,280,50,"You scored " + str(self.score) + " points... is your badge malfunctioning?", "Roboto_Regular12",ugfx.WHITE,ugfx.justifyLeft)
        elif self.score > 100:
            ugfx.string_box(0,70,280,50,"You scored a whopping " + str(self.score) + " points and reached level " + str(self.level) + ". Nice!", "Roboto_Regular18",ugfx.WHITE,ugfx.justifyLeft)
        else:
            ugfx.string_box(0,70,280,50,"You scored only " + str(self.score) + " points.", "Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        ugfx.flush()
        self.leds_off()
        self.leds_on(self.maxbright)
        if self.buzzer:
            badge.vibrator_activate(7)
        utime.sleep(0.1)
        self.leds_off()
        if self.buzzer:
            badge.vibrator_activate(3)
        utime.sleep(0.1)
        self.leds_on(self.maxbright / 2)
        if self.buzzer:
            badge.vibrator_activate(7)
        utime.sleep(0.1)
        self.leds_off()
        if self.buzzer:
            badge.vibrator_activate(0)
        ugfx.flush()
    
    def draw_menu(self):
        ugfx.clear(ugfx.BLACK)
        ugfx.string_box(0,0,300,20,"Menu","Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        ugfx.string_box(0,20,280,20,"Start : instructions","Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        ugfx.string_box(0,30,280,45,"Select: Quit game ","Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        ugfx.string_box(0,50,280,45,"UP    : Night mode (" + ("br. redux " + str(self.nightmode) if self.nightmode else "disabled") + ")","Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        ugfx.string_box(0,65,280,50,"B or A: Start game","Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        ugfx.string_box(0,80,280,50,"LEFT  : Toggle buzzer (" + ("enabled" if self.buzzer else "disabled") + ")","Roboto_Regular12",ugfx.WHITE,ugfx.justifyCenter)
        ugfx.flush()

    def program_main_menu(self):
        print("[LEDGAME] main menu.")
        self.button_pressed = None
        self.nightmode = 0
        self.maxbright = 255
        self.buzzer    = 1 # enable buzzer
        now = utime.time()
        tm = utime.localtime(now)
        if tm[3] > 20 or tm[3] < 5:
            print("[LEDGAME] Defaulted to night mode due to the time....")
            self.nightmode = 192

        ugfx.input_attach(ugfx.BTN_SELECT, self.action_select)
        ugfx.input_attach(ugfx.BTN_START, self.action_start)
        ugfx.input_attach(ugfx.BTN_B, self.action_b)
        ugfx.input_attach(ugfx.BTN_A, self.action_a)
        ugfx.input_attach(ugfx.JOY_UP, self.action_up)
        ugfx.input_attach(ugfx.JOY_DOWN, self.action_down)
        ugfx.input_attach(ugfx.JOY_LEFT, self.action_left)
        ugfx.input_attach(ugfx.JOY_RIGHT, self.action_right)

        # Wait for WiFi connection
        try:
            self.wifi = wifi.sta_if.isconnected()
        except Exception as e:
            print("[LEDGAME] Still emulator....")
            self.wifi = 0

        self.draw_menu()
        while True:
            if not self.wifi:
                try:
                    self.wifi = wifi.sta_if.isconnected()
                except Exception as e:
                   pass
                if self.wifi:
                    print("[LEDGAME] Wifi connected.")
            if self.button_pressed == None:
                utime.sleep_ms(10)
                pass
            elif self.button_pressed == "SELECT":
                print("[LEDGAME] Quit request. Bye")
                import appglue
                appglue.home()
                return
            elif self.button_pressed == "LEFT":
                self.button_pressed = None
                self.buzzer = 0 if self.buzzer else 1
                self.draw_menu()
            elif self.button_pressed == "UP":
                self.button_pressed = None
                self.nightmode = self.nightmode + 16
                if self.nightmode > 255:
                    self.nightmode = 0
                self.draw_menu()
            elif self.button_pressed == "DOWN":
                self.button_pressed = None
                self.nightmode = self.nightmode - 16
                if self.nightmode < 0:
                    self.nightmode = 240
                self.draw_menu()
            elif self.button_pressed == "START":
                self.button_pressed = None
                self.instructions()
            elif self.button_pressed == "A" or self.button_pressed == "B":
                self.button_pressed = None
                print("[LEDGAME] Start game!")
                self.game_start()

    def run(self):
        self.program_main_menu()

# Start game
LedGame().run()
