/*
 *  Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "BWETestBase.h"

#include <algorithm> // sort
#include <fstream>
#include <string>
#include <vector>
#include <math.h>

#include "TestSenderReceiver.h"
#include "TestLoadGenerator.h"
#include "event_wrapper.h"
#include "thread_wrapper.h"
#include "tick_util.h"
#include "critical_section_wrapper.h"


double StatVec::Mean()
{
    double sum = 0;

    // sanity
    if (size() <= 0) return (0);

    std::vector<double>::iterator it;
    for (it = begin(); it < end(); ++it)
    {
        sum += (*it);
    }

    return (sum / size());
}

double StatVec::Variance()
{
    double sumSqaure = 0;
    double sum = 0;

    std::vector<double>::iterator it;
    for (it = begin(); it < end(); ++it)
    {
        sum += (*it);
        sumSqaure += (*it) * (*it);
    }

    // Normalizes by N-1. This produces the best unbiased estimate of the
    // variance if X is a sample from a normal distribution.
    int M = static_cast<int> (size() - 1);

    if (M > 0)
    {
        double var = (sumSqaure / M) - (sum / (M+1)) * (sum / M);
        assert(var >= 0);
        return (var);
    }
    else
    {
        return (0);
    }
}

double StatVec::Std()
{
    return (sqrt(Variance()));
}

double StatVec::Max()
{
    // sanity
    if (size() <= 0) return (0);

    std::vector<double>::iterator it = begin();
    double maxVal = (*it);
    ++it;

    for (; it < end(); ++it)
    {
        if ((*it) > maxVal) maxVal = (*it);
    }

    return (maxVal);
}

double StatVec::Min()
{
    // sanity
    if (size() <= 0) return (0);

    std::vector<double>::iterator it = begin();
    double minVal = (*it);
    ++it;

    for (; it < end(); ++it)
    {
        if ((*it) < minVal) minVal = (*it);
    }

    return (minVal);
}

double StatVec::Median()
{
    double median;

    // sanity
    if (size() <= 0) return (0);

    // sort the vector
    sort(begin(), end());

    if ((size() % 2) == 0)
    {
        // even size; use average of two center elements
        median = (at(size()/2 - 1) + at(size()/2)) / 2.0;
    }
    else
    {
        // odd size; take center element
        median = at(size()/2);
    }

    return (median);
}

double StatVec::Percentile(double p)
{
    // sanity
    if (size() <= 0) return (0);

    // sort the vector
    sort(begin(), end());

    int rank = static_cast<int> (((size() - 1) * p) / 100 + 0.5); // between 1 and size()
    rank -= 1; // between 0 and size()-1

    assert(rank >= 0);
    assert(rank < static_cast<int>(size()));

    return (at(rank));
}

void StatVec::Export(std::fstream &file, bool colVec /*= false*/)
{
    // sanity
    if (size() <= 0) return;

    std::string separator;
    if (colVec) separator = "\n";
    else separator = ", ";

    std::vector<double>::iterator it = begin();
    file << (*it);
    ++it;

    for (; it < end(); ++it)
    {
        file << separator << (*it);
    }

    file << std::endl;
}


bool BWETestProcThreadFunction(void *obj)
{
    if (obj == NULL)
    {
        return false;
    }
    BWETest *theObj = static_cast<BWETest *>(obj);

    theObj->ProcLoop();

    theObj->Stop();

    return(true);
}


BWETest::BWETest(std::string testName, int startRateKbps):
_testName(testName),
_startRateKbps(startRateKbps),
_master(false),
_sendrec(NULL),
_gen(NULL),
_initialized(false),
_started(false),
_running(false),
_eventPtr(NULL),
_procThread(NULL),
_startTimeMs(-1),
_stopTimeMs(-1),
_statCritSect(CriticalSectionWrapper::CreateCriticalSection())
{
    _sendrec = new TestSenderReceiver();
}


BWETest::~BWETest()
{
    if (_running)
    {
        Stop();
    }

    _statCritSect->Enter();
    delete &_statCritSect;

    if (_sendrec)
    {
        delete _sendrec;
        _sendrec = NULL;
    }
}


bool BWETest::SetMaster(bool isMaster /*= true*/)
{
    if (!_initialized)
    {
        // Can only set status before initializing.
        _master = isMaster;
    }

    return (_master);
}


int BWETest::Init(std::string ip, WebRtc_UWord16 port)
{
    if (_initialized)
    {
        // cannot init twice
        return (-1);
    }

    if (!_sendrec)
    {
        throw "SenderReceiver must be created";
        exit(1);
    }

    if (_started)
    {
        // cannot init after start
        return (-1);
    }

    // initialize receiver port (for feedback)
    _sendrec->InitReceiver(port);

    // initialize sender
    _sendrec->SetLoadGenerator(_gen);
    _sendrec->InitSender(_startRateKbps, ip.c_str(), port);
    //_gen->Start();

    _sendrec->SetCallback(this);

    _initialized = true;

    return 0;
}


bool BWETest::Start()
{
    if (!_initialized)
    {
        // must init first
        return (false);
    }
    if (_started)
    {
        // already started, do nothing
        return (true);
    }

    if (_sendrec->Start() != 0)
    {
        // failed
        return (false);
    }

    if (_gen)
    {
        if (_gen->Start() != 0)
        {
            // failed
            return (false);
        }
    }

    _eventPtr = EventWrapper::Create();

    _startTimeMs = TickTime::MillisecondTimestamp();
    _started = true;
    _running = true;

    return (true);
}


bool BWETest::Stop()
{
    if (_procThread)
    {
        _stopTimeMs = TickTime::MillisecondTimestamp();
        _procThread->SetNotAlive();
        _running = false;
        _eventPtr->Set();

        while (!_procThread->Stop())
        {
            ;
        }

        delete _procThread;
        _procThread = NULL;

    }

    if (_eventPtr)
    {
        delete _eventPtr;
        _eventPtr = NULL;
    }

    _procThread = NULL;

    if(_gen)
    {
        _gen->Stop();
    }

    return(true);
}


bool BWETest::ProcLoop(void)
{
    bool receiving = false;

    // no critSect
    while (_running)
    {

        // check stopping criterions
        if (_master && StoppingCriterionMaster())
        {
            printf("StoppingCriterionMaster()\n");
            _stopTimeMs = TickTime::MillisecondTimestamp();
            _running = false;
        }
        else if (!_master && StoppingCriterionSlave())
        {
            printf("StoppingCriterionSlave()\n");
            _running = false;
        }

        // wait
        _eventPtr->Wait(1000); // 1000 ms

    }

    return true;
}


void BWETest::Report(std::fstream &log)
{
    // cannot report on a running test
    if(_running) return;

    CriticalSectionScoped cs(_statCritSect);

    log << "\n\n*** Test name = " << _testName << "\n";
    log << "Execution time = " <<  static_cast<double>(_stopTimeMs - _startTimeMs) / 1000 << " s\n";
    log << "\n";
    log << "RTT statistics\n";
    log << "\tMin     = " << _rttVecMs.Min() << " ms\n";
    log << "\tMax     = " << _rttVecMs.Max() << " ms\n";
    log << "\n";
    log << "Loss statistics\n";
    log << "\tAverage = " << _lossVec.Mean() << "%\n";
    log << "\tMax     = " << _lossVec.Max() << "%\n";

    log << "\n" << "Rates" << "\n";
    _rateVecKbps.Export(log);

    log << "\n" << "RTT" << "\n";
    _rttVecMs.Export(log);

}


// SenderReceiver callback
void BWETest::OnOnNetworkChanged(const WebRtc_UWord32 bitrateTargetBps,
                                 const WebRtc_UWord8 fractionLost,
                                 const WebRtc_UWord16 roundTripTimeMs,
                                 const WebRtc_UWord32 jitterMS,
                                 const WebRtc_UWord16 bwEstimateKbitMin,
                                 const WebRtc_UWord16 bwEstimateKbitMax)
{
    CriticalSectionScoped cs(_statCritSect);

    // bitrate statistics
    WebRtc_Word32 newBitrateKbps = bitrateTargetBps/1000;

    _rateVecKbps.push_back(newBitrateKbps);
    _rttVecMs.push_back(roundTripTimeMs);
    _lossVec.push_back(static_cast<double>(fractionLost) / 255.0);
}


int BWEOneWayTest::Init(std::string ip, WebRtc_UWord16 port)
{

    if (!_master)
    {
        // Use timeout stopping criterion by default for receiver
        UseRecvTimeout();
    }

    return (BWETest::Init(ip, port));

}


bool BWEOneWayTest::Start()
{
    bool ret = BWETest::Start();

    if (!_master)
    {
        // send one dummy RTP packet to enable RTT measurements
        const WebRtc_UWord8 dummy = 0;
        //_gen->sendPayload(TickTime::MillisecondTimestamp(), &dummy, 0);
        _sendrec->SendOutgoingData(
            static_cast<WebRtc_UWord32>(TickTime::MillisecondTimestamp()*90),
            &dummy, 1, webrtc::kVideoFrameDelta);
    }

    return ret;
}
