/***************************************************************************
 *   Copyright (C) 2021 - 2023 by Federico Amedeo Izzo IU2NUO,             *
 *                                Niccolò Izzo IU2KIN                      *
 *                                Frederik Saraci IU2NRO                   *
 *                                Silvano Seva IU2KWO                      *
 *                                                                         *
 *   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/>   *
 ***************************************************************************/

#ifndef AT1846S_H
#define AT1846S_H

#include <stdint.h>
#include <stdbool.h>
#include <datatypes.h>

/**
 * Enumeration type defining the bandwidth settings supported by the AT1846S chip.
 */
enum class AT1846S_BW : uint8_t
{
    _12P5 = 0,    ///< 12.5kHz bandwidth.
    _25   = 1     ///< 25kHz bandwidth.
};

/**
 * Enumeration type defining the possible operating mode configurations for the
 * AT1846S chip.
 */
enum class AT1846S_OpMode : uint8_t
{
    FM  = 0,      ///< Analog FM operation.
    DMR = 1       ///< DMR operation.
};

/**
 * Enumeration type defining the AT1846S functional modes.
 */
enum class AT1846S_FuncMode : uint8_t
{
    OFF = 0,      ///< Both TX and RX off.
    RX  = 1,      ///< RX enabled.
    TX  = 2,      ///< TX enabled.
};

/**
 * Low-level driver for AT1846S "radio on a chip" integrated circuit.
 */

class AT1846S
{
public:

    /**
     * \return a reference to the instance of the AT1846S class (singleton).
     */
    static AT1846S& instance()
    {
        static AT1846S instance;
        return instance;
    }

    /**
     * Destructor.
     * When called it implicitly shuts down the AT146S chip.
     */
    ~AT1846S()
    {
        terminate();
    }

    /**
     * Initialise the AT146S chip.
     */
    void init();

    /**
     * Shut down the AT146S chip.
     */
    inline void terminate()
    {
        disableCtcss();
        setFuncMode(AT1846S_FuncMode::OFF);
    }

    /**
     * Set the VCO frequency, either for transmission or reception.
     *
     * @param freq: VCO frequency.
     */
    void setFrequency(const freq_t freq)
    {
        // The value to be written in registers is given by: 0.0016*freqency
        uint32_t val = (freq/1000)*16;
        uint16_t fHi = (val >> 16) & 0xFFFF;
        uint16_t fLo = val & 0xFFFF;

        i2c_writeReg16(0x29, fHi);
        i2c_writeReg16(0x2A, fLo);

        reloadConfig();
    }

    /**
     * Set the transmission and reception bandwidth.
     *
     * @param band: bandwidth.
     */
    void setBandwidth(const AT1846S_BW band);

    /**
     * Set the operating mode.
     *
     * @param mode: operating mode.
     */
    void setOpMode(const AT1846S_OpMode mode);

    /**
     * Set the functional mode.
     *
     * @param mode: functional mode.
     */
    void setFuncMode(const AT1846S_FuncMode mode)
    {
        /*
         * Functional mode is controlled by bits 5 (RX on) and 6 (TX on) in
         * register 0x30. With a cast and shift we can set it easily.
         */

        uint16_t value = static_cast< uint16_t >(mode) << 5;
        maskSetRegister(0x30, 0x0060, value);
    }

    /**
     * Enable the CTCSS tone for transmission.
     *
     * @param freq: CTCSS tone frequency.
     */
    void enableTxCtcss(const tone_t freq)
    {
        i2c_writeReg16(0x4A, freq*10);          // Set CTCSS1 frequency reg.
        i2c_writeReg16(0x4B, 0x0000);           // Clear CDCSS bits
        i2c_writeReg16(0x4C, 0x0000);
        maskSetRegister(0x4E, 0x0600, 0x0600);  // Enable CTCSS TX
    }

    /**
     * Enable the CTCSS tone detection during reception.
     *
     * @param freq: CTCSS tone frequency.
     */
    void enableRxCtcss(const tone_t freq)
    {
        i2c_writeReg16(0x4D, freq*10);          // Set CTCSS2 frequency reg.
        i2c_writeReg16(0x5B, getCtcssThreshFromTone(freq));
        maskSetRegister(0x3A, 0x001F, 0x0008);  // Enable CTCSS2 freq. detection
    }

    /**
     * Check if CTCSS tone is detected when in RX mode.
     *
     * @return true if the RX CTCSS tone is being detected.
     */
    inline bool rxCtcssDetected()
    {
        // Check if CTCSS detection is enabled: if not, return false.
        if((i2c_readReg16(0x3A) & 0x0008) == 0) return false;

        // Check CTCSS2 compare flag
        uint16_t reg  = i2c_readReg16(0x1C);
        return ((reg & 0x100) != 0);
    }

    /**
     * Turn off both transmission CTCSS tone and reception CTCSS tone decoding.
     */
    inline void disableCtcss()
    {
        maskSetRegister(0x4E, 0x0600, 0x0000);  // Disable TX CTCSS
        maskSetRegister(0x3A, 0x001F, 0x0000);  // Disable CTCSS freq. detection
        i2c_writeReg16(0x4A, 0x0000);           // Clear CTCSS1 frequency reg.
        i2c_writeReg16(0x4D, 0x0000);           // Clear CTCSS2 frequency reg.
    }

    /**
     * Get current RSSI value.
     *
     * @return current RSSI in dBm.
     */
    inline int16_t readRSSI()
    {
        // RSSI value is contained in the upper 8 bits of register 0x1B.
        return -137 + static_cast< int16_t >(i2c_readReg16(0x1B) >> 8);
    }

    /**
     * Set the gain of internal programmable gain amplifier.
     *
     * @param gain: PGA gain.
     */
    inline void setPgaGain(const uint8_t gain)
    {
        uint16_t pga = (gain & 0x1F) << 6;
        maskSetRegister(0x0A, 0x07C0, pga);
    }

    /**
     * Set microphone gain for transmission.
     *
     * @param gain: microphone gain.
     */
    inline void setMicGain(const uint8_t gain)
    {
        maskSetRegister(0x41, 0x007F, static_cast< uint16_t >(gain));
    }

    /**
     * Set maximum FM transmission deviation.
     *
     * @param dev: maximum allowed deviation.
     */
    inline void setTxDeviation(const uint16_t dev)
    {
        uint16_t value = (dev & 0x03FF) << 6;
        maskSetRegister(0x59, 0xFFC0, value);
    }

    /**
     * Set the gain for internal automatic gain control system.
     *
     * @param gain: AGC gain.
     */
    inline void setAgcGain(const uint8_t gain)
    {
        uint16_t agc = (gain & 0x0F) << 8;
        maskSetRegister(0x44, 0x0F00, agc);
    }

    /**
     * Set audio gain for recepion.
     *
     * @param analogDacGain: "analog DAC gain" in AT1846S manual.
     * @param digitalGain: "digital voice gain" in AT1846S manual.
     */
    inline void setRxAudioGain(const uint8_t analogDacGain,
                               const uint8_t digitalGain)
    {
        uint16_t value = (analogDacGain & 0x0F) << 4;
        maskSetRegister(0x44, 0x00F0, value);
        maskSetRegister(0x44, 0x000F, static_cast< uint16_t >(digitalGain));
    }

    /**
     * Set noise1 thresholds for squelch opening and closing.
     *
     * @param highTsh: upper threshold.
     * @param lowTsh: lower threshold.
     */
    inline void setNoise1Thresholds(const uint8_t highTsh, const uint8_t lowTsh)
    {
        uint16_t value = ((highTsh & 0x1F) << 8) | (lowTsh & 0x1F);
        i2c_writeReg16(0x48, value);
    }

    /**
     * Set noise2 thresholds for squelch opening and closing.
     *
     * @param highTsh: upper threshold.
     * @param lowTsh: lower threshold.
     */
    inline void setNoise2Thresholds(const uint8_t highTsh, const uint8_t lowTsh)
    {
        uint16_t value = ((highTsh & 0x1F) << 8) | (lowTsh & 0x1F);
        i2c_writeReg16(0x60, value);
    }

    /**
     * Set RSSI thresholds for squelch opening and closing.
     *
     * @param highTsh: upper threshold.
     * @param lowTsh: lower threshold.
     */
    inline void setRssiThresholds(const uint8_t highTsh, const uint8_t lowTsh)
    {
        uint16_t value = ((highTsh & 0x1F) << 8) | (lowTsh & 0x1F);
        i2c_writeReg16(0x3F, value);
    }

    /**
     * Set PA drive control bits.
     *
     * @param value: PA drive value.
     */
    inline void setPaDrive(const uint8_t value)
    {
        uint16_t pa = value << 11;
        maskSetRegister(0x0A, 0x7800, pa);
    }

    /**
     * Set threshold for analog FM squelch opening.
     *
     * @param thresh: squelch threshold.
     */
    inline void setAnalogSqlThresh(const uint8_t thresh)
    {
        i2c_writeReg16(0x49, static_cast< uint16_t >(thresh));
    }

    /**
     * Mute the RX audio output while keeping the chip in RX mode.
     */
    inline void muteRxOutput()
    {
        // Setting bit 7 of register 0x30 mutes the RX audio output
        maskSetRegister(0x30, 0x0080, 0x0080);
    }

    /**
     * Unmute the RX audio output.
     */
    inline void unmuteRxOutput()
    {
        // Clearing bit 7 of register 0x30 unmutes the RX audio output
        maskSetRegister(0x30, 0x0080, 0x0000);
    }

private:

    /**
     * Constructor.
     */
    AT1846S()
    {
        i2c_init();
    }

    /**
     * Helper function to set/clear some specific bits in a register.
     *
     * @param reg: address of the register to be changed.
     * @param mask: bitmask to select which bits to change. To modify the i-th
     * bit in the register, set its value to "1" in the bitmask.
     * @param value: New value for the masked bits.
     */
    inline void maskSetRegister(const uint8_t reg, const uint16_t mask,
                                const uint16_t value)
    {
        uint16_t regVal = i2c_readReg16(reg);
        regVal = (regVal & ~mask) | (value & mask);
        i2c_writeReg16(reg, regVal);
    }

    /**
     * Helper function to be called to make effective some of the AT1846S
     * configuration, when changed with TX or RX active.
     * It has been observed that, to make effective a change in some of the main
     * AT1846S parameters, the chip must be "power cycled" by turning it off and
     * then switching back the previous functionality.
     */
    inline void reloadConfig()
    {
        uint16_t funcMode = i2c_readReg16(0x30) & 0x0060;   // Get current op. status
        maskSetRegister(0x30, 0x0060, 0x0000);              // RX and TX off
        maskSetRegister(0x30, 0x0060, funcMode);            // Restore op. status
    }

    /**
     * Initialise the I2C interface.
     */
    void i2c_init();

    /**
     * Write one register via I2C interface.
     *
     * @param reg: address of the register to be written.
     * @param value: value to be written to the register.
     */
    void i2c_writeReg16(const uint8_t reg, const uint16_t value);

    /**
     * Read one register via I2C interface.
     *
     * @param reg: address of the register to be read.
     */
    uint16_t i2c_readReg16(const uint8_t reg);

    /**
     * This function returns the value to be written into the AT1846S CTCSS
     * threshold register when enabling the detection in RX mode.
     * Values were obtained from the function contained in TYT firmware for
     * MD-UV380 version S18.16 at address 0x0806ba2c.
     *
     * @param tone: tone_t variable specifying the CTCSS tone.
     * @return an uint16_t value to be written directly into AT1846S
     *         CTCSS threshold register.
     */
    uint16_t getCtcssThreshFromTone(const tone_t tone)
    {
        switch(tone)
        {
            case 670:  return 0x0C0D; break;    // 67.0 Hz
            case 693:  return 0x0C0C; break;    // 69.3 Hz
            case 719:  return 0x0B0B; break;    // 71.9 Hz
            case 744:                           // 74.4 Hz
            case 770:  return 0x0A0A; break;    // 77.0 Hz
            case 797:                           // 79.7 Hz
            case 825:  return 0x0909; break;    // 82.5 Hz
            case 854:                           // 85.4 Hz
            case 885:  return 0x0808; break;    // 88.5 Hz
            case 915:                           // 91.5 Hz
            case 948:  return 0x0707; break;    // 94.8 Hz
            case 974:  return 0x0706; break;    // 97.4 Hz
            case 1000:                          // 100.0Hz
            case 1034: return 0x0606; break;    // 103.4Hz
            case 1072:                          // 107.2Hz
            case 1109: return 0x0605; break;    // 110.9Hz
            case 1148: return 0x0505; break;    // 114.8Hz
            case 1188:                          // 118.8Hz
            case 1230: return 0x0504; break;    // 123.0Hz
            case 1273:                          // 127.3Hz
            case 1318: return 0x0404; break;    // 131.8Hz
            case 1365:                          // 136.5Hz
            case 1413:                          // 141.3Hz
            case 1462: return 0x0403; break;    // 146.2Hz
            case 1514: return 0x0504; break;    // 151.4Hz
            case 1567:                          // 156.7Hz
            case 1622:                          // 162.2Hz
            case 1679:                          // 167.9Hz
            case 1713:                          // 171.3Hz
            case 1799: return 0x0403; break;    // 179.9Hz
            case 1862: return 0x0400; break;    // 186.2Hz
            case 1928: return 0x0302; break;    // 192.8Hz
            case 2035:                          // 203.5Hz
            case 2107:                          // 210.7Hz
            case 2181:                          // 218.1Hz
            case 2257: return 0x0302; break;    // 225.7Hz
            case 2336:                          // 233.6Hz
            case 2418:                          // 241.8Hz
            case 2503: return 0x0300; break;    // 250.3Hz

                                                // 159.8Hz, 165.5Hz, 173.8Hz,
                                                // 177.3Hz, 183.5Hz, 189.9Hz,
                                                // 196.6Hz, 199.5Hz, 206.5Hz,
            default:   return 0x0505; break;    // 229.1Hz, 254.1Hz
        }
    }
};

#endif /* AT1846S_H */
