/******************************************************************************!
 * \file receiver.c
 * \author Sebastien Beaugrand
 * \sa http://beaugrand.chez.com/
 * \copyright CeCILL 2.1 Free Software license
 * \note Source: https://github.com/jpiat/arduino
 ******************************************************************************/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include "lifi.h"
#include "wiring.h"
#include "timer1.h"
#include "debug.h"

#define PIN_LED 11
unsigned int gCount;

enum receiver_state
{
    IDLE,  // Waiting for sync
    SYNC,  // Synced, waiting for STX
    START,  // STX received
    DATA  // Receiving DATA
};
enum receiver_state frame_state = IDLE;

// This defines receiver properties
#define SAMPLE_PER_SYMBOL 4

// Global variables for frame decoding
char frame_buffer[38];
int frame_index = -1;
int frame_size = -1;

// State variables of the thresholder
unsigned int signal_mean = 0;
unsigned long acc_sum = 0;  // Used to compute the signal mean value
unsigned int acc_counter = 0;

// Manechester decoder state variable
long shift_reg = 0;

/******************************************************************************!
 * \fn is_a_word
 ******************************************************************************/
#define START_SYMBOL 0x02
#define STOP_SYMBOL 0x01
#define START_STOP_MASK ((STOP_SYMBOL << 20) | \
                         (START_SYMBOL << 18) | \
                         STOP_SYMBOL)  // STOP/START/16bits/STOP
#define SYNC_SYMBOL_MANCHESTER (0x6665)
inline int
is_a_word(long* manchester_word,
          int time_from_last_sync,
          unsigned int* detected_word)
{
    if (time_from_last_sync >= 20 || frame_state == IDLE) {
        // We received enough bits to test the sync
        if (((*manchester_word) & START_STOP_MASK) == (START_STOP_MASK)) {
            // Testing first position
            (*detected_word) = ((*manchester_word) >> 2) & 0xFFFF;
            if (frame_state == IDLE) {
                if ((*detected_word) == SYNC_SYMBOL_MANCHESTER) {
                    return 2;
                }
            }
            return 1;
            // Byte with correct framing
        } else if (frame_state != IDLE && time_from_last_sync == 20) {
            (*detected_word) = ((*manchester_word) >> 2) & 0xFFFF;
            return 1;
        }
    }
    return 0;
}

/******************************************************************************!
 * \fn insert_edge
 ******************************************************************************/
inline int
insert_edge(long* manchester_word,
            int8_t edge,
            int edge_period,
            int* time_from_last_sync,
            unsigned int* detected_word)
{
    int new_word = 0;
    int is_a_word_value;
    int sync_word_detect = 0;
    if (((*manchester_word) & 0x01) != edge) {
        // Make sure we don't have same edge
        if (edge_period > (SAMPLE_PER_SYMBOL + 1)) {
            unsigned char last_bit = (*manchester_word) & 0x01;
            // Signal was steady for longer than a single symbol
            (*manchester_word) = ((*manchester_word) << 1) | last_bit;
            (*time_from_last_sync) += 1;
            is_a_word_value = is_a_word(manchester_word,
                                        (*time_from_last_sync),
                                        detected_word);
            if (is_a_word_value > 0) {  // Found start stop framing
                new_word = 1;
                (*time_from_last_sync) = 0;
                if (is_a_word_value > 1) {
                    // We detected framing and sync word in manchester format
                    sync_word_detect = 1;
                }
            }
        }
        // Storing edge value in word
        if (edge < 0) {
            // Signal goes down
            (*manchester_word) = ((*manchester_word) << 1) | 0x00;
        } else {
            // Signal goes up
            (*manchester_word) = ((*manchester_word) << 1) | 0x01;
        }
        (*time_from_last_sync) += 1;
        is_a_word_value = is_a_word(manchester_word,
                                    (*time_from_last_sync),
                                    detected_word);
        // If sync word was detected at previous position,
        // don't take word detection into account
        if (sync_word_detect == 0 && is_a_word_value > 0) {
            new_word = 1;
            (*time_from_last_sync) = 0;
        }
    } else {
        new_word = -1;
    }
    return new_word;
}

/******************************************************************************!
 * \fn sample_signal_edge
 ******************************************************************************/
#define EDGE_THRESHOLD 0
int oldValue = 0;
int steady_count = 0;
int dist_last_sync = 0;
unsigned int detected_word = 0;
int gNewWord = 0;
char old_edge_val = 0;

void
sample_signal_edge()
{
    char edge_val;
    int sensorValue = digitalRead(PIN_LED);
#   ifndef NDEBUG
    fprintf(stderr, "%d", sensorValue);
#   endif
    if ((sensorValue - oldValue) > EDGE_THRESHOLD) {
        edge_val = -1;
    } else if ((oldValue - sensorValue) > EDGE_THRESHOLD) {
        edge_val = 1;
    } else {
        edge_val = 0;
    }
    oldValue = sensorValue;
    if (edge_val == 0 || edge_val == old_edge_val ||
        (edge_val != old_edge_val && steady_count < 2)) {
        if (steady_count < (4 * SAMPLE_PER_SYMBOL)) {
            steady_count++;
        }
    } else {
        gNewWord =
            insert_edge(&shift_reg,
                        edge_val,
                        steady_count,
                        &(dist_last_sync),
                        &detected_word);
        if (dist_last_sync > (8 * SAMPLE_PER_SYMBOL)) {
            // Limit dist_last_sync to avoid overflow problems
            dist_last_sync = 32;
        }
        // if (gNewWord >= 0) {
        steady_count = 0;
        // }
    }
    old_edge_val = edge_val;
}

/******************************************************************************!
 * \fn add_byte_to_frame
 ******************************************************************************/
int
add_byte_to_frame(char* frame_buffer,
                  int* frame_index,
                  int* frame_size,
                  enum receiver_state* frame_state,
                  unsigned char data)
{
    if (data == SYNC_SYMBOL  /*&& (*frame_index) < 0*/) {
        DEBUG("SYNC");
        (*frame_index) = 0;
        (*frame_size) = 0;
        (*frame_state) = SYNC;
        return 0;
    }
    if ((*frame_state) != IDLE) {  // We are synced
        frame_buffer[*frame_index] = data;
        (*frame_index)++;
        if (data == STX) {
            DEBUG("START");
            (*frame_state) = START;
            return 0;
        } else if (data == ETX) {
            DEBUG("END");
            (*frame_size) = (*frame_index);
            (*frame_index) = -1;
            (*frame_state) = IDLE;
            return 1;
        } else if ((*frame_index) >= 38) {
            // Frame is larger than max size of frame
            (*frame_index) = -1;
            (*frame_size) = -1;
            (*frame_state) = IDLE;
            return -1;
        } else {
            (*frame_state) = DATA;
        }
        return 0;
    }
    return -1;
}

/******************************************************************************!
 * \fn setup
 ******************************************************************************/
void
setup()
{
    int ret;
    if ((ret = digitalInit(PIN_LED, INPUT)) != 0) {
        ERROR("digitalInit = %d", ret);
    }
    timer1SetPeriod(SYMBOL_PERIOD / SAMPLE_PER_SYMBOL);
    timer1AttachInterrupt(sample_signal_edge);
}

/******************************************************************************!
 * \fn loop
 ******************************************************************************/
void
loop()
{
    int i;
    unsigned char received_data;
    if (gNewWord == 1) {
        received_data = 0;
        for (i = 0; i < 16; i = i + 2) {  // Decoding Manchester
            received_data = received_data << 1;
            if (((detected_word >> i) & 0x03) == 0x01) {
                received_data |= 0x01;
            } else {
                received_data &= ~0x01;
            }
        }
        received_data = received_data & 0xFF;
        DEBUG("%d, %c", received_data & 0xFF, received_data);
        gNewWord = 0;
        if (add_byte_to_frame(frame_buffer, &frame_index, &frame_size,
                              &frame_state,
                              received_data) > 0) {
            frame_buffer[frame_size - 1] = '\0';
            fprintf(stdout, "%s\n", &frame_buffer[1]);
            --gCount;
        }
        if (frame_state != IDLE) {
            DEBUG("%x", received_data);
        }
    }
}

/******************************************************************************!
 * \fn main
 ******************************************************************************/
int
main(int argc, char* argv[])
{
    setup();
    if (argc > 1) {
        gCount = atoi(argv[1]);
        while (gCount) {
            loop();
        }
        return digitalQuit(PIN_LED);
    } else {
        for (;;) {
            loop();
        }
    }
}