#include <avr/io.h> //int
#include <util/delay.h>
#include <stdbool.h>
#include <avr/interrupt.h> //int
#include <stdint.h> //int
#include <stdio.h>

#define SERIAL_OUT true

#define NEOPIXEL_PIN PB3
#define NEOPIXEL_PORT PORTB
#define NEOPIXEL_DDR DDRB

#define PULSE_IN_PIN PB4
#define PULSE_IN_PORT PORTB
#define PULSE_IN_DDR DDRB
#define PULSE_IN_PIN_REG PINB

#define UART_TX_PORT PORTB
#define UART_TX_DDR DDRB
#define UART_TX_PIN PB0

#define F_CPU 8000000UL
#define NUMPIXELS 2
#define UART_BAUD 9600
#define UART_BIT_DELAY_US (1000000UL / UART_BAUD)



typedef struct {
    uint8_t g;
    uint8_t r;
    uint8_t b;
} RGBColor;

RGBColor pixels[NUMPIXELS];

// !time carefully for 800kHz (1.25us cycle) on 8MHz F_CPU
void ws2812_send() {
    uint8_t *ptr = (uint8_t *)pixels;
    uint16_t count = NUMPIXELS * 3;
    uint8_t mask = _BV(NEOPIXEL_PIN);
    uint8_t cli_mask = ~mask;

    NEOPIXEL_DDR |= mask; //set to output

    uint8_t sreg = SREG;
    cli(); // disable interrupts

    NEOPIXEL_PORT &= cli_mask;
    __asm__ __volatile__ (
            "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"
            "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"
            "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"
            );

    while (count--) {
        uint8_t data = *ptr++;

        for (uint8_t i = 8; i--; ) {
            if (data & 0x01) {
                /*__asm__ __volatile__ (
                  "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"
                  "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"
                  );*/
                NEOPIXEL_PORT |= mask;
                __asm__ __volatile__ (
                        "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"
                        "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"

                        );
                NEOPIXEL_PORT &= cli_mask;
            } else {
                NEOPIXEL_PORT |= mask;
                __asm__ __volatile__ (
                        "nop\n\t" "nop\n\t"
                        );
                NEOPIXEL_PORT &= cli_mask;
            }

            data <<= 1;
            __asm__ __volatile__ (
                    "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"
                    "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"
            );
        }
    }

    SREG = sreg;
    sei(); // enable interrupts
}

void setPixelColor(uint8_t n, uint8_t r, uint8_t g, uint8_t b) {
    if (n < NUMPIXELS) {
        pixels[n].r = r;
        pixels[n].g = g;
        pixels[n].b = b;
    }
}



int uart_putchar(char c, FILE *stream);

void uart_init(void) {
    UART_TX_DDR |= (1 << UART_TX_PIN); // set to output
    
    UART_TX_PORT |= (1 << UART_TX_PIN); // set to HIGH - idle state in UART

    fdevopen(uart_putchar, NULL);
}

void uart_transmit(uint8_t data) {
    uint8_t i;
    
    UART_TX_PORT &= ~(1 << UART_TX_PIN); // LOW for start bit
    _delay_us(UART_BIT_DELAY_US);

    // send 8 bits LSB
    for (i = 0; i < 8; i++) {
        if (data & (1 << i)) {
            UART_TX_PORT |= (1 << UART_TX_PIN); // set high
        } else {
            UART_TX_PORT &= ~(1 << UART_TX_PIN); // set low
        }
        
        _delay_us(UART_BIT_DELAY_US); 
    }

    UART_TX_PORT |= (1 << UART_TX_PIN); // high for stop bit
    _delay_us(UART_BIT_DELAY_US); 
}

void uart_print_str(const char* str) {
    while (*str) {
        uart_transmit(*str++);
    }
}

int uart_putchar(char c, FILE *stream) {
    uint8_t sreg = SREG;
    cli();

    if (c == '\n') {
        uart_transmit('\r');
    }
    uart_transmit(c);

    SREG = sreg;
    sei();

    return 0;
}



uint16_t tim1_ic_val = 0;
char tim1_ic_flag = 0;

uint16_t read_chained_timer(void){
    uint8_t sreg = SREG;
    cli();
    
    uint8_t high_byte = TCNT0;
    uint8_t low_byte = TCNT1;
    
    SREG = sreg;
    sei();
    
    return ((uint16_t)high_byte << 8) | low_byte;
}

ISR(TIMER1_OVF_vect) {
    TCNT0++; 
}

ISR(PCINT0_vect) {
    if(!(PINB & (1 << PULSE_IN_PIN))){ // pulsein pin is low
        TCNT0 = 0x00; // reset tim0
        TCNT1 = 0x00; // reset tim1
    }else{ // pulsein pin is high
        tim1_ic_val = read_chained_timer(); 
        tim1_ic_flag = 1;
        //GIFR |= (1 << PCIF); // Clear the PCINT Flag (PCIF) after processing the interrupt
    }
}



int main() {
    int d = 0;
    int code = 0;

    DDRB &= ~(1 << PULSE_IN_PIN); // set to input
    // PORTB |= (1 << PULSE_IN_PIN); // enable pullup
    PCMSK |= (1 << PCINT4); // enable pin change interrupt 4
    GIMSK |= (1 << PCIE); // enable pin change interrupts

    //TCCR1B |= (1 << CS10); // clk/1 prescaler
    TCNT1 = 0x00; // reset tim1
    TCCR1 &= ~((1 << CS13) | (1 << CS12) | (1 << CS11) | (1 << CS10)); // clear prescalers - stop tim1
    TCCR1 |= (1 << CS12); // CS12 high for clk/8 prescaler
    //TCCR1 |= (1 << CS11) | (1 << CS10);
    TIMSK |= (1 << TOIE1); // enable tim1 overflow int

    TCCR0B = 0x00; // clear prescalers to stop tim0
    TCNT0 = 0x00; 
    
    sei(); // enable int


    //if(SERIAL_OUT){
        uart_init();
        _delay_ms(500);
    //}else{
        // Initialize I/O: Set the NeoPixel pin as an output
        DDRB |= (1 << NEOPIXEL_PIN);
    //}



    //if(SERIAL_OUT){
        printf("starting..\n");
    //}else{
        //setPixelColor(0, 0, 255, 0);
        ws2812_send();
    //}

    while(1){
        if(tim1_ic_flag){
            //if(SERIAL_OUT){
                //printf("%d\n", (int)tim1_ic_val);
            //}

            if(tim1_ic_val >= 400){
                if(tim1_ic_val > 2000){
                    //if(SERIAL_OUT){
                        printf(" %d\n", code);
                    //}

                    if(code == 7774){
                        //if(SERIAL_OUT){
                            printf("+\n");
                        //}else{
                            setPixelColor(1, 0, 255, 0);
                            ws2812_send();
                        //}
                    }else if(code == 15964){
                        //if(SERIAL_OUT){
                            printf("-\n");
                        //}else{
                            setPixelColor(1, 255, 0, 0);
                            ws2812_send();
                        //}
                    }

                    code = 0;
                }else if(tim1_ic_val > 605){
                    //if(SERIAL_OUT){
                        printf("1");
                    //}
                    ++code;
                }else{
                    //if(SERIAL_OUT){
                        printf("0");
                    //}
                }
                code *=2;
            }

            tim1_ic_flag = 0;
        }
        _delay_us(10);
    }

    return 0;
}
