/* 
    Author: Ryan Klassing 
    Date: 11/29/22
    Version: */ #define BLYNK_FIRMWARE_VERSION "0.0.42" /*
    Description:
        This is intended to run a small ESP32 based PCB that looks
        like a ghost, for simple/fun Christmas decotrations.  The
        ESP32 controls 2x "eye" LEDs (for the ghost's eyes) as well
        as an additional 8x "ambiance" LEDs (for glowing effects) around
        the ghost.  The board also contains USB Type C (for programming / debugging),
        and can be operated from a battery range of ~1.6V (startup, then can operate down to ~1.0V)
        up to ~5.2V.  This is heavily based on the "Halloween Ghost" project, but will expand the
        lighting control offboard as well (the lighting will be an array driven from the end of the
        on-board "ambiance" lighting, and will be manually soldered on to the external lighting cable).

    License: see "LICENSE" file

    12/21/2022 - Cochise Push
    Description:
        Added light patterns "red_and_green_curtain_lights_to_middle" and "stack_lights_in_the_middle"
        in "Cochise.h" file.  The main.cpp code was modified to call out these light patterns in the
        christmas_patterns function list and to add #include of the Cochise.h file just below "[END]
        HW Configuration Setup" section.  This placement ensures that #defines used by the functions
        have been completed before Cochise.h inclusion. Lastly, modified LED_PER_START_POS from 2 to 10
        and added LED_PER_MID_POS 60 which marks the mid-point of the LED string offset by the start
        position.

*/

/* ------------ [START] Custom Flag to support easier simulations -------------- */
    /*
        By default, this is commented out (i.e. - not defined) when running on physical HW.
        When the user wants to run a simulation, simply uncomment the line below to optimize
        several parts of the code that wouldn't be necessary (BT/WiFi/OTA/etc..)
    */
    #define ONLINE_SIMULATION
/* ------------   [End] Custom Flag to support easier simulations -------------- */

/* ------------ [START] Early definitions for Blynk -------------- */
    #define BLYNK_TEMPLATE_ID "TMPLmXb3Shr8"
    #define BLYNK_DEVICE_NAME "ChristmasGhost"
    #define BLYNK_PRINT Serial
/* ------------   [End] Early definitions for Blynk -------------- */

/*
    lightTools.h - built from 'lib_template.h'
    This library is intended to hold common tools for creating
    various light sequences for the AOEM lab
*/

#ifndef lightTools_h
    #define lightTools_h

    /* Include standard libraries needed */
    #include <Arduino.h>
    #include <FastLED.h>

    /* Create an ARRAY_SIZE calculator for the light users if desired */
    #define LIGHT_ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))

    /* Class container */
    class lightTools
    {   
        public: 
            /* Constructor of the class */
            /* Not currently needed to initialize anything - let default constructor be called
                lightTools();
            */

            /* public simple function to draw a pattern, defined by an incoming array to be repeated over the full LED string */
                /* Note - pattern_starting_index can be incremented to simmulate a "walking pattern" if desired, but needs to be incremented outside of this function */
                /* Note - if "fade_amount" is provided, each light will blend towards the next index of the pattern by 'fade_amount' (set to 0 for no blending to occur and transition to be instant) */
            void fill_light_pattern(CRGB *led_arr, uint16_t led_qty, uint32_t *light_pattern, uint16_t pattern_qty, uint8_t pattern_starting_index=0, uint8_t fade_amount=0);

            /* public function to fade from one color to a different color by a specified amount */
            CRGB fadeToColor(CRGB fromCRGB, CRGB toCRGB, uint8_t amount);
        private:

            /* class-bound function to blend between two unsignedINTs by a specified amount */
            uint8_t blendU8(uint8_t fromU8, uint8_t toU8, uint8_t amount);

    };
#endif

/*
    lightTools.h - built from 'lib_template.h'
    This library is intended to hold common tools for creating
    various light sequences for the AOEM lab
*/


/* Included header file, unless this is the online simulation */
#ifndef ONLINE_SIMULATION
    #include <lightTools.h>
#endif

/* Constructor of the class */
    /* Not currently needed to initialize anything - let default constructor be called
        lightTools::lightTools() {

        }
    */

/* public simple function to draw a pattern, defined by an incoming array to be repeated over the full LED string */
    /* Note - pattern_starting_index can be incremented to simmulate a "walking pattern" if desired, but needs to be incremented outside of this function */
    /* Note - if "fade_amount" is provided, each light will blend towards the next index of the pattern by 'fade_amount' (set to 0 for no blending to occur and transition to be instant) */
void lightTools::fill_light_pattern(CRGB *led_arr, uint16_t led_qty, uint32_t *light_pattern, uint16_t pattern_qty, uint8_t pattern_starting_index/*=0*/, uint8_t fade_amount/*=0*/) {
    /* Do a quick check to make sure we weren't passed a starting index > pattern array.  If so - just start at the end of the pattern array */
    uint16_t pattern_index = (pattern_starting_index < pattern_qty) ? pattern_starting_index : pattern_qty - 1;

    /* Fill the passed led array with the pattern provided, with blending if needed */
    for (uint16_t led_index = 0; led_index < led_qty; led_index++) {

        /* If blending is desired, smoothly fade towards the light pattern color.  Otherwise, immediately load the light pattern color directly */
        if (fade_amount > 0) {led_arr[led_index] = fadeToColor(led_arr[led_index], light_pattern[pattern_index], fade_amount);}
        else {led_arr[led_index] = light_pattern[pattern_index];}

        /* Move to the next pattern color */
        pattern_index = (pattern_index + 1) % pattern_qty;
    }
}

/* public function to fade from one color to a different color by a specified amount */
CRGB lightTools::fadeToColor(CRGB fromCRGB, CRGB toCRGB, uint8_t amount) {
    return CRGB(
        blendU8(fromCRGB.r, toCRGB.r, amount),
        blendU8(fromCRGB.g, toCRGB.g, amount),
        blendU8(fromCRGB.b, toCRGB.b, amount)
    );
}

/* class-bound function to blend between two unsignedINTs by a specified amount */
uint8_t lightTools::blendU8(uint8_t fromU8, uint8_t toU8, uint8_t amount) {
    /* don't do anything if they're already equivalent */
    if (fromU8 == toU8) {return fromU8;}

    /* Calculate how far apart the uint8_t values are, and scale it by the 'amount' passed to blend by */
    uint8_t dU8 = scale8_video(abs(toU8 - fromU8), amount);

    /* Increment or Decrement the fromU8 by the scaled delta */
    return (fromU8 > toU8) ? fromU8 - dU8 : fromU8 + dU8;
}

/*
    klassyLights.h - built from 'lib_template.h' on 12/27/2022
    This library is intended to act as a safegaurd for
    writing various LED lighting functions by various team members
    and to prevent naming conflicts during the distributed development.

    The intention is that each 'user' will have their own light library
    to minimize changes made to the common 'main' file running the lighting loops.

    To allow the 'main.cpp' file to call function prototypes from an array, the following
    standards must be applied to each light function desired to be called externally:
        1) The function must defined within the class below (to prevent naming conflicts with other users)
        2) The public class-function must be public
        3) The public class-function must be of the type 'void' (i.e. - it must not return any value)
        4) The public class-function must not take any parameters as an input
        5) The public class-function must be static

    Other supporting functions (not needed to be called by 'main.cpp') may not have the requirements above.

    Note: there might be some uses of a #ifndef ONLINE_SIMULATION  --> these are to support a custom
    script that will concatenate all libraries directly into the main.cpp, which allows the use of
    online simulators to test code executions without the need of physical hardware
*/

#ifndef klassyLights_h
    #define klassyLights_h

    /* Include standard libraries needed */
    #include <Arduino.h>
    #include <FastLED.h>

    /* Include the standard light tools, unless this is the online simulation */
    #ifndef ONLINE_SIMULATION
        #include <lightTools.h>
    #endif

    /* Class container */
    class klassyLights
    {   
        public: 
            /* Constructor of the class - pass the LED array pointer + qty of used LEDs + lightTools class member */
            klassyLights(CRGB *led_arr, uint16_t led_qty, lightTools *lightTools);
            
            /* Function to fill the entire LED array with a rainbow pattern */
            static void rainbow_pattern();

            /* Rotates through the colors of the candy cane LED by LED */
            static void rotating_candy_cane();

            /* Rotates through the colors of the christmas spirit LED by LED */
            static void rotating_christmas_spirit();

            /* Bouncing lights from end-to-end, with the colors of a candy cane */
            static void juggle_candy_cane();

            /* Bouncing lights from end-to-end, with the colors of a christmas spirit */
            static void juggle_christmas_spirit();

            /* Red/white pattern that fades between the two colors */
            static void fading_candy_cane();

            /* Red/white pattern that fades between the christmas spirit colors */
            static void fading_christmas_spirit();

            /* Light starts at both ends of the string, and travels towards each other before making a big flash */
            static void jacobs_ladder();

        private:
            /* Draw a simple red/white pattern to resemble a candy cane */
            /* Return the size of the pattern to a calling function, to allow upstream functions to cycle through if desired */
            static uint8_t candy_cane(uint8_t starting_index=0, uint8_t fade_amount=0);

            /* Draw a simple red/green/white pattern to resemble christmas spirit */
            /* Return the size of the pattern to a calling function, to allow upstream functions to cycle through if desired */
            static uint8_t christmas_spirit(uint8_t starting_index=0, uint8_t fade_amount=0);

            /* Reset the indeces of the class-bound travelling lights */
            static void reset_travelling_lights();

            /* Draw light that travels from each end of the string, towards the center */
            /*  returns false while the light is travelling */
            /*  returns true once the lights have met in the middle */
            static uint8_t travel_light_to_mid(CRGB lead_light_color=CRGB::White, uint16_t trail_length=65535, CRGB trail_color=CRGB::Black);

            /* Class bound lightTools pointer */
            static lightTools *_lightTools;

            /* Class bound led array pointer */
            static CRGB *_led_arr;

            /* Class bound led quantity */
            static uint16_t _led_qty;

            /* Class bound (calculated) led midpoint */
            static uint16_t _led_midpoint;

            /* Class bound rotating light index */
            static uint16_t _rotating_light_index;

            /* Class bound traveling light indeces */
            static uint16_t _travelling_light_start_pos;
            static uint16_t _travelling_light_end_pos;
    };
#endif

/*
    klassyLights.h - built from 'lib_template.h' on 12/27/2022
    This library is intended to act as a safegaurd for
    writing various LED lighting functions by various team members
    and to prevent naming conflicts during the distributed development.

    The intention is that each 'user' will have their own light library
    to minimize changes made to the common 'main' file running the lighting loops.

    To allow the 'main.cpp' file to call function prototypes from an array, the following
    standards must be applied to each light function desired to be called externally:
        1) The function must defined within the class below (to prevent naming conflicts with other users)
        2) The public class-function must be public
        3) The public class-function must be of the type 'void' (i.e. - it must not return any value)
        4) The public class-function must not take any parameters as an input
        5) The public class-function must be static

    Other supporting functions (not needed to be called by 'main.cpp') may not have the requirements above.

    Note: there might be some uses of a #ifndef ONLINE_SIMULATION  --> these are to support a custom
    script that will concatenate all libraries directly into the main.cpp, which allows the use of
    online simulators to test code executions without the need of physical hardware
*/

/* Included header file, unless this is the online simulation */
#ifndef ONLINE_SIMULATION
  #include <klassyLights.h>
#endif

/* Initialize static class variables defined in the header file */
lightTools *klassyLights::_lightTools = NULL;
CRGB *klassyLights::_led_arr = NULL;
uint16_t klassyLights::_led_qty = 0;
uint16_t klassyLights::_led_midpoint = 0;
uint16_t klassyLights::_rotating_light_index = 0;
uint16_t klassyLights::_travelling_light_start_pos = 0;
uint16_t klassyLights::_travelling_light_end_pos = 0;

/* Constructor of the class - pass the LED array pointer + qty of used LEDs + lightTools class member */
klassyLights::klassyLights(CRGB *led_arr, uint16_t led_qty, lightTools *lightTools) {
    /* Point to the provided lightTools member */
    _lightTools = lightTools;
    
    /* update the class-bound pointers / variables */
    _led_arr = led_arr;
    _led_qty = led_qty;

    /* calculate the midpoint */
    _led_midpoint = _led_qty / 2;

    /* initialize the travelling lights */
    reset_travelling_lights();
}

/* Function to fill the entire LED array with a rainbow pattern */
void klassyLights::rainbow_pattern() {
    /* Initialize the persistent hue */
    static uint8_t rainbow_hue = 0;

    /* Increment the hue - since it's a uint8_t, and FastLED hue is 8b, it will auto-wrap */
    EVERY_N_MILLISECONDS(20) {rainbow_hue++;}

    /* Run the FastLED rainbow function */
    fill_rainbow(_led_arr, _led_qty, rainbow_hue,  7);
}

/* Draw a simple red/white pattern to resemble a candy cane */
/* Return the size of the pattern to a calling function, to allow upstream functions to cycle through if desired */
uint8_t klassyLights::candy_cane(uint8_t starting_index/*=0*/, uint8_t fade_amount/*=0*/) {
  /* set entire strip to RedRedWhiteWhite pattern */
  uint32_t light_pattern[] = {CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, CRGB::Red, CRGB::White, CRGB::White, CRGB::White, CRGB::White, CRGB::White, CRGB::White};
  uint8_t qty_of_pattern = LIGHT_ARRAY_SIZE(light_pattern);

  /* draw the pattern - skipping over the "eye" positions */
  _lightTools->fill_light_pattern(_led_arr, _led_qty, light_pattern, qty_of_pattern, starting_index, fade_amount);

  return qty_of_pattern;
}

/* Draw a simple red/green/white pattern to resemble christmas spirit */
/* Return the size of the pattern to a calling function, to allow upstream functions to cycle through if desired */
uint8_t klassyLights::christmas_spirit(uint8_t starting_index/*=0*/, uint8_t fade_amount/*=0*/) {
  /* set entire strip to RedWhiteGreen pattern */
  uint32_t light_pattern[] = {CRGB::Red, CRGB::White, CRGB::Green};
  uint8_t qty_of_pattern = LIGHT_ARRAY_SIZE(light_pattern);

  /* draw the pattern - skipping over the "eye" positions */
  _lightTools->fill_light_pattern(_led_arr, _led_qty, light_pattern, qty_of_pattern, starting_index, fade_amount);

  return qty_of_pattern;
}

/* Rotates through the colors of the candy cane LED by LED */
void klassyLights::rotating_candy_cane() {
  EVERY_N_MILLISECONDS( 500 ) { _rotating_light_index = (_rotating_light_index + 1) % candy_cane(_rotating_light_index);}
}

/* Rotates through the colors of the christmas spirit LED by LED */
void klassyLights::rotating_christmas_spirit() {
  EVERY_N_MILLISECONDS( 500 ) { _rotating_light_index = (_rotating_light_index + 1) % christmas_spirit(_rotating_light_index);}
}

/* Bouncing lights from end-to-end, with the colors of a candy cane */
void klassyLights::juggle_candy_cane() {
  /* Fade all of the lights by 20 */
  fadeToBlackBy(_led_arr, _led_qty, 20);

  /* First light = Red */
  _led_arr[beatsin16(7, 0, _led_qty - 1)] |= CHSV(0, 255, 255);

  /* Second light = Pink */
  _led_arr[beatsin16(14, 0, _led_qty - 1)] |= CHSV(225, 201, 255);

  /* Third light = Offwhite */
  _led_arr[beatsin16(21, 0, _led_qty - 1)] |= 0xFFFCE6;  //offwhite

}

/* Bouncing lights from end-to-end, with the colors of a christmas spirit */
void klassyLights::juggle_christmas_spirit() {
  /* Fade all of the lights by 20 */
  fadeToBlackBy(_led_arr, _led_qty, 20);

  /* First light = Red */
  _led_arr[beatsin16(7, 0, _led_qty - 1)] |= CHSV(0, 255, 255);

  /* Second light = Offwhite */
  _led_arr[beatsin16(14, 0, _led_qty - 1)] |= 0xFFFCE6;  //offwhite

  /* Third light = Green */
  _led_arr[beatsin16(21, 0, _led_qty - 1)] |= CHSV(80, 255, 255);

}

/* Red/white pattern that fades between the two colors */
void klassyLights::fading_candy_cane() {
  EVERY_N_MILLISECONDS( 200 ) { _rotating_light_index = (_rotating_light_index + 1) % candy_cane(_rotating_light_index, 1);}
  EVERY_N_MILLISECONDS( 1 ) {candy_cane(_rotating_light_index, 3);}
}

/* Red/white pattern that fades between the christmas spirit colors */
void klassyLights::fading_christmas_spirit() {
  EVERY_N_MILLISECONDS( 200 ) { _rotating_light_index = (_rotating_light_index + 1) % christmas_spirit(_rotating_light_index, 1);}
  EVERY_N_MILLISECONDS( 1 ) {christmas_spirit(_rotating_light_index, 3);}
}

/* Light starts at both ends of the string, and travels towards each other before making a big flash */
void klassyLights::jacobs_ladder() {
  /* initialize persistent variables */
  static uint8_t explosion_time = false;

  if (!explosion_time) {explosion_time = travel_light_to_mid(CRGB::Aquamarine, 5);}
  else {
      EVERY_N_MILLISECONDS( 1 ) {fadeToBlackBy(_led_arr, _led_qty, 10);}
      EVERY_N_SECONDS( 2 ) { 
        explosion_time = !explosion_time;
        reset_travelling_lights();
      }
  }
}

/* Reset the indeces of the class-bound travelling lights */
void klassyLights::reset_travelling_lights() {
  _travelling_light_start_pos = 0;
  _travelling_light_end_pos = _led_qty - 1;
}

/* Draw light that travels from each end of the string, towards the center */
/*  returns false while the light is travelling */
/*  returns true once the lights have met in the middle */
/* Note: if 'reset_travelling_lights' isn't called, the lights will cross the middle and keep going until the opposite end */
uint8_t klassyLights::travel_light_to_mid(CRGB lead_light_color/*=CRGB::White*/, uint16_t trail_length/*=65535*/, CRGB trail_color/*=CRGB::Black*/) {
  #define TRAVEL_LIGHT_TO_MID_TIMER_MS 25       //Move the lights every 'this' ms

  /* If the trail length exists, but is set to black (default) --> match the lead light color */
  if (trail_length && !trail_color) {trail_color = lead_light_color;}

  /* Based on the travel speed, set the light colors/positions */
  EVERY_N_MILLISECONDS( TRAVEL_LIGHT_TO_MID_TIMER_MS ) {
    /* Set the lead lights' color */
    _led_arr[_travelling_light_start_pos] += lead_light_color;
    _led_arr[_travelling_light_end_pos] += lead_light_color;

    /* Set a floating fade_amount, in case the trail length is long enough that each bulb wouldn't be dimmed due to integer math */
    float fade_amount = 0.0;

    /* Skip the trail if we're on the first lighting position */
    if (_travelling_light_start_pos) {
      /* Set the trailing lights' colors and fade them as necessary */
      for (int32_t trail = _travelling_light_start_pos - 1; trail >= 0; trail--) {
        _led_arr[trail] = trail_color;
        _led_arr[(_led_qty - 1) - trail] = trail_color;

        /* if trail_length is set to max value (2^16 - 1), don't do any fading */
        if (trail_length != 65535) {
          /* fade each light an even amount, based on the trail length (each light will incrementally be dimmer) */
          /*   example: if trail length is 1, we want to fade the first trail by 50%, then next light by 100% and so on */
          fade_amount = constrain(fade_amount + 255.0 / (trail_length + 1), 0.0, 255.0);

          /* Blend the light towards 'off' (black), but first cast the floating fade amount to an unsigned int */
          _led_arr[trail] = _lightTools->fadeToColor(_led_arr[trail], CRGB::Black, (uint8_t) fade_amount);
          _led_arr[(_led_qty - 1) - trail] = _lightTools->fadeToColor(_led_arr[(_led_qty - 1) - trail], CRGB::Black, (uint8_t) fade_amount);
        }
      }
    }

    /* Travel the light towards the opposite ends */
    _travelling_light_start_pos = (_travelling_light_start_pos + 1) % _led_qty;
    _travelling_light_end_pos = (_led_qty - 1) - _travelling_light_start_pos;
  }

  /* 
    Fade all of the lights (based on desired trail_length) to make it a trail.
    Note: if trail_length = 0, no fading will be done
  */
  //uint8_t fade_amount = (trail_length ? (uint8_t) (6 * 255 / (TRAVEL_LIGHT_TO_MID_TIMER_MS * trail_length)) : 0);
  //EVERY_N_MILLISECONDS( 1 ) {fadeToBlackBy(_led_arr, _led_qty, fade_amount);}

  return (_travelling_light_start_pos > _travelling_light_end_pos);
}

/*
    cochise.h - built from 'lib_template.h' on 12/27/2022
    This library is intended to act as a safegaurd for
    writing various LED lighting functions by various team members
    and to prevent naming conflicts during the distributed development.

    The intention is that each 'user' will have their own light library
    to minimize changes made to the common 'main' file running the lighting loops.

    To allow the 'main.cpp' file to call function prototypes from an array, the following
    standards must be applied to each light function desired to be called externally:
        1) The function must defined within the class below (to prevent naming conflicts with other users)
        2) The public class-function must be public
        3) The public class-function must be of the type 'void' (i.e. - it must not return any value)
        4) The public class-function must not take any parameters as an input
        5) The public class-function must be static
        
    Other supporting functions (not needed to be called by 'main.cpp') may not have the requirements above.

    Note: there might be some uses of a #ifndef ONLINE_SIMULATION  --> these are to support a custom
    script that will concatenate all libraries directly into the main.cpp, which allows the use of
    online simulators to test code executions without the need of physical hardware
*/

#ifndef cochise_h
    #define cochise_h

    /* Include standard libraries needed */
    #include <Arduino.h>
    #include <FastLED.h>

    /* Include the standard light tools, unless this is the online simulation */
    #ifndef ONLINE_SIMULATION
        #include <lightTools.h>
    #endif

    /* Class container */
    class cochise
    {   
        public: 
            /* Constructor of the class - pass the LED array pointer + qty of used LEDs */
            cochise(CRGB *led_arr, uint16_t led_qty, lightTools *lightTools);
            
            /* ADD PUBLIC USER LIGHT FUNCTIONS HERE (to be called by 'main.cpp')*/
                static void red_and_green_curtain_lights_to_middle();
                static void stack_lights_in_the_middle();
        private:
            /* ADD class-bound VARIABLES / FUNCTIONS HERE (to be used by this class only) */

            /* Class bound lightTools pointer */
            static lightTools *_lightTools;

            /* Class bound led array pointer */
            static CRGB *_led_arr;

            /* Class bound led quantity */
            static uint16_t _led_qty;

            /* Class bound (calculated) led midpoint */
            static uint16_t _led_midpoint;
                        
    };

#endif

/*
    cochise.h - built from 'lib_template.h' on 12/27/2022
    This library is intended to act as a safegaurd for
    writing various LED lighting functions by various team members
    and to prevent naming conflicts during the distributed development.

    The intention is that each 'user' will have their own light library
    to minimize changes made to the common 'main' file running the lighting loops.

    To allow the 'main.cpp' file to call function prototypes from an array, the following
    standards must be applied to each light function desired to be called externally:
        1) The function must defined within the class below (to prevent naming conflicts with other users)
        2) The public class-function must be public
        3) The public class-function must be of the type 'void' (i.e. - it must not return any value)
        4) The public class-function must not take any parameters as an input
        5) The public class-function must be static

    Other supporting functions (not needed to be called by 'main.cpp') may not have the requirements above.

    Note: there might be some uses of a #ifndef ONLINE_SIMULATION  --> these are to support a custom
    script that will concatenate all libraries directly into the main.cpp, which allows the use of
    online simulators to test code executions without the need of physical hardware
*/

/* Included header file, unless this is the online simulation */
#ifndef ONLINE_SIMULATION
    #include "cochise.h"
#endif

/* Initialize static class variables defined in the header file */
lightTools *cochise::_lightTools = NULL;
CRGB *cochise::_led_arr = NULL;
uint16_t cochise::_led_qty = 0;
uint16_t cochise::_led_midpoint = 0;

/* Constructor of the class - pass the LED array pointer + qty of used LEDs */
cochise::cochise(CRGB *led_arr, uint16_t led_qty, lightTools *lightTools) {
    /* Point to the provided lightTools member */
    _lightTools = lightTools;

    /* update the class-bound pointers / variables */
    _led_arr = led_arr;
    _led_qty = led_qty;

    /* calculate the midpoint */
    _led_midpoint = _led_qty / 2;
}

/* RED and GREEN "curtains" lights close from left and right and meet in the middle of the LED string */
void cochise::red_and_green_curtain_lights_to_middle() {
    /* Define persistent variables */
    static uint16_t curtain_UpCounter = 0;                   //Begining of the led strand
    static uint16_t curtain_DownCounter = _led_qty - 1;      //Ending of the led strand
    static uint16_t curtain_LightToggle = false;

    // Every 25ms we'll update the LED pattern
    EVERY_N_MILLISECONDS( 25 ) { 
        if(curtain_UpCounter < _led_midpoint) {
            _led_arr[curtain_UpCounter++] = curtain_LightToggle ? CRGB::Green : CRGB::Red;        // As long as the LED upCounter has not reached the middle of the LED string increment and turn the next light
            _led_arr[curtain_DownCounter--] = curtain_LightToggle ? CRGB::Green : CRGB::Red;      // Same condition as above but this walks in our LED pattern from the "farside" of the LED string in toward the middle
        } else {
            curtain_UpCounter = 0;                          // After reaching the middle we now want to start the pattern over so we send the counters back to the "far ends" of the LED string
            curtain_DownCounter = _led_qty - 1;             
            curtain_LightToggle = !curtain_LightToggle;     // As we don't want to repeat the pattern with the same light color anymore (wouldn't be visible) we need to toggle our 'LightToggle' variable to 'change states'
        }
    }
}

void cochise::stack_lights_in_the_middle(){
    /* Define persistent variables */
    static uint16_t stack_UpCounter = 0;
    static uint16_t stack_DownCounter = _led_qty - 1;
    static uint16_t stack_LightStop = _led_midpoint;
    static uint16_t stack_pattern_count = 0;
    static uint32_t stack_color_pattern[] = {CRGB::Red, CRGB::White, CRGB::Green, CRGB::Gold};
    
    EVERY_N_MILLISECONDS(15){
        /* if LightStop is non-zero, stack towards the LightStop */
        if(stack_LightStop){
            if( stack_UpCounter < stack_LightStop ){
                /* If we're not at the begning of the strand, set the previous light to be off */
                if (stack_UpCounter > 0) {_led_arr[stack_UpCounter - 1] = CRGB::Black;}
                if (stack_DownCounter < (_led_qty - 1)) { _led_arr[stack_DownCounter + 1] = CRGB::Black;}

                /* Set the current light, and move the counters towards the LightStop */
                _led_arr[ stack_UpCounter++ ] = stack_color_pattern[stack_pattern_count];
                _led_arr[ stack_DownCounter-- ] = stack_color_pattern[stack_pattern_count];

            } else {
                /* Counters have reached the LightStop - move the LightStop and reset counters */
                stack_UpCounter = 0;
                stack_DownCounter = _led_qty - 1;
                stack_LightStop--;

                /* Move the color pattern, wrapping around as needed */
                stack_pattern_count = (stack_pattern_count + 1) % LIGHT_ARRAY_SIZE(stack_color_pattern);
            }
        } else {
            /* LightStop has reached the begining of the strand, reset everything*/
            fadeToBlackBy(_led_arr, _led_qty, 255);
            stack_LightStop = _led_midpoint;
            stack_UpCounter = 0;
            stack_DownCounter = _led_qty - 1;
        }
    }
}


/* ------------ [START] Include necessary libraries -------------- */
    #include <FastLED.h>        // Tested with v3.5.0 - https://github.com/FastLED/FastLED.git#3.5.0
    #include <Button2.h>        // Tested with v2.0.3 - https://github.com/LennartHennigs/Button2.git#2.0.3
    #ifndef ONLINE_SIMULATION   // Only include these when running on physical HW
        #include <esp_bt.h>         // Included in ESP32 Arduino Core - Tested with v2.0.5 - https://github.com/platformio/platform-espressif32.git#v5.2.0
        #include <BlynkEdgent.h>    // Tested with v1.1.0 - https://github.com/blynkkk/blynk-library.git#1.1.0
        #include <klassyLights.h>   // Light function library by Ryan K.
        #include <cochise.h>        // Light function library by Cochise F.
    #endif

/* -------------- [END] Include necessary libraries -------------- */

/* ------------ [START] HW Configuration Setup -------------- */
    /* Pin Configurations */
    #define LED_PWR_EN_PIN 25
    #ifndef ONLINE_SIMULATION       //pins for physical HW (ESP32)
        #define LED_DATA_PIN 26
        #define LEFT_TOUCH_PIN 15
        #define RIGHT_TOUCH_PIN 14
    #else                           //pins for online simulation (AVR)
        #define LED_DATA_PIN 5
        #define LEFT_TOUCH_PIN 12
        #define RIGHT_TOUCH_PIN 13
    #endif


    /* LED Configurations */
    #ifndef ONLINE_SIMULATION           //If running on the physical HW (ESP32)
        #define LED_TYPE WS2811
        #define LED_COLOR_ORDER RGB
        #define LED_ARR_QTY 200         //TODO --> figure out why the lights are flickering when defining the array to be the actual size of the lights (this works fine on other projects....)
        #define LED_STRAND_QTY 100      //Actual QTY of lights in the strands --> USE THIS FOR LIGHT FUNCTIONS
        #define LED_PER_START_POS 10    //Starting array position for the peripheral LEDs
        #define LED_MAX_BRIGHTNESS 255  //Maximum allowed brightness for the LEDs
    #else                               //If running on virtual arduino simulation (AVR)
        #define LED_TYPE WS2811
        #define LED_COLOR_ORDER RGB
        #define LED_ARR_QTY 100         //TODO --> figure out why the lights are flickering when defining the array to be the actual size of the lights (this works fine on other projects....)
        #define LED_STRAND_QTY 100      //Actual QTY of lights in the strands --> USE THIS FOR LIGHT FUNCTIONS
        #define LED_PER_START_POS 0     //Starting array position for the peripheral LEDs
        #define LED_MAX_BRIGHTNESS 255  //Maximum allowed brightness for the LEDs
    #endif
    CRGB LED_ARR[LED_ARR_QTY];      //global LED array

/* -------------- [END] HW Configuration Setup -------------- */

/* ------------ [START] Debug compile options -------------- */
    #define LOG_DEBUG true          //true = logging printed to terminal, false = no logging
/* -------------- [END] Debug compile options -------------- */

/* ------------ [START] Serial Terminal Configuration -------------- */
    #define SERIAL_BAUD 115200
/* -------------- [END] Serial Terminal Configuration -------------- */

/* ---------- [START] Button Configuration -------------- */
    Button2 left_hand_btn;          //Button for pressing the ghost's left hand
    Button2 right_hand_btn;         //Button for pressing the ghost's right hand
/* ------------ [End] Button Configuration -------------- */

/* ------------ [START] Define Function Prototypes -------------- */
    /* General Protoypes */
    void pin_config();                          //Function to initialize HW config
    void print_welcome_message();               //Function to print a welcome message with the SW version
    void next_pattern();                        //Cycle through the pattern list periodically, wrapping around once reaching the end of the array

    /* Power Management Prototypes */
    void disableWiFi();                                             //Function to disable WiFi for power savings
    void disableBT();                                               //Function to disable BT for power savings

    /* LED Management Prototypes */
    void led_handler();                         //Handler function to execute various LED management tasks

    /* Input Button Management Prototypes */
    void button_handler();                      //Handler function to execute various input button management tasks
    void init_buttons();                         //Function to initialize the button configurations
    void button_left_click(Button2& btn);     //Callback function to be executed when left is pressed/held
    void button_right_click(Button2& btn);      //Callback function to be executed when right is clicked

    /* Debug / Printing Prototypes */
    void print(String message);                 // Function to print a message */
    void println(String message);               // Function to print a message, with CRLF
    void time_print(String message);            //Function to pre-pend a message with the current CPU running timestamp
    void time_println(String message);          //Function to pre-pend a message with the current CPU running timestamp, with CRLF
    void log(String message);                   //Function to log debug messages during development
    void logln(String message);                 //Function to log debug messages during development, with CRLF
    void time_log(String message);              //Function to log debug messages during development, prepended with a timestamp
    void time_logln(String message);            //Function to log debug messages during development, prepended with a timestamp, with CRLF

/* -------------- [END] Define Function Prototypes -------------- */

 /* ----------- [START] Construct all User Light Libraries ------------- */
    lightTools lightTools;  //Common lightTools member, to be used by any user classes
    klassyLights klassyLights(&LED_ARR[LED_PER_START_POS], LED_STRAND_QTY, &lightTools);
    cochise cochise(&LED_ARR[LED_PER_START_POS], LED_STRAND_QTY, &lightTools);
 /* ------------- [END] Construct all User Light Libraries ------------- */

/* ------------ [START] Define Pattern List -------------- */

    /* Define an array of functions to cycle through - each function added will be called periodically to run the lights */
    typedef void (*FunctionList[])();

    /* Update this array whenever new functions need to be added, and the led_handler will automatically loop through them */
    FunctionList christmas_patterns = {klassyLights.jacobs_ladder, cochise.stack_lights_in_the_middle, cochise.red_and_green_curtain_lights_to_middle, klassyLights.fading_candy_cane};

    /* function list index to loop through the patterns */
    uint8_t christmas_patterns_idx = 0;

    /* update this to set the duration (in seconds) of each pattern (how long it will run before moving to the next pattern) */
    #define PATTERN_DURATION 60

    /* Macro to calculate array sizes */
    #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

/* -------------- [END] Define Pattern List -------------- */

void setup() {
    /* Initialize the Serial Terminal */
    Serial.begin(SERIAL_BAUD);

    /* Set WiFi to automatic sleep to reduce power */
    #ifndef ONLINE_SIMULATION
        WiFi.setSleep(true);
    #endif

    /* Disable BT to reduce power */
    disableBT();

    /* Initialize the HW pins / buttons */
    pin_config();
    init_buttons();

    /* Print Welcome Message */
    print_welcome_message();

    /* Finish initialization depending on physical HW vs Virtual Simulation */
    #ifndef ONLINE_SIMULATION       //If running on physical HW
        FastLED.addLeds<LED_TYPE, LED_DATA_PIN, LED_COLOR_ORDER>(LED_ARR, LED_ARR_QTY).setCorrection(TypicalLEDStrip);
        FastLED.setBrightness(LED_MAX_BRIGHTNESS);

        /* Initiate Blynk Edgent */
        BlynkEdgent.begin();
    #else                           //If running online simulation
        FastLED.addLeds<NEOPIXEL, LED_DATA_PIN>(LED_ARR, LED_ARR_QTY).setCorrection(TypicalLEDStrip);
        FastLED.setBrightness(LED_MAX_BRIGHTNESS);
    #endif

    /* Create LED array / Set master brightness */

}

void loop() {
    /* Handle LED tasks */
    led_handler();

    /* Handle input tasks */
    button_handler();

    /* Handle BlynkEdgent */
    #ifndef ONLINE_SIMULATION
        BlynkEdgent.run();
    #endif

    /* Delay a small amount to pet the watchdog */
    delayMicroseconds(1);
}

/* Function to initialize HW config */
void pin_config() {
    /* Set the pin modes (output/input) */
    pinMode(LED_PWR_EN_PIN, OUTPUT);

    /* Set the default state for output pins */
    digitalWrite(LED_PWR_EN_PIN, HIGH);
}

/* Function to print a welcome message with the SW version */
void print_welcome_message() {
    time_println("***************************");
    time_println("***** Christmas Ghost *****");
    time_print("*****    SW: v"); print(BLYNK_FIRMWARE_VERSION); println("   *****");
    time_println("***************************");
}

/* Function to disable WiFi for power savings */
void disableWiFi() {
    #ifndef ONLINE_SIMULATION
        WiFi.disconnect(true);
        WiFi.mode(WIFI_OFF);
    #endif
}

/* Function to disable BT for power savings */
void disableBT() {
    #ifndef ONLINE_SIMULATION
        btStop();
    #endif
}

/* Handler function to execute various LED management tasks */
void led_handler() {

    /* Cycle through the pattern list periodically, wrapping around once reaching the end of the array */
    EVERY_N_SECONDS(PATTERN_DURATION) {next_pattern();}

    /* Run the currently selected pattern */
    christmas_patterns[christmas_patterns_idx]();

    /* push LED data */
    FastLED.show();
}

/* Cycle through the pattern list periodically, wrapping around once reaching the end of the array */
void next_pattern() {
    christmas_patterns_idx = (christmas_patterns_idx + 1) % ARRAY_SIZE(christmas_patterns);
    time_logln("Moving to next pattern index: " + String(christmas_patterns_idx, DEC));
}

/* Handler function to execute various input button management tasks */
void button_handler() {
    left_hand_btn.loop();
    right_hand_btn.loop();

    #ifndef ONLINE_SIMULATION   //running on real HW
        /* Temporary work around since the button callbacks aren't working properly */
        if (left_hand_btn.isPressed() || right_hand_btn.isPressed()) {EVERY_N_SECONDS(2) {next_pattern();}}
    #else
        /* Online simulation has the Button2 library, but it doesn't seem to work - another temporary workaround */
        static uint8_t run_once = false;

        if (!run_once) {
            pinMode(LEFT_TOUCH_PIN, INPUT);
            pinMode(RIGHT_TOUCH_PIN, INPUT);
            run_once = true;
        }

        if (digitalRead(LEFT_TOUCH_PIN) || digitalRead(RIGHT_TOUCH_PIN)) {EVERY_N_SECONDS(2) {next_pattern();}}
    #endif


}

/* Function to initialize the button configurations */
void init_buttons() {
    /* Configure Left Hand */
    left_hand_btn.begin(LEFT_TOUCH_PIN, INPUT, false, false);       //(pin, mode, isCapacitive, activeLow)
    //left_hand_btn.setDebounceTime(1000);
    //left_hand_btn.setClickHandler(button_left_click);
    //left_hand_btn.setLongClickTime(5000);
    //left_hand_btn.setLongClickHandler(button_left_long_click);

    /* Configure Right Hand */
    right_hand_btn.begin(RIGHT_TOUCH_PIN, INPUT, false, false);       //(pin, mode, isCapacitive, activeLow)
    //right_hand_btn.setDebounceTime(1000);
    //right_hand_btn.setClickHandler(button_right_click);
}

/* Callback function to be executed when left is clicked */
void button_left_click(Button2& btn) {
    /* Increment the pattern index */
    next_pattern();
}

/* Callback function to be executed when right is clicked */
void button_right_click(Button2& btn) {
    /* Increment the pattern index */
    next_pattern();
}

/* Function to print a message */
void print(String message) {
    Serial.print(message);
}

/* Function to print a message, with CRLF */
void println(String message) {
    print(message + "\r\n");
}

/* Function to pre-pend a message with the current CPU running timestamp */
void time_print(String message) {
    String timed_message = "[" + String((millis()), DEC) + "] " + message;
    Serial.print(timed_message);
}

/* Function to pre-pend a message with the current CPU running timestamp, with CRLF */
void time_println(String message) {
    time_print(message + "\r\n");
}

/* Function to log debug messages during development */
void log(String message) {
    if(LOG_DEBUG) {print(message);}
}

/* Function to log debug messages during development, with CRLF */
void logln(String message) {
    log(message + "\r\n");
}

/* Function to log debug messages during development, prepended with a timestamp */
void time_log(String message) {
    if(LOG_DEBUG) {time_print(message);}
}

/* Function to log debug messages during development, prepended with a timestamp, with CRLF */
void time_logln(String message) {
    time_log(message + "\r\n");
}