//
//  This is the Superfilter sketch I use with the DCF Analyzer/Clock 2.0 
//  Udo Klein did an amazing job with this filter
//
//  Erik de Ruiter

/*
    Arduino Uno pin connections I used for the DCF Analyzer Clock

    DCF input ................. A5 (19)  = dcf77_sample_pin
    Output DCF Filtered ....... 12       = dcf77_filtered_pin
    Output DCF Semi Synthesized A2 (16)  = dcf77_semi_synthesized_pin
    Output DCF Synthesized ....  6       = dcf77_synthesized_pin
    LED DCF output filtered ... A4 (18)  = dcf77_monitor_pin                    = DCF Monitor LED
    LED 1 Hz pulse ............ 10       = dcf77_second_pulse_pin               = Filter Locked LED
    LED DCF OK ................ 13       = dcf77_signal_good_indicator_pin      = Signal Quality LED
    LED Difference Filtered ...  7       = dcf77_filter_diff_pin            \      
    LED Difference Semi Synth.. A0       = dcf77_semi_synthesized_diff_pin   -> = Signal Difference LED
    LED Difference Synthesized   4       = dcf77_synthesized_diff_pin       /
*/
    
//
//  www.blinkenlight.net
//
//  Copyright 2014, 2015 Udo Klein
//
//  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 3 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, see http://www.gnu.org/licenses/

#include <dcf77.h>
/*
const uint8_t pon_pin  = 51; // connect pon to ground !!!
const uint8_t data_pin = 19;
const uint8_t gnd_pin  = 51;
const uint8_t vcc_pin  = 49;
*/

const uint8_t dcf77_analog_samples = false;
const uint8_t dcf77_analog_sample_pin = 5;
const uint8_t dcf77_sample_pin = 19; // A5
const uint8_t dcf77_inverted_samples = 0;
#if defined(__AVR__)
#define ledpin(led) (led)
#else
#define ledpin(led) (led<14? led: led+(54-14))
#endif

const uint8_t dcf77_monitor_pin = ledpin(18); // A4

const bool provide_filtered_output = true;
const uint8_t dcf77_filtered_pin          = ledpin(12);
const uint8_t dcf77_inverted_filtered_pin = ledpin(11);
const uint8_t dcf77_filter_diff_pin       = ledpin(7);

const bool provide_semi_synthesized_output = true;
const uint8_t dcf77_semi_synthesized_pin          = ledpin(16);
const uint8_t dcf77_inverted_semi_synthesized_pin = ledpin(15);
const uint8_t dcf77_semi_synthesized_diff_pin     = ledpin(14);

const bool provide_synthesized_output = true;
const uint8_t dcf77_synthesized_pin          = ledpin(6);
const uint8_t dcf77_inverted_synthesized_pin = ledpin(5);
const uint8_t dcf77_synthesized_diff_pin     = ledpin(4);

const uint8_t dcf77_second_pulse_pin = ledpin(10);


const uint8_t dcf77_signal_good_indicator_pin = ledpin(13);

volatile uint16_t ms_counter = 0;
volatile Internal::DCF77::tick_t tick = Internal::DCF77::undefined;


template <bool enable, uint8_t threshold,
          uint8_t filtered_pin, uint8_t inverted_filtered_pin, uint8_t diff_pin>
void set_output(uint8_t clock_state, uint8_t sampled_data, uint8_t synthesized_signal)
{
    if (enable) {
        const uint8_t filtered_output = clock_state < threshold? sampled_data: synthesized_signal;
        digitalWrite(filtered_pin, filtered_output);
        digitalWrite(inverted_filtered_pin, !filtered_output);
        digitalWrite(diff_pin, filtered_output ^ sampled_data);
    }
}

namespace {
    struct Scope {
        static const uint16_t samples_per_second = 1000;
        static const uint8_t bins                = 100;
        static const uint8_t samples_per_bin     = samples_per_second / bins;

        volatile uint8_t gbin[bins];
        volatile boolean samples_pending = false;
        volatile uint32_t count = 0;

        void process_one_sample(const uint8_t sample) {
            static uint8_t sbin[bins];

            static uint16_t ticks = 999;  // first pass will init the bins
            ++ticks;

            if (ticks == 1000) {
                ticks = 0;
                memcpy((void *)gbin, sbin, bins);
                memset(sbin, 0, bins);
                samples_pending = true;
                ++count;
            }
            sbin[ticks/samples_per_bin] += sample;
        }

        void print() {
            uint8_t lbin[bins];

            if (samples_pending) {
                noInterrupts();
                memcpy(lbin, (void *)gbin, bins);
                samples_pending = false;
                interrupts();

                // ensure the count values will be aligned to the right
                for (int32_t val=count; val < 100000000; val *= 10) {
                    Serial.print(' ');
                }
                Serial.print((int32_t)count);
                Serial.print(", ");
                for (uint8_t bin=0; bin<bins; ++bin) {
                    switch (lbin[bin]) {
                        case  0: Serial.print(bin%10? '-': '+'); break;
                        case 10: Serial.print('X');              break;
                        default: Serial.print(lbin[bin]);
                    }
                }
                Serial.println();
            }
        }
    };
}

Scope scope_1;
Scope scope_2;

uint8_t sample_input_pin() {
    const uint8_t clock_state = DCF77_Clock::get_clock_state();
    const uint8_t sampled_data =
        #if defined(__AVR__)
        dcf77_inverted_samples ^ (dcf77_analog_samples? (analogRead(dcf77_analog_sample_pin) > 200)
                                                      : digitalRead(dcf77_sample_pin));
        #else
        dcf77_inverted_samples ^ digitalRead(dcf77_sample_pin);
        #endif

    digitalWrite(dcf77_monitor_pin, sampled_data);
    digitalWrite(dcf77_second_pulse_pin, ms_counter < 500 && clock_state >= Clock::locked);

    const uint8_t synthesized_signal =
        tick == Internal::DCF77::long_tick  ? ms_counter < 200:
        tick == Internal::DCF77::short_tick ? ms_counter < 100:
        tick == Internal::DCF77::sync_mark  ? 0:
                                              // tick == DCF77::undefined --> default handling
                                              // allow signal to pass for the first 200ms of each second
                                              (ms_counter <=200 && sampled_data) ||
                                              // if the clock has valid time data then undefined ticks
                                              // are data bits --> first 100ms of signal must be high
                                              ms_counter <100;

    set_output<provide_filtered_output, Clock::locked,
              dcf77_filtered_pin, dcf77_inverted_filtered_pin, dcf77_filter_diff_pin>
              (clock_state, sampled_data, synthesized_signal);

    set_output<provide_semi_synthesized_output, Clock::unlocked,
               dcf77_semi_synthesized_pin, dcf77_inverted_semi_synthesized_pin, dcf77_semi_synthesized_diff_pin>
               (clock_state, sampled_data, synthesized_signal);

    set_output<provide_synthesized_output, Clock::free,
               dcf77_synthesized_pin, dcf77_inverted_synthesized_pin, dcf77_synthesized_diff_pin>
               (clock_state, sampled_data, synthesized_signal);

    ms_counter+= (ms_counter < 1000);

    scope_1.process_one_sample(sampled_data);
    scope_2.process_one_sample(digitalRead(dcf77_synthesized_pin));

    return sampled_data;
}




void output_handler(const Clock::time_t &decoded_time) {
    // reset ms_counter for 1 Hz ticks
    ms_counter = 0;

    // status indicator --> always on if signal is good
    //                      blink 3s on 1s off if signal is poor
    //                      blink 1s on 3s off if signal is very poor
    //                      always off if signal is bad
    const uint8_t clock_state = DCF77_Clock::get_clock_state();
    digitalWrite(dcf77_signal_good_indicator_pin,
                 clock_state >= Clock::locked  ? 1:
                 clock_state == Clock::unlocked? (decoded_time.second.digit.lo & 0x03) != 0:
                 clock_state == Clock::free    ? (decoded_time.second.digit.lo & 0x03) == 0:
                                                  0);
    // compute output for signal synthesis
    Internal::DCF77_Encoder now;
    now.second                    = BCD::bcd_to_int(decoded_time.second);
    now.minute                    = decoded_time.minute;
    now.hour                      = decoded_time.hour;
    now.weekday                   = decoded_time.weekday;
    now.day                       = decoded_time.day;
    now.month                     = decoded_time.month;
    now.year                      = decoded_time.year;
    now.uses_summertime           = decoded_time.uses_summertime;
    now.leap_second_scheduled     = decoded_time.leap_second_scheduled;
    now.timezone_change_scheduled = decoded_time.timezone_change_scheduled;

    now.undefined_minute_output                         = false;
    now.undefined_uses_summertime_output                = false;
    now.undefined_abnormal_transmitter_operation_output = false;
    now.undefined_timezone_change_scheduled_output      = false;

    now.advance_minute();
    tick = now.get_current_signal();
}

void setup_serial() {
    Serial.begin(115200);
}

void output_splash_screen() {
    Serial.println();
    Serial.println(F("DCF77 Superfilter 3.0"));
    Serial.println(F("(c) 2015 Udo Klein"));
    Serial.println(F("www.blinkenlight.net"));
    Serial.println();
    Serial.print(F("Sample Pin:               ")); Serial.println(dcf77_sample_pin);
    Serial.print(F("Inverted Mode:            ")); Serial.println(dcf77_inverted_samples);
    #if defined(__AVR__)
    Serial.print(F("Analog Mode:              ")); Serial.println(dcf77_analog_samples);
    #endif
    Serial.print(F("Monitor Pin:              ")); Serial.println(dcf77_monitor_pin);
    Serial.println();

    if (provide_filtered_output) {
        Serial.println(F("Filtered Output"));
        Serial.print(F("    Filtered Pin:         ")); Serial.println(dcf77_filtered_pin);
        Serial.print(F("    Diff Pin:             ")); Serial.println(dcf77_filter_diff_pin);
        Serial.print(F("    Inverse Filtered Pin: ")); Serial.println(dcf77_inverted_filtered_pin);
        Serial.println();
    }

    if (provide_semi_synthesized_output) {
        Serial.println(F("Semi Synthesized Output"));
        Serial.print(F("    Filtered Pin:         ")); Serial.println(dcf77_semi_synthesized_pin);
        Serial.print(F("    Diff Pin:             ")); Serial.println(dcf77_semi_synthesized_diff_pin);
        Serial.print(F("    Inverse Filtered Pin: ")); Serial.println(dcf77_inverted_semi_synthesized_pin);
        Serial.println();
    }

    if (provide_synthesized_output) {
        Serial.println(F("Synthesized Output"));
        Serial.print(F("    Filtered Pin:         ")); Serial.println(dcf77_synthesized_pin);
        Serial.print(F("    Diff Pin:             ")); Serial.println(dcf77_synthesized_diff_pin);
        Serial.print(F("    Inverse Filtered Pin: ")); Serial.println(dcf77_inverted_synthesized_pin);
        Serial.println();
    }

    Serial.print(F("Second Pulse Pin:         ")); Serial.println(dcf77_second_pulse_pin);
    Serial.print(F("Signal Good Pin:          ")); Serial.println(dcf77_signal_good_indicator_pin);

    Serial.println();

    Serial.println();
    Serial.println(F("Initializing..."));
    Serial.println();
};

void setup_pins() {
    if (provide_filtered_output) {
        pinMode(dcf77_filtered_pin, OUTPUT);
        pinMode(dcf77_filter_diff_pin, OUTPUT);
        pinMode(dcf77_inverted_filtered_pin, OUTPUT);
    }

    if (provide_semi_synthesized_output) {
        pinMode(dcf77_semi_synthesized_pin, OUTPUT);
        pinMode(dcf77_semi_synthesized_diff_pin, OUTPUT);
        pinMode(dcf77_inverted_semi_synthesized_pin, OUTPUT);
    }

    if (provide_synthesized_output) {
        pinMode(dcf77_synthesized_pin, OUTPUT);
        pinMode(dcf77_synthesized_diff_pin, OUTPUT);
        pinMode(dcf77_inverted_synthesized_pin, OUTPUT);
    }

    pinMode(dcf77_monitor_pin, OUTPUT);
    pinMode(dcf77_signal_good_indicator_pin, OUTPUT);
    pinMode(dcf77_second_pulse_pin, OUTPUT);
    pinMode(dcf77_sample_pin, INPUT);
    digitalWrite(dcf77_sample_pin, HIGH);
}

void setup_clock() {
    DCF77_Clock::setup();
    DCF77_Clock::set_input_provider(sample_input_pin);
    DCF77_Clock::set_output_handler(output_handler);
}

void setup() {
    setup_serial();
    output_splash_screen();
    setup_pins();
    setup_clock();
/*
    pinMode(gnd_pin, OUTPUT);
    digitalWrite(gnd_pin, LOW);
    pinMode(pon_pin, OUTPUT);
    digitalWrite(pon_pin, LOW);
    pinMode(vcc_pin, OUTPUT);
    digitalWrite(vcc_pin, HIGH);
    */
}

void loop() {
    Clock::time_t now;
    DCF77_Clock::get_current_time(now);

    if (now.month.val > 0) {
        Serial.println();
        Serial.print(F("Decoded time: "));

        DCF77_Clock::print(now);
        Serial.println();
    }

    Serial.print(DCF77_Clock::get_clock_state());
    Serial.print(' ');
    DCF77_Clock::debug();

    scope_1.print();
    scope_2.print();
}
