//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
//

#ifndef __INET_BMACLAYER_H
#define __INET_BMACLAYER_H

#include "inet/physicallayer/contract/packetlevel/IRadio.h"
#include "inet/linklayer/contract/IMACProtocol.h"
#include "inet/linklayer/common/MACAddress.h"
#include "inet/linklayer/base/MACProtocolBase.h"
#include "inet/linklayer/bmac/BMacFrame_m.h"

namespace inet {

using namespace physicallayer;

/**
 * @brief Implementation of B-MAC (called also Berkeley MAC, Low Power
 * Listening or LPL).
 *
 * The protocol works as follows: each node is allowed to sleep for
 * slotDuration. After waking up, it first checks the channel for ongoing
 * transmissions.
 * If a transmission is catched (a preamble is received), the node stays awake
 * for at most slotDuration and waits for the actual data packet.
 * If a node wants to send a packet, it first sends preambles for at least
 * slotDuration, thus waking up all nodes in its transmission radius and
 * then sends out the data packet. If a mac-level ack is required, then the
 * receiver sends the ack immediately after receiving the packet (no preambles)
 * and the sender waits for some time more before going back to sleep.
 *
 * B-MAC is designed for low traffic, low power communication in WSN and is one
 * of the most widely used protocols (e.g. it is part of TinyOS).
 * The finite state machine of the protocol is given in the below figure:
 *
 * \image html BMACFSM.png "B-MAC Layer - finite state machine"
 *
 * A paper describing this implementation can be found at:
 * http://www.omnet-workshop.org/2011/uploads/slides/OMNeT_WS2011_S5_C1_Foerster.pdf
 *
 * @class BMacLayer
 * @ingroup macLayer
 * @author Anna Foerster
 *
 */
class INET_API BMacLayer : public MACProtocolBase, public IMACProtocol
{
  private:
    /** @brief Copy constructor is not allowed.
     */
    BMacLayer(const BMacLayer&);
    /** @brief Assignment operator is not allowed.
     */
    BMacLayer& operator=(const BMacLayer&);

  public:
    BMacLayer() {}
    virtual ~BMacLayer();

    /** @brief Initialization of the module and some variables*/
    virtual int numInitStages() const override { return NUM_INIT_STAGES; }
    virtual void initialize(int) override;
    virtual void refreshDisplay() const override;

    /** @brief Delete all dynamically allocated objects of the module*/
    virtual void finish() override;

    /** @brief Handle messages from lower layer */
    virtual void handleLowerPacket(cPacket *) override;

    /** @brief Handle messages from upper layer */
    virtual void handleUpperPacket(cPacket *) override;

    /** @brief Handle self messages such as timers */
    virtual void handleSelfMessage(cMessage *) override;

    /** @brief Handle control messages from lower layer */
    virtual void receiveSignal(cComponent *source, simsignal_t signalID, long value, cObject *details) override;

  protected:
    typedef std::list<BMacFrame *> MacQueue;

    /** @brief The MAC address of the interface. */
    MACAddress address;

    /** @brief A queue to store packets from upper layer in case another
       packet is still waiting for transmission.*/
    MacQueue macQueue;

    /** @brief The radio. */
    IRadio *radio = nullptr;
    IRadio::TransmissionState transmissionState = IRadio::TRANSMISSION_STATE_UNDEFINED;

    /** @name Different tracked statistics.*/
    /*@{*/
    long nbTxDataPackets = 0;
    long nbTxPreambles = 0;
    long nbRxDataPackets = 0;
    long nbRxPreambles = 0;
    long nbMissedAcks = 0;
    long nbRecvdAcks = 0;
    long nbDroppedDataPackets = 0;
    long nbTxAcks = 0;
    /*@}*/

    /** @brief MAC states
     *
     *  The MAC states help to keep track what the MAC is actually
     *  trying to do.
     *  INIT -- node has just started and its status is unclear
     *  SLEEP -- node sleeps, but accepts packets from the network layer
     *  CCA -- Clear Channel Assessment - MAC checks
     *         whether medium is busy
     *  SEND_PREAMBLE -- node sends preambles to wake up all nodes
     *  WAIT_DATA -- node has received at least one preamble from another node
     *                  and wiats for the actual data packet
     *  SEND_DATA -- node has sent enough preambles and sends the actual data
     *                  packet
     *  WAIT_TX_DATA_OVER -- node waits until the data packet sending is ready
     *  WAIT_ACK -- node has sent the data packet and waits for ack from the
     *                 receiving node
     *  SEND_ACK -- node send an ACK back to the sender
     *  WAIT_ACK_TX -- node waits until the transmission of the ack packet is
     *                    over
     */
    enum States {
        INIT,    //0
        SLEEP,    //1
        CCA,    //2
        SEND_PREAMBLE,    //3
        WAIT_DATA,    //4
        SEND_DATA,    //5
        WAIT_TX_DATA_OVER,    //6
        WAIT_ACK,    //7
        SEND_ACK,    //8
        WAIT_ACK_TX    //9
    };
    /** @brief The current state of the protocol */
    States macState = (States)-1;

    /** @brief Types of messages (self messages and packets) the node can
     * process **/
    enum TYPES {
        // packet types
        BMAC_PREAMBLE = 191,
        BMAC_DATA,
        BMAC_ACK,
        // self message types
        BMAC_RESEND_DATA,
        BMAC_ACK_TIMEOUT,
        BMAC_START_BMAC,
        BMAC_WAKE_UP,
        BMAC_SEND_ACK,
        BMAC_CCA_TIMEOUT,
        BMAC_ACK_TX_OVER,
        BMAC_SEND_PREAMBLE,
        BMAC_STOP_PREAMBLES,
        BMAC_DATA_TX_OVER,
        BMAC_DATA_TIMEOUT
    };

    // messages used in the FSM
    cMessage *resend_data = nullptr;
    cMessage *ack_timeout = nullptr;
    cMessage *start_bmac = nullptr;
    cMessage *wakeup = nullptr;
    cMessage *send_ack = nullptr;
    cMessage *cca_timeout = nullptr;
    cMessage *ack_tx_over = nullptr;
    cMessage *send_preamble = nullptr;
    cMessage *stop_preambles = nullptr;
    cMessage *data_tx_over = nullptr;
    cMessage *data_timeout = nullptr;

    /** @name Help variables for the acknowledgment process. */
    /*@{*/
    MACAddress lastDataPktSrcAddr;
    MACAddress lastDataPktDestAddr;
    int txAttempts = 0;
    /*@}*/

    /** @brief The maximum length of the queue */
    unsigned int queueLength = 0;
    /** @brief Animate (colorize) the nodes.
     *
     * The color of the node reflects its basic status (not the exact state!)
     * BLACK - node is sleeping
     * GREEN - node is receiving
     * YELLOW - node is sending
     */
    bool animation = false;
    /** @brief The duration of the slot in secs. */
    double slotDuration = 0;
    /** @brief Length of the header*/
    int headerLength = 0;
    /** @brief The bitrate of transmission */
    double bitrate = 0;
    /** @brief The duration of CCA */
    double checkInterval = 0;
    /** @brief Use MAC level acks or not */
    bool useMacAcks = false;
    /** @brief Maximum transmission attempts per data packet, when ACKs are
     * used */
    int maxTxAttempts = 0;
    /** @brief Gather stats at the end of the simulation */
    bool stats = false;

    /** @brief Generate new interface address*/
    virtual void initializeMACAddress();
    virtual InterfaceEntry *createInterfaceEntry() override;
    virtual void handleCommand(cMessage *msg) {}

    /** @brief Internal function to send the first packet in the queue */
    void sendDataPacket();

    /** @brief Internal function to send an ACK */
    void sendMacAck();

    /** @brief Internal function to send one preamble */
    void sendPreamble();

    /** @brief Internal function to attach a signal to the packet */
    void attachSignal(BMacFrame *macPkt);

    /** @brief Internal function to add a new packet from upper to the queue */
    bool addToQueue(cMessage *msg);

    virtual void flushQueue();

    virtual void clearQueue();

    cPacket *decapsMsg(BMacFrame *msg);
    BMacFrame *encapsMsg(cPacket *netwPkt);
    cObject *setUpControlInfo(cMessage *const pMsg, const MACAddress& pSrcAddr);
};

} // namespace inet

#endif // ifndef __INET_BMACLAYER_H

