#include "audiorecordingpopup.h"

// Tnz6 includes
#include "tapp.h"
#include "menubarcommandids.h"
#include "mainwindow.h"

// TnzQt includes
#include "toonzqt/menubarcommand.h"
#include "toonzqt/flipconsole.h"
#include "toonzqt/gutil.h"

// Tnzlib includes
#include "toonz/tproject.h"
#include "toonz/tscenehandle.h"
#include "toonz/toonzscene.h"
#include "toonz/sceneproperties.h"
#include "toonz/txshleveltypes.h"
#include "toonz/toonzfolders.h"
#include "toonz/tframehandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/txsheethandle.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/levelproperties.h"
#include "toonz/preferences.h"

// TnzCore includes
#include "tsystem.h"
#include "tpixelutils.h"
#include "iocommand.h"

// Qt includes
#include <QMainWindow>
#include <QAudio>
#include <QMediaRecorder>
#include <QAudioProbe>
#include <QAudioRecorder>
#include <QAudioFormat>
#include <QWidget>
#include <QAudioBuffer>
#include <QMediaPlayer>
#include <QObject>
#include <QComboBox>
#include <QPushButton>
#include <QGroupBox>
#include <QCheckBox>
#include <QLabel>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QGridLayout>
#include <QMultimedia>
#include <QPainter>
#include <QElapsedTimer>
#include <QMenu>
#include <QContextMenuEvent>

#ifndef WAVE_FORMAT_PCM
#define WAVE_FORMAT_PCM 1
#endif
#ifndef WAVE_FORMAT_IEEE_FLOAT
#define WAVE_FORMAT_IEEE_FLOAT 3
#endif

//=============================================================================

AudioRecordingPopup::AudioRecordingPopup()
    : Dialog(TApp::instance()->getMainWindow(), false, true, "AudioRecording")
    , m_isRoomBound(false)
    , m_boundRoomName("") {
  setWindowTitle(tr("Audio Recording"));

  m_isPlaying            = false;
  m_syncPlayback         = true;
  m_currentFrame         = 0;
  m_recordButton         = new QPushButton(this);
  m_playButton           = new QPushButton(this);
  m_saveButton           = new QPushButton(tr("Save and Insert"));
  m_pauseRecordingButton = new QPushButton(this);
  m_pausePlaybackButton  = new QPushButton(this);
  m_refreshDevicesButton = new QPushButton(this);
  m_duration             = new QLabel("00:00.000");
  m_playDuration         = new QLabel("00:00.000");
  m_deviceListCB         = new QComboBox();
  m_audioLevelsDisplay   = new AudioLevelsDisplay(this);
  m_playXSheetCB         = new QCheckBox(tr("Sync with XSheet/Timeline"), this);
  m_timer                = new QElapsedTimer();
  m_recordedLevels       = QMap<qint64, double>();
  m_player               = new QMediaPlayer(this);
  m_console              = FlipConsole::getCurrent();

  m_labelDevice     = new QLabel(tr("Device: "));
  m_labelSamplerate = new QLabel(tr("Sample rate: "));
  m_labelSamplefmt  = new QLabel(tr("Sample format: "));
  m_comboSamplerate = new QComboBox();
  m_comboSamplefmt  = new QComboBox();
  m_comboSamplerate->addItem(tr("8000 Hz"), QVariant::fromValue(8000));
  m_comboSamplerate->addItem(tr("11025 Hz"), QVariant::fromValue(11025));
  m_comboSamplerate->addItem(tr("22050 Hz"), QVariant::fromValue(22050));
  m_comboSamplerate->addItem(tr("44100 Hz"), QVariant::fromValue(44100));
  m_comboSamplerate->addItem(tr("48000 Hz"), QVariant::fromValue(48000));
  m_comboSamplerate->addItem(tr("96000 Hz"), QVariant::fromValue(96000));
  m_comboSamplerate->addItem(tr("192000 Hz"), QVariant::fromValue(192000));
  m_comboSamplerate->setCurrentIndex(3);  // 44.1KHz
  m_comboSamplefmt->addItem(tr("Mono 8-Bits"), QVariant::fromValue(9));
  m_comboSamplefmt->addItem(tr("Stereo 8-Bits"), QVariant::fromValue(10));
  m_comboSamplefmt->addItem(tr("Mono 16-Bits"), QVariant::fromValue(17));
  m_comboSamplefmt->addItem(tr("Stereo 16-Bits"), QVariant::fromValue(18));
  m_comboSamplefmt->addItem(tr("Mono 24-Bits"), QVariant::fromValue(25));
  m_comboSamplefmt->addItem(tr("Stereo 24-Bits"), QVariant::fromValue(26));
  m_comboSamplefmt->addItem(tr("Mono 32-Bits"), QVariant::fromValue(33));
  m_comboSamplefmt->addItem(tr("Stereo 32-Bits"), QVariant::fromValue(34));
  m_comboSamplefmt->setCurrentIndex(2);  // Mono 16-Bits

  m_recordButton->setMaximumWidth(32);
  m_playButton->setMaximumWidth(32);
  m_pauseRecordingButton->setMaximumWidth(32);
  m_pausePlaybackButton->setMaximumWidth(32);
  m_refreshDevicesButton->setMaximumWidth(25);

  QString playDisabled    = QString(":Resources/play_disabled.svg");
  QString pauseDisabled   = QString(":Resources/pause_disabled.svg");
  QString stopDisabled    = QString(":Resources/stop_disabled.svg");
  QString recordDisabled  = QString(":Resources/record_disabled.svg");
  QString refreshDisabled = QString(":Resources/repeat_icon.svg");

  m_pauseIcon = createQIcon("pause");
  m_pauseIcon.addFile(pauseDisabled, QSize(), QIcon::Disabled);
  m_playIcon = createQIcon("play");
  m_playIcon.addFile(playDisabled, QSize(), QIcon::Disabled);
  m_recordIcon = createQIcon("record");
  m_recordIcon.addFile(recordDisabled, QSize(), QIcon::Disabled);
  m_stopIcon = createQIcon("stop");
  m_stopIcon.addFile(stopDisabled, QSize(), QIcon::Disabled);
  m_refreshIcon = createQIcon("repeat");
  m_refreshIcon.addFile(refreshDisabled, QSize(), QIcon::Disabled);

  m_pauseRecordingButton->setIcon(m_pauseIcon);
  m_pauseRecordingButton->setIconSize(QSize(17, 17));
  m_playButton->setIcon(m_playIcon);
  m_playButton->setIconSize(QSize(17, 17));
  m_recordButton->setIcon(m_recordIcon);
  m_recordButton->setIconSize(QSize(17, 17));
  m_pausePlaybackButton->setIcon(m_pauseIcon);
  m_pausePlaybackButton->setIconSize(QSize(17, 17));
  m_refreshDevicesButton->setIcon(m_refreshIcon);
  m_refreshDevicesButton->setIconSize(QSize(17, 17));

  // Enumerate devices and initialize default device
  enumerateAudioDevices("");
  QAudioDeviceInfo m_audioDeviceInfo = QAudioDeviceInfo::defaultInputDevice();
  QAudioFormat format;
  format.setSampleRate(44100);
  format.setChannelCount(1);
  format.setSampleSize(16);
  format.setSampleType(QAudioFormat::SignedInt);
  format.setByteOrder(QAudioFormat::LittleEndian);
  format.setCodec("audio/pcm");
  if (!m_audioDeviceInfo.isFormatSupported(format)) {
    format = m_audioDeviceInfo.nearestFormat(format);
  }
  m_audioInput = new QAudioInput(m_audioDeviceInfo, format);

  // WAV Writter
  m_audioWriterWAV = new AudioWriterWAV(format);

  // Tool tips to provide additional info to the user
  m_deviceListCB->setToolTip(tr("Audio input device to record"));
  m_comboSamplerate->setToolTip(
      tr("Number of samples per second, 44.1KHz = CD Quality"));
  m_comboSamplefmt->setToolTip(
      tr("Number of channels and bits per sample, 16-bits recommended"));
  m_playXSheetCB->setToolTip(
      tr("Play animation from current frame while recording/playback"));
  m_saveButton->setToolTip(tr("Save recording and insert into new column"));
  m_refreshDevicesButton->setToolTip(
      tr("Refresh list of connected audio input devices"));

  m_topLayout->setContentsMargins(5, 5, 5, 5);
  m_topLayout->setSpacing(8);

  QVBoxLayout *mainLay = new QVBoxLayout();
  mainLay->setSpacing(3);
  mainLay->setContentsMargins(3, 3, 3, 3);
  {
    QGridLayout *recordGridLay = new QGridLayout();
    recordGridLay->setHorizontalSpacing(2);
    recordGridLay->setVerticalSpacing(3);
    {
      recordGridLay->addWidget(m_labelDevice, 0, 0, 1, 2, Qt::AlignRight);
      recordGridLay->addWidget(m_deviceListCB, 0, 2, 1, 2, Qt::AlignLeft);

      recordGridLay->addWidget(m_labelSamplerate, 1, 0, 1, 2, Qt::AlignRight);
      recordGridLay->addWidget(m_comboSamplerate, 1, 2, 1, 1, Qt::AlignLeft);
      recordGridLay->addWidget(m_refreshDevicesButton, 1, 3, Qt::AlignRight);

      recordGridLay->addWidget(m_labelSamplefmt, 2, 0, 1, 2, Qt::AlignRight);
      recordGridLay->addWidget(m_comboSamplefmt, 2, 2, 1, 2, Qt::AlignLeft);

      recordGridLay->addWidget(m_audioLevelsDisplay, 3, 0, 1, 4,
                               Qt::AlignCenter);
      recordGridLay->addWidget(m_playXSheetCB, 4, 0, 1, 5, Qt::AlignCenter);
      QHBoxLayout *recordLay = new QHBoxLayout();
      recordLay->setSpacing(4);
      recordLay->setContentsMargins(0, 0, 0, 0);
      {
        recordLay->addStretch();
        recordLay->addWidget(m_recordButton);
        recordLay->addWidget(m_pauseRecordingButton);
        recordLay->addWidget(m_duration);
        recordLay->addStretch();
      }
      recordGridLay->addLayout(recordLay, 5, 0, 1, 4, Qt::AlignCenter);
      QHBoxLayout *playLay = new QHBoxLayout();
      playLay->setSpacing(4);
      playLay->setContentsMargins(0, 0, 0, 0);
      {
        playLay->addStretch();
        playLay->addWidget(m_playButton);
        playLay->addWidget(m_pausePlaybackButton);
        playLay->addWidget(m_playDuration);
        playLay->addStretch();
      }
      recordGridLay->addLayout(playLay, 6, 0, 1, 4, Qt::AlignCenter);
      recordGridLay->addWidget(new QLabel(tr(" ")), 7, 0, Qt::AlignCenter);
      recordGridLay->addWidget(m_saveButton, 8, 0, 1, 4,
                               Qt::AlignCenter | Qt::AlignVCenter);
    }
    recordGridLay->setColumnStretch(0, 0);
    recordGridLay->setColumnStretch(1, 0);
    recordGridLay->setColumnStretch(2, 0);
    recordGridLay->setColumnStretch(3, 0);
    recordGridLay->setColumnStretch(4, 0);
    recordGridLay->setColumnStretch(5, 0);

    mainLay->addLayout(recordGridLay);
  }
  m_topLayout->addLayout(mainLay, 0);

  makePaths();

  m_playXSheetCB->setChecked(true);

  bool ret = connect(m_playXSheetCB, SIGNAL(stateChanged(int)), this,
                     SLOT(onPlayXSheetCBChanged(int)));

  ret = ret && connect(m_saveButton, SIGNAL(clicked()), this,
                       SLOT(onSaveButtonPressed()));
  ret = ret && connect(m_recordButton, SIGNAL(clicked()), this,
                       SLOT(onRecordButtonPressed()));
  ret = ret && connect(m_playButton, SIGNAL(clicked()), this,
                       SLOT(onPlayButtonPressed()));
  ret = ret && connect(m_pauseRecordingButton, SIGNAL(clicked()), this,
                       SLOT(onPauseRecordingButtonPressed()));
  ret = ret && connect(m_pausePlaybackButton, SIGNAL(clicked()), this,
                       SLOT(onPausePlaybackButtonPressed()));
  ret = ret && connect(m_audioWriterWAV, SIGNAL(update(qint64)), this,
                       SLOT(updateRecordDuration(qint64)));

  if (m_console) {
    ret = ret && connect(m_console, SIGNAL(playStateChanged(bool)), this,
                         SLOT(onPlayStateChanged(bool)));
  }

  ret =
      ret && connect(m_deviceListCB, SIGNAL(currentTextChanged(const QString)),
                     this, SLOT(onInputDeviceChanged()));
  ret = ret && connect(m_refreshDevicesButton, SIGNAL(clicked()), this,
                       SLOT(onRefreshButtonPressed()));
  ret = ret &&
        connect(m_comboSamplerate, SIGNAL(currentTextChanged(const QString)),
                this, SLOT(onAudioSettingChanged()));
  ret = ret &&
        connect(m_comboSamplefmt, SIGNAL(currentTextChanged(const QString)),
                this, SLOT(onAudioSettingChanged()));

  assert(ret);
}

//-----------------------------------------------------------------------------

AudioRecordingPopup::~AudioRecordingPopup() {}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::contextMenuEvent(QContextMenuEvent *event) {
  QMenu *menu = new QMenu(this);
  
  // Add "Bind to Current Room" option
  QAction *bindAction = menu->addAction(tr("Bind to Current Room"));
  bindAction->setCheckable(true);
  bindAction->setChecked(m_isRoomBound);
  
  connect(bindAction, &QAction::triggered, [this](bool checked) {
    MainWindow *mw = dynamic_cast<MainWindow *>(
        TApp::instance()->getMainWindow());
    if (!mw) return;
    
    Room *currentRoom = mw->getCurrentRoom();
    if (!currentRoom) return;
    
    // Update binding state
    setRoomBound(checked);
    if (checked) {
      // Bind to current room
      setBoundRoomName(currentRoom->getName());
    } else {
      // Unbind
      setBoundRoomName("");
      // Show if hidden
      if (isHidden()) {
        show();
      }
    }
  });
  
  menu->exec(event->globalPos());
  delete menu;
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::onRecordButtonPressed() {
  if (m_audioInput->state() == QAudio::StoppedState) {
    if (!m_console) {
      DVGui::warning(
          tr("Record failed: "
             "\nMake sure there's XSheet or Timeline in the room."));
      return;
    }
    // clear the player in case the file is open there
    // can't record to an opened file
    if (m_player->mediaStatus() != QMediaPlayer::NoMedia) {
      m_player->stop();
      delete m_player;
      m_player = new QMediaPlayer(this);
    }
    // I tried using a temp file in the cache, but copying and inserting
    // (rarely)
    // could cause a crash.  I think OT tried to import the level before the
    // final file was fully copied to the new location
    if (TSystem::doesExistFileOrLevel(m_filePath)) {
      TSystem::removeFileOrLevel(m_filePath);
    }
    // The audio writer support either writing to buffer or directly to disk
    // each method have their own pros and cons
    // For now using false to mimic previous QAudioRecorder behaviour
    m_audioWriterWAV->reset(m_audioInput->format());
    if (!m_audioWriterWAV->start(m_filePath.getQString(), false)) {
      DVGui::warning(
          tr("Failed to save WAV file:\nMake sure you have write permissions "
             "in folder."));
      return;
    }
    m_recordButton->setIcon(m_stopIcon);
    m_saveButton->setDisabled(true);
    m_playButton->setDisabled(true);
    m_pausePlaybackButton->setDisabled(true);
    m_pauseRecordingButton->setEnabled(true);
    m_deviceListCB->setDisabled(true);
    m_refreshDevicesButton->setDisabled(true);
    m_comboSamplerate->setDisabled(true);
    m_comboSamplefmt->setDisabled(true);
    m_recordedLevels.clear();
    m_pausedTime   = 0;
    m_startPause   = 0;
    m_endPause     = 0;
    m_stoppedAtEnd = false;
    m_playDuration->setText("00:00.000");
    m_timer->restart();
    m_audioInput->start(m_audioWriterWAV);
    // this sometimes sets to one frame off, so + 1.
    m_currentFrame = TApp::instance()->getCurrentFrame()->getFrame() + 1;
    if (m_syncPlayback && !m_isPlaying) {
      m_console->setCurrentFrame(m_currentFrame);
      m_console->pressButton(FlipConsole::ePlay);
      m_isPlaying = true;
    }

  } else {
    m_audioInput->stop();
    bool success = m_audioWriterWAV->stop();
    if (success) {
      m_saveButton->setEnabled(true);
      m_playButton->setEnabled(true);
    }
    m_audioLevelsDisplay->setLevel(-1, -1);
    m_recordButton->setIcon(m_recordIcon);
    m_pauseRecordingButton->setDisabled(true);
    m_pauseRecordingButton->setIcon(m_pauseIcon);
    m_deviceListCB->setEnabled(true);
    m_refreshDevicesButton->setEnabled(true);
    m_comboSamplerate->setEnabled(true);
    m_comboSamplefmt->setEnabled(true);
    if (m_syncPlayback) {
      if (m_isPlaying) {
        m_console->pressButton(FlipConsole::ePause);
      }
      // put the frame back to before playback
      TApp::instance()->getCurrentFrame()->setCurrentFrame(m_currentFrame);
    }
    m_isPlaying = false;
    if (!success) {
      DVGui::warning(
          tr("Failed to save WAV file:\nMake sure you have write permissions "
             "in folder."));
    }
  }
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::updateRecordDuration(qint64 duration) {
  int minutes        = duration / 60000;
  int seconds        = (duration / 1000) % 60;
  int milis          = duration % 1000;
  QString strMinutes = QString::number(minutes).rightJustified(2, '0');
  QString strSeconds = QString::number(seconds).rightJustified(2, '0');
  QString strMilis   = QString::number(milis).rightJustified(3, '0');
  m_duration->setText(strMinutes + ":" + strSeconds + "." + strMilis);

  // Show and record amplitude
  qreal level = m_audioWriterWAV->level();
  qreal peakL = m_audioWriterWAV->peakLevel();
  m_audioLevelsDisplay->setLevel(level, peakL);
  m_recordedLevels[duration / 20] = level;
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::updatePlaybackDuration(qint64 duration) {
  int minutes        = duration / 60000;
  int seconds        = (duration / 1000) % 60;
  int milis          = duration % 1000;
  QString strMinutes = QString::number(minutes).rightJustified(2, '0');
  QString strSeconds = QString::number(seconds).rightJustified(2, '0');
  QString strMilis   = QString::number(milis).rightJustified(3, '0');
  m_playDuration->setText(strMinutes + ":" + strSeconds + "." + strMilis);

  // the qmediaplayer probe doesn't work on all platforms, so we fake it by
  // using
  // a map that is made during recording
  if (m_recordedLevels.contains(duration / 20)) {
    m_audioLevelsDisplay->setLevel(m_recordedLevels.value(duration / 20), -1);
  }
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::onPlayButtonPressed() {
  if (m_player->state() == QMediaPlayer::StoppedState) {
    m_player->setMedia(QUrl::fromLocalFile(m_filePath.getQString()));
    m_player->setVolume(50);
    m_player->setNotifyInterval(20);
    connect(m_player, SIGNAL(positionChanged(qint64)), this,
            SLOT(updatePlaybackDuration(qint64)));
    connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)), this,
            SLOT(onMediaStateChanged(QMediaPlayer::State)));
    m_playButton->setIcon(m_stopIcon);
    m_recordButton->setDisabled(true);
    m_saveButton->setDisabled(true);
    m_pausePlaybackButton->setEnabled(true);
    m_deviceListCB->setDisabled(true);
    m_refreshDevicesButton->setDisabled(true);
    m_comboSamplerate->setDisabled(true);
    m_comboSamplefmt->setDisabled(true);
    m_stoppedAtEnd = false;
    m_player->play();
    // this sometimes sets to one frame off, so + 1.
    // m_currentFrame = TApp::instance()->getCurrentFrame()->getFrame() + 1;
    if (m_syncPlayback && !m_isPlaying) {
      TApp::instance()->getCurrentFrame()->setCurrentFrame(m_currentFrame);
      m_console->setCurrentFrame(m_currentFrame);
      m_console->pressButton(FlipConsole::ePlay);
      m_isPlaying = true;
    }
  } else {
    m_player->stop();
    m_playButton->setIcon(m_playIcon);
    m_pausePlaybackButton->setDisabled(true);
    m_pausePlaybackButton->setIcon(m_pauseIcon);
    m_deviceListCB->setEnabled(true);
    m_refreshDevicesButton->setEnabled(true);
    m_comboSamplerate->setEnabled(true);
    m_comboSamplefmt->setEnabled(true);
  }
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::onPauseRecordingButtonPressed() {
  if (m_audioInput->state() == QAudio::StoppedState) {
    return;
  } else if (m_audioInput->state() == QAudio::SuspendedState) {
    m_endPause = m_timer->elapsed();
    m_pausedTime += m_endPause - m_startPause;
    m_audioInput->resume();
    m_pauseRecordingButton->setIcon(m_pauseIcon);
    if (m_syncPlayback && !m_isPlaying && !m_stoppedAtEnd) {
      m_console->pressButton(FlipConsole::ePlay);
      m_isPlaying = true;
    }
  } else {
    m_audioInput->suspend();
    m_pauseRecordingButton->setIcon(m_recordIcon);
    m_startPause = m_timer->elapsed();
    if (m_syncPlayback && m_isPlaying) {
      m_isPlaying = false;
      m_console->pressButton(FlipConsole::ePause);
    }
  }
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::onPausePlaybackButtonPressed() {
  if (m_player->state() == QMediaPlayer::StoppedState) {
    return;
  } else if (m_player->state() == QMediaPlayer::PausedState) {
    m_player->play();
    m_pausePlaybackButton->setIcon(m_pauseIcon);
    if (m_syncPlayback && !m_isPlaying && !m_stoppedAtEnd) {
      m_console->pressButton(FlipConsole::ePlay);
      m_isPlaying = true;
    }
  } else {
    m_player->pause();
    m_pausePlaybackButton->setIcon(m_playIcon);
    if (m_syncPlayback && m_isPlaying) {
      m_isPlaying = false;
      m_console->pressButton(FlipConsole::ePause);
    }
  }
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::onMediaStateChanged(QMediaPlayer::State state) {
  // stopping can happen through the stop button or the file ending
  if (state == QMediaPlayer::StoppedState) {
    m_audioLevelsDisplay->setLevel(-1, -1);
    if (m_syncPlayback) {
      if (m_isPlaying) {
        m_console->pressButton(FlipConsole::ePause);
      }
      // put the frame back to before playback
      TApp::instance()->getCurrentFrame()->setCurrentFrame(m_currentFrame);
    }
    m_playButton->setIcon(m_playIcon);
    m_pausePlaybackButton->setIcon(m_pauseIcon);
    m_pausePlaybackButton->setDisabled(true);
    m_recordButton->setEnabled(true);
    m_deviceListCB->setEnabled(true);
    m_refreshDevicesButton->setEnabled(true);
    m_comboSamplerate->setEnabled(true);
    m_comboSamplefmt->setEnabled(true);
    m_saveButton->setEnabled(true);
    m_isPlaying = false;
  }
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::onPlayXSheetCBChanged(int status) {
  if (status == 0) {
    m_syncPlayback = false;
  } else
    m_syncPlayback = true;
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::onRefreshButtonPressed() {
  QAudioDeviceInfo m_audioDeviceInfo =
      m_deviceListCB->itemData(m_deviceListCB->currentIndex())
          .value<QAudioDeviceInfo>();

  enumerateAudioDevices(m_audioDeviceInfo.deviceName());
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::onInputDeviceChanged() { reinitAudioInput(); }

//-----------------------------------------------------------------------------

void AudioRecordingPopup::onAudioSettingChanged() { reinitAudioInput(); }

//-----------------------------------------------------------------------------

void AudioRecordingPopup::onSaveButtonPressed() {
  if (m_audioInput->state() != QAudio::StoppedState) {
    m_audioInput->stop();
    m_audioLevelsDisplay->setLevel(-1, -1);
  }
  if (m_player->state() != QMediaPlayer::StoppedState) {
    m_player->stop();
    m_audioLevelsDisplay->setLevel(-1, -1);
  }
  if (!TSystem::doesExistFileOrLevel(m_filePath)) return;

  std::vector<TFilePath> filePaths;
  filePaths.push_back(m_filePath);

  if (filePaths.empty()) return;
  if (m_syncPlayback) {
    TApp::instance()->getCurrentFrame()->setCurrentFrame(m_currentFrame);
    m_console->setCurrentFrame(m_currentFrame);
  }
  IoCmd::LoadResourceArguments args;
  args.resourceDatas.assign(filePaths.begin(), filePaths.end());
  IoCmd::loadResources(args);

  makePaths();
  resetEverything();
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::makePaths() {
  TFilePath savePath =
      TApp::instance()->getCurrentScene()->getScene()->getDefaultLevelPath(
          TXshLevelType::SND_XSHLEVEL);
  savePath =
      TApp::instance()->getCurrentScene()->getScene()->decodeFilePath(savePath);
  savePath = savePath.getParentDir();

  std::string strPath = savePath.getQString().toStdString();
  int number          = 1;
  TFilePath finalPath =
      savePath + TFilePath("recordedAudio" + QString::number(number) + ".wav");
  while (TSystem::doesExistFileOrLevel(finalPath)) {
    number++;
    finalPath = savePath +
                TFilePath("recordedAudio" + QString::number(number) + ".wav");
  }
  m_filePath = finalPath;
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::onPlayStateChanged(bool playing) {
  // m_isPlaying = playing;
  if (!playing && m_isPlaying) m_stoppedAtEnd = true;
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::showEvent(QShowEvent *event) { resetEverything(); }

//-----------------------------------------------------------------------------

void AudioRecordingPopup::resetEverything() {
  m_saveButton->setDisabled(true);
  m_playButton->setDisabled(true);
  m_recordButton->setEnabled(true);
  m_recordButton->setIcon(m_recordIcon);
  m_playButton->setIcon(m_playIcon);
  m_pausePlaybackButton->setIcon(m_pauseIcon);
  m_pauseRecordingButton->setIcon(m_pauseIcon);
  m_pauseRecordingButton->setDisabled(true);
  m_pausePlaybackButton->setDisabled(true);
  m_deviceListCB->setEnabled(true);
  m_refreshDevicesButton->setEnabled(true);
  m_comboSamplerate->setEnabled(true);
  m_comboSamplefmt->setEnabled(true);
  m_recordedLevels.clear();
  m_duration->setText("00:00.000");
  m_playDuration->setText("00:00.000");
  m_audioLevelsDisplay->setLevel(-1, -1);
  if (!m_console) {
    m_console = FlipConsole::getCurrent();
    if (m_console)
      connect(m_console, SIGNAL(playStateChanged(bool)), this,
              SLOT(onPlayStateChanged(bool)));
  }
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::hideEvent(QHideEvent *event) {
  if (m_audioInput->state() != QAudio::StoppedState) {
    m_audioInput->stop();
    m_audioWriterWAV->stop();
  }
  if (m_player->state() != QMediaPlayer::StoppedState) {
    m_player->stop();
  }
  // make sure the file is freed before deleting
  delete m_player;
  m_player = new QMediaPlayer(this);
  // this should only remove files that haven't been used in the scene
  // make paths checks to only create path names that don't exist yet.
  if (TSystem::doesExistFileOrLevel(TFilePath(m_filePath.getQString()))) {
    TSystem::removeFileOrLevel(TFilePath(m_filePath.getQString()));
  }
  // Free up memory used in recording
  m_recordedLevels.clear();
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::enumerateAudioDevices(
    const QString &selectedDeviceName) {
  const QAudioDeviceInfo &defaultDeviceInfo =
      QAudioDeviceInfo::defaultInputDevice();

  m_blockAudioSettings = true;
  m_deviceListCB->clear();
  m_deviceListCB->addItem(defaultDeviceInfo.deviceName(),
                          QVariant::fromValue(defaultDeviceInfo));

  for (auto &deviceInfo :
       QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) {
    if (deviceInfo != defaultDeviceInfo &&
        m_deviceListCB->findText(deviceInfo.deviceName()) == -1) {
      m_deviceListCB->addItem(deviceInfo.deviceName(),
                              QVariant::fromValue(deviceInfo));
    }
  }

  int deviceIndex = m_deviceListCB->findText(selectedDeviceName);
  if (deviceIndex != -1) m_deviceListCB->setCurrentIndex(deviceIndex);
  m_blockAudioSettings = false;
}

//-----------------------------------------------------------------------------

void AudioRecordingPopup::reinitAudioInput() {
  if (m_blockAudioSettings) return;

  QAudioDeviceInfo m_audioDeviceInfo =
      m_deviceListCB->itemData(m_deviceListCB->currentIndex())
          .value<QAudioDeviceInfo>();
  int samplerate =
      m_comboSamplerate->itemData(m_comboSamplerate->currentIndex())
          .value<int>();
  int sampletype =
      m_comboSamplefmt->itemData(m_comboSamplefmt->currentIndex()).value<int>();
  int bitdepth = sampletype & 60;
  int channels = sampletype & 3;

  QAudioFormat format;
  format.setSampleRate(samplerate);
  format.setChannelCount(channels);
  format.setSampleSize(bitdepth);
  if (bitdepth == 32)
    format.setSampleType(QAudioFormat::Float);
  else if (bitdepth == 8)
    format.setSampleType(QAudioFormat::UnSignedInt);
  else
    format.setSampleType(QAudioFormat::SignedInt);
  format.setByteOrder(QAudioFormat::LittleEndian);
  format.setCodec("audio/pcm");
  if (!m_audioDeviceInfo.isFormatSupported(format)) {
    DVGui::warning(tr(
        "Audio format unsupported:\nNearest format will be internally used."));
    format = m_audioDeviceInfo.nearestFormat(format);
  }

  // Recreate input
  delete m_audioInput;
  m_audioInput = new QAudioInput(m_audioDeviceInfo, format);
  m_audioWriterWAV->reset(format);
}

//-----------------------------------------------------------------------------
// AudioWriterWAV Class
//-----------------------------------------------------------------------------
// IODevice to write standard WAV files, performs peak level calc
//
// 8-bits audio must be unsigned
// 16-bits audio must be signed
// 32-bits isn't supported

AudioWriterWAV::AudioWriterWAV(const QAudioFormat &format)
    : m_level(0.0)
    , m_peakL(0.0)
    , m_state(false)
    , m_wrRawB(0)
    , m_wavFile(NULL)
    , m_wavBuff(NULL) {
  reset(format);
}

bool AudioWriterWAV::reset(const QAudioFormat &format) {
  m_format = format;

  int samplesPerSec = m_format.sampleRate() * m_format.channelCount();
  if (m_format.sampleSize() == 8) {
    m_rbytesms = 1000.0 / samplesPerSec;
  } else if (m_format.sampleSize() == 16) {
    m_rbytesms = 1000.0 / 2.0 / samplesPerSec;
  } else if (m_format.sampleSize() == 24) {
    m_rbytesms = 1000.0 / 3.0 / samplesPerSec;
  } else {  // 32-bits
    m_rbytesms = 1000.0 / 4.0 / samplesPerSec;
  }

  m_wrRawB = 0;
  m_peakL  = 0.0;
  m_state  = false;

  if (m_wavBuff) m_wavBuff->clear();
  if (m_wavFile) {
    m_wavFile->close();
    delete m_wavFile;
    m_wavFile = NULL;
  }

  return QIODevice::reset();
}

// Just a tiny define to avoid a magic number
// this is the size of a WAV header with PCM format
#define AWWAV_HEADER_SIZE 44

bool AudioWriterWAV::start(const QString &filename, bool useMem) {
  if (m_state) return false;

  open(QIODevice::WriteOnly);
  m_filename = filename;

  if (useMem) {
    m_wavBuff = new QByteArray();
  } else {
    m_wavFile = new QFile(m_filename);
    if (!m_wavFile->open(QIODevice::WriteOnly | QIODevice::Truncate))
      return false;
    m_wavFile->seek(AWWAV_HEADER_SIZE);  // skip header
  }

  m_wrRawB = 0;
  m_peakL  = 0.0;
  m_state  = true;
  return true;
}

bool AudioWriterWAV::stop() {
  if (!m_state) return false;
  close();

  if (m_wavBuff) {
    // Using memory
    QFile file;
    file.setFileName(m_filename);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) return false;
    writeWAVHeader(file);
    file.write(m_wavBuff->constData(), m_wavBuff->size());
    delete m_wavBuff;
    m_wavBuff = NULL;
  } else {
    // Using disk directly
    writeWAVHeader(*m_wavFile);
    m_wavFile->close();
    delete m_wavFile;
    m_wavFile = NULL;
  }

  m_wrRawB = 0;
  m_peakL  = 0.0;
  m_state  = false;
  return true;
}

void AudioWriterWAV::writeWAVHeader(QFile &file) {
  quint16 channels   = m_format.channelCount();
  quint32 samplerate = m_format.sampleRate();
  quint16 bitrate    = m_format.sampleSize();

  qint64 pos = file.pos();
  file.seek(0);

  QDataStream out(&file);
  out.setByteOrder(QDataStream::LittleEndian);
  out.writeRawData("RIFF", 4);
  out << (quint32)(m_wrRawB + AWWAV_HEADER_SIZE);
  out.writeRawData("WAVEfmt ", 8);
  out << (quint32)16;  // Chunk size
  out << (quint16)(bitrate == 32 ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM);
  out << channels << samplerate;
  out << quint32(samplerate * channels * bitrate / 8);
  out << quint16(channels * bitrate / 8);
  out << bitrate;
  out.writeRawData("data", 4);
  out << (quint32)m_wrRawB;
}

qint64 AudioWriterWAV::readData(char *data, qint64 maxlen) {
  Q_UNUSED(data)
  Q_UNUSED(maxlen)
  return 0;
}

qint64 AudioWriterWAV::writeData(const char *data, qint64 len) {
  int tmp, peak = 0.0;

  // Measure peak
  if (m_format.sampleSize() == 8) {
    const quint8 *sdata = (const quint8 *)data;
    int slen            = len;
    for (int i = 0; i < slen; ++i) {
      tmp = qAbs<int>(sdata[i] - 128);
      if (tmp > peak) peak = tmp;
    }
    m_level = qreal(peak) / 127.0;
  } else if (m_format.sampleSize() == 16) {
    const qint16 *sdata = (const qint16 *)data;
    int slen            = len / 2;
    for (int i = 0; i < slen; ++i) {
      tmp = qAbs<int>(sdata[i]);
      if (tmp > peak) peak = tmp;
    }
    m_level = qreal(peak) / 32767.0;
  } else if (m_format.sampleSize() == 24) {
    const qint8 *sdata = (const qint8 *)data;
    int slen           = len / 3;
    for (int i = 0; i < slen; ++i) {
      tmp = qAbs<int>(sdata[i * 3 + 2]);
      if (tmp > peak) peak = tmp;
    }
    m_level = qreal(peak) / 127.0;
  } else {  // 32-bits
    const float *sdata = (const float *)data;
    int slen           = len / 4;
    for (int i = 0; i < slen; ++i) {
      tmp = qAbs<int>(sdata[i] * 32767.0f);
      if (tmp > peak) peak = tmp;
    }
    m_level = peak;
  }
  if (m_level > m_peakL) m_peakL = m_level;

  // Write to memory or disk
  if (m_wavBuff) {
    m_wavBuff->append(data, len);
  } else {
    m_wavFile->write(data, len);
  }
  m_wrRawB += len;

  // Emit an update
  emit update(qreal(m_wrRawB) * m_rbytesms);
  return len;
}

//-----------------------------------------------------------------------------
// AudioLevelsDisplay Class
//-----------------------------------------------------------------------------

AudioLevelsDisplay::AudioLevelsDisplay(QWidget *parent)
    : QWidget(parent), m_level(0.0), m_peakL(0.0) {
  setFixedHeight(20);
  setFixedWidth(300);
}

void AudioLevelsDisplay::setLevel(qreal level, qreal peakLevel) {
  if (m_level != level || m_peakL != peakLevel) {
    m_level = level;
    m_peakL = peakLevel;
    update();
  }
}

void AudioLevelsDisplay::paintEvent(QPaintEvent *event) {
  Q_UNUSED(event);

  QPainter painter(this);
  QColor color;

  if (m_level < 0.0) {
    return;  // draw nothing...
  } else if (m_level < 0.5) {
    color = Qt::green;
  } else if (m_level < 0.75) {
    color = QColor(204, 205, 0);  // yellow
  } else if (m_level < 0.95) {
    color = QColor(255, 115, 0);  // orange
  } else
    color = Qt::red;

  int widthLevel = m_level * width();
  int widthPeakL = m_peakL * width();
  painter.fillRect(widthLevel, 0, width(), height(), Qt::black);
  if (widthPeakL >= 0) {
    if (m_peakL >= 0.995) {
      painter.fillRect(width() - 4, 0, 4, height(), Qt::red);
    } else {
      painter.setPen(QColor(64, 64, 64));  // very dark gray
      painter.drawLine(widthPeakL, 0, widthPeakL, height());
    }
  }
  painter.fillRect(0, 0, widthLevel, height(), color);
}

//-----------------------------------------------------------------------------

OpenPopupCommandHandler<AudioRecordingPopup> openAudioRecordingPopup(
    MI_AudioRecording);
