// MAX7219 Dot matrix array controlling.

// Present limitations:
//  - Maximum number of chained MAX7219 Dot Matrices is 32 (Untested).
//  - Sprite image columns width is 8Bits, rows height n (specified when the sprite is declared).

// To-Dos:
//  - Modify the sprite struct (And all functions that use it) to handle images of n 
//    rows high and m columns wide.
//  - (Completed) Implement yOffset in the displayImage() function.
//  - (Bug fix) Trailing pixels above the sprite when it descends.

#include <SPI.h>
#include "defines.h"

#define SS_PIN 53
// How long one row is in pixels.
#define displayLength 32

uint32_t displayBlock2[8] = {
  0b11111111111111111111111111111111,
  0b11111111111111111111111111111111,
  0b00011111111111111111111111111111,
  0b11111111111111111111111111111111,
  0b11111111111111111111111111111111,
  0b11111111111111111111111111111111,
  0b11111111111111111111111111111111,
  0b11111111111111111111111111111111,
};

uint8_t img1[8] = {
  0b00011000,
  0b00111100,
  0b01111110,
  0b11111111,
  0b11111111,
  0b01111110,
  0b00111100,
  0b00011000,
};

Sprite img2 = {
  8,
  8,
  {
  0b00011000,
  0b00111100,
  0b01111110,
  0b11100111,
  0b11100111,
  0b01111110,
  0b00111100,
  0b00011000,
  }
};

void maxTransferCMD(uint8_t address, uint8_t value) {
  digitalWrite(SS_PIN, LOW);
  SPI.transfer(address);      // Send address.
  SPI.transfer(value);        // Send the value.
  SPI.transfer(address);      // Send address.
  SPI.transfer(value);        // Send the value.
  digitalWrite(SS_PIN, HIGH); // Finish transfer.
}

void maxTransferDATA(uint8_t row, uint8_t sequence) {
  digitalWrite(SS_PIN, LOW);
  SPI.transfer(row);      // Send address.
  SPI.transfer(sequence);        // Send the value.
  digitalWrite(SS_PIN, HIGH); // Finish transfer.
}

void maxTransferDisplayBlock() {
  for(uint8_t row = 0; row < 8; row++){
    // Starts the transfer.
    digitalWrite(SS_PIN, LOW);
    // Sends the updated row for the entire length of the display.
    for(uint8_t displayIndex = 0; displayIndex < 32; displayIndex +=8){
      // The MAX7219 rows are 1 indexed instead of 0 indexed, so here we apply the offset.
      SPI.transfer(row+1);
      // Send the row byte for the displayIndex matrix (Furthest matrix from the MCU first).
      SPI.transfer((uint8_t) (displayBlock2[row] >> displayIndex));
    }
    // Finishes the transfer.
    digitalWrite(SS_PIN, HIGH);
  }
}

void displayBlockWithOffset(int8_t offset) {

  for(uint8_t row = 0; row < 8; row++){
    // Starts the transfer (Halts any visual changes to the row from this point, just changes
    // the input registers of the MAX7219).
    digitalWrite(SS_PIN, LOW);

    // Sends the updated row for the entire length of the display.
    // Furthest matrix from the MCU first, just because thats how the MAX7219 daisychaining works.
    for(uint8_t displayIndex = 0; displayIndex < 31; displayIndex +=8){
      // The MAX7219 rows are 1 indexed instead of 0 indexed, so here we apply the offset.
      SPI.transfer(row+1);

      // Send the row byte for the displayIndex matrix.
      // The bytes will be left/right shifted (If the offest has a negative or positive value, 
      // respectively) then clipped from a uint32_t (Unsigned long) to the size of a uint8_t 
      // before sending.
      if(offset < 0){
        SPI.transfer((uint8_t) ((displayBlock2[row] << (0-offset)) >> displayIndex));
      }
      else if(offset > 0){
        SPI.transfer((uint8_t) ((displayBlock2[row] >> offset) >> displayIndex));
      }
      else{
        SPI.transfer((uint8_t) (displayBlock2[row] >> displayIndex));
      }
    }

    // Finishes the transfer (Makes visible all changes we've just made to this entire-display-lengthed row by 
    // propagating the MAX7219 input register contents we just changed into display memory).
    digitalWrite(SS_PIN, HIGH);
  }
}

void displayImageWithOffset(uint8_t image[], uint8_t imageRows, int8_t xOffset, int8_t yOffset) {

  //Serial.println(xOffset);

  // The MAX7219 rows are 1 indexed instead of 0 indexed, so here we apply the offset.
  for(uint8_t row = 0; row < imageRows; row++){
    // Starts the transfer (Halts any visual changes to the row from this point, just changes
    // the input registers of the MAX7219).
    digitalWrite(SS_PIN, LOW);

    // Sends the updated row for the entire length of the display.
    // Furthest matrix from the MCU first, just because thats how the MAX7219 daisychaining works.
    for(uint8_t displayIndex = 0; displayIndex < (displayLength-1); displayIndex +=8){
      SPI.transfer(row+1);

      // Send the row byte for the displayIndex matrix.
      // The bytes will be left/right shifted (If the offest has a negative or positive value, 
      // respectively) then clipped from a uint32_t (Unsigned long) to the size of a uint8_t 
      // before sending.
      if(xOffset < 0){
        SPI.transfer(((uint32_t)image[row] << 0-xOffset) >> displayIndex);
      }
      else if(xOffset > 0){
        SPI.transfer(((uint32_t)image[row] >> xOffset) >> displayIndex);
      }
      else{
        SPI.transfer((uint32_t)image[row] >> displayIndex);
      }
    }

    // Finishes the transfer (Makes visible all changes we've just made to this entire-display-lengthed row by 
    // propagating the MAX7219 input register contents we just changed into display memory).
    digitalWrite(SS_PIN, HIGH);
  }
}

void displayImage(Sprite *img, int8_t xOffset, int8_t yOffset) {

  //Serial.println(xOffset);

  // The MAX7219 rows are 1 indexed instead of 0 indexed, so here we apply the offset.
  for(uint8_t row = 0; row < img->rows; row++){
    // Starts the transfer (Halts any visual changes to the row from this point, just changes
    // the input registers of the MAX7219).
    digitalWrite(SS_PIN, LOW);

    // Sends the updated row for the entire length of the display.
    // Furthest matrix from the MCU first, just because thats how the MAX7219 daisychaining works.
    for(uint8_t displayIndex = 0; displayIndex < (displayLength-1); displayIndex +=8){
      SPI.transfer(row+1);

      // Send the row byte for the displayIndex matrix.
      // The bytes will be left/right shifted (If the offest has a negative or positive value, 
      // respectively) then clipped from a uint32_t (Unsigned long) to the size of a uint8_t 
      // before sending.
      if(xOffset < 0){
        SPI.transfer(((uint32_t)img->pixels[row] << 0-xOffset) >> displayIndex);
      }
      else if(xOffset > 0){
        SPI.transfer(((uint32_t)img->pixels[row] >> xOffset) >> displayIndex);
      }
      else{
        SPI.transfer((uint32_t)img->pixels[row] >> displayIndex);
      }
    }

    // Finishes the transfer (Makes visible all changes we've just made to this entire-display-lengthed row by 
    // propagating the MAX7219 input register contents we just changed into display memory).
    digitalWrite(SS_PIN, HIGH);
  }
}

void displayImageY(Sprite *img, int8_t xOffset, int8_t yOffset) {

  uint8_t displayRow = 1;

  //Serial.println("----");

  if(yOffset>-1){
    // Start rendering the rows of the image, starting with the first row that is visible on screen.
    for(int8_t imgRow = yOffset; imgRow < img->rows; imgRow++){

      //Serial.println(imgRow);

      // Starts the transfer (Halts any visual changes to the row from this point, just changes
      // the input registers of the MAX7219).
      digitalWrite(SS_PIN, LOW);

      // Sends the updated row for the entire length of the display.
      // Furthest matrix from the MCU first, just because thats how the MAX7219 daisychaining works.
      for(uint8_t displayIndex = 0; displayIndex < (displayLength-1); displayIndex +=8){
        SPI.transfer(displayRow);

        // Send the row byte for the displayIndex matrix.
        // The bytes will be left/right shifted (If the offest has a negative or positive value, 
        // respectively) then clipped from a uint32_t (Unsigned long) to the size of a uint8_t 
        // before sending.
        if(xOffset < 0){
          SPI.transfer(((uint32_t)img->pixels[imgRow] << 0-xOffset) >> displayIndex);
        }
        else if(xOffset > 0){
          SPI.transfer(((uint32_t)img->pixels[imgRow] >> xOffset) >> displayIndex);
        }
        else{
          SPI.transfer((uint32_t)img->pixels[imgRow] >> displayIndex);
        }
      }

      // Finishes the transfer (Makes visible all changes we've just made to this entire-display-lengthed row by 
      // propagating the MAX7219 input register contents we just changed into display memory).
      digitalWrite(SS_PIN, HIGH);

      displayRow++;
    }
  }
  // If 0 or minus value
  else{
    // Start rendering the rows of the image, starting with the first row that is visible on screen.
    for(int8_t imgRow = 0; imgRow < img->rows; imgRow++){

      //Serial.println(imgRow);

      // Starts the transfer (Halts any visual changes to the row from this point, just changes
      // the input registers of the MAX7219).
      digitalWrite(SS_PIN, LOW);

      // Sends the updated row for the entire length of the display.
      // Furthest matrix from the MCU first, just because thats how the MAX7219 daisychaining works.
      for(uint8_t displayIndex = 0; displayIndex < (displayLength-1); displayIndex +=8){
        SPI.transfer(displayRow-yOffset);

        // Send the row byte for the displayIndex matrix.
        // The bytes will be left/right shifted (If the offest has a negative or positive value, 
        // respectively) then clipped from a uint32_t (Unsigned long) to the size of a uint8_t 
        // before sending.
        if(xOffset < 0){
          SPI.transfer(((uint32_t)img->pixels[imgRow] << 0-xOffset) >> displayIndex);
        }
        else if(xOffset > 0){
          SPI.transfer(((uint32_t)img->pixels[imgRow] >> xOffset) >> displayIndex);
        }
        else{
          SPI.transfer((uint32_t)img->pixels[imgRow] >> displayIndex);
        }
      }

      // Finishes the transfer (Makes visible all changes we've just made to this entire-display-lengthed row by 
      // propagating the MAX7219 input register contents we just changed into display memory).
      digitalWrite(SS_PIN, HIGH);

      displayRow++;
    }
  }
}

void setup() {
  Serial.begin(9600);

  pinMode(SS_PIN, OUTPUT);

  // Optionally reverse the SPI output bit order (Horizontally mirrors the displayed image).
  SPI.setBitOrder(MSBFIRST);
  SPI.begin();

  // Run test - All LED segments lit.
  //maxTransferCMD(MAX7219_TEST, 0x01);  delay(1000);
  //maxTransferCMD(MAX7219_TEST, 0x00);        // Finish test mode.
  maxTransferCMD(MAX7219_DECODE_MODE, 0x00); // Disable BCD mode.
  maxTransferCMD(MAX7219_BRIGHTNESS, 0x0F);  // Use highest intensity.
  maxTransferCMD(MAX7219_SCAN_LIMIT, 0x0f);  // Scan all digits.
  maxTransferCMD(MAX7219_SHUTDOWN, 0x01);    // Turn on chip.

  /////////////////////////////////////////////
  // Offset values and their effects:
  // Right shift (32) = Display block is off the right of the screen.
  // No Shift (0) = Display block fills the display.
  // Left shift (-32) = Display block is off the left side of the screen.

  // // Move from offscreen (Right) to off screen (Left).
  // for(int8_t i = -32; i <= img2.columns; i++){
  //   displayImage(&img2,i,0);
  //   delay(100);
  // }

  // // Move from offscreen (Left) to off screen (Right)).
  // for(int8_t i = img2.columns; i >= -displayLength; i--){
  //   displayImage(&img2,i,0);
  //   delay(100);
  // }

  // Move from offscreen (Below) to off screen (Above).
  for(int8_t i = 16; i >= -16; i--){
    displayImageY(&img2,-12,i);
    delay(100);
  }
  //////////////////////////////////////////////
}

void loop() {
}