// Danijel Ptičar

#define OP_DECODEMODE  9
#define OP_INTENSITY   10
#define OP_SCANLIMIT   11
#define OP_SHUTDOWN    12
#define OP_DISPLAYTEST 15

#define leftButton A2
#define rightButton A4
#define rotateButton A3
#define downButton A1

int DIN = 11; //
int CLK = 13; //
int LOAD = 10; //

byte matrix[8];
byte piece[2];
int8_t pieceX, pieceY;
uint16_t pieceCount = 0;
uint8_t refreshDisplay = true;

void sendData( byte address, byte data)
{
  uint16_t bitmask;
  uint16_t pck;

  pck = address;
  pck <<= 8;
  pck |= data;

  for ( bitmask = 0x8000; bitmask; bitmask >>= 1)
  {
    /*
          if (bitmask & pck) PORTB |= 0b00001000; //digitalWrite(DIN, 1);
          else PORTB &= ~0b00001000; //digitalWrite(DIN, 0);

          PORTB |= 0b00100000; //digitalWrite(CLK, HIGH);
          PORTB &= ~0b00100000; //digitalWrite(CLK, LOW);
    */

    if (bitmask & pck) digitalWrite(DIN, 1);
    else digitalWrite(DIN, 0);

    digitalWrite(CLK, HIGH);
    digitalWrite(CLK, LOW);


  }
  digitalWrite(LOAD, HIGH);
  digitalWrite(LOAD, LOW);
}

void clearMatrix()  // brise matricu i lik
{
  piece[0] = 0b00000000;
  piece[1] = 0b00000000;
  for (int x = 0; x < 8; x++) matrix[x] = 0;
}

#define np 6
unsigned long Randomtime;
byte rotatable;
void createPiece()
{
  unsigned long time;
  time = Randomtime + millis();
  rotatable = 0;
  if (time % np == 0)
  {
    piece[0] = 0b00000011;
    piece[1] = 0b00000011;
  }
  if (time % np == 1)
  {
    piece[0] = 0b00000001; // left
    piece[1] = 0b00000011; // right
    rotatable = 1;
  }
  if (time % np == 2)
  {
    piece[0] = 0b00000001; // left
    piece[1] = 0b00000000; // right
  }

  if (time % np == 3)
  {
    piece[0] = 0b00000001; // left
    piece[1] = 0b00000001; // right
  }
  if (time % np == 4)
  {
    piece[0] = 0b00000011; // left
    piece[1] = 0b00000000; // right
  }
  if (time % np == 5)
  {
    piece[0] = 0b00000001; // left
    piece[1] = 0b00000010; // right
    rotatable = 1;
  }

  pieceX = 3;
  pieceY = 0;
}

byte checkCollision()
{
  if (matrix[pieceX]   & (piece[0] << pieceY)) return 1;
  if (matrix[pieceX + 1] & (piece[1] << pieceY)) return 1;
  return 0;
}


void rotate() // rotira lik
{
  byte bit1, bit2, bit3, bit4;
  if (!rotatable) return;
  bit1 = piece[0] & 0b00000010;
  bit2 = piece[0] & 0b00000001;
  bit3 = piece[1] & 0b00000010;
  bit4 = piece[1] & 0b00000001;
  piece[0] = piece[1] = 0x00;
  if (bit1) piece[0] |= 0b00000001;
  if (bit2) piece[1] |= 0b00000001;
  if (bit3) piece[0] |= 0b00000010;
  if (bit4) piece[1] |= 0b00000010;

}

byte checkBottomBorder()
{
  int8_t numberOfPixels = 0; // prebrojavam koliko ima pixela u liku
  for (byte bitmask = 0x80; bitmask; bitmask >>= 1) if (bitmask & piece[0]) numberOfPixels++;
  for (byte bitmask = 0x80; bitmask; bitmask >>= 1) if (bitmask & piece[1]) numberOfPixels++;

  // prebrojavam koliko ima pixela nakon shiftanja, ako je broj isti sve OK, inace je lik ispao van
  for (byte bitmask = 0x80; bitmask; bitmask >>= 1) if (bitmask & (piece[0] << pieceY)) numberOfPixels--;
  for (byte bitmask = 0x80; bitmask; bitmask >>= 1) if (bitmask & (piece[1] << pieceY)) numberOfPixels--;
  if (numberOfPixels != 0) return 1;
  return 0;
}

byte checkBordersLR()
{

  if (piece[0] && pieceX < 0) return 1;
  if (piece[1] && pieceX < -1) return 1;

  if (piece[0] && pieceX > 7) return 1;
  if (piece[1] && pieceX > 6) return 1;
  return 0;
}


void mergeMatrixPiece()
{
  matrix[pieceX]   |= (piece[0] << pieceY);
  matrix[pieceX + 1] |= (piece[1] << pieceY);
  piece[0] = 0;
  piece[1] = 0;
}


byte clearFullRow()
{
  byte bitmask = 0x80;
  byte bm1 = 0x00;
  for (bitmask = 0x80; bitmask; bitmask >>= 1)
  {
    byte pixelCount = 0;
    for (byte x = 0; x < 8; x++)
    {
      if (bitmask & matrix[x]) pixelCount++;
    }

    if (pixelCount == 8)
    {
      for (byte k = 0; k < 5; k++)
      {
        for (byte x = 0; x < 8; x++)
        {
          matrix[x] =  matrix[x] & ~bitmask;
        }
        drawMatrix();
        delay(50);
        for (byte x = 0; x < 8; x++)
        {
          matrix[x] =  matrix[x] | bitmask;
        }
        drawMatrix();
        delay(50);
      }

      // ovo brise pixele u punom redu
      for (byte x = 0; x < 8; x++) {
        matrix[x] = (matrix[x] & bm1) | ( (matrix[x] << 1) & (bm1 ^ 0xFF) );
      }
      return 1;
    } else bm1 = bm1 | bitmask;
  }
  return 0;
}

void drawMatrix()
{
  for (int x = 0; x < 8; x++)
  {
    if (x == pieceX) sendData(x + 1, matrix[x] | piece[0] << pieceY);
    else if (x == pieceX + 1) sendData(x + 1, matrix[x] | piece[1] << pieceY);
    else sendData(x + 1, matrix[x]);
  }

  refreshDisplay = false;
}

unsigned long time1;
byte intensity = 0;

void setup() {
  // put your setup code here, to run once:
  pinMode(DIN, OUTPUT);
  pinMode(CLK, OUTPUT);
  pinMode(LOAD, OUTPUT);
  digitalWrite(LOAD, LOW);

  pinMode(leftButton, INPUT_PULLUP);
  pinMode(rightButton, INPUT_PULLUP);
  pinMode(rotateButton, INPUT_PULLUP);
  pinMode(downButton, INPUT_PULLUP);



  sendData(OP_DISPLAYTEST, 0);
  sendData(OP_SCANLIMIT, 7);
  sendData(OP_DECODEMODE, 0);
  sendData(OP_SHUTDOWN, 1);
  for (int i = 0; i < 8; i++) sendData(i + 1, 0b00000000);
  sendData(OP_INTENSITY, 0);
  Serial.begin(57600);
  time1 = millis();
  clearMatrix();
  createPiece();
}

uint32_t buttonMillis = 0;
uint8_t buttonState[20] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
uint8_t checkButton(int bp)
{
  if (millis() - buttonMillis > 50)
    if (digitalRead(bp) == 0 && buttonState[bp] == 1)
    {
      buttonState[bp] = 0;
      buttonMillis = millis();
      return 1;
    }

  if (millis() - buttonMillis > 50)
    if (digitalRead(bp) == 1 && buttonState[bp] == 0)
    {
      buttonState[bp] = 1;
      buttonMillis = millis();
      return 0;
    }
  return 0;
}

#define startSpeed 900

unsigned long defSpeed = startSpeed;
byte pixelX = 0, pixelY = 0;
int16_t score = 0;
byte gameEND = 0;
unsigned long currentSpeed = startSpeed;

void loop() {
  // put your main code here, to run repeatedly:

  Randomtime = time1 = millis();
  score = 0;
  defSpeed = startSpeed;
  currentSpeed = startSpeed;
  clearMatrix();
  createPiece();
  gameEND = 0;
  pieceCount = 0;

  while (!gameEND)
  {
    if (refreshDisplay) drawMatrix();

    byte newCode = 0;
    if (checkButton(leftButton)) newCode = 3;
    if (checkButton(rightButton)) newCode = 4;
    if (checkButton(rotateButton)) newCode = 1;
    if (checkButton(downButton)) newCode = 2;

    Randomtime = millis();
    if ( newCode )
    {
      if (newCode == 1) {
        rotate();
        if (checkBordersLR() | checkCollision())  rotate(), rotate(), rotate();
      }
      if (newCode == 2) {
        currentSpeed = 100;
      }

      if (newCode == 3) { // left
        pieceX--;
        if (checkBordersLR() | checkCollision())  pieceX++;
      }

      if (newCode == 4) {// right
        pieceX++;
        if (checkBordersLR() | checkCollision())  pieceX--;
      }
      if (newCode == 5) sendData(OP_INTENSITY, intensity++);

      refreshDisplay = true;
    }


    if (millis() - time1 > currentSpeed)
    {
      pieceY++;

      if (checkBottomBorder()) {
        pieceY--;
        mergeMatrixPiece();
        for (int i = 0; i < 2; i++) score += clearFullRow(); // dovoljno 2 puta
        if (defSpeed > 300) defSpeed -= 25;
        else defSpeed -= 5;
        //Serial.print(currentSpeed); Serial.print(" "); Serial.print(pieceCount); Serial.print(" "); Serial.println(score);
        createPiece();
        pieceCount++;
        currentSpeed = defSpeed;
      }

      if (checkCollision()) {
        pieceY--;
        mergeMatrixPiece();
        for (int i = 0; i < 2; i++) score += clearFullRow();
        if (defSpeed > 300) defSpeed -= 25;
        else defSpeed -= 5;
        //Serial.print(defSpeed); Serial.print(" "); Serial.print(pieceCount); Serial.print(" "); Serial.println(score);
        createPiece();     // mora biti zajedno, cim kreiram lik da vidim dali lezi na necemu !
        pieceCount++;
        if (checkCollision()) gameEND = 1;
        currentSpeed = defSpeed;
      }

      time1 = millis();
      refreshDisplay = true;
    }
  }// gameEND


  clearMatrix();
  drawMatrix();
  if (score) {
    for (int x = 0; score; x++)
    {
      byte bitmask = 0x80;
      while (score && bitmask)
      {
        matrix[x] |= bitmask;
        bitmask >>= 1;
        score--;
        drawMatrix();
        delay(200);
      }
    }
    delay(4000);
  }
  delay(1000);
}