#include "gameObjs.h"
#include "linmath.h"
#include "renderer.h"
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>

#define PI 3.141592653589793f
#define WIDTH 512
#define HEIGHT 512
#define PIX_COUNT (WIDTH * HEIGHT)

volatile sig_atomic_t stop = 0;
struct termios oldt;
void handle_sigint(int sig) { stop = 1; }

void restore_terminal() {
  tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
  printf("\033[?1049l");
}
static char encoding_table[] = {
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
    'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
    'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};

void base64encode(render_target *t, char *buff) {
  for (int i = 0; i < PIX_COUNT * 4; i += 4) {
    uint8_t r = t->pixels[i];
    uint8_t g = t->pixels[i + 1];
    uint8_t b = t->pixels[i + 2];
    int s = 0;
    s |= r << 16;
    s |= g << 8;
    s |= b;

    buff[i] = encoding_table[s >> 18];
    s &= 0777777;
    buff[i + 1] = encoding_table[s >> 12];
    s &= 07777;
    buff[i + 2] = encoding_table[s >> 6];
    s &= 077;
    buff[i + 3] = encoding_table[s];
  }
}

void setupKeyboard() {

  struct termios newt;

  // Save original terminal settings
  if (tcgetattr(STDIN_FILENO, &oldt) == -1) {
    perror("tcgetattr failed");
    exit(1);
  }

  // Copy old settings to newt
  newt = oldt;

  // Disable canonical mode (ICANON) and echo (ECHO)
  newt.c_lflag &= ~(ICANON | ECHO);
  newt.c_cc[VMIN] = 0;  // minimum bytes
  newt.c_cc[VTIME] = 0; // timeout (0 = no wait)

  // Apply new settings
  if (tcsetattr(STDIN_FILENO, TCSANOW, &newt) == -1) {
    perror("tcsetattr failed");
    exit(1);
  }
}

void printImage(char *buff) {
  int buffSize = PIX_COUNT * 4;
  int start = 0;
  int chunkSize = MIN(4096, buffSize - start);
  int notLast = start < buffSize - 4096;

  printf("\033_Gf=24,s=%d,v=%d,a=T,t=d,m=%d;%.*s\033\\", HEIGHT, WIDTH, notLast,
         chunkSize, buff);
  start += 4096;
  while (start < buffSize) {
    notLast = start < buffSize - 4096;
    int chunkSize = MIN(4096, buffSize - start);
    printf("\033_Gm=%d;%.*s\033\\", notLast, chunkSize, buff + start);
    start += 4096;
  }
  printf("\n");
}

int main(void) {
  signal(SIGINT, handle_sigint);
  printf("\033[?1049h\n");

  setupKeyboard();

  render_target target = {WIDTH, HEIGHT, malloc(PIX_COUNT * 4)};
  clearTarget(&target);

  vec4 *buffer = malloc(vertBufferSize());
  mat4x4 projMat = {};
  mat4x4 viewMat = {};
  mat4x4 drawMat = {};
  mat4x4_identity(viewMat);
  mat4x4_perspective(projMat, 45.0 / 180.0 * PI, 1, 2.f, 20.0f);

  const int buffSize = PIX_COUNT * 4;

  char *encodeBuff = malloc(buffSize);
  memset(encodeBuff, 0, buffSize);
  char lastKey;

  clock_t start = clock();
  clock_t now = clock();
  clock_t renderC;

  float deltaTime; // in seconds

  while (!stop) {
    now = clock();
    deltaTime = ((float)(now - start)) / CLOCKS_PER_SEC;
    start = now;
    printf("total time: %fms\n", deltaTime * 1000);
    printf("render time: %fms\n", ((float)renderC) / (0.001 * CLOCKS_PER_SEC));

    if (read(STDIN_FILENO, &lastKey, 1) == -1) {
      perror("read failed");
      stop = 1;
      continue;
    }

    switch (lastKey) {
    case 'w':
      rocket.velocity[1] = (rocket.velocity[1] * 0.9 + 20 * 0.1);
      break;
    case 's':
      mat4x4_translate_in_place(viewMat, 0, 0, 0.2);
      break;
    case 'a':
      mat4x4_translate_in_place(viewMat, -0.2, 0, 0);
      break;
    case 'd':
      mat4x4_translate_in_place(viewMat, 0.2, 0, 0);
      break;
    }

    lastKey = 0;

    rocket.velocity[1] +=
        (-4.0f + 0.5 * powf(MIN(rocket.velocity[1], 0.0), 2.0f)) * deltaTime;

    mat4x4_translate_in_place(rocket.transform, rocket.velocity[0] * deltaTime,
                              rocket.velocity[1] * deltaTime,
                              rocket.velocity[2] * deltaTime);
    // mat4x4_translate_in_place(viewMat, 0, 0, .1);
    mat4x4_mul(drawMat, projMat, rocket.transform);
    clearTarget(&target);
    renderC = clock();
    render(&target, &rocket.model, &drawMat, buffer);
    renderC = clock() - renderC;

    base64encode(&target, encodeBuff);
    printf("\033[H");
    printImage(encodeBuff);
    printf("\033[J");
  }

  restore_terminal();
}
