/*
 *  Copyright (c) 2012 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 "webrtc/modules/video_coding/codecs/test_framework/benchmark.h"

#include <assert.h>

#include <iostream>
#include <sstream>
#include <vector>
#if defined(_WIN32)
    #include <windows.h>
#endif

#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
#include "webrtc/modules/video_coding/codecs/test_framework/video_source.h"
#include "webrtc/system_wrappers/interface/event_wrapper.h"
#include "webrtc/test/testsupport/fileutils.h"
#include "webrtc/test/testsupport/metrics/video_metrics.h"

#define SSIM_CALC 0 // by default, don't compute SSIM

using namespace webrtc;

Benchmark::Benchmark()
:
NormalAsyncTest("Benchmark", "Codec benchmark over a range of test cases", 6),
_resultsFileName(webrtc::test::OutputPath() + "benchmark.txt"),
_codecName("Default")
{
}

Benchmark::Benchmark(std::string name, std::string description)
:
NormalAsyncTest(name, description, 6),
_resultsFileName(webrtc::test::OutputPath() + "benchmark.txt"),
_codecName("Default")
{
}

Benchmark::Benchmark(std::string name, std::string description, std::string resultsFileName, std::string codecName)
:
NormalAsyncTest(name, description, 6),
_resultsFileName(resultsFileName),
_codecName(codecName)
{
}

void
Benchmark::Perform()
{
    std::vector<const VideoSource*> sources;
    std::vector<const VideoSource*>::iterator it;

    // Configuration --------------------------
    sources.push_back(new const VideoSource(webrtc::test::ProjectRootPath() +
                                            "resources/foreman_cif.yuv", kCIF));
//    sources.push_back(new const VideoSource(webrtc::test::ProjectRootPath() +
//                                            "resources/akiyo_cif.yuv", kCIF));

    const VideoSize size[] = {kQCIF, kCIF};
    const int frameRate[] = {10, 15, 30};
    // Specifies the framerates for which to perform a speed test.
    const bool speedTestMask[] = {false, false, false};
    const int bitRate[] = {50, 100, 200, 300, 400, 500, 600, 1000};
    // Determines the number of iterations to perform to arrive at the speed result.
    enum { kSpeedTestIterations = 10 };
    // ----------------------------------------

    const int nFrameRates = sizeof(frameRate)/sizeof(*frameRate);
    assert(sizeof(speedTestMask)/sizeof(*speedTestMask) == nFrameRates);
    const int nBitrates = sizeof(bitRate)/sizeof(*bitRate);
    int testIterations = 10;

    webrtc::test::QualityMetricsResult psnr[nBitrates];
    webrtc::test::QualityMetricsResult ssim[nBitrates];
    double fps[nBitrates];
    double totalEncodeTime[nBitrates];
    double totalDecodeTime[nBitrates];

    _results.open(_resultsFileName.c_str(), std::fstream::out);
    _results << GetMagicStr() << std::endl;
    _results << _codecName << std::endl;

    for (it = sources.begin() ; it < sources.end(); it++)
    {
        for (int i = 0; i < static_cast<int>(sizeof(size)/sizeof(*size)); i++)
        {
            for (int j = 0; j < nFrameRates; j++)
            {
                std::stringstream ss;
                std::string strFrameRate;
                std::string outFileName;
                ss << frameRate[j];
                ss >> strFrameRate;
                outFileName = (*it)->GetFilePath() + "/" + (*it)->GetName() + "_" +
                    VideoSource::GetSizeString(size[i]) + "_" + strFrameRate + ".yuv";

                _target = new const VideoSource(outFileName, size[i], frameRate[j]);
                (*it)->Convert(*_target);
                if (VideoSource::FileExists(outFileName.c_str()))
                {
                    _inname = outFileName;
                }
                else
                {
                    _inname = (*it)->GetFileName();
                }

                std::cout << (*it)->GetName() << ", " << VideoSource::GetSizeString(size[i])
                    << ", " << frameRate[j] << " fps" << std::endl << "Bitrate [kbps]:";
                _results << (*it)->GetName() << "," << VideoSource::GetSizeString(size[i])
                    << "," << frameRate[j] << " fps" << std::endl << "Bitrate [kbps]";

                if (speedTestMask[j])
                {
                    testIterations = kSpeedTestIterations;
                }
                else
                {
                    testIterations = 1;
                }

                for (int k = 0; k < nBitrates; k++)
                {
                    _bitRate = (bitRate[k]);
                    double avgFps = 0.0;
                    totalEncodeTime[k] = 0;
                    totalDecodeTime[k] = 0;

                    for (int l = 0; l < testIterations; l++)
                    {
                        PerformNormalTest();
                        _appendNext = false;

                        avgFps += _framecnt / (_totalEncodeTime + _totalDecodeTime);
                        totalEncodeTime[k] += _totalEncodeTime;
                        totalDecodeTime[k] += _totalDecodeTime;

                    }
                    avgFps /= testIterations;
                    totalEncodeTime[k] /= testIterations;
                    totalDecodeTime[k] /= testIterations;

                    double actualBitRate = ActualBitRate(_framecnt) / 1000.0;
                    std::cout << " " << actualBitRate;
                    _results << "," << actualBitRate;
                    webrtc::test::QualityMetricsResult psnr_result;
                    I420PSNRFromFiles(_inname.c_str(), _outname.c_str(),
                                      _inst.width, _inst.height, &psnr[k]);
                    if (SSIM_CALC)
                    {
                        webrtc::test::QualityMetricsResult ssim_result;
                        I420SSIMFromFiles(_inname.c_str(), _outname.c_str(),
                                          _inst.width, _inst.height, &ssim[k]);

                    }
                    fps[k] = avgFps;
                }
                std::cout << std::endl << "Y-PSNR [dB]:";
                _results << std::endl << "Y-PSNR [dB]";
                for (int k = 0; k < nBitrates; k++)
                {
                    std::cout << " " << psnr[k].average;
                    _results << "," << psnr[k].average;

                }
                if (SSIM_CALC)
                {
                    std::cout << std::endl << "SSIM: ";
                    _results << std::endl << "SSIM ";
                    for (int k = 0; k < nBitrates; k++)
                    {
                        std::cout << " " << ssim[k].average;
                        _results << "," << ssim[k].average;
                    }

                }

                std::cout << std::endl << "Encode Time[ms]:";
                _results << std::endl << "Encode Time[ms]";
                for (int k = 0; k < nBitrates; k++)
                {
                    std::cout << " " << totalEncodeTime[k];
                    _results << "," << totalEncodeTime[k];

                }

                std::cout << std::endl << "Decode Time[ms]:";
                _results << std::endl << "Decode Time[ms]";
                for (int k = 0; k < nBitrates; k++)
                {
                    std::cout << " " << totalDecodeTime[k];
                    _results << "," << totalDecodeTime[k];

                }

                if (speedTestMask[j])
                {
                    std::cout << std::endl << "Speed [fps]:";
                    _results << std::endl << "Speed [fps]";
                    for (int k = 0; k < nBitrates; k++)
                    {
                        std::cout << " " << static_cast<int>(fps[k] + 0.5);
                        _results << "," << static_cast<int>(fps[k] + 0.5);
                    }
                }
                std::cout << std::endl << std::endl;
                _results << std::endl << std::endl;

                delete _target;
            }
        }
        delete *it;
    }
    _results.close();
}

void
Benchmark::PerformNormalTest()
{
    _encoder = GetNewEncoder();
    _decoder = GetNewDecoder();
    CodecSettings(_target->GetWidth(), _target->GetHeight(), _target->GetFrameRate(), _bitRate);
    Setup();
    EventWrapper* waitEvent = EventWrapper::Create();
    _encoder->InitEncode(&_inst, 4, 1440);
    CodecSpecific_InitBitrate();
    _decoder->InitDecode(&_inst,1);

    FrameQueue frameQueue;
    VideoEncodeCompleteCallback encCallback(_encodedFile, &frameQueue, *this);
    VideoDecodeCompleteCallback decCallback(_decodedFile, *this);
    _encoder->RegisterEncodeCompleteCallback(&encCallback);
    _decoder->RegisterDecodeCompleteCallback(&decCallback);

    SetCodecSpecificParameters();

    _totalEncodeTime = _totalDecodeTime = 0;
    _totalEncodePipeTime = _totalDecodePipeTime = 0;
    bool complete = false;
    _framecnt = 0;
    _encFrameCnt = 0;
    _sumEncBytes = 0;
    _lengthEncFrame = 0;
    while (!complete)
    {
        complete = Encode();
        if (!frameQueue.Empty() || complete)
        {
            while (!frameQueue.Empty())
            {
                _frameToDecode = static_cast<FrameQueueTuple *>(frameQueue.PopFrame());
                DoPacketLoss();
                int ret = Decode();
                delete _frameToDecode;
                _frameToDecode = NULL;
                if (ret < 0)
                {
                    fprintf(stderr,"\n\nError in decoder: %d\n\n", ret);
                    exit(EXIT_FAILURE);
                }
                else if (ret == 0)
                {
                    _framecnt++;
                }
                else
                {
                    fprintf(stderr, "\n\nPositive return value from decode!\n\n");
                }
            }
        }
        waitEvent->Wait(5);
    }

    _encodedVideoBuffer.Free();

    _encoder->Release();
    _decoder->Release();
    delete waitEvent;
    delete _encoder;
    delete _decoder;
    Teardown();
}

void
Benchmark::CodecSpecific_InitBitrate()
{
    if (_bitRate == 0)
    {
        _encoder->SetRates(600, _inst.maxFramerate);
    }
    else
    {
        _encoder->SetRates(_bitRate, _inst.maxFramerate);
    }
}
