#include <vectrex.h>
#include <assert.h>			
#include "controller.h"

static inline void set_scale(unsigned int scale) {/*VIA_t1_cnt_lo*/ *(volatile unsigned char *)0xD004 = scale;}

typedef struct table {
  int ch;
  char *code;
} table;

// we will be printing the partially-received sequence on screen and it will
// need a \0x80\0x00 termination, and we'll also be comparing that sequence
// against the morse table, so might as well add the 0x80 here statically
// rather than mess around with the string at runtime.

table morse[] = {
  {'A',".-\x80"},
  {'B',"-...\x80"},
  {'C',"-.-.\x80"},
  {'D',"-..\x80"},
  {'E',".\x80"},
  {'F',"..-.\x80"},
  {'G',"--.\x80"},
  {'H',"....\x80"},
  {'I',"..\x80"},
  {'J',".---\x80"},
  {'K',"-.-\x80"},
  {'L',".-..\x80"},
  {'M',"--\x80"},
  {'N',"-.\x80"},
  {'O',"---\x80"},
  {'P',".--.\x80"},
  {'Q',"--.-\x80"},
  {'R',".-.\x80"},
  {'S',"...\x80"},
  {'T',"-\x80"},
  {'U',"..-\x80"},
  {'V',"...-\x80"},
  {'W',".--\x80"},
  {'X',"-..-\x80"},
  {'Y',"-.--\x80"},
  {'Z',"--..\x80"},
  {'0',"-----\x80"},
  {'1',".----\x80"},
  {'2',"..---\x80"},
  {'3',"...--\x80"},
  {'4',"....-\x80"},
  {'5',".....\x80"},
  {'6',"-....\x80"},
  {'7',"--...\x80"},
  {'8',"---..\x80"},
  {'9',"----.\x80"},
  {'.', ".-.-.-\x80"},
  {',', "--..--\x80"},
  {'?', "..--..\x80"},
  {':', "---...\x80"},
  {'+', ".-.-.\x80"},
  {'-', "-....-\x80"},
  {'/', "-..-.\x80"},
  {'=', "-...-\x80"},
  {'\'', ".----.\x80"},
  {'(', "-.--.\x80"},
  {')', "-.--.-\x80"},
  {'_', "..--.-\x80"},
  {'!', "-.-.--\x80"},
  {'&', ".-...\x80"},
  {'"', ".-..-.\x80"},
  {';', "-.-.-.\x80"},
  {'$', "...-..-\x80"},
  {':', "---...\x80"},
  {'@', ".--.-.\x80"},
#define EM_DASH (-1)
  {-1, "-...-\x80"},     /* '<em-dash:-->' */
  {-2, "...-.\x80"},     /* '<understood:u>' */
#define ERROR (-3)
  {-3, "........\x80"},  /* '<error:e>' */
  {-4, "-.-\x80"},       /* '<invitation to transmit:k>' */
  {-5, ".-...\x80"},     /* '<wait:w>' */
  {-6, "...-.-\x80"},    /* '<end of work:]>' */
  {-7, "-.-.-\x80"},     /* '<start of transmission:[>' */
  {-8, "-.-...\x80"},    /* '<end of paragraph:\n>' */
#define SOS (-9)
  {-9, "...---...\x80"}, /* '<SOS>' */
  {0, 0}
};

#define MAX_LINES 9
#define LETTERS_PER_LINE 20
#define MAX_CHARS (1+LETTERS_PER_LINE+1+1)
static char line[MAX_LINES][MAX_CHARS];

int strcmp(char *a, char *b) {
  for (;;) {
    if (*a == *b) {
      if (*a == 0) return 0;
    } else return (int)(*a - *b);
    a++; b++;
  }
}

// there's a lack of checking of buffer lengths throughout this code.
// not really urgent. Will fix later.
#define LONGEST_SEQUENCE 30 // overkill
static char letter[1+LONGEST_SEQUENCE+1+1] = { 0x80, 0 }; // for the morse representation of one letter, being decoded.
char *letterp = letter;

int DECODE_LAST_LETTER(void) {
  // This is an expensive loop and could be optimised considerably by using a tree,
  // however at the moment it is fast enough to perform on every clock tick.
  int i = 0;
  for (;;) {
    if (morse[i].ch == 0) return 0;
    if (strcmp(letter, morse[i].code) == 0) return morse[i].ch;
    i += 1;
  }
}

void start_800hz_tone() {
}

void silence_800hz_tone() {
}

#define BASE 50  // increase this to move the text lower down the screen
int main(void) {
/*
  The length of a dot is 1 time unit.
  A dash is 3 time units.
  The space between symbols (dots and dashes) of the same letter is 1 time unit.
  The space between letters is 3 time units.
  The space between words is 7 time units.
 */
#define DOT_DURATION 2
#define DASH_DURATION 6
#define SYMBOL_GAP 2
#define LETTER_GAP 18U
#define WORD_GAP   80U  // 42U but stretched for beginners
#define NEWLINE_GAP 250U

  int i,j;
  unsigned int ticks = 0U;
  char *xword = &line[0][0];   // for the reconstructed ascii word.
#define word (xword+1)
  char *wordp;

  letterp = letter; letter[0] = 0x80; letter[1] = 0;
  xword[0] = ' '; xword[1] = ' ';
  wordp   = xword+1;   xword[2] = 0x80; xword[3]   = 0;
  for (i = 0; i < MAX_LINES; i++) {
    line[i][0] = ' '; // Print_Str_d needs a string of >= 2 characters to work properly!
    line[i][1] = ' ';
    line[i][2] = 0x80;
    line[i][3] = 0;
  }

// state machine:
#define WAITING_FOR_KEY_DOWN 1
#define COUNTING_GAP         2
#define WAITING_FOR_KEY_UP   3
  int state = WAITING_FOR_KEY_DOWN;

  for (;;) {
    Wait_Recal();
    check_buttons();
    set_scale(0x7F);

    // echo current letter being entered:
    Reset0Ref();
    Intensity_3F();
    Moveto_d(-24-BASE, -120); Draw_Line_d(0,20);Draw_Line_d(6,-10);Moveto_d(-6,10);Draw_Line_d(-6,-10);
    Intensity_7F();

    if (letter[0] != 0x80) {
      i = 0;;
      Reset0Ref();
      Moveto_d(-24-BASE, -90);
      while (letter[i] != 0x80) {
         Draw_Line_d(0, (letter[i] == '.' ? 2 : 6));
         Moveto_d(0, 6);
         i += 1;
      }
    }

    for (i = 0; i < MAX_LINES; i++) {
      if (line[i][1] != ' ' || line[i][2] != 0x80) {
        Reset0Ref(); Intensity_5F();
        Print_Str_d((i<<4)-BASE, -108, &line[i][0]);
      }
    }

    switch (state) {
      case WAITING_FOR_KEY_UP:

          if (button_1_4_held()) break; // keep waiting while key is pressed. Ticks will increase.

          silence_800hz_tone();
          // key was just now released.  How long was it?
          *letterp++ = (char)(ticks <= DASH_DURATION ? '.' : '-');
          *letterp = 0x80; letterp[1] = 0;

          ticks = 0L;
          state = COUNTING_GAP;
        break;

      case COUNTING_GAP:
        if (ticks == LETTER_GAP) { // end of letter gap. Could be longer. Or letter could be empty.
          int ch = DECODE_LAST_LETTER();
          if (ch > 0) {
            *wordp++ = (char)ch; wordp[0] = 0x80; wordp[1] = 0;
          } else if (ch == ERROR) {
            while ((wordp > word) && (wordp[-1] == ' ')) wordp -= 1;
            if (wordp > word) { // ........ means erase last character received.
              wordp -= 1; wordp[0] = 0x80; wordp[1] = 0;
              while ((wordp > word) && (wordp[-1] == ' ')) wordp -= 1;
            }
          } else if (ch == EM_DASH) {
            *wordp++ = (char)'-';
            *wordp++ = (char)'-';
            wordp[0] = 0x80; wordp[1] = 0;
          } else if (ch == SOS) {
            // Sent as a group with no gaps
            *wordp++ = (char)'S';
            *wordp++ = (char)'O';
            *wordp++ = (char)'S';
            wordp[0] = 0x80; wordp[1] = 0;
          }
          letterp = letter; letter[0] = 0x80; letter[1] = 0;
        } else if (ticks == WORD_GAP) { // word gap - extra long for beginners. Letter has already been saved.
          if ((wordp != word) && (wordp[-1] != ' ')) {
            *wordp++ = ' '; wordp[0] = 0x80; wordp[1] = 0;
          }
        } else if ((ticks == NEWLINE_GAP) && (wordp != word)) { // not a standard morse convention
          char *to = &line[0][0];
          wordp = xword;
          for (;;) {
            char c = *wordp++;
            *to++ = c;
            if (c == 0) break;
          }
          // scroll
          for (i = MAX_LINES-1; i > 0; i--) {
            for (j = 0; j < MAX_CHARS; j++) {
              line[i][j] = line[i-1][j];
            }
          }
          xword[0] = ' '; xword[1] = ' '; xword[2] = 0x80; xword[3] = 0;
          wordp = word; 
        } else if (ticks == 255U) {
          ticks = 254U; // maxed out. Will stay at 255 through every loop until key pressed again.
        }
        // fall through...

      case WAITING_FOR_KEY_DOWN:
        if (button_1_4_pressed()) {
          ticks = 0U;
          start_800hz_tone();
          state = WAITING_FOR_KEY_UP;
        }
        break;
    }

    ticks += 1U; // counting length of 'off' sequence.
  }
  return 0;
}