//========================================================================
//  CFSOCHANNEL.CC - part of
//
//                 OMNeT++/OMNEST
//              Discrete System Simulation in C++
//
//========================================================================

/*--------------------------------------------------------------*
  Copyright (C) 1992-2008 Andras Varga
  Copyright (C) 2006-2008 OpenSim Ltd.

  This file is distributed WITHOUT ANY WARRANTY. See the file
  `license' for details on this and other legal matters.
*--------------------------------------------------------------*/

#include "cfsochannel.h"
#include "cmessage.h"
#include "cmodule.h"
#include "cenvir.h"
#include "random.h"
#include "distrib.h"
#include "csimulation.h"
#include "globals.h"
#include "cgate.h"
#include "cexception.h"
#include "ctimestampedvalue.h"

#ifdef WITH_PARSIM
#include "ccommbuffer.h"
#endif

//USING_NAMESPACE

using std::ostream;

Register_Class(cFSOChannel);
Define_Channel(cFSOChannel);


simsignal_t cFSOChannel::channelBusySignal;
simsignal_t cFSOChannel::messageSentSignal;
simsignal_t cFSOChannel::messageDiscardedSignal;

cFSOChannel::cFSOChannel(const char *name) : cChannel(name)
{
    txfinishtime = 0;
    delay = 0;
    datarate = 0;
}

cFSOChannel::~cFSOChannel()
{
}

cFSOChannel *cFSOChannel::create(const char *name)
{
    return dynamic_cast<cFSOChannel *>(cChannelType::getDatarateChannelType()->create(name));
}

std::string cFSOChannel::info() const
{
    return cChannel::info();
}

void cFSOChannel::initialize()
{
    channelBusySignal = registerSignal("channelBusy");
    messageSentSignal = registerSignal("messageSent");
    messageDiscardedSignal = registerSignal("messageDiscarded");

    emit(channelBusySignal, 0);
}

void cFSOChannel::rereadPars()
{
    delay = par("delay");
    datarate = par("datarate");

    setFlag(FL_ISDISABLED, par("disabled"));
    setFlag(FL_DELAY_NONZERO, delay!=0);
    setFlag(FL_DATARATE_NONZERO, datarate!=0);
}

void cFSOChannel::handleParameterChange(const char *)
{
    rereadPars();
}

void cFSOChannel::setDelay(double d)
{
    par("delay").setDoubleValue(d);
}

void cFSOChannel::setDatarate(double d)
{
    par("datarate").setDoubleValue(d);
}

void cFSOChannel::setDisabled(bool d)
{
    par("disabled").setBoolValue(d);
}

simtime_t cFSOChannel::calculateDuration(cMessage *msg) const
{
    if (flags & FL_DATARATE_NONZERO && msg->isPacket())
        return ((cPacket *)msg)->getBitLength() / datarate;
    else
        return SIMTIME_ZERO;
}

//normal cumulative distribution function
double normCFD(double val)
{
    return 0.5*erfc(-1*val * M_SQRT1_2);
}

void cFSOChannel::processMessage(cMessage *msg, simtime_t t, result_t& result)
{
    static simtime_t oldt = 0;  //last packet finish time
    const int N = 32;           //FIR response
    double tau = 5e-3;          //Correlation time
    double Ts = tau/50;         //sample period
    double para = 0.5;          //parameters of autocorrelation function
    double parb = 1;
    double PSI  = 1;          //scintillation index
    static double ran0[2*N+1];  //random generated numbers for computing zrx and ztx
    static double ran1[2*N+1];
    static double ran2[2*N+1];
    static double zrx[2*N+1];   //random generated numbers for receiver correlated with ztx
    static double ztx[2*N+1];   //random generated numbers for transmitter correlated with zrx
    double Rcorr = 0.99;            //channel correlation
    double kcorr = log((Rcorr*PSI)+1)/log(PSI+1); //correlation coefficient
    double xrx;                 //zrx after FIR
    double xtx;                 //ztx after FIR
    double atrx;                //atmospheric attenuation (xrx after NL)
    double attx;                //atmospheric attenuation (xtx after NL)
    double time[2*N+1];         //time for autocorrelation function
    double R[2*N+1];            //autocorrelation function
    double pm0=-7.0345;         //normal inverse cdf of PBE0=1e-12
    double MdB=9;               //link fade margin [dB]
    double M=pow(10, MdB/10);   //link fade margin absolute
    static double datacnt = 0;  //bytes transfered
    int error = 0;              //error flag
    int toss = 0;               //packet toss flag
    int delaycnt = 0;           //number of Ts in delay when buffer is used
    double berrx = 1e-6;        //bit error rate
    double bertx = 1e-6;

    // if channel is disabled, signal that message should be deleted
    if (flags & FL_ISDISABLED)
    {
        result.discard = true;
        cTimestampedValue tmp(t, msg);
        emit(messageDiscardedSignal, &tmp);
        return;
    }

    // propagation delay modeling
    result.delay = delay;

    // bit error modeling
    if ((bertx > 0.0) && msg->isPacket())
    {
        cPacket *pkt = (cPacket *)msg;

        double ksig = 0;
        double sigma = 0;
        for (int i=(-2*N-1); i<=(2*N+1); i++)       //calculating time for autocorrelation function
        {
            time[i+2*N+1] = i*Ts;
        }
        for (int i=0; i<(2*N+1); i++)          //calculating autocorrelation function
        {
            double ti;
            if (time[i]<0)         //absolute value for double
                ti = -1*time[i];
            else
                ti = time[i];
            R[i] = exp(-1*para*pow(ti/tau, parb));
            ksig = ksig + (R[i]*R[i]);
        }
        ksig = sqrt(ksig);
        sigma = sqrt(log(PSI+1))/ksig;

        if ((oldt == 0) || (t-oldt > ((Ts)*(2*N+1))))
        {
            for (int i=0; i<(2*N+1); i++)       //generating normal distributed random numbers (mean=0, stddev=1)
            {
                ran0[i] = normal(0, 1);
                ran1[i] = normal(0, 1);
                ran2[i] = normal(0, 1);
                zrx[i] = (sqrt(kcorr)*ran0[i]) + (sqrt(1-kcorr)*ran1[i]);
                ztx[i] = (sqrt(kcorr)*ran0[i]) + (sqrt(1-kcorr)*ran2[i]);
            }
            xrx = 0;
            xtx = 0;
            for (int j=2*N; j>=0; j--)
            {
                xrx = xrx + R[2*N-j]*zrx[j];          //FIR filter
                xtx = xtx + R[2*N-j]*ztx[j];
            }
        }
        else
        {
            int cnt=1;
            while ((t-oldt)>Ts)                 //number of Ts elapsed
            {
                oldt = oldt - Ts;
                cnt++;
            }
            for (int i=0; i<cnt; i++)
            {
                for(int j=0; j<(2*N); j++)
                {
                    ran0[j] = ran0[j+1];
                    ran1[j] = ran1[j+1];
                    ran2[j] = ran2[j+1];
                    zrx[j] = zrx[j+1];
                    ztx[j] = ztx[j+1];
                }
                ran0[2*N] = normal(0, 1);
                ran1[2*N] = normal(0, 1);
                ran2[2*N] = normal(0, 1);
                zrx[2*N] = (sqrt(kcorr)*ran0[2*N]) + (sqrt(1-kcorr)*ran1[2*N]);
                ztx[2*N] = (sqrt(kcorr)*ran0[2*N]) + (sqrt(1-kcorr)*ran2[2*N]);
                xrx = 0;
                xtx = 0;
                for (int k=2*N; k>=0; k--)
                {
                    xrx = xrx + R[2*N-k]*zrx[k];
                    xtx = xtx + R[2*N-k]*ztx[k];
                }
            }
        }

        atrx = exp(xrx*sigma - (pow(sqrt(log(PSI+1)), 2)/2));  //calculating atmosphere attenuation
        attx = exp(xtx*sigma - (pow(sqrt(log(PSI+1)), 2)/2));
        berrx = normCFD(M*atrx*pm0);    //calculating bit error rate
        bertx = normCFD(M*attx*pm0);

        if ((datacnt >= 1073741824) || (t >= 60))   //checking for the end of simmulation
        {
            int i=0;
            i++;
            i=i-1;
        }

        if (berrx > 1e-8)   //receiver BER threshold
        {
            error = 1;      //comment when not with buffer
        }
        if (error != 1)
        {
            if (dblrand() < 1.0 - pow(1.0-bertx, (double)pkt->getBitLength()))      //packet error
                {
                    pkt->setBitError(true);
                    toss = 1;
                }
        }

        while ((berrx > 1e-8) && (error == 1))
        {
            for(int i=0; i<(2*N); i++)
            {
                ran0[i] = ran0[i+1];
                ran1[i] = ran1[i+1];
                ran2[i] = ran2[i+1];
                zrx[i] = zrx[i+1];
                ztx[i] = ztx[i+1];
            }
            ran0[2*N] = normal(0, 1);
            ran1[2*N] = normal(0, 1);
            ran2[2*N] = normal(0, 1);
            zrx[2*N] = (sqrt(kcorr)*ran0[2*N]) + (sqrt(1-kcorr)*ran1[2*N]);
            ztx[2*N] = (sqrt(kcorr)*ran0[2*N]) + (sqrt(1-kcorr)*ran2[2*N]);
            xrx = 0;
            xtx = 0;
            for (int k=2*N; k>=0; k--)
            {
                xrx = xrx + R[2*N-k]*zrx[k];
                xtx = xtx + R[2*N-k]*ztx[k];
            }
            atrx = exp(xrx*sigma - (pow(sqrt(log(PSI+1)), 2)/2));
            attx = exp(xtx*sigma - (pow(sqrt(log(PSI+1)), 2)/2));
            berrx = normCFD(M*atrx*pm0);
            bertx = normCFD(M*attx*pm0);
            delaycnt++;
        }

        if (toss != 1)
        {
            datacnt = datacnt + ((double)pkt->getBitLength()/8);     //transfered data in Bytes
        }
    }

    // datarate modeling
    if ((flags & FL_DATARATE_NONZERO) && msg->isPacket())
        {
            cPacket *pkt = (cPacket *)msg;
            simtime_t duration = pkt->getBitLength() / datarate;
            if (error==1)
            {
                simtime_t buffdelay = delaycnt*Ts;
                duration = duration + buffdelay;
                error = 0;
            }
            result.duration = duration;
            txfinishtime = t + duration;
            oldt = t + duration;
        }
        else
        {
            txfinishtime = t;
        }

    // emit signals
    if (mayHaveListeners(messageSentSignal))
    {
        MessageSentSignalValue tmp(t, msg, &result);
        emit(messageSentSignal, &tmp);
    }
    if (mayHaveListeners(channelBusySignal))
    {
        cTimestampedValue tmp(t, 1L);
        emit(channelBusySignal, &tmp);

        tmp.timestamp = txfinishtime; tmp.l = 0L;
        emit(channelBusySignal, &tmp);
    }
}

void cFSOChannel::forceTransmissionFinishTime(simtime_t t)
{
    //TODO record this into the eventlog so that it can be visualized in the sequence chart
    txfinishtime = t;
}
