# cpb_chase_lights.py

# Demonstration of a programming structure which nests continuous background
# tasks within a scripted sequence.  This method is useful when the main action
# is expressed well as a sequence of events over time but there are ongoing
# processes which need to execute all the time.  Examples of these tasks include
# processing sensor data, animating actuator outputs, or managing communication.

# This demo is specific to the Adafruit Circuit Playground Bluefruit board.
# This program uses only onboard hardware: NeoPixel LEDs.

#================================================================
# Import the standard Python time functions.
import time, math

# Import the board-specific input/output library.
from adafruit_circuitplayground import cp

#================================================================
# The setup() function is a convention borrowed from the Arduino.  All global
# initialization and hardware setup should be placed in setup(), which will be
# called once after the system starts.

def setup():
    pass

# The script() function will be called once to run the main program logic.  It
# should never call the time.sleep() function, but instead use the chase.delay()
# function (defined below) that can wait while continuing background tasks.

def script():
    for i in range(10):
        print("Starting iteration", i)
        chase.forward()
        chase.delay(2.0)
        chase.backward()
        chase.delay(2.0)

#================================================================
# Following are the background tasks, defined within an object to hold the state.

class Chase(object):
    def __init__(self):
        self.chase_phase     = 0
        self.chase_direction = 0
        self.chase_timer     = 0
        self.chase_interval  = 0.1
        self.last_time       = time.monotonic()

        # Configure the NeoPixel LED array for bulk update using show().
        cp.pixels.auto_write = False

        # turn down the brightness, otherwise it is somewhat blinding
        cp.pixels.brightness = 0.2

    def _poll(self):
        # Measure elapsed time and wait until the update timer has elapsed.
        now = time.monotonic()
        interval = now - self.last_time
        self.last_time = now

        self.chase_timer -= interval
        if self.chase_timer < 0.0:
            self.chase_timer += self.chase_interval
            self.chase_phase += self.chase_direction

            for led in range(10):
                brightness = max(0, 255 * math.sin(0.3 * self.chase_phase + led * math.pi * 0.4) - 100)
                cp.pixels[led] = (brightness, brightness, brightness)

            # Send new data to the physical LED chain.
            cp.pixels.show()

    #================================================================
    # Following are the action primitives which can be called from the script.
    def delay(self, seconds):
        start_time = time.monotonic()
        while time.monotonic() < start_time + seconds:
            self._poll()

    def forward(self):
        self.chase_direction = 1

    def backward(self):
        self.chase_direction = -1

#================================================================
# Script to start the system.

# Create the singleton background controller.
chase = Chase()

# Run the main script.
setup()
script()
print("Script is done.")
