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

#ifndef INCLUDED_DIGITAL_TIMING_ERROR_DETECTOR_H
#define INCLUDED_DIGITAL_TIMING_ERROR_DETECTOR_H

#include <gnuradio/digital/constellation.h>
#include <gnuradio/digital/timing_error_detector_type.h>
#include <gnuradio/gr_complex.h>
#include <deque>

namespace gr {
namespace digital {

/*!
 * \brief Base class for the composite symbol clock timing error
 * detector object used by the symbol_synchronizer_xx blocks.
 * \ingroup internal
 *
 * \details
 * This is the base class for the symbol clock timing error detector
 * object used by the symbol_synchronizer_xx blocks to provide a
 * user selectable timing error detector type.
 *
 * This base class provides the enumeration type for the available
 * types of timing error detectors.
 *
 * This base class also provides most of the methods update, manage, and
 * store the data points required to compute the symbol clock timing error,
 * and the timing error result itself.
 *
 * The derived classes only have to provide the simple functions
 * to actually compute the timing error.
 */
class timing_error_detector
{
public:
    /*!
     * \brief Create a timing error detector object of the specified
     * type
     * \param type The type/algorithm to use for the timing error detector
     * \param constellation A constellation to be used as a decision slicer
     *                      for decision directed timing error detector
     *                      algorithms
     */
    static std::unique_ptr<timing_error_detector>
    make(enum ted_type type, constellation_sptr constellation = constellation_sptr());

    virtual ~timing_error_detector(){};

    /*!
     * \brief Return the number of input samples per symbol this timing
     * error detector algorithm requires.
     */
    virtual int inputs_per_symbol() { return d_inputs_per_symbol; }

    /*!
     * \brief Provide a complex input sample to the TED algorithm
     * \param x the input sample
     * \param dx the derivative at the input sample, if required by the
     *           timinig error detector algorithm
     */
    virtual void input(const gr_complex& x,
                       const gr_complex& dx = gr_complex(0.0f, 0.0f));

    /*!
     * \brief Provide a float input sample to the TED algorithm
     * \param x the input sample
     * \param dx the derivative at the input sample, if required by the
     *           timinig error detector algorithm
     */
    virtual void input(float x, float dx = 0.0f);

    /*!
     * \brief Return if this timing error detector algorithm requires a
     * look-ahead input sample to be input to produce a new error estimate
     * for the symbol sampling instant.
     */
    virtual bool needs_lookahead() { return d_needs_lookahead; }

    /*!
     * \brief Provide a complex input lookahead sample to the TED algorithm
     * \param x the input lookahead sample
     * \param dx the derivative at the input lookahead sample, if required
     *           by the timinig error detector algorithm
     */
    virtual void input_lookahead(const gr_complex& x,
                                 const gr_complex& dx = gr_complex(0.0f, 0.0f));

    /*!
     * \brief Provide a float input lookahead sample to the TED algorithm
     * \param x the input lookahead sample
     * \param dx the derivative at the input lookahead sample, if required
     *           by the timinig error detector algorithm
     */
    virtual void input_lookahead(float x, float dx = 0.0f);

    /*!
     * \brief Return if this timing error detector algorithm requires
     * derivative input samples in addition to normal input samples.
     */
    virtual bool needs_derivative() { return d_needs_derivative; }

    /*!
     * \brief Return the current symbol timing error estimate
     */
    virtual float error() { return d_error; }

    /*!
     * \brief Revert the timing error detector processing state back one
     * step.
     * \param preserve_error If true, don't revert the error estimate.
     */
    virtual void revert(bool preserve_error = false);

    /*!
     * \brief Reset the timing error detector
     */
    virtual void sync_reset();

private:
protected:
    /*!
     * \brief Create a timing error detector abstract base class object
     * object
     * \param type The type/algorithm to use for the timing error detector
     * \param inputs_per_symbol The input samples per symbol this TED
     *                          algorithm requires
     * \param error_computation_depth The number of input samples this TED
     *                                algorithm needs to compute the error
     * \param needs_lookahead True if this TED algorithm needs a lookahead
     *                        input sample to compute the error for the
     *                        symbol sampling instant
     * \param needs_derivative True if this TED algorithm needs a derivative
     *                         input sample in addition to the normal input
     *                         sample
     * \param constellation A constellation to be used as a decision slicer
     *                      for decision directed timing error detector
     *                      algorithms
     */
    timing_error_detector(enum ted_type type,
                          int inputs_per_symbol,
                          int error_computation_depth,
                          bool needs_lookahead = false,
                          bool needs_derivative = false,
                          constellation_sptr constellation = constellation_sptr());

    /*!
     * \brief Advance the TED input clock, so the input() methods will
     * compute the TED error term at the proper symbol sampling instant.
     */
    void advance_input_clock()
    {
        d_input_clock = (d_input_clock + 1) % d_inputs_per_symbol;
    }

    /*!
     * \brief Revert the TED input clock one step
     */
    void revert_input_clock()
    {
        if (d_input_clock == 0)
            d_input_clock = d_inputs_per_symbol - 1;
        else
            d_input_clock--;
    }

    /*!
     * \brief Reset the TED input clock, so the next input clock advance
     * corresponds to a symbol sampling instant.
     */
    void sync_reset_input_clock() { d_input_clock = d_inputs_per_symbol - 1; }

    /*!
     * \brief Convert the soft symbol sample into a hard symbol decision.
     * \param x the soft input symbol sample
     */
    gr_complex slice(const gr_complex& x);

    /*!
     * \brief Compute the TED error term for complex input samples
     */
    virtual float compute_error_cf() = 0;

    /*!
     * \brief Compute the TED error term for float input samples
     */
    virtual float compute_error_ff() = 0;

    constellation_sptr d_constellation;
    float d_error;
    float d_prev_error;
    int d_inputs_per_symbol;
    int d_input_clock;
    int d_error_depth;
    std::deque<gr_complex> d_input;
    std::deque<gr_complex> d_decision;
    std::deque<gr_complex> d_input_derivative;
    bool d_needs_lookahead;
    bool d_needs_derivative;
};

/*!
 * \brief Mueller and Müller (M&M) timing error detector algorithm class
 * \ingroup internal
 *
 * \details
 * This is the Mueller and Müller (M&M) timing error detector algorithm
 * class.  It is a decision directed timing error detector, and requires
 * an input slicer constellation to make decisions.
 *
 * See equation (49) of:
 * Mueller, Kurt H., Müller, Markus, "Timing Recovery in Digital
 * Synchronous Data Receivers", _IEEE_Transactions_on_Communications_,
 * Vol. COM-24, No. 5, May 1976, pp. 516-531
 */
class ted_mueller_and_muller : public timing_error_detector
{
public:
    /*!
     * \brief Create a Mueller and Müller (M&M) timing error detector
     * \param constellation A constellation to be used as a decision slicer
     */
    ted_mueller_and_muller(constellation_sptr constellation)
        : timing_error_detector(TED_MUELLER_AND_MULLER, 1, 2, false, false, constellation)
    {
    }
    ~ted_mueller_and_muller() override{};

private:
    float compute_error_cf() override;
    float compute_error_ff() override;
};

/*!
 * \brief Modified Mueller and Müller (M&M) timing error detector algorithm
 * class
 * \ingroup internal
 *
 * \details
 * This is the modified Mueller and Müller (M&M) timing error detector
 * algorithm class.  It is a decision directed timing error detector, and
 * requires an input slicer constellation to make decisions.
 *
 * The modification of the standard M&M TED is done to remove
 * self-noise that is present in the standard M&M TED.
 *
 * See:
 * G. R. Danesfahani, T.G. Jeans, "Optimisation of modified Mueller
 * and Muller algorithm," Electronics Letters, Vol. 31, no. 13, 22
 * June 1995, pp. 1032 - 1033.
 */
class ted_mod_mueller_and_muller : public timing_error_detector
{
public:
    /*!
     * \brief Create a modified Mueller and Müller (M&M) timing error
     * detector
     * \param constellation A constellation to be used as a decision slicer
     */
    ted_mod_mueller_and_muller(constellation_sptr constellation)
        : timing_error_detector(
              TED_MOD_MUELLER_AND_MULLER, 1, 3, false, false, constellation)
    {
    }
    ~ted_mod_mueller_and_muller() override{};

private:
    float compute_error_cf() override;
    float compute_error_ff() override;
};

/*!
 * \brief Zero Crossing timing error detector algorithm class
 * \ingroup internal
 *
 * \details
 * This is the Zero Crossing timing error detector algorithm class.
 * It is a decision directed timing error detector, and
 * requires an input slicer constellation to make decisions.
 */
class ted_zero_crossing : public timing_error_detector
{
public:
    /*!
     * \brief Create a Zero Crossing timing error detector
     * \param constellation A constellation to be used as a decision slicer
     */
    ted_zero_crossing(constellation_sptr constellation)
        : timing_error_detector(TED_ZERO_CROSSING, 2, 3, false, false, constellation)
    {
    }
    ~ted_zero_crossing() override{};

private:
    float compute_error_cf() override;
    float compute_error_ff() override;
};

/*!
 * \brief Gardner timing error detector algorithm class
 * \ingroup internal
 *
 * \details
 * This is the Gardner timing error detector algorithm class.
 *
 * See:
 * F.M. Gardner, “A BPSK/QPSK Timing Error Detector for Sampled Receivers”,
 * IEEE Trans., COM-34, (5), 1988, pp. 423 – 429.
 */
class ted_gardner : public timing_error_detector
{
public:
    /*!
     * \brief Create a Gardner timing error detector
     */
    ted_gardner()
        : timing_error_detector(TED_GARDNER, 2, 3, false, false, constellation_sptr())
    {
    }
    ~ted_gardner() override{};

private:
    float compute_error_cf() override;
    float compute_error_ff() override;
};

/*!
 * \brief Early-Late timing error detector algorithm class
 * \ingroup internal
 *
 * \details
 * This is the Early-Late timing error detector algorithm class.
 * It requires a lookahead sample to compute the symbol timing error
 * at the symbol sampling instant.
 */
class ted_early_late : public timing_error_detector
{
public:
    /*!
     * \brief Create a Early-Late timing error detector
     */
    ted_early_late()
        : timing_error_detector(TED_EARLY_LATE, 2, 3, true, false, constellation_sptr())
    {
    }
    ~ted_early_late() override{};

private:
    float compute_error_cf() override;
    float compute_error_ff() override;
};

/*!
 * \brief Generalized MSK timing error detector algorithm class
 * \ingroup internal
 *
 * \details
 * This is the Generalized MSK timing error detector algorithm class.
 * It operates on the CPM MSK signal directly and not the FM pulses.
 *
 * See:
 * A.N. D'Andrea, U. Mengali, R. Reggiannini, "A digital approach to clock
 * recovery in generalized minimum shift keying", IEEE Transactions on
 * Vehicular Technology, Vol. 39, Issue 3, August 1990, pp. 227-234
 */
class ted_generalized_msk : public timing_error_detector
{
public:
    /*!
     * \brief Create a Generalized MSK timing error detector
     */
    ted_generalized_msk()
        : timing_error_detector(
              TED_DANDREA_AND_MENGALI_GEN_MSK, 4, 8, false, false, constellation_sptr())
    {
    }
    ~ted_generalized_msk() override{};

private:
    float compute_error_cf() override;
    float compute_error_ff() override;
};

/*!
 * \brief Gaussian MSK timing error detector algorithm class
 * \ingroup internal
 *
 * \details
 * This is the Gaussian MSK timing error detector algorithm class.
 * It operates on the CPM MSK signal directly and not the FM pulses.
 *
 * See:
 * U. Mengali, A.N. D'Andrea, _Synchronization_Techniques_for_Digital_
 * Receviers_, New York, Plenum Press 1997
 */
class ted_gaussian_msk : public timing_error_detector
{
public:
    /*!
     * \brief Create a Generalized MSK timing error detector
     */
    ted_gaussian_msk()
        : timing_error_detector(
              TED_MENGALI_AND_DANDREA_GMSK, 2, 4, false, false, constellation_sptr())
    {
    }
    ~ted_gaussian_msk() override{};

private:
    float compute_error_cf() override;
    float compute_error_ff() override;
};

/*!
 * \brief Signal times Slope Maximum Likelihood solution approximation
 * timing error detector algorithm class
 * \ingroup internal
 *
 * \details
 * This is the Signal times Slope Maximum Likelihood solution approximation
 * timing error detector algorithm class.  This approximation is good for
 * small signals (< 1.0) and/or situations with low SNR.
 *
 * See equation (5) and the 2 following paragraphs of:
 * Fredric J. Harris, Michael Rice, "Multirate Digital Filters for
 * Symbol Timing Synchronization in Software Defined Radios",
 * _IEEE_Journal_on_Selected_Areas_in_Communications_, Vol. 19, No. 12,
 * December 2001, pp. 2346 - 2357
 */
class ted_signal_times_slope_ml : public timing_error_detector
{
public:
    /*!
     * \brief Create a Signal times Slope Maximum Likelihood solution
     * approximation timing error detector
     */
    ted_signal_times_slope_ml()
        : timing_error_detector(
              TED_SIGNAL_TIMES_SLOPE_ML, 1, 1, false, true, constellation_sptr())
    {
    }
    ~ted_signal_times_slope_ml() override{};

private:
    float compute_error_cf() override;
    float compute_error_ff() override;
};

/*!
 * \brief Signum times Slope Maximum Likelihood solution approximation
 * timing error detector algorithm class
 * \ingroup internal
 *
 * \details
 * This is the Signum times Slope Maximum Likelihood solution approximation
 * timing error detector algorithm class.  This approximation is good for
 * large signals (> 1.0) and/or situations with high SNR.
 *
 * See equation (5) and the 2 following paragraphs of:
 * Fredric J. Harris, Michael Rice, "Multirate Digital Filters for
 * Symbol Timing Synchronization in Software Defined Radios",
 * _IEEE_Journal_on_Selected_Areas_in_Communications_, Vol. 19, No. 12,
 * December 2001, pp. 2346 - 2357
 */
class ted_signum_times_slope_ml : public timing_error_detector
{
public:
    /*!
     * \brief Create a Signum times Slope Maximum Likelihood solution
     * approximation timing error detector
     */
    ted_signum_times_slope_ml()
        : timing_error_detector(
              TED_SIGNUM_TIMES_SLOPE_ML, 1, 1, false, true, constellation_sptr())
    {
    }
    ~ted_signum_times_slope_ml() override{};

private:
    float compute_error_cf() override;
    float compute_error_ff() override;
};

} /* namespace digital */
} /* namespace gr */

#endif /* INCLUDED_DIGITAL_TIMING_ERROR_DETECTOR_H */
