#ifndef TRANSCEIVER_HPP__
#define TRANSCEIVER_HPP__

#include <QObject>

#include "JS8_Main/Radio.h"
#include "JS8_Main/qt_helpers.h"

class QString;

//
// Abstract Transceiver Interface
//
//  This is the minimal generic interface to a rig.
//
// Responsibilities
//
//  Provides a  Qt slot  to set  the frequency, mode  and PTT  of some
//  transceiver. This is a Qt slot so  that it may be invoked across a
//  thread boundary.
//
//  Provides a synchronisation Qt slot  which should be implemented in
//  sub-classes in such a way that  normal operation of the rig is not
//  disturbed.  This  is  intended  to   be  use  to  poll  rig  state
//  periodically and changing  VFO to read the other  VFO frequency or
//  mode for  example should  not be  done since  the operator  may be
//  tuning the VFO at the time and would be surprised by an unprompted
//  VFO change.
//
//  Provides a control interface using Qt  slots to start and stop the
//  rig control and PTT connections.
//
//  These  are Qt  slots rather  than the  constructor and  destructor
//  because   it   is   expected   that   the   concrete   Transceiver
//  implementations will run in a  separate thread from where they are
//  constructed.
//
//  Qt signals are defined to notify clients of asynchronous rig state
//  changes and failures.  These can and are expected  to cross thread
//  boundaries.
//
//  A  signal   finished()  is   defined  that   concrete  Transceiver
//  implementations must emit when they are ripe for destruction. This
//  is  intended to  be  used  by clients  that  move the  Transceiver
//  instance to  a thread  and need  to use  QObject::deleteLater() to
//  safely dispose of the Transceiver instance. Implementations should
//  expect Qt  slot calls  after emitting  finished, it  is up  to the
//  implementation whether these slot invocations are ignored.
//
class Transceiver : public QObject {
    Q_OBJECT
    Q_ENUMS(MODE)

  public:
    using Frequency = Radio::Frequency;

  protected:
    Transceiver(QObject *parent) : QObject{parent} {}

  public:
    virtual ~Transceiver() {}

    enum MODE {
        UNK,
        CW,
        CW_R,
        USB,
        LSB,
        FSK,
        FSK_R,
        DIG_U,
        DIG_L,
        AM,
        FM,
        DIG_FM
    };
    Q_ENUM(MODE)

    //
    // Aggregation of all of the rig and PTT state accessible via this
    // interface.
    //
    class TransceiverState {
      public:
        TransceiverState()
            : online_{false}, rx_frequency_{0}, tx_frequency_{0}, mode_{UNK},
              split_{Split::unknown}, ptt_{false} {}

        bool online() const { return online_; }
        Frequency frequency() const { return rx_frequency_; }
        Frequency tx_frequency() const { return tx_frequency_; }
        bool split() const { return Split::on == split_; }
        MODE mode() const { return mode_; }
        bool ptt() const { return ptt_; }

        void online(bool state) { online_ = state; }
        void frequency(Frequency f) { rx_frequency_ = f; }
        void tx_frequency(Frequency f) { tx_frequency_ = f; }
        void split(bool state) { split_ = state ? Split::on : Split::off; }
        void mode(MODE m) { mode_ = m; }
        void ptt(bool state) { ptt_ = state; }

      private:
        bool online_;
        Frequency rx_frequency_;
        Frequency tx_frequency_; // 0 means use Rx
        MODE mode_;
        enum class Split { unknown, off, on } split_;
        bool ptt_;
        // Don't forget to update the debug print and != operator if you
        // add more members here

        friend QDebug operator<<(QDebug, TransceiverState const &);
        friend bool operator!=(TransceiverState const &,
                               TransceiverState const &);
    };

    //
    // The following  slots and signals are  expected to all run  in the
    // same thread which  is not necessarily the main GUI  thread. It is
    // up  to  the client  of  the  Transceiver  class to  organise  the
    // allocation to a thread and the lifetime of the object instances.
    //

    // Apply  state changes  to the  rig. The  sequence_number parameter
    // will  be included  in  any status  updates  generated after  this
    // transaction  is processed.  The sequence  number may  be used  to
    // ignore any status  updates until the results  of this transaction
    // have been processed thus avoiding any unwanted "ping-pong" due to
    // signals crossing in transit.
    Q_SLOT virtual void set(Transceiver::TransceiverState const &,
                            unsigned sequence_number) noexcept = 0;

    // Connect and disconnect.
    Q_SLOT virtual void start(unsigned sequence_number) noexcept = 0;
    Q_SLOT virtual void stop() noexcept = 0;

    //
    // asynchronous status updates
    //

    // 0 - 1Hz
    // 1 - 10Hz rounded
    // -1 - 10Hz truncated
    // 2 - 100Hz rounded
    // -2 - 100Hz truncated
    Q_SIGNAL void resolution(int);

    // rig state changed
    Q_SIGNAL void update(Transceiver::TransceiverState const &,
                         unsigned sequence_number) const;

    // something went wrong - not recoverable, start new instance
    Q_SIGNAL void failure(QString const &reason) const;

    // Ready to be destroyed.
    Q_SIGNAL void finished() const;
};

Q_DECLARE_METATYPE(Transceiver::TransceiverState);
#if QT_VERSION < 0x050500
Q_DECLARE_METATYPE(Transceiver::MODE);
#endif

#if !defined(QT_NO_DEBUG_STREAM)
ENUM_QDEBUG_OPS_DECL(Transceiver, MODE);

QDebug operator<<(QDebug, Transceiver::TransceiverState const &);
#endif

ENUM_QDATASTREAM_OPS_DECL(Transceiver, MODE);
ENUM_CONVERSION_OPS_DECL(Transceiver, MODE);

bool operator!=(Transceiver::TransceiverState const &,
                Transceiver::TransceiverState const &);
bool operator==(Transceiver::TransceiverState const &,
                Transceiver::TransceiverState const &);

#endif
