// A basic everyday NeoPixel strip test program.
// NEOPIXEL BEST PRACTICES for most reliable operation:
// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections.
// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel.
// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR.
// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS
//   connect GROUND (-) first, then +, then data.
// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip,
//   a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED.
// (Skipping these may work OK on your workbench but can fail in the field)
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
 #include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
// Which pin on the Arduino is connected to the NeoPixels?
// On a Trinket or Gemma we suggest changing this to 1:
#define LED_PIN_1 2
#define LED_PIN_2 3
#define LED_PIN_3 4
#define LED_PIN_4 5
#define LED_PIN_5 6
#define LED_PIN_6 7
#define LED_PIN_7 8
#define LED_PIN_8 9
#define LED_PIN_9 10
// How many NeoPixels are attached to the Arduino?
#define LED_COUNT_1 50
#define LED_COUNT_2 50
#define LED_COUNT_3 20
#define LED_COUNT_4 20
#define LED_COUNT_5 20
#define LED_COUNT_6 20
#define LED_COUNT_7 20
#define LED_COUNT_8 20
#define LED_COUNT_9 20
// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip_1(LED_COUNT_1, LED_PIN_1, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip_2(LED_COUNT_2, LED_PIN_2, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip_3(LED_COUNT_3, LED_PIN_3, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip_4(LED_COUNT_4, LED_PIN_4, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip_5(LED_COUNT_5, LED_PIN_5, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip_6(LED_COUNT_6, LED_PIN_6, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip_7(LED_COUNT_7, LED_PIN_7, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip_8(LED_COUNT_8, LED_PIN_8, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip_9(LED_COUNT_9, LED_PIN_9, NEO_GRB + NEO_KHZ800);
// Argument 1 = Number of pixels in NeoPixel strip
// Argument 2 = Arduino pin number (most are valid)
// Argument 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)
//   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
// setup() function -- runs once at startup --------------------------------
void setup() {
  // These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
  // Any other board, you can remove this part (but no harm leaving it):
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
  clock_prescale_set(clock_div_1);
#endif
  // END of Trinket-specific code.
  Serial.begin(115200);      // Inicjalizacja portu szeregowego dla komunikacji z komputerem
  Serial.println("LED Driver\n");
  strip_1.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip_1.show();            // Turn OFF all pixels ASAP
  strip_1.setBrightness(10); // Set BRIGHTNESS to about 1/5 (max = 255)
  strip_2.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip_2.show();            // Turn OFF all pixels ASAP
  strip_2.setBrightness(10); // Set BRIGHTNESS to about 1/5 (max = 255)
  
  strip_3.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip_3.show();            // Turn OFF all pixels ASAP
  strip_3.setBrightness(10); // Set BRIGHTNESS to about 1/5 (max = 255)
  
  strip_4.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip_4.show();            // Turn OFF all pixels ASAP
  strip_4.setBrightness(10); // Set BRIGHTNESS to about 1/5 (max = 255)
  
  strip_5.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip_5.show();            // Turn OFF all pixels ASAP
  strip_5.setBrightness(10); // Set BRIGHTNESS to about 1/5 (max = 255)
  
  strip_6.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip_6.show();            // Turn OFF all pixels ASAP
  strip_6.setBrightness(10); // Set BRIGHTNESS to about 1/5 (max = 255)
  
  strip_7.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip_7.show();            // Turn OFF all pixels ASAP
  strip_7.setBrightness(10); // Set BRIGHTNESS to about 1/5 (max = 255)
  
  strip_8.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip_8.show();            // Turn OFF all pixels ASAP
  strip_8.setBrightness(10); // Set BRIGHTNESS to about 1/5 (max = 255)
  
  strip_9.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip_9.show();            // Turn OFF all pixels ASAP
  strip_9.setBrightness(10); // Set BRIGHTNESS to about 1/5 (max = 255)
  
}
// loop() function -- runs repeatedly as long as board is on ---------------
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
void loop() {
  // Sprawdź, czy użytkownik wysłał coś z seriala
  greenMotorEffect();
  if (Serial.available() > 0) {
    int choice = Serial.parseInt();  // Wczytaj numer funkcji
    // Sprawdź, czy otrzymano prawidłowy numer
    if (choice >= 1 && choice <= 5) {
      // Uruchom odpowiednią funkcję na podstawie numeru
      switch (choice) {
        case 1:
          iluminatingEffect();
          break;
        case 2:
          greenMotorEffect();
          break;
        case 3:
          flashingEffect();
          break;
        case 4:
          drumRotationEffect();
          break;
        case 5:
          heatExchangerEffect();
          break;
        default:
          Serial.println("Invalid choice!");
          break;
      }
    } else {
      Serial.println("Invalid choice! Please enter a number between 1 and 5.");
    }
  }
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
void iluminatingEffect() {
  // Fill all in white for ilumination
  colorWipe(strip_1,
            strip_1.Color(255, 255, 255),   // Kolor niebieski
            50,                             // Predkosc animacji w ms
            0);                             // Piksel startowy
}
void greenMotorEffect() {
  // Simulate motor work in green
  
  int segmentSizes[4] = {10, 15, 20, 5}; // Rozmiary segmentów
  sequentialVariableSegments(strip_1, 
                            strip_1.Color(0, 255, 0), // Zielony kolor
                            segmentSizes,            // Tablica rozmiarów segmentów
                            500,                     // Opóźnienie w ms między segmentami
                            3,                       // 3 iteracje całej animacji
                            true,                    // Kierunek: do przodu
                            500);                    // Czas oczekiwania po zgaszeniu: 500 ms
}
void flashingEffect() {
  // Fill along the length of the strip_n in various colors (R,G,B) starting from given pixel and delay n ms...
  colorWipe(strip_1,
            strip_1.Color(0, 0, 255),   // Kolor niebieski
            50,                         // Predkosc animacji w ms
            0);                         // Piksel startowy
 
  simpleWaterEffect(strip_1,                    // Paski led
                    strip_1.Color(0, 0, 255),   // Kolor animacji
                    200,                        // Predkosc animacji w ms
                    25);                        // Ilosc powtorzen
  colorWipe(strip_1,                    // Wypełnij niebieskim bez opoznienia
            strip_1.Color(0, 0, 255),
            0,
            0);     
  colorWipe(strip_1,                    // Wypelnij czarnym z opoznieniem 50 ms
            strip_1.Color(0, 0, 0),
            50,
            0);          
  colorWipe(strip_2,strip_2.Color(0, 0, 255),50,0);    
  simpleWaterEffect(strip_2,strip_2.Color(0, 0, 255),200,25);
  colorWipe(strip_2,strip_2.Color(0, 0, 255),0,0);                        
  colorWipe(strip_2,strip_2.Color(0, 0, 0),50,0);    
  // colorWipe(strip_3,strip_2.Color(0, 0, 255),50,0);    
  // simpleWaterEffect(strip_3,strip_3.Color(0, 0, 255),200,25);
  // colorWipe(strip_3,strip_2.Color(0, 0, 255),0,0);                        
  // colorWipe(strip_3,strip_2.Color(0, 0, 0),50,0);    
  // colorWipe(strip_4,strip_2.Color(0, 0, 255),50,0);    
  // simpleWaterEffect(strip_4,strip_2.Color(0, 0, 255),200,25);
  // colorWipe(strip_4,strip_2.Color(0, 0, 255),0,0);                        
  // colorWipe(strip_4,strip_2.Color(0, 0, 0),50,0);    
}
void drumRotationEffect() {
  // Simulate drum rotation right/left
  // 1 belt
  rotateRim(strip_1,
            strip_1.Color(0, 0, 255), // kolor 1
            strip_1.Color(255, 0, 0), // Kolor 2
            5,                        // Przerwa
            50,                       // Predkosc animacji (50 ms)
            10,                       // Liczba obrotow
            true);                    // Obrót: w prawo
  delay(1000);
  rotateRim(strip_1,
            strip_1.Color(0, 0, 255), // kolor 1
            strip_1.Color(255, 0, 0), // Kolor 2
            5,                        // Przerwa
            50,                       // Predkosc animacji (50 ms)
            10,                       // Liczba obrotow
            false);                   // Obrót w lewo
  delay(1000);
  strip_1.clear();
  // 2 belts
  rotateRimMulti(strip_1, strip_2,
            strip_1.Color(0, 0, 255),  // Kolor 1 
            strip_1.Color(255, 0, 0), // Kolor 2 
            5,                       // Przerwa
            50,                      // Prędkość animacji (50 ms)
            10,                      // Liczba obrotów
            true);                   // Obrót prawo
  delay(1000);
  rotateRimMulti(strip_1, strip_2,
            strip_1.Color(0, 0, 255),  // Kolor 1
            strip_1.Color(255, 0, 0), // Kolor 2 
            5,                       // Przerwa
            50,                      // Prędkość animacji (50 ms)
            10,                      // Liczba obrotów
            false);                   // Obrót lewo
  delay(1000);          
  strip_1.clear();
  strip_2.clear();
  strip_1.show();
  strip_2.show();
}
void heatExchangerEffect() {
  // Simulate heat exchanger flow
  animatedWhitePixelStripes(strip_1, 
                          strip_1.Color(255, 0, 0),   // Kolor 1: czerwony
                          strip_1.Color(0, 0, 255),   // Kolor 2: niebieski
                          strip_1.Color(0, 255, 0),   // Separator 1: zielony
                          strip_1.Color(255, 255, 0), // Separator 2: żółty
                          20, 20, 5, 5,               // Rozmiary segmentów i separatorów
                          strip_1.Color(255, 255, 255), // Kolor animacji: biały
                          2,                          // Piksele na pasek animacji
                          6,                          // Liczba pasków animacji
                          50,                         // Prędkość animacji (50 ms)
                          10,                         // Liczba iteracji
                          true);                      // Kierunek: w prawo
  strip_1.clear();
  strip_1.show();
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Effect functions
// Strip filing pixels after pixel. Strip is NOT cleared
void colorWipe(Adafruit_NeoPixel &strip,
               uint32_t color,
               int wait,
               int startPixel) {
  for (int i = startPixel; i < strip.numPixels(); i++) { // Start from the specified pixel
    strip.setPixelColor(i, color);                      // Set pixel's color (in RAM)
    strip.show();                                       // Update strip to match
    delay(wait);                                        // Pause for a moment
  }
}
// Sequential strip filling 
void sequentialVariableSegments(Adafruit_NeoPixel &strip, 
                                uint32_t color, 
                                int segmentSizes[4], // Tablica rozmiarów segmentów
                                int speed, 
                                int iterations, 
                                bool forward, 
                                int waitAfterClear) { // Czas oczekiwania po zgaszeniu segmentów
  int numPixels = strip.numPixels(); // Liczba pikseli na pasku
  for (int iter = 0; iter < iterations; iter++) {
    if (forward) {
      // Podświetlanie od pierwszego do ostatniego segmentu
      for (int s = 0; s < 4; s++) {
        strip.clear();
        int pixelOffset = 0;
        for (int i = 0; i <= s; i++) {
          for (int j = 0; j < segmentSizes[i]; j++) {
            int pixelIndex = pixelOffset + j;
            if (pixelIndex < numPixels) {
              strip.setPixelColor(pixelIndex, color); // Ustaw kolor na piksel
            }
          }
          pixelOffset += segmentSizes[i];
        }
        strip.show(); // Aktualizacja paska LED
        delay(speed); // Czas opóźnienia między segmentami
      }
    } else {
      // Podświetlanie od ostatniego do pierwszego segmentu
      for (int s = 3; s >= 0; s--) {
        strip.clear();
        int pixelOffset = 0;
        for (int i = 0; i <= s; i++) {
          for (int j = 0; j < segmentSizes[i]; j++) {
            int pixelIndex = pixelOffset + j;
            if (pixelIndex < numPixels) {
              strip.setPixelColor(pixelIndex, color); // Ustaw kolor na piksel
            }
          }
          pixelOffset += segmentSizes[i];
        }
        strip.show(); // Aktualizacja paska LED
        delay(speed); // Czas opóźnienia między segmentami
      }
    }
    // Zgaszenie wszystkich segmentów na końcu iteracji
    strip.clear();
    strip.show(); // Aktualizacja paska LED
    delay(waitAfterClear); // Oczekiwanie po zgaszeniu
  }
}
void simpleWaterEffect(Adafruit_NeoPixel &strip,
                       uint32_t color,
                       int wait,
                       int pulses) {
  int numPixels = strip.numPixels();
  for (int pulse = 0; pulse < pulses; pulse++) {
    // Pierwsza faza: co druga dioda zapalona (parzyste)
    for (int i = 0; i < numPixels; i++) {
      if (i % 2 == 0) {
        strip.setPixelColor(i, color); // Zapal diodę
      } else {
        strip.setPixelColor(i, 0);    // Zgaś diodę
      }
    }
    strip.show();
    delay(wait);
    // Druga faza: co druga dioda zapalona (nieparzyste)
    for (int i = 0; i < numPixels; i++) {
      if (i % 2 == 1) {
        strip.setPixelColor(i, color); // Zapal diodę
      } else {
        strip.setPixelColor(i, 0);    // Zgaś diodę
      }
    }
    strip.show();
    delay(wait);
  }
}
void simpleWaterEffectMulti(Adafruit_NeoPixel &strip1,
                       Adafruit_NeoPixel &strip2,
                       Adafruit_NeoPixel &strip3,
                       Adafruit_NeoPixel &strip4,
                       uint32_t color,
                       int wait,
                       int pulses) {
  int numPixels1 = strip1.numPixels();
  int numPixels2 = strip2.numPixels();
  int numPixels3 = strip3.numPixels();
  int numPixels4 = strip4.numPixels();
  for (int pulse = 0; pulse < pulses; pulse++) {
    // Pierwsza faza: co druga dioda zapalona (parzyste)
    for (int i = 0; i < max(max(numPixels1, numPixels2), max(numPixels3, numPixels4)); i++) {
      if (i < numPixels1) {
        strip1.setPixelColor(i, (i % 2 == 0) ? color : 0);
      }
      if (i < numPixels2) {
        strip2.setPixelColor(i, (i % 2 == 0) ? color : 0);
      }
      if (i < numPixels3) {
        strip3.setPixelColor(i, (i % 2 == 0) ? color : 0);
      }
      if (i < numPixels4) {
        strip4.setPixelColor(i, (i % 2 == 0) ? color : 0);
      }
    }
    strip1.show();
    strip2.show();
    strip3.show();
    strip4.show();
    delay(wait);
    // Druga faza: co druga dioda zapalona (nieparzyste)
    for (int i = 0; i < max(max(numPixels1, numPixels2), max(numPixels3, numPixels4)); i++) {
      if (i < numPixels1) {
        strip1.setPixelColor(i, (i % 2 == 1) ? color : 0);
      }
      if (i < numPixels2) {
        strip2.setPixelColor(i, (i % 2 == 1) ? color : 0);
      }
      if (i < numPixels3) {
        strip3.setPixelColor(i, (i % 2 == 1) ? color : 0);
      }
      if (i < numPixels4) {
        strip4.setPixelColor(i, (i % 2 == 1) ? color : 0);
      }
    }
    strip1.show();
    strip2.show();
    strip3.show();
    strip4.show();
    delay(wait);
  }
}
// Rotate the rim with two colors pattern
void rotateRim(Adafruit_NeoPixel &strip,
               uint32_t color1, uint32_t color2,
               int emptyPixels,
               int speed,
               int rotations,
               bool clockwise) {
  int numPixels = strip.numPixels();  
  int segmentSize = 4; // Liczba pikseli na jeden pasek koloru
  int patternSize = 2 * segmentSize + emptyPixels; // Rozmiar całego wzorca (kolor1, kolor2, przerwa)
  for (int rotation = 0; rotation < rotations; rotation++) {
    for (int offset = 0; offset < patternSize; offset++) {
      strip.clear(); // Czyści wszystkie piksele
      for (int i = 0; i < numPixels; i++) {
        int position = (clockwise ? i - offset : i + offset) % patternSize; 
        if (position < 0) position += patternSize;
        // Ustaw kolory dla odpowiednich pikseli w obwodzie
        if (position < segmentSize) {
          strip.setPixelColor(i, color1); // Pierwszy kolor
        } else if (position >= segmentSize && position < 2 * segmentSize) {
          strip.setPixelColor(i, color2); // Drugi kolor
        }
      }
      strip.show();  // Wyświetl zmiany
      delay(speed);  // Czas opóźnienia między krokami
    }
  }
}
// Rotate the rim with two colors pattern for two LED strips
void rotateRimMulti(Adafruit_NeoPixel &strip1, Adafruit_NeoPixel &strip2,
               uint32_t color1, uint32_t color2,
               int emptyPixels,
               int speed,
               int rotations,
               bool clockwise) {
  int numPixels1 = strip1.numPixels();
  int numPixels2 = strip2.numPixels();
  int segmentSize = 4; // Liczba pikseli na jeden pasek koloru
  int patternSize = 2 * segmentSize + emptyPixels; // Rozmiar całego wzorca (kolor1, kolor2, przerwa)
  for (int rotation = 0; rotation < rotations; rotation++) {
    for (int offset = 0; offset < patternSize; offset++) {
      // Czyść wszystkie piksele na obu paskach
      strip1.clear();
      strip2.clear();
      for (int i = 0; i < numPixels1; i++) {
        int position = (clockwise ? i - offset : i + offset) % patternSize;
        if (position < 0) position += patternSize;
        // Ustaw kolory dla odpowiednich pikseli na pierwszym pasku
        if (position < segmentSize) {
          strip1.setPixelColor(i, color1);
        } else if (position >= segmentSize && position < 2 * segmentSize) {
          strip1.setPixelColor(i, color2);
        }
      }
      for (int i = 0; i < numPixels2; i++) {
        int position = (clockwise ? i - offset : i + offset) % patternSize;
        if (position < 0) position += patternSize;
        // Ustaw kolory dla odpowiednich pikseli na drugim pasku
        if (position < segmentSize) {
          strip2.setPixelColor(i, color1);
        } else if (position >= segmentSize && position < 2 * segmentSize) {
          strip2.setPixelColor(i, color2);
        }
      }
      // Wyświetl zmiany na obu paskach
      strip1.show();
      strip2.show();
      delay(speed);
    }
  }
}
// Static pattern with moving pixels
void animatedWhitePixelStripes(Adafruit_NeoPixel &strip, 
                               uint32_t color1, uint32_t color2, 
                               uint32_t dividerColor1, uint32_t dividerColor2, 
                               int segment1Size, int segment2Size, 
                               int divider1Size, int divider2Size, 
                               uint32_t movingColor, int movingPixelsPerStripe, 
                               int stripeCount, int speed, int iterations, 
                               bool directionRight) {
  int numPixels = strip.numPixels();
  int patternSize = segment1Size + divider1Size + segment2Size + divider2Size; // Całkowity rozmiar wzorca
  int stripeSpacing = numPixels / stripeCount; // Odstęp między początkami pasków animacji
  for (int iter = 0; iter < iterations; iter++) {
    // Pętla dla przesunięcia pikseli
    for (int offset = 0; offset < numPixels; offset++) {
      strip.clear();
      // Tworzenie statycznego wzorca
      for (int i = 0; i < numPixels; i++) {
        int position = i % patternSize;
        if (position < segment1Size) {
          strip.setPixelColor(i, color1);
        } else if (position < segment1Size + divider1Size) {
          strip.setPixelColor(i, dividerColor1);
        } else if (position < segment1Size + divider1Size + segment2Size) {
          strip.setPixelColor(i, color2);
        } else {
          strip.setPixelColor(i, dividerColor2);
        }
      }
      // Nałożenie animowanych pasków
      for (int s = 0; s < stripeCount; s++) {
        int stripeStart = (s * stripeSpacing) % numPixels;
        for (int i = 0; i < movingPixelsPerStripe; i++) {
          int movingPosition;
          if (directionRight) {
            // Ruch w prawo
            movingPosition = (stripeStart + offset + i) % numPixels;
          } else {
            // Ruch w lewo
            movingPosition = (numPixels + stripeStart + offset - i) % numPixels;
          }
          strip.setPixelColor(movingPosition, movingColor); // Ustaw piksel animacji
        }
      }
      strip.show(); // Wyświetlenie całości
      delay(speed); // Czas między krokami animacji
    }
  }
}