#include "main.h"
#include "parser.h"


tetromino playfield[FULL_HEIGHT][FULL_WIDTH] = {};

uint8_t level = 0;
uint16_t all_cleared_lines = 0;
uint32_t score = 0;


int main () {
  

  /* Setting up the seed */
  
  srand(time(NULL));


  /* Setting up terminal with termios */
  
  struct termios old, new;

  tcgetattr(STDIN_FILENO, &old);
  new = old;

  new.c_lflag &= ~(ECHO | ICANON);
  new.c_cc[VMIN] = 0;
  new.c_cc[VTIME] = 0;

  tcsetattr(STDIN_FILENO, TCSANOW, &new);

  struct winsize init_size, current_size;
  ioctl(STDOUT_FILENO, TIOCGWINSZ, &init_size);
  current_size = init_size;

  printf("\033[?25l\033[?1049h\033[2J\033[H");


  /* Polling */

  struct pollfd polling[1];

  polling[0].fd = 0;
  polling[0].events = POLLIN;


  /* Init */

  init_field();
 
  bool blink[FIELD_HEIGHT] = {};

  bool is_piece = false;

  bool paused = false;
  
  uint16_t lines_to_level = 0;

  uint16_t pot_score_lines = 0;
  uint16_t pot_score_fall = 0;

  bool add_score = false;

  piece current_piece = {};
  piece next_piece = init_piece((rand() % Z_PIECE) + 1);


  /* Time keeping */

  struct timespec start, current;
  clock_gettime(CLOCK_MONOTONIC_RAW, &start);

  uint64_t time_acc = 0;

  uint16_t tick_fall = 0;
  uint16_t tick_blink = 0;
  uint16_t tick_spin = 0;
  uint16_t tick_fall_prot = 0;
  uint16_t tick_wait_key = 0;

  uint8_t i = 0;
  bool is_tetris = false;

  /* Game Loop */

  while (true) {

    int8_t move = 0;
    bool down = false;
    bool draw = false;
    bool rotate = false;
    bool key_lock = false;


    /* Polling for input */

    int is_poll = poll(polling, 1, paused ? -1 : 17);


    /* Dealing with Ticks */

    uint16_t ticks = 0;

    {

      clock_gettime(CLOCK_MONOTONIC_RAW, &current);

      uint64_t time_diff = 
        (current.tv_sec - start.tv_sec) * 1000000000ULL + 
        (current.tv_nsec - start.tv_nsec) + time_acc;

      if (!paused) {

        while (time_diff >= FRAME) {

          time_diff -= FRAME;
          ticks++;
        }

        time_acc = time_diff;
      }

      start = current;
    }


    /* Take care of the input */

    if (is_poll > 0 && polling[0].revents & POLLIN) {

      uint8_t buf[256] = {};

      int read_len;

      if ((read_len = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {

        if (add_to_buf(buf, read_len)) {

          keypress input = UNKNOWN;

          while (input != NO_KEY) {

            input = parse_input();
 
            if (input == INCOMPLETE) {
              if(tick_wait_key >= KEY_PROTECTION) {
                break_unfinished();
              } else {
                key_lock = true;
                break;
              }

            } else {

              switch (input) {
                case PAUSE:
                  paused = !paused; 
                  break;
                case QUIT:
                  goto end;
                  break;
                case UP_ARROW:
                  rotate = true; 
                  break;
                case DOWN_ARROW:
                  down = true;
                  break;
                case RIGHT_ARROW:
                  move = 1;
                  break;
                case LEFT_ARROW:
                  move = -1;
                  break;
                default:
                  break;
              }

              tick_wait_key = 0;
            }
          }
        } else {
          break; /* TODO error */
        }
      }
    }

    if (key_lock)
      tick_wait_key++;

    if (paused) {
      draw_pause();
      continue;
    }


    /* Dealing with cleared Lines */

    if (is_tetris) {


      tick_blink += ticks;

      while (tick_blink >= BLINK && i < 4) { 
 
        tick_blink -= BLINK;

        draw_board(current_piece, next_piece, (i % 2 == 0) ? blink : NULL, is_piece);
        i++;
      }
      

      if (i == 4) {
      
        int8_t j, k;

        for (j = 0; j < FIELD_HEIGHT; j++) {
          if (blink[j]) {

            for (k = j; k > 0; k--) {
              int8_t l;
          
              for (l = 0; l < FIELD_WIDTH; l++) {
                playfield[k][l + BORDER_WIDTH] = playfield[k - 1][l + BORDER_WIDTH];
              }
            }

            all_cleared_lines++;
          }
        }

        i = 0;
        is_tetris = false;
      }

      continue;
    }

    /* Adding score */

    if (add_score) {
      score += pot_score_fall + pot_score_lines;
      pot_score_fall = pot_score_lines = 0;
      add_score = false;
    }

    /* Creates new piece */

    if (!is_piece) {
      current_piece = next_piece;
      next_piece = init_piece((rand() % Z_PIECE) + 1);
      is_piece = true;
      draw = true;

      tick_fall = tick_blink = tick_spin = tick_fall_prot = 0;
    }


    /* Move piece */

    if (move != 0) {
      if (
        current_piece.x + move >= 0 && 
        current_piece.x + move + current_piece.size <= FULL_WIDTH
      ) {
       
        piece moved_piece = current_piece;
        moved_piece.x += move;

        if (valid_position(moved_piece)) {
          current_piece.x += move;
          draw = true;
        }
      }
    }


    /* Piece rotates */

    if (tick_spin < SPIN)
      tick_spin += ticks;

    if (rotate && current_piece.positions > 1 && tick_spin >= SPIN) {
      
      piece rotated = current_piece;

      rotated.rotation = (current_piece.rotation + 1) % (current_piece.positions);
      
      if (valid_position(rotated)) {
        current_piece = rotated;
        draw = true;
      }

      tick_spin = 0;
    } 
    

    /* Piece falls */

    tick_fall += ticks;
  
    if (tick_fall_prot < FALL_PROTECTION)
      tick_fall_prot += ticks;

    uint8_t fall_delay = level_delay(); 

    while (tick_fall >= fall_delay || (down && tick_fall_prot >= FALL_PROTECTION)) {
      current_piece.y++;
      

      if (tick_fall >= fall_delay) {
        tick_fall -= fall_delay;
      } else {
        tick_fall = 0;
      }

      if (down) {
        pot_score_fall++;
      } else {
        pot_score_fall = 0;
      }
      
      down = false;

      draw = true;
    }
    


    /* Looks for stuck piece */

    if (!valid_position(current_piece)) {

      int8_t j, k;

      for (j = 0; j < current_piece.size; j++) {
        for (k = 0; k < current_piece.size; k++) {
          
          if (current_piece.piece[j][k]) {
            
            coord solid = find_direction(current_piece, k, j);

            playfield[current_piece.y - 1 + solid.y][current_piece.x + solid.x] = current_piece.name;
          }
        }
      }
      is_piece = false;
      draw = true;

      add_score = true;
    }


    /* Looks for cleared line */

    int8_t j, k;
    
    {

      uint8_t current_lines = 0;
      
      for (j = FIELD_HEIGHT - 1; j >= 0; j--) {

        uint8_t blocked = 0;


        for (k = 0; k < FIELD_WIDTH; k++) {
          if (playfield[j][k + BORDER_WIDTH] != EMPTY)
            blocked++;
        }

        if (blocked == 10) {
          
          blink[j] = true;
          is_tetris = true;
          lines_to_level++;
          current_lines++;
        } else {
          blink[j] = false;
        }
      }

      if (current_lines > 0) {
        pot_score_lines += line_points_add(current_lines, level);
        add_score = true;
      }
    }

    /* Level up */

    if (lines_to_level >= LINES_LEVEL && level < MAX_LEVEL) {
      level++;
      lines_to_level -= LINES_LEVEL;
    }

    /* Looks for game over */

    for (j = 0; j < FIELD_WIDTH; j++) {
      if (playfield[TOP_GAP][j + BORDER_WIDTH] > 0) {
        draw_board(current_piece, next_piece, NULL, is_piece);
        sleep(2);
        printf("\033[2J\033[H");
        printf("HAHA LOSER\n");
        sleep(1);
        goto end;
      }
    }

    if (draw)
      draw_board(current_piece, next_piece, NULL, is_piece);
  }

end:

  printf("\033[?1049l\033[?25h");

  tcsetattr(STDIN_FILENO, TCSANOW, &old);

  return 0;
}
