/*
 * Traffic light
 *
 * Copyright (C) 2019-2022 Aurelien Jarno
 *
 * Authors:
 *  Aurelien Jarno <aurelien@aurel32.net>
 *
 * This work is licensed under the terms of the GNU GPL, version 2.
 * See the COPYING file in the same directory as this file.
 */

/**********************************************************************
 * Global defines                                                     *
 **********************************************************************/
/* CPU */
#define F_CPU                   8000000UL       /* 8 MHz */

/* I/O pins */
#define BUTTON                  PB0             /* Also DATA prog */
#define LED_DATA                PB1             /* Also CLK prog */
#define LED_PWR                 PB2
#define RESET                   PB3


/**********************************************************************
 * Includes                                                           *
 **********************************************************************/
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <stdint.h>
#include <util/delay.h>


/**********************************************************************
 * State machine                                                      *
 **********************************************************************/
enum state {
    state_off,
    state_red,
    state_orange,
    state_green,
    state_blinking_on,
    state_blinking_off,
};

struct state_desc {
    const struct __attribute__((__packed__)) {
        uint8_t r;
        uint8_t g;
        uint8_t b;
    } leds[3];
    uint16_t duration;
    enum state next;
    enum state next_button_short;
    enum state next_button_medium;
    enum state next_button_long;
};

const struct state_desc states_desc[] = {
    [state_off] = {
        .leds = {
            { .r = 0x00, .g = 0x00, .b = 0x00, },
            { .r = 0x00, .g = 0x00, .b = 0x00, },
            { .r = 0x00, .g = 0x00, .b = 0x00, },
        },
        .duration = 1,
        .next = state_off,
        .next_button_short = state_off,
        .next_button_medium = state_off,
        .next_button_long = state_off,
    },

    [state_green] = {
        .leds = {
            { .r = 0x00, .g = 0x00, .b = 0x00, },
            { .r = 0x00, .g = 0x00, .b = 0x00, },
            { .r = 0x00, .g = 0x7f, .b = 0x00, },
        },
        .duration = 13000,
        .next = state_orange,
        .next_button_short = state_orange,
        .next_button_medium = state_blinking_on,
        .next_button_long = state_off,
    },
    [state_orange] = {
        .leds = {
            { .r = 0x00, .g = 0x00, .b = 0x00, },
            { .r = 0x7f, .g = 0x2f, .b = 0x00, },
            { .r = 0x00, .g = 0x00, .b = 0x00, },
        },
        .duration = 1500,
        .next = state_red,
        .next_button_short = state_red,
        .next_button_medium = state_blinking_on,
        .next_button_long = state_off,
    },
    [state_red] = {
        .leds = {
            { .r = 0x7f, .g = 0x00, .b = 0x00, },
            { .r = 0x00, .g = 0x00, .b = 0x00, },
            { .r = 0x00, .g = 0x00, .b = 0x00, },
        },
        .duration = 15000,
        .next = state_green,
        .next_button_short = state_green,
        .next_button_medium = state_blinking_on,
        .next_button_long = state_off,
    },

    [state_blinking_on] = {
        .leds = {
            { .r = 0x00, .g = 0x00, .b = 0x00, },
            { .r = 0x7f, .g = 0x2f, .b = 0x00, },
            { .r = 0x00, .g = 0x00, .b = 0x00, },
        },
        .duration = 500,
        .next = state_blinking_off,
        .next_button_short = state_blinking_off,
        .next_button_medium = state_red,
        .next_button_long = state_off,
    },
    [state_blinking_off] = {
        .leds = {
            { .r = 0x00, .g = 0x00, .b = 0x00, },
            { .r = 0x00, .g = 0x00, .b = 0x00, },
            { .r = 0x00, .g = 0x00, .b = 0x00, },
        },
        .duration = 500,
        .next = state_blinking_on,
        .next_button_short = state_blinking_on,
        .next_button_medium = state_red,
        .next_button_long = state_off,
    },
};


/**********************************************************************
 * Interrupt handlers                                                 *
 **********************************************************************/
/* Do nothing, this is only used to wake-up the MCU */
EMPTY_INTERRUPT(PCINT0_vect);


/**********************************************************************
 * Functions                                                          *
 **********************************************************************/
static void set_leds(const uint8_t *data, uint8_t len)
{
    uint8_t outlo, outhi;

    outlo = PORTB & ~(1 << LED_DATA);
    outhi = PORTB | (1 << LED_DATA);

    /* Reset signal */
    PORTB = outlo;
    _delay_us(280);

    /* Send all bits */
    for (uint8_t i = 0; i < len; i++) {
        asm volatile(
            "       ldi   __tmp_reg__, 8          \n\t"
            "loop:                                \n\t"
            "       out   %[port], %[outhi]       \n\t"
            "       nop                           \n\t"
            "       sbrs  %[data], 7              \n\t"
            "       out   %[port], %[outlo]       \n\t"
            "       lsl   %[data]                 \n\t"
            "       nop                           \n\t"
            "       nop                           \n\t"
            "       out   %[port], %[outlo]       \n\t"
            "       dec   __tmp_reg__             \n\t"
            "       brne  loop                    \n\t"
            :
            : [data] "r" (data[i]),
              [port] "I" (_SFR_IO_ADDR(PORTB)),
              [outhi] "r" (outhi),
              [outlo] "r" (outlo)
            : "cc"
        );
    }
}


/**********************************************************************
 * Main code                                                          *
 **********************************************************************/
int main(void)
{
    /* Initialization: disable interrupts, clear watchdog */
    cli();
    wdt_reset();

    /* Switch to calibrated internal 8 MHz oscillator without prescaler,
       corresponding to a 8 MHz CPU clock */
    CCP = 0xD8;
    CLKMSR = 0x00;
    CCP = 0xD8;
    CLKPSR = 0x00;

    /* Disable unused peripherals: ANA_COMP, TIM0, VLM, WDT */
    ACSR = (1 << ACD);
    PRR = (1 << PRADC) | (1 << PRTIM0);
    VLMCSR = 0x00;
    CCP = 0xD8;
    WDTCSR = 0x00;

    /* Setup LED pins as low output, BUTTON and RESET pins as input with
     * pull-up */
    PORTCR = 0x00;
    PORTB = 0x00;
    PUEB = (1 << BUTTON) | (1 << RESET);
    DDRB = (1 << LED_PWR) | (1 << LED_DATA);

    /* Setup interrupt on button change */
    PCMSK = (1 << PCINT0);
    PCICR = (1 << PCIE0);

    for (;;) {
        /* Reset power-on duration and start with the red color */
        uint32_t on_duration = 0;
        uint16_t button_duration = 0;
        enum state state = state_red;

        /* Power-on the LED and wait for it to be ready */
        PORTB |= (1 << LED_PWR);
        _delay_ms(10);

        /* Loop through all the states until reaching the off state */
        while (state != state_off) {
            set_leds((const uint8_t *)states_desc[state].leds,
                     sizeof(states_desc[state].leds));

            for (uint16_t d = states_desc[state].duration; d > 0; d--) {
                _delay_ms(1);
                on_duration++;

                /* Count the button push time, and debounce the button by
                 * ignoring short pulses. */
                if ((PINB & (1 << BUTTON)) == 0) {
                    if (button_duration != 65535) {
                        button_duration++;
                    }
                } else if (button_duration > 10) {
                    break;
                } else {
                    button_duration = 0;
                }
            }

            if (((PINB & (1 << BUTTON)) != 0) && button_duration > 10) {
                if (button_duration < 500) {
                    state = states_desc[state].next_button_short;
                } else if (button_duration < 1500) {
                    state = states_desc[state].next_button_medium;
                } else {
                    state = states_desc[state].next_button_long;
                }
                on_duration = 0;
                button_duration = 0;
            } else if (on_duration > 1800000) {
                state = state_off;
            } else {
                state = states_desc[state].next;
            }
        }

        /* Power-off the LED */
        PORTB &= ~(1 << LED_PWR);

        /* Clear and allow interrupts to wake-up the MCU and go to power down mode */
        PCIFR |= (1 << PCIF0);
        set_sleep_mode(SLEEP_MODE_PWR_DOWN);
        sei();
        sleep_mode();

        /* Returned from interrupt, disable interrupts */
        cli();

        /* Wait for the button to be released */
        while ((PINB & (1 << BUTTON)) == 0) {
            ;;
        }
    }

    /* Never reached */
    return 0;
}