// https://forum.arduino.cc/t/timer-function-for-staggered-switching/1076239
// https://wokwi.com/projects/353770726969195521

# include "Button.h"
# include <BitArray.h>

unsigned long now;    // the only use of millis() is to set this time

# define SIZE     8
# define CHANNELS 4
# define NSX      4  // actiual number of buttons attached

Button buttons[NSX] = {2, 3, 4, 5};
unsigned char buttonVector[SIZE];

unsigned char relayState[SIZE];   // very closely equal to the output
BitArray presetsBits;             // 32 bits of preset on/off

# define FRONT  1000      // ms of mutation before a relay can change state
# define BACK   3000      // ms after realy change that mutation is kept on

const byte muteLED = 7;
Button upChannel = 9;
Button downChannel = 8;


# include <Adafruit_NeoPixel.h>

# define LED_PIN    A0
# define LED_COUNT  (SIZE + CHANNELS)

Adafruit_NeoPixel statusLEDs(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);


void setup() {
  Serial.begin(115200);
  xprintf("mad leech button mute midi POD\n");

// gang of 8 (only NSX are wired yet)
  setupButtons();

// a few more buttons

  upChannel.begin();
  downChannel.begin();
  
  presetsBits.begin(1, SIZE * CHANNELS);     // single bits 0..31 as 4x8
  presetsBits.clear();

  statusLEDs.begin();

  pinMode(muteLED, OUTPUT);
}

// nice mute functions

bool muteIsOn;
unsigned long muteTimer;
unsigned long muteWentOn;

void muteOn()
{
  muteTimer = now;                  // refresh the lockout timer
  if (muteIsOn == false) {
    muteIsOn = true; 

    muteWentOn = now;
    digitalWrite(muteLED, HIGH);    // do relay or whatever needs doing
  }
}

void muteCheck()
{
  if (now - muteTimer < BACK)      // time to unmute?
    return;

  if (muteIsOn == true) {
    muteIsOn = false;
    digitalWrite(muteLED, LOW);     // undo relay or whatever needs doing
  }
}

unsigned char currentChannel; 

void loop() {

  now = millis();

  channelButtonCheck();

  fillButtonVector();

  for (unsigned char ii = 0; ii < NSX; ii++) {
    if (buttonVector[ii]) {
      unsigned char bitNumber = chanSlotToBit(currentChannel, ii);
      presetsBits.set(bitNumber, !presetsBits.get(bitNumber));
    }
  }

// adjust relay states
  relayFSM();

// time to unmute?
  muteCheck();

// copy relayState to the hardware dirvers and lamps. muting has been handled.

  for (unsigned char tt = 0; tt < NSX; tt++)
    if (relayState[tt] ==  presetsBits.get(chanSlotToBit(currentChannel, tt)))
      statusLEDs.setPixelColor(tt, relayState[tt] ? 0x0000ff : 0xff0000);
    else
      statusLEDs.setPixelColor(tt, 0xffff00);

  statusLEDs.show(); // light 'em up

  static unsigned long lastReport;
  if (now - lastReport > 777) {
    report();
    lastReport = now;
  }
}


void channelButtonCheck()
{
  upChannel.read();
  downChannel.read();

  if (upChannel.uolPress()) {
    currentChannel++;
    if (currentChannel >= CHANNELS)
      currentChannel = CHANNELS - 1;
  }

  if (downChannel.uolPress()) {
    if (currentChannel)
      currentChannel--; 
  }

// channel indicators are the n CHANNELS after the m SIZE relay LEDs
  for (unsigned char tt = 0; tt < CHANNELS; tt++)
    statusLEDs.setPixelColor(tt + SIZE, (tt == currentChannel) ? 0xff0000 : 0x006666);
}


// recall Button buttons[NSX] = {2, 3, 4, 5};

void setupButtons()
{
  for (unsigned char ii = 0; ii < NSX; ii++)
    buttons[ii].begin();
}

// however you detect a button going down
void fillButtonVector()
{
  for (unsigned char ii = 0; ii < NSX; ii++)
    buttons[ii].read();
  
  for (unsigned char ii = 0; ii < NSX; ii++)
    buttonVector[ii] = buttons[ii].uolPress();
}

// update the relayState, start in necessary and respect muting
void relayFSM()
{
  for (unsigned char tt = 0; tt < SIZE; tt++) {

// if the relay (still) disagrees with the desired state, poke it along somehow
    unsigned char bitNumber = chanSlotToBit(currentChannel, tt);

    if (relayState[tt] != presetsBits.get(bitNumber)) {
      if (muteIsOn) {
        if (now - muteWentOn > FRONT) {
          relayState[tt] = presetsBits.get(bitNumber);
          muteOn();   // yes, it was. this extends it
        }
      }
      muteOn();       // maybe turn it on, maybe extend it. either way...
    }
  }
}

// 8 bits per slot, so
unsigned char chanSlotToBit(unsigned char theChannel, unsigned char theSlot)
{
  return (theChannel << 3) + theSlot;
}
 
void report()
{
  if (1) return;  // we may need a stinkin' report at some point

  for (unsigned char tt = 0; tt < SIZE; tt++)
    xprintf("%1d   ", tt);
  
  xprintf("\n");

  for (unsigned char tt = 0; tt < SIZE; tt++)
    xprintf("%s", relayState[tt] ? "rN  " : "rF  ");
  
  xprintf("\n\n");

  for (unsigned char cc = 0; cc < CHANNELS; cc++) {
    for  (unsigned char rr = 0; rr < SIZE; rr++)
      xprintf("%s   ", presetsBits.get(chanSlotToBit(cc, rr)) ? "X  " : "_   ");

    xprintf("\n");
  }
  xprintf("\n");
}

// programmer misses printf... THX @Danois90 https://forum.arduino.cc/t/pure-c-hello-world/1042838/21
void xprintf(const char *format, ...)
{
  char buffer[256];
  va_list args;
  va_start(args, format);
  vsprintf(buffer, format, args);
  va_end(args);
  Serial.print(buffer);
}
CH--
UNUSED \ LEDS
CH++
PRESET TOGGLE
RELAY