/*******************************************************************************
 * plugins/VOIP/gui/AudioInputConfig.h                                         *
 *                                                                             *
 * Copyright (C) 2008, Andreas Messer <andi@bupfen.de>                         *
 * Copyright (C) 2005-2010 Thorvald Natvig <thorvald@natvig.com>               *
 * Copyright (C) 2012 by Retroshare Team <retroshare.project@gmail.com>        *
 *                                                                             *
 * This program is free software: you can redistribute it and/or modify        *
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.                         *
 *                                                                             *
 * You should have received a copy of the GNU Affero General Public License    *
 * along with this program. If not, see <https://www.gnu.org/licenses/>.       *
 *                                                                             *
 *******************************************************************************/

#include "AudioStats.h"
#include "VOIPConfigPanel.h"
#include "audiodevicehelper.h"
#include "AudioWizard.h"
#include "gui/VideoProcessor.h"
#include "gui/VideoProcessor.h"
#include "util/misc.h"
#include "util/RsProtectedTimer.h"

#include <interface/rsVOIP.h>

#define iroundf(x) ( static_cast<int>(x) )

class voipGraphSource: public RSGraphSource
{
public:
    voipGraphSource() : video_input(NULL) {}
    
    void setVideoInput(const QVideoInputDevice *vid) { video_input = vid ; }
    
     virtual QString displayName(int) const { return tr("Required bandwidth") ;}
    
    virtual QString displayValue(float v) const
    {
        if(v < 1000)
            return QString::number(v,10,2) + " B/s" ;
        else if(v < 1000*1024)
            return QString::number(v/1024,10,2) + " KB/s" ;
        else
            return QString::number(v/(1024*1024),10,2) + " MB/s" ;
    }
    
    virtual void getValues(std::map<std::string,float>& vals) const
	 {
        vals.clear() ;
        
	if(video_input)
		vals[std::string("bw")] = video_input->currentBandwidth() ;
    }
    
private:
    const QVideoInputDevice *video_input ;
};

void voipGraph::setVoipSource(voipGraphSource *gs) 
{
_src = gs ;
RSGraphWidget::setSource(gs) ;
}

voipGraph::voipGraph(QWidget *parent)
	: RSGraphWidget(parent)
{
     setFlags(RSGraphWidget::RSGRAPH_FLAGS_SHOW_LEGEND) ;
     setFlags(RSGraphWidget::RSGRAPH_FLAGS_PAINT_STYLE_PLAIN) ;
     
     _src = NULL ;
}

/** Constructor */
VOIPConfigPanel::VOIPConfigPanel(QWidget * parent, Qt::WindowFlags flags)
    : ConfigPage(parent, flags)
{
    std::cerr << "Creating audioInputConfig object" << std::endl;

    /* Invoke the Qt Designer generated object setup routine */
    ui.setupUi(this);

    loaded = false;

    inputAudioProcessor = NULL;
    inputAudioDevice = NULL;
    graph_source = nullptr;
    videoInput = nullptr;
    videoProcessor = nullptr;
    qtTick = NULL;

    ui.qcbTransmit->addItem(tr("Continuous"), RsVOIP::AudioTransmitContinous);
    ui.qcbTransmit->addItem(tr("Voice Activity"), RsVOIP::AudioTransmitVAD);
    ui.qcbTransmit->addItem(tr("Push To Talk"), RsVOIP::AudioTransmitPushToTalk);

    ui.abSpeech->qcBelow = Qt::red;
    ui.abSpeech->qcInside = Qt::yellow;
    ui.abSpeech->qcAbove = Qt::green;

    QList<QString> input_devices;
    QVideoInputDevice::getAvailableDevices(input_devices);
    ui.inputDevice_CB->clear();
    ui.inputDevice_CB->addItem(tr("[No video]"),QString(""));
    for(auto& s:input_devices)
        ui.inputDevice_CB->addItem(s,QVariant(s));

    if(!input_devices.empty())
        whileBlocking(ui.inputDevice_CB)->setCurrentIndex(1);	// select default cam

    connect( ui.qsTransmitHold, SIGNAL( valueChanged ( int ) ), this, SLOT( on_qsTransmitHold_valueChanged(int) ) );
    connect( ui.qsNoise, SIGNAL( valueChanged ( int ) ), this, SLOT( on_qsNoise_valueChanged(int) ) );
    connect( ui.qsAmp, SIGNAL( valueChanged ( int ) ), this, SLOT( on_qsAmp_valueChanged(int) ) );
    connect( ui.qcbTransmit, SIGNAL( currentIndexChanged ( int ) ), this, SLOT( on_qcbTransmit_currentIndexChanged(int) ) );
    connect( ui.inputDevice_CB, SIGNAL( currentIndexChanged ( int ) ), this, SLOT( on_changedCurrentInputDevice(int) ) );
}

void VOIPConfigPanel::showEvent(QShowEvent *)
{
    std::cerr << "Creating the audio pipeline" << std::endl;

    inputAudioProcessor = new QtSpeex::SpeexInputProcessor();
    inputAudioProcessor->open(QIODevice::WriteOnly | QIODevice::Unbuffered);

    inputAudioDevice = AudioDeviceHelper::getPreferedInputDevice();
    inputAudioDevice->start(inputAudioProcessor);

    connect(inputAudioProcessor, SIGNAL(networkPacketReady()), this, SLOT(emptyBuffer()));

    std::cerr << "Creating the video pipeline" << std::endl;

    // Create the video pipeline.
    //

    videoInput = new QVideoInputDevice(this) ;

    videoProcessor = new VideoProcessor() ;
    videoProcessor->setDisplayTarget(NULL) ;

    videoProcessor->setMaximumBandwidth(ui.availableBW_SB->value()) ;

    videoInput->setVideoProcessor(videoProcessor) ;

    graph_source = new voipGraphSource ;
    ui.voipBwGraph->setSource(graph_source);

    graph_source->setVideoInput(videoInput) ;
    graph_source->setCollectionTimeLimit(1000*300) ;
    graph_source->start() ;

    if(ui.showEncoded_CB->isChecked())
    {
        videoInput->setEchoVideoTarget(nullptr) ;
        videoProcessor->setDisplayTarget(ui.videoDisplay) ;
    }
    else
    {
        videoInput->setEchoVideoTarget(ui.videoDisplay) ;
        videoProcessor->setDisplayTarget(nullptr);
    }

    QObject::connect(ui.showEncoded_CB,SIGNAL(toggled(bool)),this,SLOT(togglePreview(bool))) ;
    QObject::connect(ui.availableBW_SB,SIGNAL(valueChanged(double)),this,SLOT(updateAvailableBW(double))) ;

    loadSettings();

    qtTick = new RsProtectedTimer(this);
    connect( qtTick, SIGNAL( timeout ( ) ), this, SLOT( on_Tick_timeout() ) );
    qtTick->start(20);

    videoInput->start();
}

void VOIPConfigPanel::hideEvent(QHideEvent *)
{
    std::cerr << "Deleting the video pipeline" << std::endl;

    clearPipeline();
}

void VOIPConfigPanel::updateAvailableBW(double r)
{
    std::cerr << "Setting max bandwidth to " << r << " KB/s" << std::endl;
    videoProcessor->setMaximumBandwidth((uint32_t)(r*1024)) ;
}

void VOIPConfigPanel::togglePreview(bool b)
{
    if(b)
    {
        videoInput->setEchoVideoTarget(NULL) ;
        videoProcessor->setDisplayTarget(ui.videoDisplay) ;
    }
    else
    {
        videoProcessor->setDisplayTarget(NULL) ;
        videoInput->setEchoVideoTarget(ui.videoDisplay) ;
    }
}

VOIPConfigPanel::~VOIPConfigPanel()
{
    clearPipeline();
}

void VOIPConfigPanel::clearPipeline()
{
    if (qtTick) {
        delete qtTick;
        qtTick = nullptr;
    }

    if (graph_source) {
        graph_source->stop() ;
        graph_source->setVideoInput(NULL) ;
        graph_source=nullptr; // is deleted by setSource below. This is a bad design.
    }

    ui.voipBwGraph->setSource(nullptr);

	std::cerr << "Deleting audioInputConfig object" << std::endl;
	if(videoInput != NULL)
	{
		videoInput->stop() ;
		delete videoInput ;

        videoInput = nullptr;
	}
    if (videoProcessor) {
        delete videoProcessor;
        videoProcessor = nullptr;
    }

    if (inputAudioDevice) {
        inputAudioDevice->stop();
		  delete inputAudioDevice ;
          inputAudioDevice = nullptr ;
    }

	 if(inputAudioProcessor)
	 {
		 delete inputAudioProcessor ;
         inputAudioProcessor = nullptr ;
	 }
}

void VOIPConfigPanel::load()
{
}

/** Loads the settings for this page */

void VOIPConfigPanel::loadSettings()
{
    ui.qcbTransmit->setCurrentIndex(rsVOIP->getVoipATransmit());
    on_qcbTransmit_currentIndexChanged(rsVOIP->getVoipATransmit());
    ui.qsTransmitHold->setValue(rsVOIP->getVoipVoiceHold());
    on_qsTransmitHold_valueChanged(rsVOIP->getVoipVoiceHold());
    ui.qsTransmitMin->setValue(rsVOIP->getVoipfVADmin());
    ui.qsTransmitMax->setValue(rsVOIP->getVoipfVADmax());
    ui.qcbEchoCancel->setChecked(rsVOIP->getVoipEchoCancel());

    if (rsVOIP->getVoipiNoiseSuppress() != 0)
        ui.qsNoise->setValue(-rsVOIP->getVoipiNoiseSuppress());
    else
        ui.qsNoise->setValue(14);

    on_qsNoise_valueChanged(-rsVOIP->getVoipiNoiseSuppress());

    ui.qsAmp->setValue(20000 - rsVOIP->getVoipiMinLoudness());
    on_qsAmp_valueChanged(20000 - rsVOIP->getVoipiMinLoudness());

    loaded = true;
}

bool VOIPConfigPanel::save(QString &/*errmsg*/)
{
    //mainly useless beacause saving occurs in realtime
    //s.iQuality = qsQuality->value();
    rsVOIP->setVoipiNoiseSuppress((ui.qsNoise->value() == 14) ? 0 : - ui.qsNoise->value());
    rsVOIP->setVoipiMinLoudness(20000 - ui.qsAmp->value());
    rsVOIP->setVoipVoiceHold(ui.qsTransmitHold->value());
    rsVOIP->setVoipfVADmin(ui.qsTransmitMin->value());
    rsVOIP->setVoipfVADmax(ui.qsTransmitMax->value());
    /*s.uiDoublePush = qsDoublePush->value() * 1000;*/
    rsVOIP->setVoipATransmit(static_cast<RsVOIP::enumAudioTransmit>(ui.qcbTransmit->currentIndex() ));
    rsVOIP->setVoipEchoCancel(ui.qcbEchoCancel->isChecked());

    return true;
}

void VOIPConfigPanel::on_qsTransmitHold_valueChanged(int v) {
        float val = static_cast<float>(v * FRAME_SIZE);
        val = val / SAMPLING_RATE;
        ui.qlTransmitHold->setText(tr("%1 s").arg(val, 0, 'f', 2));
        rsVOIP->setVoipVoiceHold(v);
}

void VOIPConfigPanel::on_qsNoise_valueChanged(int v) {
	QPalette pal;

	if (v < 15) {
                ui.qlNoise->setText(tr("Off"));
                pal.setColor(ui.qlNoise->foregroundRole(), Qt::red);
	} else {
                ui.qlNoise->setText(tr("-%1 dB").arg(v));
	}
        ui.qlNoise->setPalette(pal);
        rsVOIP->setVoipiNoiseSuppress(- ui.qsNoise->value());
}

void VOIPConfigPanel::on_qsAmp_valueChanged(int v) {
        v = 20000 - v;
	float d = 20000.0f/static_cast<float>(v);
        ui.qlAmp->setText(QString::fromLatin1("%1").arg(d, 0, 'f', 2));
        rsVOIP->setVoipiMinLoudness(20000 - ui.qsAmp->value());
}

void VOIPConfigPanel::on_qcbEchoCancel_clicked() {
    rsVOIP->setVoipEchoCancel(ui.qcbEchoCancel->isChecked());
}


void VOIPConfigPanel::on_qcbTransmit_currentIndexChanged(int v) {
	switch (v) {
		case 0:
                        ui.qswTransmit->setCurrentWidget(ui.qwContinuous);
			break;
		case 1:
                        ui.qswTransmit->setCurrentWidget(ui.qwVAD);
			break;
		case 2:
                        ui.qswTransmit->setCurrentWidget(ui.qwPTT);
			break;
	}
        if (loaded)
            rsVOIP->setVoipATransmit(static_cast<RsVOIP::enumAudioTransmit>(ui.qcbTransmit->currentIndex() ));
}


void VOIPConfigPanel::on_Tick_timeout()
{
    // update the sound capture bar

    ui.abSpeech->iBelow = ui.qsTransmitMin->value();
    ui.abSpeech->iAbove = ui.qsTransmitMax->value();

    if (loaded) {
        rsVOIP->setVoipfVADmin(ui.qsTransmitMin->value());
        rsVOIP->setVoipfVADmax(ui.qsTransmitMax->value());
    }

    ui.abSpeech->iValue = iroundf(inputAudioProcessor->dVoiceAcivityLevel * 32767.0f + 0.5f);
    ui.abSpeech->update();

    // also transmit encoded video

    RsVOIPDataChunk chunk ;

    while((!videoInput->stopped()) && videoInput->getNextEncodedPacket(chunk))
    {
        videoProcessor->receiveEncodedData(chunk) ;
        chunk.clear() ;
    }
}

void VOIPConfigPanel::emptyBuffer() {
    while(inputAudioProcessor->hasPendingPackets()) {
        inputAudioProcessor->getNetworkPacket(); //that will purge the buffer
    }
}

void VOIPConfigPanel::on_qpbAudioWizard_clicked() {
    AudioWizard aw(this);
    aw.exec();
    loadSettings();
}

void VOIPConfigPanel::on_changedCurrentInputDevice(int i)
{
    QString s = dynamic_cast<RSComboBox*>(sender())->itemData(i).toString();

    videoInput->stop();

    // check that the camera still exists

    QList<QString> input_devices;
    QVideoInputDevice::getAvailableDevices(input_devices);

    for(const QString& cams:input_devices)
        if(s == cams)
        {
            std::cerr << "Switching to camera \"" << s.toStdString() << "\"" << std::endl;

            videoInput->start(s);

            return;
        }

    // if not, re-create the ComboBox

    checkAvailableCameras();
}

void VOIPConfigPanel::checkAvailableCameras()
{
    // save current camera
    QString current_cam = videoInput->currentCameraDescriptionString();

    // Check that the list of cams we had previously is the same than the list of available camera.

    QList<QString> input_devices;
    QVideoInputDevice::getAvailableDevices(input_devices);

    bool same = true;
    if(input_devices.size() != ui.inputDevice_CB->count())
        same = false;

    if(same)
    {
        int n=0;
        for(auto& s:input_devices)
        {
            if(ui.inputDevice_CB->itemData(n).toString() != s)
            {
                same = false;
                break;
            }
            n++;
        }
    }

    // If not, re-add them to the comboBox and make sure to re-select the same camera.

    if(!same)
    {
        whileBlocking(ui.inputDevice_CB)->clear();	// remove existing entries
        whileBlocking(ui.inputDevice_CB)->addItem(tr("[No video]"),QString(""));
        int n=0;
        int found_index = -1;

        for(auto& s:input_devices)
        {
            whileBlocking(ui.inputDevice_CB)->addItem(s,QVariant(s));

            if(s == current_cam)
                found_index = n;

            n++;
        }

        if(found_index >= 0)
            ui.inputDevice_CB->setCurrentIndex(found_index);
        else
            ui.inputDevice_CB->setCurrentIndex(0);	// no video
    }
}