// https://wokwi.com/projects/393553254409100289
// https://forum.arduino.cc/t/simultaneous-short-and-long-presses/1240315

# include "Toggle.h" // Toggle Library, by dlloydev https://github.com/Dlloydev/Toggle

# include <Adafruit_NeoPixel.h>

# define NINE       9
# define LED_PIN    8
# define LED_COUNT  NINE

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

const byte b1Pin = 2;
const byte b2Pin = 3;
Toggle b1, b2;

const uint16_t deltaT = 3500; // ms after that duration, a pressed button is considered in long press state

enum SystemState : byte {
  B1R_B2R, B1P_B2R, B1L_B2R,
  B1R_B2P, B1P_B2P, B1L_B2P,
  B1R_B2L, B1P_B2L, B1L_B2L, NO_STATE,
} state = B1R_B2R;

const char* stateDescriptor[] = {
  "B1R_B2R", "B1P_B2R", "B1L_B2R",

  "B1R_B2P", "B1P_B2P", "B1L_B2P",
  "B1R_B2L", "B1P_B2L", "B1L_B2L", "NO STATE",
};

const unsigned long stateColor[] = {
  0xff0000, 0xffda33, 0x00ff00,
  0xffda33, 0xffff00, 0xff00ff,
  0x00ff00, 0xff00ff, 0x0000ff, 0xffffff,
};

const struct {
  int code;
  char *act;
} transAction[] = {
  {(B1R_B2R << 4) + B1R_B2P, "GREEN"},
  {(B1R_B2P << 4) + B1R_B2R, "ungreen"},

  {(B1R_B2R << 4) + B1P_B2R, "BLUE"}, 
  {(B1P_B2R << 4) + B1R_B2R, "unblue"},

  {(B1R_B2P << 4) + B1R_B2L, "LONG GREEN"},
  {(B1R_B2L << 4) + B1R_B2R, "allawy back"},

  {(B1P_B2R << 4) + B1L_B2R, "LONG BLUE"},
  {(B1L_B2R << 4) + B1R_B2R, "allawy back"},

  {(B1R_B2L << 4) + B1P_B2L, "long green + BLUE"},
  {(B1L_B2R << 4) + B1L_B2P, "long blue + GREEN"},

  {(B1P_B2L << 4) + B1L_B2L, "long green + long BLUE"},
  {(B1L_B2P << 4) +B1L_B2L, "long blue + long GREEN"},
};

const int nTransActions = sizeof transAction / sizeof *transAction;

void manageState() {

  b1.poll();
  b2.poll();

  switch (state) {
    case B1R_B2R:
      //b1.poll();
      if (b1.onPress()) {
        b1.clearTimer();
        state = B1P_B2R;
      } else {
        //b2.poll();
        if (b2.onPress()) {
          b2.clearTimer();
          state = B1R_B2P;
        }
      }
      break;

    case B1R_B2P:
      //b1.poll();
      if (b1.onPress()) {
        b1.clearTimer();
        state = B1P_B2P;
      } else {
        //b2.poll();
        if (b2.onRelease()) {
          state = B1R_B2R;
        } else {
          if (b2.pressedFor(deltaT)) {
            state = B1R_B2L;
          }
        }
      }
      break;

    case B1R_B2L:
      //b1.poll();
      if (b1.onPress()) {
        b1.clearTimer();
        state = B1P_B2L;
      } else {
        //b2.poll();
        if (b2.onRelease()) {
          state = B1R_B2R;
        }
      }
      break;

    case B1P_B2R:
      //b1.poll();
      if (b1.onRelease()) {
        state = B1R_B2R;
      } else {
        //b2.poll();
        if (b2.onPress()) {
          b2.clearTimer();
          state = B1P_B2P;
        }
        else if (b1.pressedFor(deltaT)) {
          state = B1L_B2R;
        }
      }
      break;

    case B1P_B2P:
      //b1.poll();
      if (b1.onRelease()) {
        state = B1R_B2P;
      } else {
        //b2.poll();
        if (b2.onRelease()) {
          state = B1P_B2R;
        }
        else if (b1.pressedFor(deltaT)) {
          state = B1L_B2P;
        }
        else if (b2.pressedFor(deltaT)) {
          state = B1P_B2L;
        }
      }
      break;

    case B1P_B2L:
      //b1.poll();
      if (b1.onRelease()) {
        state = B1R_B2L;
      } else {
        //b2.poll();
        if (b2.onRelease()) {
          state = B1P_B2R;
        }
        else if (b1.pressedFor(deltaT)) {
          state = B1L_B2L;
        }
      }
      break;

    case B1L_B2R:
      //b1.poll();
      if (b1.onRelease()) {
        state = B1R_B2R;
      } else {
        //b2.poll();
        if (b2.onPress()) {
          b2.clearTimer();
          state = B1L_B2P;
        }
      }
      break;

    case B1L_B2P:
      //b1.poll();
      if (b1.onRelease()) {
        state = B1R_B2P;
      } else {
        //b2.poll();
        if (b2.onRelease()) {
          state = B1L_B2R;
        }
        else if (b2.pressedFor(deltaT)) {
          state = B1L_B2L;
        }
      }
      break;

    case B1L_B2L:
      //b1.poll();
      if (b1.onRelease()) {
        state = B1R_B2L;
      } else {
        //b2.poll();
        if (b2.onRelease()) {
          state = B1L_B2R;
        }
      }
      break;
  }
}

void reportAndAct()
{
  static SystemState previousState = NO_STATE;

  if (previousState != state) {

    if (0) {
      Serial.print("State changed from ");
      Serial.print(stateDescriptor[previousState]);
      Serial.print(" to ");
      Serial.println(stateDescriptor[state]);
    }

    int theTransition = (previousState << 4) + state;
    for (int ii = 0; ii < nTransActions; ii++) {
      if (transAction[ii].code == theTransition) {
        Serial.print("            ");
        Serial.println(transAction[ii].act);
      }
    }

    previousState = state;
    
    disaply.clear();
    disaply.setPixelColor(state, stateColor[state]);
    disaply.show(); 
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("Jello Whirled!\n");

  b1.begin(b1Pin);
  b2.begin(b2Pin);

  disaply.begin();
  disaply.setPixelColor(1, 0xff0000);
  disaply.show();

  delay(777);
}

void loop() {
  manageState();
  reportAndAct();
}