/*
   3BC Language - Low level language,
   tiny virtual machine that works on computers and microcontrollers.
   https://github.com/RodrigoDornelles/3bc-lang
*/

#define ARDUINO_ARCH_AVR
#define _3BC_DISABLE_INTERPRETER
#define _3BC_ENABLE_CUSTOM

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include <util/atomic.h>
#include <util/delay.h>

#include "3bc.h"

// Constantes predefinidas
#define F_CPU           16000000
#define USART_BAUDRATE  9600
#define UBRR_VALUE      (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)
#define BLINK_DELAY_MS  1000
#define UPDATE_DELAY_MS 100

static int uart_putchar(char c, FILE *stream);
static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL,
                       _FDEV_SETUP_WRITE);

void uart_init()
{
    // Inicializa a USART
    UBRR0 = UBRR_VALUE;					     // Define a taxa de transmissão
    UCSR0B |= (1 << TXEN0);					 // Habilita o TX
    UCSR0B |= (1 << RXEN0);					 // Habilita o RX
    UCSR0B |= (1 << RXCIE0);				 // Interrupção completa do RX
    UCSR0C |= (1 << UCSZ01) | (1 << UCSZ01); // Define sem paridade, 1 bit de parada, dados de 8 bits
    stdout = &mystdout;
}

void init_timer1()
{
    TCCR1A = 0x00;
    TCCR1B = 0x00;
    TCNT1 = 0x0000;
    TCCR1A = 0xA2;                           // Fast 16 bit PWM
    TCCR1B = 0x1C;                           // Prescale /256
    ICR1 = 62500 - 1;                        // 62500 * 256 = 16x10^6
    TIMSK1 |= 0x01;                          // Overflow int

    // REMEMBER TO ENABLE GLOBAL INTERRUPTS AFTER THIS WITH sei(); !!!
}

int uart_putchar(char c, FILE *stream)
{
    if (c == '\n')
        uart_putchar('\r', stream);

    // Aguarda até que o registrador para escrita esteja disponível
    loop_until_bit_is_set(UCSR0A, UDRE0);

    // Escreve o byte no registrador
    UDR0 = c;
    return 0;
}

void print_char(char* buffer)
{
    fprintf(stdout, "%c", *buffer);
}

void port_init()
{
    // Define o pino 12 (bit 4 do PORTB) como saída
    DDRB |= 0B10000; // PORTB3
}

void init_millis(unsigned long f_cpu)
{
    unsigned long ctc_match_overflow;

    ctc_match_overflow = ((f_cpu / 1000) / 8); // When timer1 is this value, 1ms has passed

    // (Set timer to clear when matching ctc_match_overflow) | (Set clock divisor to 8)
    TCCR1B |= (1 << WGM12) | (1 << CS11);

    // High byte first, then low byte
    OCR1AH = (ctc_match_overflow >> 8);
    OCR1AL = ctc_match_overflow;

    // Enable the compare match interrupt
    TIMSK1 |= (1 << OCIE1A);

    // REMEMBER TO ENABLE GLOBAL INTERRUPTS AFTER THIS WITH sei(); !!!
}

void blink_test()
{
    PORTB ^= 0B10000; // Switch LED (PORTB4)
}

volatile unsigned long timer1_millis; 

unsigned long millis()
{
    unsigned long millis_return;
 
    // Ensure this cannot be disrupted
    ATOMIC_BLOCK(ATOMIC_FORCEON) {
        millis_return = timer1_millis;
    }

    return millis_return;
}

ISR(TIMER1_COMPA_vect)
{
  timer1_millis++;  
}

int main(void)
{
    uart_init();
    port_init();
    init_millis(F_CPU);
    sei();

    app_3bc_t VM1 = lang_3bc_init();
    app_3bc_t VM2 = lang_3bc_init();
    app_3bc_t VM3 = lang_3bc_init();
    app_3bc_t VM4 = lang_3bc_init();
    app_3bc_t VM5 = lang_3bc_init();    
    app_3bc_t VM6 = lang_3bc_init();
    app_3bc_t VM7 = lang_3bc_init();
    app_3bc_t VM8 = lang_3bc_init();
    app_3bc_t VM9 = lang_3bc_init();
    app_3bc_t VM10 = lang_3bc_init();   

    lang_io_call(tty_output, print_char);
    lang_fpga(MODE_CUSTOM_1, 1, &blink_test);

    unsigned long timerBlink = 0;
    unsigned long timerUpdate = 0;

    char msg[] = "Hello, 3bc-lang! ;)\n";

    lang_line(MODE, NILL, 2);
    lang_line(NILL, NILL, 1);

    for (uint8_t i = 0; i < sizeof(msg); i++) {
        lang_line(STRC, NILL, msg[i]);
    }

    lang_line(MODE, NILL, 9);
    lang_line(GOTO, NILL, 1);

    while (1) {
        if (millis() - timerBlink >= BLINK_DELAY_MS) {
            timerBlink = millis();
            blink_test();
        }

        if (millis() - timerUpdate >= UPDATE_DELAY_MS) {
            timerUpdate = millis();
            lang_3bc_update(VM1);
            lang_3bc_update(VM2);
            lang_3bc_update(VM3);
            lang_3bc_update(VM4);
            lang_3bc_update(VM5);
            lang_3bc_update(VM6);
            lang_3bc_update(VM7);
            lang_3bc_update(VM8);
            lang_3bc_update(VM9);
            lang_3bc_update(VM10); 
        }
    };

    return 0;
}