/*
 wrap-led -- Blinks the LEDs on a WRAP board
 Copyright (C) 2006 Michael Hanselmann

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <asm/io.h>
#include <sys/io.h>

#define GPIOBASE 0xF400

#define PRINT_BIT(x) { \
    write(STDERR_FILENO, #x, strlen(#x)); \
    write(STDERR_FILENO, " = ", 3); \
    for (int s = 0; s < (8 * sizeof((x))); ++s) { \
        if ((x) & (1 << s)) { \
            write(STDERR_FILENO, "1", 1); \
        } else { \
            write(STDERR_FILENO, "0", 1); \
        } \
    }; \
    write(STDERR_FILENO, "\n", 1); \
}

#define NONE 0
#define LED1 1
#define LED2 2
#define LED3 4
#define END 255

struct movie {
    unsigned long pause;
    uint8_t *steps;
};

uint8_t movie_none[] = {
    NONE,
    END
};

uint8_t movie_blink[] = {
    LED1 | LED2 | LED3,
    NONE,
    END
};

uint8_t movie_ltr[] = {
    LED1,
    LED2,
    LED3,
    END
};

uint8_t movie_rtl[] = {
    LED3,
    LED2,
    LED1,
    END
};

uint8_t movie_runlight[] = {
    LED1,
    LED2,
    LED3,
    LED2,
    END
};

uint8_t movie_special1[] = {
    NONE,
    LED1 | LED3,
    LED1 | LED2 | LED3,
    LED2,
    NONE,
    LED2,
    LED1 | LED2 | LED3,
    LED1 | LED3,
    END
};

struct movie movies[] = {
    { 5000000, movie_ltr },
    { 1000000, movie_blink },
    { 1000000 / 4, movie_ltr },
    { 1000000 / 4, movie_rtl },
    { 1000000 / 4, movie_runlight },
    { 1000000 / 8, movie_special1 },
    { 5000000, movie_none },
    { }
};

void set_leds(int led1, int led2, int led3) {
    uint16_t value;

    value = inw(GPIOBASE);
    if (led1)   value &= ~(1 << 2);
    else        value |=  (1 << 2);

    if (led2)   value &= ~(1 << 3);
    else        value |=  (1 << 3);
    outw(value, GPIOBASE);

    value = inw(GPIOBASE + 0x2);
    if (led3)   value &= ~(1 << 2);
    else        value |=  (1 << 2);
    outw(value, GPIOBASE + 0x2);
}

int main(int argc, char* argv[]) {
    int movie = 0, step = 0;
    uint8_t pressed = 0, started = 1;
    unsigned long pause = 0;

    /* Get root permissions (when setuid-root) */
    setuid(0);
    seteuid(0);

    /* Get permissions for direct I/O */
    if(iopl(3)) {
        char *error = strerror(errno);
        write(STDOUT_FILENO, error, strlen(error));
        exit(1);
    }

    while (1) {
        uint16_t gpio40 = inw(GPIOBASE + 20);
        uint8_t changed = 0;

        if ((gpio40 & (1 << 8)) == 0 && !pressed) {
            pressed = 1;
            /*PRINT_BIT(pressed);*/
            movie++;
            step = 0;
            pause = 0;
            changed = 1;

            if (!movies[movie].pause) movie = 0;
        } else {
            pressed = 0;
        }

        if (pause >= movies[movie].pause || changed || started) {
            uint8_t value = movies[movie].steps[step];
            if (value == END) {
                step = 0;
                value = movies[movie].steps[step];
            }

            /*PRINT_BIT(value);*/
            set_leds(value & LED1, value & LED2, value & LED3);
            step++;

            pause = 0;
        }

        usleep(1000000 / 10);
        pause += 1000000 / 10;

        started = 0;
    }

    /* Should never be reached */
    return 0;
}

