#include "mediahandler.h"
#include <QPermission>
#include <QMediaCaptureSession>
#include <QApplication>
#include <QCameraDevice>
#include <QCameraFormat>
#include <QMediaRecorder>
#include <QMediaFormat>
#include <QMimeType>
#include <QAudioInput>
#include <QCamera>
#include <QUrl>

MediaHandler::MediaHandler(QObject *parent) : QObject{parent},
  m_captureSession{nullptr},
  m_audioInput{nullptr},
  m_camera{nullptr},
  m_videoSink{nullptr},
  m_recorder{nullptr}
{
}

MediaHandler::~MediaHandler()
{
}

MediaHandler::State MediaHandler::state() const
{
  return m_state;
}

MediaHandler::CaptureMode MediaHandler::captureMode() const
{
  return m_captureMode;
}

QVideoSink *MediaHandler::videoSink() const
{
  return m_videoSink;
}

void MediaHandler::startPreview(QVideoSink *sink)
{
  if (m_state != OFF)
  {
    return;
  }

  m_pendingRecord = false;
  startCapture(sink);
}

void MediaHandler::startRecording(QVideoSink *sink)
{
  if (m_state == RECORDING || m_state == PREPARING)
  {
    return;
  }

  if (m_state == ON)
  {
    m_cancelledRecording = false;
    m_recorder->record();
    return;
  }

  // OFF
  m_pendingRecord = true;
  startCapture(sink);
}

void MediaHandler::startCapture(QVideoSink *sink)
{
  setState(PREPARING);

  // microphone permission (always needed)
  QMicrophonePermission microphonePermission;
  switch (qApp->checkPermission(microphonePermission))
  {
    case Qt::PermissionStatus::Undetermined:
      qApp->requestPermission(microphonePermission, this, [this, sink]() {
        startCapture(sink);
      });
      return;
    case Qt::PermissionStatus::Denied:
      qDebug("Microphone permission is not granted!");
      setState(OFF);
      emit micPermissionDenied();
      return;
    case Qt::PermissionStatus::Granted:
      qDebug("Mic permission granted.");
      break;
  }

  // camera permission (skip for AUDIO_ONLY)
  if (m_captureMode != AUDIO_ONLY)
  {
    QCameraPermission cameraPermission;
    switch (qApp->checkPermission(cameraPermission))
    {
      case Qt::PermissionStatus::Undetermined:
        qApp->requestPermission(cameraPermission, this, [this, sink]() {
          startCapture(sink);
        });
        return;
      case Qt::PermissionStatus::Denied:
        qDebug("Camera permission is not granted!");
        setState(OFF);
        emit cameraPermissionDenied();
        return;
      case Qt::PermissionStatus::Granted:
        qDebug("Camera permission granted.");
        break;
    }
  }

  // Create capture session and audio input
  if (!m_captureSession)
  {
    m_captureSession = new QMediaCaptureSession{this};
  }

  // TODO: handle QMediaDevices:audioInputsChanged()
  if (!m_audioInput)
  {
    m_audioInput = new QAudioInput{this};
    m_captureSession->setAudioInput(m_audioInput);
  }

  // Camera setup (skip for AUDIO_ONLY)
  // TODO: handle QMediaDevices:videoInputsChanged()
  if (m_captureMode != AUDIO_ONLY)
  {
    if (!m_camera)
    {
      QCameraDevice cameraDevice = QMediaDevices::defaultVideoInput();
      if (cameraDevice.videoFormats().empty())
      {
        setState(OFF);
        emit errorOccurred("Unable to find suitable camera formats");
        return;
      }
      QCameraFormat cameraFormat = cameraDevice.videoFormats().at(0);
      for (auto format : cameraDevice.videoFormats())
      {
        qDebug() << "Format: " << format.resolution();
        qDebug() << "framerate: " << format.minFrameRate() << " " << format.maxFrameRate();
        if (format.isNull())
        {
          continue;
        }

        if (format.resolution().width() == 1280 && format.resolution().height() == 720)
        {
          cameraFormat = format;
        }
      }

      m_camera = new QCamera(cameraDevice);
      m_camera->setCameraFormat(cameraFormat);
      connect(m_camera, &QCamera::activeChanged, this, [this](bool active) {
        if (active)
        {
          setState(ON);
          if (m_pendingRecord)
          {
            m_pendingRecord = false;
            m_cancelledRecording = false;
            m_recorder->record();
          }
        }
      });
      connect(m_camera, &QCamera::errorOccurred, this, [this](QCamera::Error error, const QString &message) {
        if (error != QCamera::NoError)
        {
          stop();
          emit errorOccurred(message);
        }
      });
      m_captureSession->setCamera(m_camera);
    }

    m_videoSink = sink;
    m_captureSession->setVideoSink(m_videoSink);
  }
  else
  {
    // AUDIO_ONLY: no camera
    m_captureSession->setCamera(nullptr);
    m_captureSession->setVideoSink(nullptr);
    m_videoSink = nullptr;
  }

  // Recorder setup
  if (!m_recorder)
  {
    QMediaFormat mediaFormat{QMediaFormat::MPEG4};
    mediaFormat.setAudioCodec(QMediaFormat::AudioCodec::AAC);
    if (m_captureMode != AUDIO_ONLY)
    {
      mediaFormat.setVideoCodec(QMediaFormat::VideoCodec::H264);
    }
    if (!mediaFormat.isSupported(QMediaFormat::Encode))
    {
      setState(OFF);
      emit errorOccurred("Unsupported codec");
      return;
    }

    m_recorder = new QMediaRecorder(this);
    m_captureSession->setRecorder(m_recorder);
    m_recorder->setQuality(QMediaRecorder::HighQuality);
    m_recorder->setAutoStop(true);
    m_recorder->setMediaFormat(mediaFormat);

    connect(m_recorder, &QMediaRecorder::recorderStateChanged, this, [this](QMediaRecorder::RecorderState state) {
      switch (state)
      {
        case QMediaRecorder::StoppedState:
          if (m_state == RECORDING)
          {
            setState(ON);
          }
          if (!m_cancelledRecording)
          {
            qint64 duration = m_recorder->duration();
            QString mimeType = m_recorder->mediaFormat().mimeType().name();
            qDebug() << "Recording location " << m_recorder->actualLocation().toString();
            emit recordingComplete(m_recorder->actualLocation(), duration, mimeType);
          }
          m_cancelledRecording = false;
          break;
        case QMediaRecorder::PausedState:
          break;
        case QMediaRecorder::RecordingState:
          setState(RECORDING);
          break;
      }
    });
  }

  if (m_captureMode != AUDIO_ONLY)
  {
    m_camera->start();
  }
  else
  {
    // AUDIO_ONLY: no async camera startup, go straight to ON
    setState(ON);
    if (m_pendingRecord)
    {
      m_pendingRecord = false;
      m_cancelledRecording = false;
      m_recorder->record();
    }
  }
}

void MediaHandler::stop()
{
  if (m_state == OFF)
  {
    return;
  }

  m_pendingRecord = false;

  if (m_state == RECORDING && m_recorder)
  {
    m_cancelledRecording = true;
    m_recorder->stop();
  }

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

  if (m_captureSession)
  {
    m_captureSession->setVideoSink(nullptr);
  }

  m_videoSink = nullptr;

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

  setState(OFF);
}

void MediaHandler::recordStop()
{
  if (m_state != RECORDING)
  {
    return;
  }

  m_cancelledRecording = false;
  m_recorder->stop();
}

void MediaHandler::recordCancel()
{
  if (m_state != RECORDING)
  {
    return;
  }

  m_cancelledRecording = true;
  m_recorder->stop();
}

void MediaHandler::setCaptureMode(CaptureMode newMode)
{
  if (m_captureMode == newMode)
  {
    return;
  }

  m_captureMode = newMode;
  emit captureModeChanged();
}

void MediaHandler::setState(State state)
{
  if (m_state == state)
  {
    return;
  }

  m_state = state;
  emit stateChanged();
}
