/**
 * Video Terminal
 * VT100 emulated by ESP32 + TV display + LK201 keyboard
 * 
 * TinTTY main sketch
 * by Nick Matantsev 2017
 *
 * Original reference: VT100 emulation code written by Martin K. Schroeder
 * and modified by Peter Scargill.
 */

#define SERIAL_KBD_BAUDRATE   4800 // hardcoded LK201 baud rate
#define SERIAL_KBD_RX_PIN     16 // hardcoded in Serial2
#define SERIAL_KBD_TX_PIN     17 // hardcoded in Serial2

#define SERIAL_TTY_BAUDRATE   9600 // configurable, depends on the TTY
#define SERIAL_TTY_CONFIG     SERIAL_8N1 // configurable, depends on the TTY
#define SERIAL_TTY_RX_PIN     18 // configurable
#define SERIAL_TTY_TX_PIN     19 // configurable

#include <SPI.h>

#include <Adafruit_GFX.h>

#include "tintty.h"

#define SerialTty Serial1
#define SerialKbd Serial2

#include "Keyboard.h"

#include "Display.h"
Display display;

int16_t scrolled = 0;

uint16_t make_bw_color(uint16_t color) {
  return ((color >> 8) | (color & 0xFF)) & 0xFF + 0xFF00;
}

#define FONT_W display.font_w
#define FONT_H display.font_h

struct tintty_display composite_display = {
  display.vw, // width
  display.vh, // height
  display.vw / display.font_w, // chars horizontal
  display.vh / display.font_h, // chars vertical

  // fill rect
  [=](int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color){
    display.fill_rect(x*FONT_W + display.ow, y*FONT_H + display.oh, w*FONT_W, h*FONT_H, make_bw_color(color));
  },

  // draw pixels
  [=](int16_t x, int16_t y, int16_t w, int16_t h, uint16_t *pixels){
    //tft.setAddrWindow(x, y, x + w - 1, y + h - 1);
    //tft.pushColors(pixels, w * h, 1);
    for (int px = 0; px < w; px++) {
      for (int py = 0; py < h; py++) {
        int i = py*w + px;
        //display.pixel(x + display.ow + px, y + display.oh + py, pixels[i]);
        //display.fill_rect(x*FONT_SCALE + display.ow + px*FONT_SCALE, y*FONT_SCALE + display.oh + py*FONT_SCALE, FONT_SCALE, FONT_SCALE, make_bw_color(pixels[i]));
      }
    }
  },

  // print character
  [=](int16_t col, int16_t row, uint16_t fg_color, uint16_t bg_color, char character){
    display.print_character(col, row - scrolled, make_bw_color(0), make_bw_color(0), ' ');
    display.print_character(col, row - scrolled, make_bw_color(fg_color), make_bw_color(bg_color), character);
  },

  // print cursor
  [=](int16_t col, int16_t row, uint16_t color){
    display.fill_rect(col*display.font_w + display.ow, (row+1 - scrolled)*display.font_h-1 + display.oh, display.font_w, 1, make_bw_color(color));
    //display.print_character(col, row, make_bw_color(color), 0, '_');
  },

  // scroll
  [=](int16_t offset){
    //tft.vertScroll(0, (ILI9341_HEIGHT - KEYBOARD_HEIGHT), offset);
    int16_t rows = display.vh / display.font_h;
    int16_t diff = (rows + offset - (scrolled % rows)) % rows;
    display.scroll(diff*display.font_h);
    scrolled += diff;
  }
};

/**
 * Keyboard callbacks.
 */
struct tintty_keyboard lk201_keyboard = {
    [=]() {
      Serial.print("<BELL RING> ");
      SerialKbd.print((char)LK_RING_BELL);
    }
};

void tty_keyboard_process()
{
  // read keyboard and send it to the host
  if (SerialKbd.available() > 0) {
    int key = SerialKbd.read();
    keyboard_handle_key(key);
  }
}

// buffer to test various input sequences
char *test_buffer = "-- \e[1mTinTTY\e[m --\r\n";
uint8_t test_buffer_cursor = 0;

void input_init() {};
void input_idle() {};

void setup() {
  // Debug port
  Serial.begin(115200);
  Serial.println("Running!");

  // TTY host
  SerialTty.begin(SERIAL_TTY_BAUDRATE, SERIAL_TTY_CONFIG, SERIAL_TTY_RX_PIN, SERIAL_TTY_TX_PIN, false, 100);

  // LK201 keyboard connected to pins 16 and 17
  SerialKbd.begin(SERIAL_KBD_BAUDRATE);
  
  display.setup();

  input_init();

  tintty_run(
    [=](){
      // peek idle loop
      while (true) {
        // first peek from the test buffer
        if (test_buffer[test_buffer_cursor]) {
          return test_buffer[test_buffer_cursor];
        }

        tty_keyboard_process();
        
        // fall back to normal blocking serial input
        if (SerialTty.available() > 0) {
          return (char)SerialTty.peek();
        }

        // idle logic only after peeks failed
        tintty_idle(&composite_display);
        input_idle();
      }
    },
    [=](){
      while(true) {
        // process at least one idle loop first to allow input to happen
        tintty_idle(&composite_display);
        input_idle();
        tty_keyboard_process();

        // first read from the test buffer
        if (test_buffer[test_buffer_cursor]) {
          return test_buffer[test_buffer_cursor++];
        }

        // fall back to normal blocking serial input
        if (SerialTty.available() > 0) {
          return (char)SerialTty.read();
        }
      }
    },
    [=](char ch){ SerialTty.print(ch); },
    &composite_display,
    &lk201_keyboard
  );
}

void loop() {
}
