#include <Arduino.h>
#include <SoftwareSerial.h>
#include "lin_frame.h"

#define IR_ARDUINO_PIN PIN_PA0 // Digital pin 10
#define LED_PIN        PIN_PA7 // Digital pin 3

#define RX_PIN         PIN_PB2 // Digital pin 2
#define TX_PIN         PIN_PB0 // Digital pin 0 (Unused dummy pin or LIN TX)
#define FAULT_PIN      PIN_PB1 // Digital pin 1
#define CS_PIN         PIN_PB3 // Digital pin 11

#define SYN_FIELD 0x55
#define SWM_ID 0x20

SoftwareSerial LINBusSerial(RX_PIN, TX_PIN);

// RC-6 timing constants
// 1 time unit (1t) = 444us
static const uint16_t RC6_T = 444;

// Mark = LOW on wire (simulating TSOP receiving an IR burst)
void sendMark(uint16_t us) {
  digitalWrite(IR_ARDUINO_PIN, LOW);
  delayMicroseconds(us);
}

// Space = HIGH on wire (simulating TSOP idle)
void sendSpace(uint16_t us) {
  digitalWrite(IR_ARDUINO_PIN, HIGH);
  delayMicroseconds(us);
}

// Send a single RC-6 bit using Manchester encoding
void sendRC6Bit(uint8_t bit, uint8_t width) {
  if (bit) {
    sendMark(RC6_T * width);
    sendSpace(RC6_T * width);
  } else {
    sendSpace(RC6_T * width);
    sendMark(RC6_T * width);
  }
}

// Send RC-6 Mode 6A (MCE) frame
void sendRC6_MCE(uint32_t data, uint8_t toggle) {
  // Disable interrupts to ensure precise IR timing
  noInterrupts();

  // Leader: 6t mark + 2t space
  sendMark(RC6_T * 6);
  sendSpace(RC6_T * 2);

  // Start bit: always 1
  sendRC6Bit(1, 1);

  // Mode bits: 1, 1, 0 (mode 6)
  sendRC6Bit(1, 1);
  sendRC6Bit(1, 1);
  sendRC6Bit(0, 1);

  // Toggle bit (double width = 2t per half-bit)
  sendRC6Bit(toggle & 1, 2);

  // 32 data bits, MSB first
  for (int i = 31; i >= 0; i--) {
    sendRC6Bit((data >> i) & 1, 1);
  }

  // Final return to idle state (HIGH)
  sendSpace(1000); 

  // Re-enable interrupts
  interrupts();
  
  // Wait to let receiver process
  delay(40); 
}

uint32_t get_mce_code(uint8_t button) {
  switch (button) {
    case 1: return 0x800f041e; // UP
    case 2: return 0x800f041f; // DOWN
    case 3: return 0x800f0420; // LEFT
    case 4: return 0x800f0421; // RIGHT
    case 5: return 0x800f0422; // ENTER / OK
    case 6: return 0x800f0423; // BACK
    case 7: return 0x800f046f; // F12 (Shutdown)
    default: return 0;
  }
}

void send_ir_for_button(uint8_t button, uint8_t toggle) {
  uint32_t code = get_mce_code(button);
  if (code == 0) return;
  
  digitalWrite(LED_PIN, HIGH); // Turn debug LED ON
  sendRC6_MCE(code, toggle);
  digitalWrite(LED_PIN, LOW);  // Turn debug LED OFF
}

byte b, n;
LinFrame frame;

unsigned long last_frame_time = 0;
uint8_t current_button = 0;
uint8_t toggle_bit = 0;
unsigned long last_ir_send_time = 0;
unsigned long last_lin_activity_time = 0;
bool is_car_on = false;

void process_button_state(uint8_t active_button) {
  unsigned long now = millis();
  
  if (active_button != 0) {
    if (current_button == 0) {
      // Button was just pressed!
      current_button = active_button;
      toggle_bit ^= 1; // Toggle bit alternates on each new press
      send_ir_for_button(current_button, toggle_bit);
      last_ir_send_time = now;
    } else if (current_button == active_button) {
      // Button is being held down!
      // Send repeat code every 250ms
      if (now - last_ir_send_time >= 250) {
        send_ir_for_button(current_button, toggle_bit);
        last_ir_send_time = now;
      }
    } else {
      // A different button was pressed!
      current_button = active_button;
      toggle_bit ^= 1;
      send_ir_for_button(current_button, toggle_bit);
      last_ir_send_time = now;
    }
  } else {
    // Idle state
    current_button = 0;
  }
  
  last_frame_time = now;
}

void handle_frame() {
  if (frame.get_byte(0) != SWM_ID)
    return;

  if (!frame.isValid())
    return;

  // Extract the data bytes
  // SWM button frame has 4 data bytes
  uint8_t d0 = frame.get_byte(1);
  uint8_t d1 = frame.get_byte(2);

  uint8_t active_button = 0;
  
  if (d0 & 0x01) active_button = 1;      // UP
  else if (d0 & 0x02) active_button = 2; // DOWN
  else if (d0 & 0x04) active_button = 3; // LEFT
  else if (d0 & 0x08) active_button = 4; // RIGHT
  else if (d1 & 0x08) active_button = 5; // ENTER / OK
  else if (d1 & 0x01) active_button = 6; // BACK

  process_button_state(active_button);
}

void setup() {
  pinMode(IR_ARDUINO_PIN, OUTPUT);
  digitalWrite(IR_ARDUINO_PIN, HIGH); // Idle state (HIGH)

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW); // LED OFF

  // Enable MCP2004
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH);

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

  LINBusSerial.begin(9600);
  frame = LinFrame();
  last_frame_time = millis();
  last_lin_activity_time = millis();
}

void loop() {
  if (LINBusSerial.available()) {
    last_lin_activity_time = millis();
    if (!is_car_on) {
      is_car_on = true;
    }
    b = LINBusSerial.read();
    n = frame.num_bytes();

    if (b == SYN_FIELD && n > 2 && frame.get_byte(n - 1) == 0) {
      frame.pop_byte();
      handle_frame();
      frame.reset();
    } else if (n == LinFrame::kMaxBytes) {
      frame.reset();
    } else {
      frame.append_byte(b);
    }
  }

  // Timeout: if no LIN frames received for 200ms, assume no button is pressed
  if (millis() - last_frame_time > 200) {
    current_button = 0;
  }

  // Car off detection: if no LIN activity for 5 seconds, send F12 to trigger shutdown
  if (is_car_on && (millis() - last_lin_activity_time > 5000)) {
    is_car_on = false;
    for (int i = 0; i < 3; i++) {
      send_ir_for_button(7, i & 1);
      delay(100);
    }
  }
}
