#include <Adafruit_NeoPixel.h>

#define LED_PIN 9

#define LED_COUNT 16

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
// 74HC595 Pins
#define dataPin 2   // DS pin of 74HC595
#define clockPin 3  // SHCP pin of 74HC595
#define latchPin 4  // STCP pin of 74HC595

// 74HC165 Pins
#define loadPin 5   // PL pin of 74HC165
#define clockEnablePin 6  // CE pin of 74HC165
#define dataPinIn 7  // QH pin of 74HC165
#define clockPinIn 8  // SHCP pin of 74HC165

#define n_rows 8 // must be even to decode the board
#define n_cols 8

typedef byte Board[n_rows]; // global variable updated by read_board()
typedef byte Decoded_Board[n_rows / 2][n_cols/2];

Board board;           // global variable updated by read_board()
Decoded_Board decoded_board; // global variable updated from board 

Decoded_Board test_board = {
  1, 2, 3, 4,
  5, 6, 7, 8,
  0, 0, 0, 0,
  0, 0, 0, 0
};
void setup() {
  //Neopixel Setup
  strip.begin();
  strip.show();

  // 74HC595 Setup
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);

  // 74HC165 Setup
  pinMode(loadPin, OUTPUT);
  pinMode(clockEnablePin, OUTPUT);
  pinMode(clockPinIn, OUTPUT);
  pinMode(dataPinIn, INPUT);

  Serial.begin(9600);
  Serial.println("Program starting.\n");
}

void loop() {
  // read_board(&board);   // Pass the board pointer to the function
  // //print_board(&board);  // Print out the board for debugging
  // decode_board(&board, &decoded_board);
  // print_decoded_board(&decoded_board);
  for(byte i = 0; i < 16; i++) {
    color_pixels(i);
    delay(500);
  }
}

void show_progress(Decoded_Board* d) {
  byte correct_pieces = 0;
  for(byte i = 0; i < n_rows/2; i++) {
    for(byte j = 0; j < n_cols/2; j++) {
      if(4*i+j + 1 == (*d)[i][j]) {
        correct_pieces += 1;
      }
    }
  }
  strip.setPixelColor(0, round(255*(15-correct_pieces)/15), round(255*correct_pieces/15), 0); //(pixel, r, g, b)
  strip.show();
}

void color_pixels(byte correct) {
  float r = (255*pow(15-correct, 2))/(15*15);
  float g = (255*pow(correct, 2))/(15*15);
  float b = (255*2*(correct)*(15-correct))/15;
  strip.setPixelColor(0, round(r), round(g), round(b)); //(pixel, r, g, b)
  strip.show();
}

void print_decoded_board(Decoded_Board* d) {
  for(byte i = 0; i < n_rows/2; i++) {
    for(byte j = 0; j < n_cols/2; j++) {
      Serial.print((*d)[i][j]); Serial.print("  ");
    }
    Serial.println();
  }
  Serial.println("\n");
}

void decode_board(Board* b, Decoded_Board* d) {
  for(byte i = 0; i < n_rows/2; i++) {
    for(byte j = 0; j < n_cols/2; j++) {
      byte raw =  (1&(*b)[2*i]>>(2*j))<<0 |
                    (1&(*b)[2*i]>>(2*j+1))<<1 |
                    (1&(*b)[2*i+1]>>(2*j))<<2 |
                    (1&(*b)[2*i+1]>>(2*j+1))<<3;
      (*d)[i][j] = 15 - raw;
    }
  }
}

void read_board(Board* b) {
  for (byte i = 0; i < n_rows; i++) {
    writeShiftRegister_595(1 << i);
    (*b)[i] = readShiftRegister_165();
  }
}

void print_board(Board* b) {
  for (byte i = 0; i < n_rows; i++) {
    //Serial.print("Row "); Serial.print(i); Serial.print(": ");
    print_byte((*b)[i]);
  }
  Serial.println("\n");
}

void print_byte(byte b) {
  for (byte j = 0; j < 8; j++) {
    Serial.print(b >> j & 1);
    Serial.print("  ");
  }
  Serial.println();
}

byte readShiftRegister_165() {
  byte inputBits = 0;

  // Load the parallel inputs into the shift register
  digitalWrite(loadPin, LOW);
  delayMicroseconds(5);
  digitalWrite(loadPin, HIGH);

  // Read the serial data
  for (int i = 0; i < 8; i++) {
    digitalWrite(clockPinIn, LOW);
    delayMicroseconds(5);
    inputBits |= digitalRead(dataPinIn) << (7 - i);
    digitalWrite(clockPinIn, HIGH);
    delayMicroseconds(5);
  }

  return inputBits;
}

void writeShiftRegister_595(byte data) {
  // Prepare to shift data
  digitalWrite(latchPin, LOW);

  // Shift out the data
  shiftOut(dataPin, clockPin, MSBFIRST, data);

  // Lock the data into the output
  digitalWrite(latchPin, HIGH);
}
74HC595
74HC165