#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH   128
#define SCREEN_HEIGHT   64
#define OLED_RESET      -1
#define OLED_ADDR     0x3C

const int baudotPin   = 25;
const int baudotRxPin = 26;
const int bitTimeUs   = 22000;

const int encoderPinA = 32;
const int encoderPinB = 33;

const int modePin = 27;       // HIGH = RX monitor, LOW = GENERATOR
const int afskTestPin = 13;   // LOW = fixed AFSK test

const int afskPin = 14;
const int afskResolution = 8;
const int afskDuty = 128;

const int markFreqHz  = 2125;
const int spaceFreqHz = 2975;

const uint8_t LTRS  = 0b11111;
const uint8_t FIGS  = 0b11011;
const uint8_t CR    = 0b01000;
const uint8_t LF    = 0b00010;
const uint8_t BELL  = 0b00101;
const uint8_t MSTOP = 0b10100;

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

enum RunMode {
  MODE_RX_MONITOR,
  MODE_GENERATOR
};

struct BaudotEntry {
  char c;
  uint8_t code;
};

bool figuresMode   = false;
bool rxFiguresMode = false;

volatile int encoderRaw = 0;
volatile long encoderCount = 0;
volatile uint8_t lastEncoded = 0;

long lastDisplayedCount = 999999;
char lastRxChar = ' ';
bool displayNeedsUpdate = true;
bool lastRxLineState = HIGH;
RunMode currentMode;
bool lastAfskTestState = false;

const int rxHistoryLen = 21;
char rxHistory[rxHistoryLen + 1] = "                     ";

bool afskActive = false;
bool afskMarkState = true;
bool afskToneInitialized = false;

RunMode readMode();
bool afskTestEnabled();
const char* modeName(RunMode mode);
void IRAM_ATTR updateEncoder();
void showStatus();
void runAfskTestStep();
void runReceiveMonitorStep();
void runGeneratorOnce();

void setup()
{
  Serial.begin(115200);

  pinMode(baudotPin, OUTPUT);
  digitalWrite(baudotPin, HIGH);

  pinMode(baudotRxPin, INPUT_PULLUP);
  lastRxLineState = digitalRead(baudotRxPin);

  pinMode(modePin, INPUT_PULLUP);
  pinMode(afskTestPin, INPUT_PULLUP);
  lastAfskTestState = afskTestEnabled();

  pinMode(encoderPinA, INPUT_PULLUP);
  pinMode(encoderPinB, INPUT_PULLUP);

  lastEncoded = (digitalRead(encoderPinA) << 1) | digitalRead(encoderPinB);

  attachInterrupt(digitalPinToInterrupt(encoderPinA), updateEncoder, CHANGE);
  attachInterrupt(digitalPinToInterrupt(encoderPinB), updateEncoder, CHANGE);

  if (!ledcAttach(afskPin, markFreqHz, afskResolution)) {
    Serial.println("LEDC attach failed on AFSK pin");
  }
  ledcWrite(afskPin, 0);

  Wire.begin(21, 22);

  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    Serial.println("OLED non trovato");
    while (true) {}
  }

  currentMode = readMode();

  display.clearDisplay();
  display.display();
  showStatus();

  Serial.println("Baudot utility sketch starting...");
  Serial.print("Initial mode: ");
  Serial.println(modeName(currentMode));
  Serial.print("AFSK on GPIO ");
  Serial.print(afskPin);
  Serial.print("  MARK=");
  Serial.print(markFreqHz);
  Serial.print(" Hz  SPACE=");
  Serial.print(spaceFreqHz);
  Serial.println(" Hz");
}

void loop()
{
  RunMode newMode = readMode();
  if (newMode != currentMode) {
    currentMode = newMode;
    displayNeedsUpdate = true;
    Serial.print("\nMode changed to: ");
    Serial.println(modeName(currentMode));
  }

  bool afskNow = afskTestEnabled();
  if (afskNow != lastAfskTestState) {
    lastAfskTestState = afskNow;
    displayNeedsUpdate = true;
    Serial.print("AFSK test ");
    Serial.println(afskNow ? "ON" : "OFF");
  }

  long countCopy;
  noInterrupts();
  countCopy = encoderCount;
  interrupts();

  if (countCopy != lastDisplayedCount) {
    lastDisplayedCount = countCopy;
    displayNeedsUpdate = true;
    Serial.print("Encoder count = ");
    Serial.println(countCopy);
  }

  if (displayNeedsUpdate) {
    displayNeedsUpdate = false;
    showStatus();
  }

  if (afskNow) {
    runAfskTestStep();
  } else {
    if (currentMode == MODE_RX_MONITOR) {
      runReceiveMonitorStep();
    } else {
      runGeneratorOnce();
    }
  }
}