/*
 * Board:   Seeeduino XAIO
 * Target:  12 x 12 NeoPixel-powered ping pong ball array
 * Author:  Clive "Max" Maxfield (max@clivemaxfield.com) 
 * License: The MIT License (See full license at the bottom of this file)
 *
 * Notes:   V1 White drips randomly appear one after the other
 *          V2 Change the drips to be random colors
 *          V3 Add a fade up and fade down
 *          V4 Add a second random color
 *          V5 Make the second color complementary to the first
 *          V6 Add a third color and make colors two and three split complementaries of the first
 */


#include <Adafruit_NeoPixel.h>             // Library for NeoPixels

#define TICK                            10 // Main cycle clock = 10 milliseconds
#define INTER_TICK_PAD_DELAY             5 // Assume calculations and uploading pixels takes 5ms,
                                           // so use this as padding to build up to TICK

#define NUM_NEOS                       145 // 144 in array plus "sacrificial" pixel as voltage level shifter
#define NUM_ROWS                        12
#define NUM_COLS                        12

#define MIN_X                            0
#define MAX_X                           11
#define MIN_Y                            0
#define MAX_Y                           11

#define MIN_INTER_DRIP_DELAY           100
#define MAX_INTER_DRIP_DELAY           250
#define MIN_DRIP_ON_DELAY              100
#define MAX_DRIP_ON_DELAY              250

#define FADE_UP_TIME                   200 // Time to fade up first color at the beginning
#define CROSSFADE_TIME                 500 // Time to fade from one color to another
#define FADE_DOWN_TIME                 500 // Time to fade last color down at the end

#define NUM_WHEEL_COLORS                12 // Number of primary, secondary, and tertiary colors we're using

#define COLOR_WHITE              0xFFFFFFU
#define COLOR_BLACK              0x000000U

#define COLOR_RED                0xFF0000U
#define COLOR_GREEN              0x00FF00U
#define COLOR_BLUE               0x0000FFU

#define COLOR_YELLOW             0xFFFF00U
#define COLOR_CYAN               0x00FFFFU
#define COLOR_MAGENTA            0xFF00FFU

#define COLOR_FLUSH_ORANGE       0xFF8000U
#define COLOR_AMBER              0xFFC000U
#define COLOR_ROSE               0xFF0080U
#define COLOR_PURPLE_PIZZAZZ     0xFF00C0U

#define COLOR_CHARTREUSE         0x80FF00U
#define COLOR_LIME_GREEN         0xC0FF00U
#define COLOR_SPRING_GREEN       0x00FF80U
#define COLOR_BRIGHT_TURQUOISE   0x00FFC0U

#define COLOR_AZURE              0x0080FFU
#define COLOR_CERULEAN           0x00C0FFU
#define COLOR_ELECTRIC_INDIGO    0x8000FFU
#define COLOR_ELECTRIC_VIOLET    0xC000FFU


// Assign any input/output (I/O) pins
const int PinNeos  = 10;


// Instantiate NeoPixel(s)
// Parameter 1 = number of pixels in strip
// Parameter 2 = pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
Adafruit_NeoPixel Neos(NUM_NEOS, PinNeos, NEO_GRB + NEO_KHZ800);


// Define any global variables

uint32_t ColorWheel[NUM_WHEEL_COLORS] = 
{
    COLOR_RED,             COLOR_FLUSH_ORANGE, COLOR_YELLOW, 
    COLOR_CHARTREUSE,      COLOR_GREEN,        COLOR_SPRING_GREEN, 
    COLOR_CYAN,            COLOR_AZURE,        COLOR_BLUE,   
    COLOR_ELECTRIC_INDIGO, COLOR_MAGENTA,      COLOR_ROSE
};
                                     
int OldX;
int OldY;


void setup ()
{
//    Serial.begin (9600);
    pinMode(PinNeos, OUTPUT);

    Neos.begin();
    Neos.show();

    OldX = random(MIN_X, (MAX_X + 1));
    OldY = random(MIN_Y, (MAX_Y + 1));
}


void loop ()
{
    DropDrip();
}


void DropDrip ()
{
    int iNeo;
    int iColor;
    int iColorTmp;
    int newX;
    int newY;
    int interDripDelay;
    int dripOnDelay;
 
    uint32_t dripColorOne;
    uint32_t dripColorTwo;
    uint32_t dripColorThree;
    
    // Generate new (X,Y) values
    do
    {
        newX = random(MIN_X, (MAX_X + 1));
        newY = random(MIN_Y, (MAX_Y + 1));
//  } while ( (OldX == newX) && (OldY == newY) ); // Can't be in the same location 
    } while ( (OldX == newX) || (OldY == newY) ); // Can't be on same row or column 

    // Calculate corresponding pixel number
    iNeo = GetNeoNum(newX, newY);

    // Get drip colors
    iColor = random(0, NUM_WHEEL_COLORS);
    dripColorOne = ColorWheel[iColor];

    iColorTmp = (iColor + (NUM_WHEEL_COLORS / 2) - 1) % NUM_WHEEL_COLORS;
    dripColorTwo = ColorWheel[iColorTmp];

    iColorTmp = (iColor + (NUM_WHEEL_COLORS / 2) + 1) % NUM_WHEEL_COLORS;
    dripColorThree = ColorWheel[iColorTmp];

    // Get delays
    dripOnDelay = random(MIN_DRIP_ON_DELAY, (MAX_DRIP_ON_DELAY + 1));
    interDripDelay = random(MIN_INTER_DRIP_DELAY, (MAX_INTER_DRIP_DELAY + 1));

    // Fade from black to first drip color, then hold
    CrossFade(iNeo, COLOR_BLACK, dripColorOne, FADE_UP_TIME, dripOnDelay);

    // Fade from first drip color to second drip color, then hold
    CrossFade(iNeo, dripColorOne, dripColorTwo, CROSSFADE_TIME, dripOnDelay);

    // Fade from second drip color to third drip color, then hold
    CrossFade(iNeo, dripColorTwo, dripColorThree, CROSSFADE_TIME, dripOnDelay);

    // Fade from third drip color to black, then wait
    CrossFade(iNeo, dripColorThree, COLOR_BLACK, FADE_DOWN_TIME, interDripDelay);    

    OldX = newX;
    OldY = newY;
}


// Accept an x/y (column/row) value and return the corresponding pixel number
int GetNeoNum (int xInd, int yInd)
{
    int iNeo;

    iNeo = yInd * NUM_COLS;

    if ( (yInd % 2) == 0)
    {   // Even row
        iNeo = iNeo + (12 - xInd);
    }
    else
    {   // Odd row
        iNeo = iNeo + (xInd + 1);
    }

    return iNeo;
}


// Performs a cross fade from one color to another
void CrossFade (int iNeo, uint32_t startColor, uint32_t endColor, int fadeDuration, int addThisDelay)
{
    int numFadeSteps;
    uint32_t fadeColor;
        
    numFadeSteps = fadeDuration / TICK;
    
    for (int iStep = 1; iStep <= numFadeSteps; iStep++)
    {
        fadeColor = CrossFadeColor(startColor, endColor, numFadeSteps, iStep);
        Neos.setPixelColor(iNeo, fadeColor);
        Neos.show();
        delay(INTER_TICK_PAD_DELAY);
    }

    delay(addThisDelay);
}


// Returns a 32-bit color with incremental RGB values from a start color and an end color
uint32_t CrossFadeColor (uint32_t startColor, uint32_t endColor, int numSteps, int currentStep)
{
    uint32_t tmpRed;
    uint32_t tmpGreen;
    uint32_t tmpBlue;
    uint32_t newColor;

    tmpRed =   ( (  GetRed(startColor) * (numSteps - currentStep) ) + (  GetRed(endColor) * currentStep) ) / numSteps;
    tmpGreen = ( (GetGreen(startColor) * (numSteps - currentStep) ) + (GetGreen(endColor) * currentStep) ) / numSteps;
    tmpBlue =  ( ( GetBlue(startColor) * (numSteps - currentStep) ) + ( GetBlue(endColor) * currentStep) ) / numSteps;

    newColor = BuildColor( ((uint8_t) tmpRed), ((uint8_t) tmpGreen), ((uint8_t) tmpBlue) );

    return newColor;
}


// Returns a 32-bit color built out of 8-bit Red, Green, and Blue elements
uint32_t BuildColor (uint8_t red, uint8_t green, uint8_t blue)
{
    return ( (((uint32_t) red) << 16) | (((uint32_t) green) << 8) | ((uint32_t) blue) );
}


// Returns the 8-bit Red component of a 32-bit color
uint8_t GetRed (uint32_t tmpColor)
{
    return (uint8_t) ( (tmpColor >> 16) & 0xFF );
}

     
// Returns the 8-bit Green component of a 32-bit color
uint8_t GetGreen (uint32_t tmpColor)
{
    return (uint8_t) ( (tmpColor >> 8) & 0xFF );
}


// Returns the 8-bit Blue component of a 32-bit color
uint8_t GetBlue (uint32_t tmpColor)
{
    return (uint8_t) (tmpColor & 0xFF);
}


/*
 * Copyright (c) 2020 Clive "Max" Maxfield
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and any associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHOR(S) OR COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */