/* This file is part of Clementine.
   Copyright 2010, David Sansome <me@davidsansome.com>

   Clementine is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Clementine 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Clementine.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "visualisationcontainer.h"

#include <QGLWidget>
#include <QGraphicsProxyWidget>
#include <QHBoxLayout>
#include <QLabel>
#include <QMenu>
#include <QMessageBox>
#include <QSettings>
#include <QShortcut>
#include <QtDebug>

#include "config.h"
#include "core/logging.h"
#include "engines/gstengine.h"
#include "projectmvisualisation.h"
#include "ui/iconloader.h"
#include "ui/screensaver.h"
#include "visualisationoverlay.h"
#include "visualisationselector.h"

// Framerates
const int VisualisationContainer::kLowFramerate = 15;
const int VisualisationContainer::kMediumFramerate = 25;
const int VisualisationContainer::kHighFramerate = 35;
const int VisualisationContainer::kSuperHighFramerate = 60;

const char* VisualisationContainer::kSettingsGroup = "Visualisations";
const int VisualisationContainer::kDefaultWidth = 828;
const int VisualisationContainer::kDefaultHeight = 512;
const int VisualisationContainer::kDefaultFps = kHighFramerate;
const int VisualisationContainer::kDefaultTextureSize = 512;

VisualisationContainer::VisualisationContainer(QWidget* parent)
    : QGraphicsView(parent),
      initialised_(false),
      engine_(nullptr),
      vis_(new ProjectMVisualisation(this)),
      overlay_(new VisualisationOverlay),
      selector_(new VisualisationSelector(this)),
      overlay_proxy_(nullptr),
      menu_(new QMenu(this)),
      fps_(kDefaultFps),
      size_(kDefaultTextureSize) {
  QSettings s;
  s.beginGroup(kSettingsGroup);
  if (!restoreGeometry(s.value("geometry").toByteArray())) {
    resize(kDefaultWidth, kDefaultHeight);
  }
  fps_ = s.value("fps", kDefaultFps).toInt();
  size_ = s.value("size", kDefaultTextureSize).toInt();

  QShortcut* close = new QShortcut(QKeySequence::Close, this);
  connect(close, SIGNAL(activated()), SLOT(close()));

  setMinimumSize(64, 64);
}

void VisualisationContainer::Init() {
  setWindowTitle(tr("Clementine Visualization"));
  setWindowIcon(QIcon(":/icon.png"));

  // Set up the graphics view
  setScene(vis_);
  setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
  setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
  setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  setFrameStyle(QFrame::NoFrame);

  // Add the overlay
  overlay_proxy_ = scene()->addWidget(overlay_);
  connect(overlay_, SIGNAL(OpacityChanged(qreal)),
          SLOT(ChangeOverlayOpacity(qreal)));
  connect(overlay_, SIGNAL(ShowPopupMenu(QPoint)), SLOT(ShowPopupMenu(QPoint)));
  ChangeOverlayOpacity(0.0);

  vis_->SetTextureSize(size_);
  SizeChanged();

  // Selector
  selector_->SetVisualisation(vis_);

  // Settings menu
  menu_->addAction(IconLoader::Load("view-fullscreen", IconLoader::Base),
                   tr("Toggle fullscreen"), this, SLOT(ToggleFullscreen()));

  QMenu* fps_menu = menu_->addMenu(tr("Framerate"));
  QActionGroup* fps_group = new QActionGroup(this);
  AddFramerateMenuItem(tr("Low (%1 fps)").arg(kLowFramerate), kLowFramerate,
                       fps_, fps_group);
  AddFramerateMenuItem(tr("Medium (%1 fps)").arg(kMediumFramerate),
                       kMediumFramerate, fps_, fps_group);
  AddFramerateMenuItem(tr("High (%1 fps)").arg(kHighFramerate), kHighFramerate,
                       fps_, fps_group);
  AddFramerateMenuItem(tr("Super high (%1 fps)").arg(kSuperHighFramerate),
                       kSuperHighFramerate, fps_, fps_group);
  fps_menu->addActions(fps_group->actions());

  QMenu* quality_menu = menu_->addMenu(tr("Quality", "Visualisation quality"));
  QActionGroup* quality_group = new QActionGroup(this);
  AddQualityMenuItem(tr("Low (256x256)"), 256, size_, quality_group);
  AddQualityMenuItem(tr("Medium (512x512)"), 512, size_, quality_group);
  AddQualityMenuItem(tr("High (1024x1024)"), 1024, size_, quality_group);
  AddQualityMenuItem(tr("Super high (2048x2048)"), 2048, size_, quality_group);
  quality_menu->addActions(quality_group->actions());

  menu_->addAction(tr("Select visualizations..."), selector_, SLOT(show()));

  menu_->addSeparator();
  menu_->addAction(IconLoader::Load("application-exit", IconLoader::Base),
                   tr("Close visualization"), this, SLOT(hide()));
}

void VisualisationContainer::AddFramerateMenuItem(const QString& name,
                                                  int value, int def,
                                                  QActionGroup* group) {
  QAction* action = group->addAction(name);
  action->setCheckable(true);
  action->setChecked(value == def);
  connect(action, &QAction::triggered, [this, value]() { SetFps(value); });
}

void VisualisationContainer::AddQualityMenuItem(const QString& name, int value,
                                                int def, QActionGroup* group) {
  QAction* action = group->addAction(name);
  action->setCheckable(true);
  action->setChecked(value == def);
  connect(action, &QAction::triggered, [this, value]() { SetQuality(value); });
}

void VisualisationContainer::SetEngine(GstEngine* engine) {
  engine_ = engine;

  if (isVisible()) engine_->AddBufferConsumer(vis_);
}

void VisualisationContainer::showEvent(QShowEvent* e) {
  qLog(Debug) << "Showing visualization";
  if (!initialised_) {
    if (!QGLFormat::hasOpenGL()) {
      hide();
      QMessageBox::warning(this, tr("Clementine Visualization"),
                           tr("Your system is missing OpenGL support, "
                              "visualizations are unavailable."),
                           QMessageBox::Close);
      return;
    }
    Init();
    initialised_ = true;
  }

  QGraphicsView::showEvent(e);
  update_timer_.start(1000 / fps_, this);

  if (engine_) engine_->AddBufferConsumer(vis_);
}

void VisualisationContainer::hideEvent(QHideEvent* e) {
  qLog(Debug) << "Hiding visualization";
  QGraphicsView::hideEvent(e);
  update_timer_.stop();

  if (engine_) engine_->RemoveBufferConsumer(vis_);
}

void VisualisationContainer::closeEvent(QCloseEvent* e) {
  // Don't close the window. Just hide it.
  e->ignore();
  hide();
}

void VisualisationContainer::resizeEvent(QResizeEvent* e) {
  QGraphicsView::resizeEvent(e);
  SizeChanged();
}

void VisualisationContainer::SizeChanged() {
  // Save the geometry
  QSettings s;
  s.beginGroup(kSettingsGroup);
  s.setValue("geometry", saveGeometry());

  // Resize the scene
  if (scene()) scene()->setSceneRect(QRect(QPoint(0, 0), size()));

  // Resize the overlay
  if (overlay_) overlay_->resize(size());
}

void VisualisationContainer::timerEvent(QTimerEvent* e) {
  QGraphicsView::timerEvent(e);
  if (e->timerId() == update_timer_.timerId()) scene()->update();
}

void VisualisationContainer::SetActions(QAction* previous, QAction* play_pause,
                                        QAction* stop, QAction* next) {
  overlay_->SetActions(previous, play_pause, stop, next);
}

void VisualisationContainer::SongMetadataChanged(const Song& metadata) {
  overlay_->SetSongTitle(
      QString("%1 - %2").arg(metadata.artist(), metadata.title()));
}

void VisualisationContainer::Stopped() {
  overlay_->SetSongTitle(tr("Clementine"));
}

void VisualisationContainer::ChangeOverlayOpacity(qreal value) {
  overlay_proxy_->setOpacity(value);

  // Hide the cursor if the overlay is hidden
  if (value < 0.5)
    viewport()->setCursor(Qt::BlankCursor);
  else
    viewport()->unsetCursor();
}

void VisualisationContainer::enterEvent(QEvent* e) {
  QGraphicsView::enterEvent(e);
  overlay_->SetVisible(true);
}

void VisualisationContainer::leaveEvent(QEvent* e) {
  QGraphicsView::leaveEvent(e);
  overlay_->SetVisible(false);
}

void VisualisationContainer::mouseMoveEvent(QMouseEvent* e) {
  QGraphicsView::mouseMoveEvent(e);
  overlay_->SetVisible(true);
}

void VisualisationContainer::mouseDoubleClickEvent(QMouseEvent* e) {
  QGraphicsView::mouseDoubleClickEvent(e);
  ToggleFullscreen();
}

void VisualisationContainer::contextMenuEvent(QContextMenuEvent* event) {
  QGraphicsView::contextMenuEvent(event);
  ShowPopupMenu(event->pos());
}

void VisualisationContainer::keyReleaseEvent(QKeyEvent* event) {
  if (event->matches(QKeySequence::Close) || event->key() == Qt::Key_Escape) {
    if (isFullScreen())
      ToggleFullscreen();
    else
      hide();
    return;
  }

  QGraphicsView::keyReleaseEvent(event);
}

void VisualisationContainer::ToggleFullscreen() {
  setWindowState(windowState() ^ Qt::WindowFullScreen);

  Screensaver* screensaver = Screensaver::GetScreensaver();
  if (screensaver)
    isFullScreen() ? screensaver->Inhibit() : screensaver->Uninhibit();
}

void VisualisationContainer::SetFps(int fps) {
  fps_ = fps;

  // Save settings
  QSettings s;
  s.beginGroup(kSettingsGroup);
  s.setValue("fps", fps_);

  update_timer_.stop();
  update_timer_.start(1000 / fps_, this);
}

void VisualisationContainer::ShowPopupMenu(const QPoint& pos) {
  menu_->popup(mapToGlobal(pos));
}

void VisualisationContainer::SetQuality(int size) {
  size_ = size;

  // Save settings
  QSettings s;
  s.beginGroup(kSettingsGroup);
  s.setValue("size", size_);

  vis_->SetTextureSize(size_);
}
