// Program to test the timer.h implementation's essential functionality.

#include "wirish.h"
#include "timer.h"

void handler1(void);
void handler2(void);
void handler3(void);
void handler4(void);

void handler3b(void);
void handler4b(void);

int t;

int count1 = 0;
int count2 = 0;
int count3 = 0;
int count4 = 0;
uint16 rate1 = 1000;
uint16 rate2 = 2000;
uint16 rate3 = 4000;
uint16 rate4 = 8000;
uint16 val1 = 10000;
uint16 val2 = 10000;
uint16 val3 = 10000;
uint16 val4 = 10000;

// FIXME [0.1.0] high density timer test (especially basic timers + DAC)
timer_dev *timers[] = {TIMER1, TIMER2, TIMER3, TIMER4};
voidFuncPtr handlers[] = {handler1, handler2, handler3, handler4};

void initTimer(timer_dev *dev);
void setTimerPeriod(timer_dev *dev, uint32 period_us);
void testSetTimerPeriod(uint32 period);
void testTimerChannels(timer_dev *dev);
int timerNumber(timer_dev *dev);

void setup() {
    // Set up the LED to blink
    pinMode(BOARD_LED_PIN, OUTPUT);

    // Setup the button as input
    pinMode(BOARD_BUTTON_PIN, INPUT);

    // Send a message out Serial2
    Serial2.begin(115200);
    Serial2.println("*** Initializing timers...");
    timer_foreach(initTimer);
    Serial2.println("*** Done. Beginning timer test.");
}

void loop() {
    Serial2.println("-----------------------------------------------------");

    Serial2.println("Testing timer_get_count()/timer_set_count()");
    Serial2.print("TIMER1 count = ");
    Serial2.println(timer_get_count(TIMER1));
    Serial2.println("timer_set_count(TIMER1, 1234)");
    timer_set_count(TIMER1, 1234);
    Serial2.print("timer_get_count(TIMER1) = ");
    Serial2.println(timer_get_count(TIMER1));

    Serial2.println("-----------------------------------------------------");
    Serial2.println("Testing pause/resume; button roughly controls TIMER4");
    // when BUT is held down, TIMER4 is in the "pause" state and the
    // timer doesn't increment, so the final counts should reflect the
    // ratio of time that BUT was held down.
    count3 = 0;
    count4 = 0;
    timer_set_mode(TIMER3, TIMER_CH1, TIMER_OUTPUT_COMPARE);
    timer_set_mode(TIMER4, TIMER_CH1, TIMER_OUTPUT_COMPARE);
    timer_pause(TIMER3);
    timer_pause(TIMER4);
    timer_set_count(TIMER3, 0);
    timer_set_count(TIMER4, 0);
    timer_set_reload(TIMER3, 30000);
    timer_set_reload(TIMER4, 30000);
    timer_set_compare(TIMER3, 1, 1000);
    timer_set_compare(TIMER4, 1, 1000);
    timer_attach_interrupt(TIMER3, TIMER_CC1_INTERRUPT, handler3b);
    timer_attach_interrupt(TIMER4, TIMER_CC1_INTERRUPT, handler4b);
    timer_resume(TIMER3);
    timer_resume(TIMER4);

    Serial2.println("Testing for ~4 seconds...");
    for(int i = 0; i < 4000; i++) {
        if (isButtonPressed()) {
            timer_pause(TIMER4);
        } else {
            timer_resume(TIMER4);
        }
        delay(1);
    }

    timer_set_mode(TIMER3, TIMER_CH1, TIMER_DISABLED);
    timer_set_mode(TIMER4, TIMER_CH1, TIMER_DISABLED);

    Serial2.print("TIMER3 count: ");
    Serial2.println(timer_get_count(TIMER3));
    Serial2.print("TIMER4 count: ");
    Serial2.println(timer_get_count(TIMER4));

    Serial2.println("-----------------------------------------------------");
    Serial2.println("Testing setTimerPeriod()");
    testSetTimerPeriod(10);
    testSetTimerPeriod(30000);
    testSetTimerPeriod(300000);
    testSetTimerPeriod(30000);

    Serial2.println("Sanity check (with hand-coded reload and prescaler for "
                    "72 MHz timers):");
    timer_set_mode(TIMER4, TIMER_CH1, TIMER_OUTPUT_COMPARE);
    timer_set_prescaler(TIMER4, 33);
    timer_set_reload(TIMER4, 65454);
    timer_pause(TIMER4);
    timer_set_count(TIMER4, 0);
    timer_set_compare(TIMER4, TIMER_CH1, 1);
    timer_attach_interrupt(TIMER4, TIMER_CC1_INTERRUPT, handler4b);
    Serial2.println("Period 30000ms, wait 2 seconds...");
    count4 = 0;
    timer_resume(TIMER4);
    delay(2000);
    timer_pause(TIMER4);
    timer_set_mode(TIMER4, TIMER_CH1, TIMER_DISABLED);
    Serial2.print("TIMER4 count: ");
    Serial2.println(count4);
    Serial2.println("  (Should be around 2sec/30000ms ~ 67)");

    // Test all the individual timer channels
    timer_foreach(testTimerChannels);
}

void initTimer(timer_dev *dev) {
    switch (dev->type) {
    case TIMER_ADVANCED:
    case TIMER_GENERAL:
        Serial2.print("Initializing timer ");
        Serial2.println(timerNumber(dev));
        for (int c = 1; c <= 4; c++) {
            timer_set_mode(dev, c, TIMER_OUTPUT_COMPARE);
        }
        Serial2.println("Done.");
        break;
    case TIMER_BASIC:
        break;
    }
}

void testSetTimerPeriod(uint32 period) {
    timer_set_mode(TIMER4, TIMER_CH1, TIMER_OUTPUT_COMPARE);
    timer_set_compare(TIMER4, TIMER_CH1, 1);
    setTimerPeriod(TIMER4, period);
    timer_pause(TIMER4);
    timer_set_count(TIMER4, 0);
    timer_attach_interrupt(TIMER4, TIMER_CC1_INTERRUPT, handler4b);
    Serial2.println("Period ");
    Serial2.print(period);
    Serial2.print(" ms. Waiting 2 seconds...");
    count4 = 0;
    timer_resume(TIMER4);
    delay(2000);
    timer_pause(TIMER4);
    timer_set_mode(TIMER4, TIMER_CH1, TIMER_DISABLED);
    Serial2.print("TIMER4 count: ");
    Serial2.println(timer_get_count(TIMER4));
    Serial2.print("  (Should be around 2 sec / ");
    Serial2.print(period);
    Serial2.print(" ms = ");
    Serial2.print(double(2) / period * 1000);
    Serial2.println(", modulo delays due to interrupts)");
}

int timerNumber(timer_dev *dev) {
    switch (dev->clk_id) {
    case RCC_TIMER1:
        return 1;
    case RCC_TIMER2:
        return 2;
    case RCC_TIMER3:
        return 3;
    case RCC_TIMER4:
        return 4;
#ifdef STM32_HIGH_DENSITY
    case RCC_TIMER5:
        return 5;
    case RCC_TIMER6:
        return 6;
    case RCC_TIMER7:
        return 7;
    case RCC_TIMER8:
        return 8;
#endif
    default:
        ASSERT(0);
        return 0;
    }
}

/* This function touches every channel of a given timer. The output
 * ratios should reflect the ratios of the rate variables.  It
 * demonstrates that, over time, the actual timing rates get blown
 * away by other system interrupts. */
void testTimerChannels(timer_dev *dev) {
    t = timerNumber(dev);
    toggleLED();
    delay(100);
    Serial2.println("-----------------------------------------------------");
    switch (dev->type) {
    case TIMER_BASIC:
        Serial2.print("NOT testing channels for basic timer ");
        Serial2.println(t);
        break;
    case TIMER_ADVANCED:
    case TIMER_GENERAL:
        Serial2.print("Testing channels for timer ");
        Serial2.println(t);
        timer_pause(dev);
        count1 = count2 = count3 = count4 = 0;
        timer_set_reload(dev, 0xFFFF);
        timer_set_prescaler(dev, 1);
        for (int c = 1; c <= 4; c++) {
            timer_set_compare(dev, c, 65535);
            timer_set_mode(dev, c, TIMER_OUTPUT_COMPARE);
            timer_attach_interrupt(dev, c, handlers[c - 1]);
        }
        timer_resume(dev);
        delay(3000);
        for (int c = 1; c <= 4; c++) {
            timer_set_mode(dev, c, TIMER_DISABLED);
        }
        Serial2.print("Channel 1 count: "); Serial2.println(count1);
        Serial2.print("Channel 2 count: "); Serial2.println(count2);
        Serial2.print("Channel 3 count: "); Serial2.println(count3);
        Serial2.print("Channel 4 count: "); Serial2.println(count4);
        break;
    }
}

// FIXME [0.1.0] move some incarnation of this into timer.h
void setTimerPeriod(timer_dev *dev, uint32 period_us) {
    if (!period_us) {
        // FIXME handle this case
        ASSERT(0);
        return;
    }

    uint32 cycles = period_us * CYCLES_PER_MICROSECOND;
    uint16 pre = (uint16)((cycles >> 16) + 1);
    timer_set_prescaler(dev, pre);
    timer_set_reload(dev, cycles / pre - 1);
}

void handler1(void) {
    val1 += rate1;
    timer_set_compare(timers[t], TIMER_CH1, val1);
    count1++;
}

void handler2(void) {
    val2 += rate2;
    timer_set_compare(timers[t], TIMER_CH2, val2);
    count2++;
}

void handler3(void) {
    val3 += rate3;
    timer_set_compare(timers[t], TIMER_CH3, val3);
    count3++;
}

void handler4(void) {
    val4 += rate4;
    timer_set_compare(timers[t], TIMER_CH4, val4);
    count4++;
}

void handler3b(void) {
    count3++;
}

void handler4b(void) {
    count4++;
}

__attribute__((constructor)) void premain() {
    init();
}

int main(void) {
    setup();

    while (true) {
        loop();
    }
    return 0;
}