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

#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;
} RGBColour;

RGBColour pixels[NUMPIXELS];


typedef enum {STILL, PULSE, STROBE, AUTO} effect_type; // AUTO is the post-show night lamp mode
typedef enum {FG, BG, RAND_COL} colour_type;
typedef struct{
    effect_type type;
    int period_ms;
    colour_type colour;
} effect;

effect choice_effects[] = {
    {STILL, 0, FG},
    {PULSE, 1500, FG}, // 1500*2 ~= 3000ms -> 0.33Hz
    {PULSE, 500, FG}, // 1000ms -> 1Hz
    {PULSE, 250, FG}, // 500ms -> 2Hz
    {PULSE, 100, FG}, // 200ms -> 5Hz
    {STROBE, 1500, FG},
    {STROBE, 500, FG},
    {STROBE, 250, FG},
    {STROBE, 100, FG},
    {STROBE, 1500, RAND_COL},
    {STROBE, 500, RAND_COL},
    {STROBE, 250, RAND_COL},
    {STROBE, 100, RAND_COL},
    {AUTO, 0, RAND_COL}
};

RGBColour choice_col_fg[] = {
    {255, 0, 0},
    {0, 255, 0},
    {0, 0, 255},
    {128, 128, 0},
    {0, 128, 128},
    {128, 0, 128},
    {85, 85, 85},
    {0, 0, 0}
};

RGBColour choice_col_bg[] = {
    {255, 0, 0},
    {0, 255, 0},
    {0, 0, 255},
    {128, 128, 0},
    {0, 128, 128},
    {128, 0, 128},
    {85, 85, 85},
    {0, 0, 0}
};

effect curr_effect = {STILL, 0, FG};
RGBColour curr_col_fg = {0,0,0};
RGBColour curr_col_bg = {0,0,0};

uint32_t ledAnim_clk = 0;

char ledAnim_flipState = 0;
uint32_t ledAnim_lastUpdate = 0;
#define LEDANIM_UPDATE_EVERY_MS 500
int16_t ledAnim_step_r;
int16_t ledAnim_step_g;
int16_t ledAnim_step_b;





// !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"
                        //"nop\n\t" "nop\n\t"
                        );
                NEOPIXEL_PORT &= cli_mask;
            } else {
                NEOPIXEL_PORT |= mask;
                /*__asm__ __volatile__ (
                        //"nop\n\t" "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 setPixel_RGB(uint8_t n, int32_t r, int32_t g, int32_t b) {
    if(r < 0){
        r = 0;
    }else if(r > 255){
        r = 255;
    }
    if(g < 0){
        g = 0;
    }else if(g > 255){
        g = 255;
    }
    if(b < 0){
        b = 0;
    }else if(b > 255){
        b = 255;
    }

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



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

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);
    //stdout = &uart_output;
}

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_putchar(*str++, NULL);
    }
}

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;
}

void uart_print_bin(uint16_t n) {
    //while(n>0){
    for(int i=0;i<14;++i){
        uart_putchar(n&1 ? '1' : '0', NULL);
        n >>= 1;
    }
}

int pow(int n, int e){
    int r = 1;
    for(int i=0;i<e;++i) r*=n;
    return r;
}

void uart_print_int(int32_t n) {
    /*int len = 0;
    uint32_t t_n = n;
    while(t_n){
        t_n /= 10;
        ++len;
    }

    for(int i=len-1;i>=0;--i){
        uart_putchar('0'+((n/pow(10,i))%10), NULL);
    }*/

    if(n < 0){
        uart_putchar('-', NULL);
        n *= -1;
    }

    if(!n) uart_putchar('0', NULL);

    while(n){
        uart_putchar('0'+(n%10), NULL);
        n /= 10;
    }
}

void uart_print_hex(uint8_t n) {
    char chars[] = "0123456789ABCDEF";
    uart_putchar(chars[n >> 4], NULL);
    uart_putchar(chars[n & 0x0F], NULL);
}



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
    }
}


ISR(WDT_vect){ // harvest the watchdog timer for the animations
    ledAnim_clk += 16; // minimum interrupt interval is ~16ms
}


int ledAnim_tick(){
    switch(curr_effect.type){
        case STILL:
            setPixel_RGBColour(1, curr_col_fg);
            ws2812_send();

            break;

        case PULSE:
            if(ledAnim_clk < curr_effect.period_ms){
                if(ledAnim_clk-ledAnim_lastUpdate >= LEDANIM_UPDATE_EVERY_MS){
                    /*setPixel_RGB(1,
                            ledAnim_step_r + (int16_t)pixels[1].r,
                            ledAnim_step_g + (int16_t)pixels[1].g,
                            ledAnim_step_b + (int16_t)pixels[1].b
                    );*/
                    //ws2812_send();

                    ledAnim_lastUpdate = ledAnim_clk;
                }
            }else if(ledAnim_clk < 2*curr_effect.period_ms){
                if(ledAnim_clk-ledAnim_lastUpdate >= LEDANIM_UPDATE_EVERY_MS){
                    /*setPixel_RGB(1,
                            (int16_t)pixels[1].r - ledAnim_step_r,
                            (int16_t)pixels[1].g - ledAnim_step_g,
                            (int16_t)pixels[1].b - ledAnim_step_b
                    );*/
                    //ws2812_send();

                    ledAnim_lastUpdate = ledAnim_clk;
                }
            }else{
                //uart_print_str("a\n");
                ledAnim_lastUpdate = 0;
                ledAnim_clk = 0;
            }

            break;

        case STROBE:
            if(ledAnim_clk < curr_effect.period_ms){
                if(ledAnim_flipState){
                    setPixel_RGBColour(1, curr_col_fg);
                    ws2812_send();

                    ledAnim_flipState = 0;
                }
            }else if(ledAnim_clk < 2*curr_effect.period_ms){
                if(!ledAnim_flipState){
                    setPixel_RGBColour(1, curr_col_bg);
                    ws2812_send();

                    ledAnim_flipState = 1;
                }
            }else{
                //uart_print_str("a\n");
                ledAnim_clk = 0;
            }

            break;

        case AUTO:
            break;

        default:
            return -1;
    }

    return 0;
}

int interp_code(int code){
    int id_effect, id_colour_fg, id_colour_bg, crc;

    crc = code & 3; // take 2 last bits
    id_colour_bg = (code >> 2) & 15; // 4bits
    id_colour_fg = (code >> 6) & 15; // 4bits
    id_effect = (code >> 10) & 15; // 4bits
    
    //printf("fx:%d,fg:%d,bg:%d,crc:%d\n", id_effect, id_colour_fg, id_colour_bg, crc);
    /*uart_print_str("fx:");
    uart_print_int(id_effect);
    uart_print_str(",fg:");
    uart_print_int(id_colour_fg);
    uart_print_str(",bg:");
    uart_print_int(id_colour_bg);
    uart_print_str(",crc:");
    uart_print_int(crc);
    uart_print_str("\n");*/

    curr_effect = choice_effects[id_effect];
    curr_col_fg = choice_col_fg[id_colour_fg];
    curr_col_bg = choice_col_bg[id_colour_bg];

    //ledAnim_step_r = (int32_t)LEDANIM_UPDATE_EVERY_MS*(int32_t)(curr_col_fg.r-curr_col_bg.r) / (int32_t)curr_effect.period_ms;
    //ledAnim_step_g = (int32_t)LEDANIM_UPDATE_EVERY_MS*(int32_t)(curr_col_fg.g-curr_col_bg.g) / (int32_t)curr_effect.period_ms;
    //ledAnim_step_b = (int32_t)LEDANIM_UPDATE_EVERY_MS*(int32_t)(curr_col_fg.b-curr_col_bg.b) / (int32_t)curr_effect.period_ms;
    /*uart_print_str("sr:");
    uart_print_int(ledAnim_step_r);
    uart_print_str(",sg:");
    uart_print_int(ledAnim_step_g);
    uart_print_str(",sb:");
    uart_print_int(ledAnim_step_b);
    uart_print_str("\n");*/
    ledAnim_tick();

    return 0;
}



int main() {
    int d = 0;
    uint16_t 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; 


    WDTCR |= (1 << WDCE) | (1 << WDE); // watchdog tim config
    WDTCR = (1 << WDIE);               // wdt: interrupt mode, 16ms timeout
    
    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");
        uart_print_str("starting..\n");
    //}else{
        //setPixelColor(0, 0, 255, 0);
        ws2812_send();
    //}


    // test of the led timing
    /*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;
    while(1){
                    NEOPIXEL_PORT |= mask;
                    __asm__ __volatile__ (
                            "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"
                            );
                    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"
                );
                    NEOPIXEL_PORT |= mask;
                    _delay_ms(100);
                    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"
                );
    }*/

    /*while(1){
        setPixel_RGB(1, 255, 0, 0);
        ws2812_send();
        _delay_ms(1000);
        setPixel_RGB(1, 0, 255, 0);
        ws2812_send();
        _delay_ms(1000);
        setPixel_RGB(1, 0, 0, 255);
        ws2812_send();
        _delay_ms(1000);
    }*/

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

            if(tim1_ic_val >= 300){ // not too short
                if(tim1_ic_val > 3000){ // terminator
                    code >>= 1; // reverse previous loop's shift
                    //if(SERIAL_OUT){
                        //printf(" %d\n", code);
                    //}

                    if(code){
                        code >>= 1; // remove the control bit
                        
                        uart_print_bin(code);
                        uart_print_str("\n");

                        interp_code(code);
                    }

                    code = 0;
                }else if(tim1_ic_val > 1000){ // is 1
                    //if(SERIAL_OUT){
                        //printf("1");
                    //}
                    ++code;
                }else{ // is 0
                    //if(SERIAL_OUT){
                        //printf("0");
                    //}
                }
                code <<= 1;
            }

            tim1_ic_flag = 0;
        }

        ledAnim_tick();
        _delay_us(10);
    }

    return 0;
}
