/* -*- c++ -*- */
/*
 * Copyright 2002, 2014 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 */

#include "atsc_single_viterbi.h"
#include <cmath>

namespace gr {
namespace dtv {

/* was_sent is a table of what symbol we get given what bit pair
   was sent and what state we where in [state][pair] */
const int atsc_single_viterbi::d_was_sent[4][4] = {
    { 0, 2, 4, 6 },
    { 0, 2, 4, 6 },
    { 1, 3, 5, 7 },
    { 1, 3, 5, 7 },
};

/* transition_table is a table of what state we were in
   given current state and bit pair sent [state][pair] */
const int atsc_single_viterbi::d_transition_table[4][4] = {
    { 0, 2, 0, 2 },
    { 2, 0, 2, 0 },
    { 1, 3, 1, 3 },
    { 3, 1, 3, 1 },
};

void atsc_single_viterbi::reset()
{
    for (unsigned int i = 0; i < 2; i++)
        for (unsigned int j = 0; j < 4; j++) {
            d_path_metrics[i][j] = 0;
            d_traceback[i][j] = 0;
        }
    d_post_coder_state = 0;
    d_phase = 0;
    d_best_state_metric = 100000;
}

atsc_single_viterbi::atsc_single_viterbi() { reset(); }

float atsc_single_viterbi::best_state_metric() const { return d_best_state_metric; }

char atsc_single_viterbi::decode(float input)
{
    unsigned int best_state = 0;
    // float best_state_metric = 100000;
    d_best_state_metric = 100000;

    /* Precompute distances from input to each possible symbol */
    float distances[8] = { fabsf(input + 7), fabsf(input + 5), fabsf(input + 3),
                           fabsf(input + 1), fabsf(input - 1), fabsf(input - 3),
                           fabsf(input - 5), fabsf(input - 7) };

    /* We start by iterating over all possible states */
    for (unsigned int state = 0; state < 4; state++) {
        /* Next we find the most probable path from the previous
           states to the state we are testing, we only need to look at
           the 4 paths that can be taken given the 2-bit input */
        int min_metric_symb = 0;
        float min_metric = distances[d_was_sent[state][0]] +
                           d_path_metrics[d_phase][d_transition_table[state][0]];

        for (unsigned int symbol_sent = 1; symbol_sent < 4; symbol_sent++)
            if ((distances[d_was_sent[state][symbol_sent]] +
                 d_path_metrics[d_phase][d_transition_table[state][symbol_sent]]) <
                min_metric) {
                min_metric =
                    distances[d_was_sent[state][symbol_sent]] +
                    d_path_metrics[d_phase][d_transition_table[state][symbol_sent]];
                min_metric_symb = symbol_sent;
            }

        d_path_metrics[d_phase ^ 1][state] = min_metric;
        d_traceback[d_phase ^ 1][state] =
            (((unsigned long long)min_metric_symb) << 62) |
            (d_traceback[d_phase][d_transition_table[state][min_metric_symb]] >> 2);

        /* If this is the most probable state so far remember it, this
           only needs to be checked when we are about to output a path
           so this test can be saved till later if needed, if performed
           later it could also be optimized with SIMD instructions.
           Even better this check could be eliminated as we are
           outputting the tail of our traceback not the head, for any
           head state path will tend towards the optimal path with a
           probability approaching 1 in just 8 or so transitions
        */
        if (min_metric <= d_best_state_metric) {
            d_best_state_metric = min_metric;
            best_state = state;
        }
    }

    if (d_best_state_metric > 10000) {
        for (unsigned int state = 0; state < 4; state++)
            d_path_metrics[d_phase ^ 1][state] -= d_best_state_metric;
    }
    d_phase ^= 1;

    int y2 = (0x2 & d_traceback[d_phase][best_state]) >> 1;
    int x2 = y2 ^ d_post_coder_state;
    d_post_coder_state = y2;

    return (x2 << 1) | (0x1 & d_traceback[d_phase][best_state]);
}

} /* namespace dtv */
} /* namespace gr */
