/*
  Able to use PROGMEM by using #include avr/pgmspace.h
  See https://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html#ga73084a8bbde259ffae72980354b3f027
  for details.

  CM 10/15/24: This sketch is working well.
  - Create a huminoid looking robot with the 8x8 display as its head. The body could 
    encorperate the ATtiny85, maybe a pulsing LED for a heart?
*/

#include <avr/pgmspace.h> // used with 'PROGMEM'
#include"data_bytes.h" // MAX7219 animation arrays
#include <avr/io.h>
#include <avr/interrupt.h> // required for ISR(INT0_vect) macro
#include <TinyDebug.h>

// These variables are for 8x8 display
uint8_t position = 0;
static uint8_t MAX7219_state[8] = {0,0,0,0,0,0,0,0}; // used with MAX7219_set_pixel
unsigned int randNum_row;
unsigned int randNum_col;
uint8_t pixel = 0;

// These variable are for PIR
#define timeSeconds 10
const int ledEyes = PB4;
const int motionSensor = PB3; // PIR pin
// Timer: Auxiliary variables
unsigned long now = millis();
unsigned long lastTrigger = 0;
boolean startTimer = false;
boolean motion = false;
int randomNum;

// Interrupt routine
// Runs when motion is detected, sets ledEyes HIGH and starts a timer
ISR (PCINT0_vect) {  // Interrupt service routine
  digitalWrite(ledEyes, HIGH);
  randomNum = random(1, 6); // random number 1 to 5
  startTimer = true;
  lastTrigger = millis();
}

void pin_change_interrupt() {
  // GIMSK – General Interrupt Mask Register
  // Setting PCIE bit enables the pin change interrupt.
  // Also need to configure the pins that will be triggered by this interrupt.
  GIMSK |= (1 << PCIE); // enable PCIE (pin change interrupt)
  PCMSK |= (1 << PCINT3); // Set which pin will trigger the interrupt

  // same as SREG – Status Register: • Bit 7 – I: Global Interrupt Enable  
  sei(); // 'Set Enable Interrupt': (global) interrupt enable bit, turning on all interrupts that have been configured.
}


void setup() {
  Debug.begin();
  pinMode(ledEyes, OUTPUT);
  pinMode(motionSensor, INPUT_PULLUP); // PIR Motion Sensor mode INPUT_PULLUP
  pin_change_interrupt();
  MAX7219_init();
  /*
    if analog input pin (pin number) is unconnected, random analog noise will cause the call to randomSeed() to generate
    different seed numbers each time the sketch runs. randomSeed() will then shuffle the random function.
  */
  randomSeed(analogRead(A0)); // Pin PB5 = ADC0
  Debug.print("motiionSensor = "); Debug.println(digitalRead(motionSensor));
  digitalWrite(ledEyes, LOW); // Set initial state of led to LOW
}

void loop() {
  now = millis(); // Current time

  if ((digitalRead(ledEyes) == HIGH) && (motion == false)) {
    Debug.println("MOTION DETECTED!!!");
    Debug.print("motionSensor = "); Debug.println(digitalRead(motionSensor));
    Debug.print("now = "); Debug.print(now); Debug.print(", lastTrigger = "); Debug.println(lastTrigger);
    if (randomNum == 1) { // - - - WORKS - - -
      Debug.print("1randomNum: "); Debug.println(randomNum);
      display_byte_array(smiley);
      Debug.println(lastTrigger);
      Debug.println(now);
      Debug.println(millis());
    }
    if (randomNum == 2) { // - - - WORKS - - - set 'delayTime' to .1
      Debug.print("2randomNum: "); Debug.println(randomNum);
      while ((millis() - lastTrigger) < timeSeconds * 1000) {
        display_byte_2D_array(dogface, 0);
        delay(200);
        display_byte_2D_array(dogface, 1);
        delay(200);
        Debug.print("lastTrigger "); Debug.println(lastTrigger);
        Debug.print("millis "); Debug.println(millis());
        Debug.print("millis - lastTrigger = "); Debug.println(millis() - lastTrigger);
      }
    }
    if (randomNum == 3) { // - - - WORKS - - - set 'delayTime' to .1
      Debug.print("3randomNum: "); Debug.println(randomNum);
      while ((millis() - lastTrigger) < timeSeconds * 1000) {
        scrolling_2D_array_with_delay(invader1);
        scrolling_2D_array_with_delay(invader2);
        Debug.print("lastTrigger "); Debug.println(lastTrigger);
        Debug.print("millis "); Debug.println(millis());
        Debug.print("millis - lastTrigger = "); Debug.println(millis() - lastTrigger);
      }
    }
    if (randomNum == 4) { // - - - WORKS - - - set 'delayTime' to .1
      Debug.print("4randomNum: "); Debug.println(randomNum);
      while ((millis() - lastTrigger) < timeSeconds * 1000) {
        scrolling_2D_array(running_man_LR, 21);
        Debug.print("lastTrigger "); Debug.println(lastTrigger);
        Debug.print("millis "); Debug.println(millis());
        Debug.print("millis - lastTrigger = "); Debug.println(millis() - lastTrigger);
      }
    }
    if (randomNum == 5) { // - - - WORKS - - - set 'delayTime' to .1
      Debug.print("5randomNum: "); Debug.println(randomNum);
      while ((millis() - lastTrigger) < timeSeconds * 1000) {
        scrolling_2D_array(LSU, 28);
        Debug.print("lastTrigger "); Debug.println(lastTrigger);
        Debug.print("millis "); Debug.println(millis());
        Debug.print("millis - lastTrigger = "); Debug.println(millis() - lastTrigger);
      }
    }
    motion = true; // setting 'motion' to true ensures if statement runs only once
    Debug.println("End of 1st if");
  }


  // Turn off MAX7219 and led after time limit assigned in timeSeconds variable
  if (startTimer) { 
    Debug.println("Motion stopped...");
    Debug.print("motionSensor = "); Debug.println(digitalRead(motionSensor));

    digitalWrite(ledEyes, LOW);
    startTimer = false;
    motion = false;
    MAX7219_clear_display();
  }









  // ---------------------------------------------
/*
  // Display a static 8x8 image from a 1D array
  display_byte_array(smiley);
  delay(1000);

  // MAX7219_clear_display();
*/
  // ---------------------------------------------
/*
  // Displays a static 8x8 image from one row of a 2D array
  display_byte_2D_array(dogface, 0);
  delay(200);
  display_byte_2D_array(dogface, 1);
  delay(200);
  display_byte_2D_array(dogface, 0);
  delay(200);
  display_byte_2D_array(dogface, 1);
  delay(200);
  display_byte_2D_array(dogface, 0);
  delay(200);
  display_byte_2D_array(dogface, 1);
  delay(200);
  display_byte_2D_array(dogface, 0);
  delay(200);
  display_byte_2D_array(dogface, 1);
  delay(200);
*/
  // ---------------------------------------------
/*
  // Displays scrolling 'invader' array, with delay (array name)
  scrolling_2D_array_with_delay(invader1);
  delay(1000);
*/
  // ---------------------------------------------
/*
  // Displays scrolling 'invader' array, with delay (array name)
  scrolling_2D_array_with_delay(invader2);
  delay(1000);
 */
  // ---------------------------------------------
/*
  // Displays scrolling array, (array name, number of rows)
  scrolling_2D_array(LSU, 28);
  delay(1000); // delay until next function is run

  MAX7219_clear_display();

  scrolling_2D_array(running_man_LR, 21);
  delay(1000); // delay until next function is run

  MAX7219_clear_display();

  scrolling_2D_array(dancing_man_RL, 45);
  delay(1000); // delay until next function is run

  MAX7219_clear_display();

  scrolling_2D_array(heart, 57);
  delay(1000); // delay until next function is run
*/
  // ---------------------------------------------
/*
  MAX7219_clear_display();
  // Display one pixel, (row 0 to 7, column 0 to 7, ON = 1)
  MAX7219_set_pixel(7, 0, 1);
  delay(1000);

  MAX7219_clear_display();
*/
  // ---------------------------------------------
/*
  // displays random pixels. Time of while loop is set in function
  // with '100' equaling 1 second. delay(10) x 100 = 1000ms
  random_pixels(500);
  delay(1000); // delay until next function is run

  // ---------------------------------------------
*/



}

// = = = = = = = = = = = FUNCTIONS = = = = = = = = = = = = = 

void MAX7219_init() {
  // PB0 -> DIN, PB1 -> CS, PB2 -> CLK
  DDRB |= (1 << PB0) | (1 << PB1) | (1 << PB2); // Set pins as outputs
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
  MAX7219_write(0x09, 0); // Decode-Mode Register: No decode for digits 7–0
  MAX7219_write(0x0A, 8); // Intensity Register: Format 0 to 16
  MAX7219_write(0x0B, 7); // Scan-Limit Register: Display digits 0 1 2 3 4 5 6 7
  MAX7219_write(0x0C, 1); // Shutdown Register: Normal Operation, turn on MAX7219
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

void SPI_send(uint8_t data) { // variable 'data' is used in 'command_byte' and 'data_byte'
  for (uint8_t i = 0; i < 8; i++) { // try uint8_t i = 8; i >= 1; i-- (no difference) // Loop 8 times to shift out MSB first, of byte to MAX7219
    PORTB &= ~(1 << PB2);           // Set CLK to LOW
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    if (data & 0b10000000) {        // Mask the MSB 0x80 of the data
      PORTB |= (1 << PB0);          // Set DIN to HIGH if MSB = 1
    }
    else (PORTB &= ~(1 << PB0));    // Set DIN to LOW if MSB = 0
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    PORTB |= (1 << PB2);            // Set CLK to HIGH
    data = data << 1;               // Shift data to left 1 bit
  }
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

// Write to a row, 1 through 8
void MAX7219_write(uint8_t command_byte, uint8_t data_byte) {
  PORTB &= ~(1 << PB1); // CS set LOW to enable MAX7219
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  SPI_send(command_byte); // Send command byte
  SPI_send(data_byte); // Send data byte
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  PORTB |= (1 << PB1); // CS set HIGH to disable MAX7219
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

void MAX7219_clear_display() {
  uint8_t i;
  for (i = 0; i < 8; i++) {
    MAX7219_write(i + 1, 0);
  }
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

// Display one pixel, (row 0 to 7, column 0 to 7, ON = 1)
void MAX7219_set_pixel(uint8_t row, uint8_t col, bool value) {
	uint8_t data;
	if (row > 7 || col > 7)
		return;

	data = 1 << col;
	if (value) {
		MAX7219_state[row] |= data;
	}
  else {
		MAX7219_state[row] &= ~data;
	}

	MAX7219_write(row + 1, MAX7219_state[row]);
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

// Display a static 8x8 image from a 1D array
void display_byte_array(uint8_t image[8]) {
  uint8_t i;
  for (i = 0; i < 8; i++) {
    MAX7219_write(i + 1, image[i]); // Write to rows 1 through 8
  }
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

// Displays a static 8x8 image from one row of a 2D array
void display_byte_2D_array(uint8_t image[][8], uint8_t num) {
  uint8_t i;
  for (i = 0; i < 8; i++) {
    // MAX7219_write(i + 1, image[num][i]); // Use without PROGMEM
    MAX7219_write(i + 1, pgm_read_byte(&image[num][i])); // Use with PROGMEM
  }
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

// Displays scrolling 'invader' array, with delay (array name)
void scrolling_2D_array_with_delay(uint8_t image[][8]) {
for (uint8_t j = 0; j < 21; j++) {
    if (j == 9 || j == 10 || j == 11 || j == 12) {
      delay(250);
    }
    else {
      delay(100);
    }
    for (uint8_t i = 0; i < 8; i++) {
      // MAX7219_write(i + 1, image[j][i]); // Use without PROGMEM
      MAX7219_write(i + 1, pgm_read_byte(&image[j][i])); // Use with PROGMEM
    }
  }
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

// Displays scrolling array, (array name, number of rows)
void scrolling_2D_array(uint8_t image[][8], uint8_t num) {
  for (uint8_t j = 0; j < num; j++) {
    delay(180);
    for (uint8_t i = 0; i < 8; i++) {
      //MAX7219_write(i + 1, image[j][i]); // Use without PROGMEM
      MAX7219_write(i + 1, pgm_read_byte(&image[j][i])); // Use with PROGMEM
    }
  }
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

// displays random pixels. Time of while loop is set in function
// with '100' equaling 1 second. delay(10) x 100 = 1000ms
void random_pixels(uint16_t count) {
  uint16_t num = 0;
  while (num < count) {
		randNum_row = random(8);
    randNum_col = random(8);
		MAX7219_set_pixel(randNum_row, randNum_col, ++pixel & 0x01);
		delay(10);
    num = num + 1;
	}
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
ATTINY8520PU