/*
 * Copyright (c) 2009 CTTC
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Nicola Baldo <nbaldo@cttc.es>
 */

#include "single-model-spectrum-channel.h"

#include "spectrum-phy.h"
#include "spectrum-propagation-loss-model.h"
#include "spectrum-transmit-filter.h"
#include "wraparound-model.h"

#include "ns3/angles.h"
#include "ns3/antenna-model.h"
#include "ns3/double.h"
#include "ns3/log.h"
#include "ns3/mobility-model.h"
#include "ns3/net-device.h"
#include "ns3/node.h"
#include "ns3/object.h"
#include "ns3/packet-burst.h"
#include "ns3/packet.h"
#include "ns3/propagation-delay-model.h"
#include "ns3/propagation-loss-model.h"
#include "ns3/simulator.h"

#include <algorithm>

namespace ns3
{

NS_LOG_COMPONENT_DEFINE("SingleModelSpectrumChannel");

NS_OBJECT_ENSURE_REGISTERED(SingleModelSpectrumChannel);

SingleModelSpectrumChannel::SingleModelSpectrumChannel()
{
    NS_LOG_FUNCTION(this);
}

void
SingleModelSpectrumChannel::DoDispose()
{
    NS_LOG_FUNCTION(this);
    m_phyList.clear();
    m_spectrumModel = nullptr;
    SpectrumChannel::DoDispose();
}

TypeId
SingleModelSpectrumChannel::GetTypeId()
{
    NS_LOG_FUNCTION_NOARGS();
    static TypeId tid = TypeId("ns3::SingleModelSpectrumChannel")
                            .SetParent<SpectrumChannel>()
                            .SetGroupName("Spectrum")
                            .AddConstructor<SingleModelSpectrumChannel>();
    return tid;
}

void
SingleModelSpectrumChannel::RemoveRx(Ptr<SpectrumPhy> phy)
{
    NS_LOG_FUNCTION(this << phy);
    auto it = std::find(begin(m_phyList), end(m_phyList), phy);
    if (it != std::end(m_phyList))
    {
        m_phyList.erase(it);
    }
}

void
SingleModelSpectrumChannel::AddRx(Ptr<SpectrumPhy> phy)
{
    NS_LOG_FUNCTION(this << phy);
    if (std::find(m_phyList.cbegin(), m_phyList.cend(), phy) == m_phyList.cend())
    {
        m_phyList.push_back(phy);
    }
    else
    {
        // PHY has switched its channel, reset m_spectrumModel
        m_spectrumModel = nullptr;
    }
}

void
SingleModelSpectrumChannel::StartTx(Ptr<SpectrumSignalParameters> txParams)
{
    NS_LOG_FUNCTION(this << txParams->psd << txParams->duration << txParams->txPhy);
    NS_ASSERT_MSG(txParams->psd, "NULL txPsd");
    NS_ASSERT_MSG(txParams->txPhy, "NULL txPhy");

    Ptr<SpectrumSignalParameters> txParamsTrace =
        txParams->Copy(); // copy it since traced value cannot be const (because of potential
                          // underlying DynamicCasts)
    m_txSigParamsTrace(txParamsTrace);

    // just a sanity check routine. We might want to remove it to save some computational load --
    // one "if" statement  ;-)
    if (!m_spectrumModel)
    {
        // first pak, record SpectrumModel
        m_spectrumModel = txParams->psd->GetSpectrumModel();
    }
    else
    {
        // all attached SpectrumPhy instances must use the same SpectrumModel
        NS_ASSERT(*(txParams->psd->GetSpectrumModel()) == *m_spectrumModel);
    }

    auto wraparound = GetObject<WraparoundModel>();
    Ptr<MobilityModel> refSenderMobility = txParams->txPhy->GetMobility();
    Ptr<MobilityModel> senderMobility = refSenderMobility;

    for (auto rxPhyIterator = m_phyList.begin(); rxPhyIterator != m_phyList.end(); ++rxPhyIterator)
    {
        Ptr<NetDevice> rxNetDevice = (*rxPhyIterator)->GetDevice();
        Ptr<NetDevice> txNetDevice = txParams->txPhy->GetDevice();

        if (rxNetDevice && txNetDevice)
        {
            // we assume that devices are attached to a node
            if (rxNetDevice->GetNode()->GetId() == txNetDevice->GetNode()->GetId())
            {
                NS_LOG_DEBUG("Skipping the pathloss calculation among different antennas of the "
                             "same node, not supported yet by any pathloss model in ns-3.");
                continue;
            }
        }

        if (m_filter && m_filter->Filter(txParams, *rxPhyIterator))
        {
            continue;
        }

        if ((*rxPhyIterator) != txParams->txPhy)
        {
            Time delay;

            Ptr<MobilityModel> receiverMobility = (*rxPhyIterator)->GetMobility();
            NS_LOG_LOGIC("copying signal parameters " << txParams);
            Ptr<SpectrumSignalParameters> rxParams = txParams->Copy();

            if (senderMobility && receiverMobility)
            {
                if (wraparound)
                {
                    // Use virtual mobility model instead
                    senderMobility =
                        wraparound->GetVirtualMobilityModel(refSenderMobility, receiverMobility);
                }
                rxParams->txMobility = senderMobility;

                double txAntennaGain = 0;
                double rxAntennaGain = 0;
                double propagationGainDb = 0;
                double pathLossDb = 0;
                if (rxParams->txAntenna)
                {
                    Angles txAngles(receiverMobility->GetPosition(), senderMobility->GetPosition());
                    txAntennaGain = rxParams->txAntenna->GetGainDb(txAngles);
                    NS_LOG_LOGIC("txAntennaGain = " << txAntennaGain << " dB");
                    pathLossDb -= txAntennaGain;
                }
                Ptr<AntennaModel> rxAntenna =
                    DynamicCast<AntennaModel>((*rxPhyIterator)->GetAntenna());
                if (rxAntenna)
                {
                    Angles rxAngles(senderMobility->GetPosition(), receiverMobility->GetPosition());
                    rxAntennaGain = rxAntenna->GetGainDb(rxAngles);
                    NS_LOG_LOGIC("rxAntennaGain = " << rxAntennaGain << " dB");
                    pathLossDb -= rxAntennaGain;
                }
                if (m_propagationLoss)
                {
                    propagationGainDb =
                        m_propagationLoss->CalcRxPower(0, senderMobility, receiverMobility);
                    NS_LOG_LOGIC("propagationGainDb = " << propagationGainDb << " dB");
                    pathLossDb -= propagationGainDb;
                }
                NS_LOG_LOGIC("total pathLoss = " << pathLossDb << " dB");
                // Gain trace
                m_gainTrace(senderMobility,
                            receiverMobility,
                            txAntennaGain,
                            rxAntennaGain,
                            propagationGainDb,
                            pathLossDb);
                // Pathloss trace
                m_pathLossTrace(txParams->txPhy, *rxPhyIterator, pathLossDb);
                if (pathLossDb > m_maxLossDb)
                {
                    // beyond range
                    continue;
                }
                double pathGainLinear = std::pow(10.0, (-pathLossDb) / 10.0);
                *(rxParams->psd) *= pathGainLinear;

                if (m_propagationDelay)
                {
                    delay = m_propagationDelay->GetDelay(senderMobility, receiverMobility);
                }
            }

            if (rxNetDevice)
            {
                // the receiver has a NetDevice, so we expect that it is attached to a Node
                uint32_t dstNode = rxNetDevice->GetNode()->GetId();
                Simulator::ScheduleWithContext(dstNode,
                                               delay,
                                               &SingleModelSpectrumChannel::StartRx,
                                               this,
                                               rxParams,
                                               *rxPhyIterator);
            }
            else
            {
                // the receiver is not attached to a NetDevice, so we cannot assume that it is
                // attached to a node
                Simulator::Schedule(delay,
                                    &SingleModelSpectrumChannel::StartRx,
                                    this,
                                    rxParams,
                                    *rxPhyIterator);
            }
        }
    }
}

void
SingleModelSpectrumChannel::StartRx(Ptr<SpectrumSignalParameters> params, Ptr<SpectrumPhy> receiver)
{
    NS_LOG_FUNCTION(this << params);
    if (m_spectrumPropagationLoss)
    {
        params->psd =
            m_spectrumPropagationLoss->CalcRxPowerSpectralDensity(params,
                                                                  params->txMobility,
                                                                  receiver->GetMobility());
    }
    receiver->StartRx(params);
}

std::size_t
SingleModelSpectrumChannel::GetNDevices() const
{
    NS_LOG_FUNCTION(this);
    return m_phyList.size();
}

Ptr<NetDevice>
SingleModelSpectrumChannel::GetDevice(std::size_t i) const
{
    NS_LOG_FUNCTION(this << i);
    return m_phyList.at(i)->GetDevice()->GetObject<NetDevice>();
}

} // namespace ns3
