/* 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 "osdpretty.h"
#include "ui_osdpretty.h"


#include <QColor>
#include <QPainter>
#include <QLayout>
#include <QApplication>
#include <QDesktopWidget>
#include <QSettings>
#include <QMouseEvent>
#include <QTimer>
#include <QBitmap>
#include <QTimeLine>

#include <QtDebug>

#ifdef Q_WS_X11
#  include <QX11Info>
#endif

#ifdef Q_OS_WIN32
#  include "qtwin.h"
#endif

const char* OSDPretty::kSettingsGroup = "OSDPretty";

const int OSDPretty::kDropShadowSize = 13;
const int OSDPretty::kBorderRadius = 10;
const int OSDPretty::kMaxIconSize = 100;

const int OSDPretty::kSnapProximity = 20;

const QRgb OSDPretty::kPresetBlue = qRgb(102, 150, 227);
const QRgb OSDPretty::kPresetOrange = qRgb(254, 156, 67);


OSDPretty::OSDPretty(Mode mode, QWidget *parent)
  : QWidget(parent),
    ui_(new Ui_OSDPretty),
    mode_(mode),
    background_color_(kPresetBlue),
    background_opacity_(0.85),
    popup_display_(0),
    timeout_(new QTimer(this)),
    fading_enabled_(false),
    fader_(new QTimeLine(300, this))
{
  Qt::WindowFlags flags = Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint |
                          Qt::X11BypassWindowManagerHint;

#ifdef Q_OS_WIN32
  flags |= Qt::ToolTip;
#endif

  setWindowFlags(flags);
  setAttribute(Qt::WA_TranslucentBackground, true);
  ui_->setupUi(this);

  // Mode settings
  switch (mode_) {
  case Mode_Popup:
    setCursor(QCursor(Qt::ArrowCursor));
    break;

  case Mode_Draggable:
    setCursor(QCursor(Qt::OpenHandCursor));
    break;
  }

  // Timeout
  timeout_->setSingleShot(true);
  timeout_->setInterval(5000);
  connect(timeout_, SIGNAL(timeout()), SLOT(hide()));

  ui_->icon->setMaximumSize(kMaxIconSize, kMaxIconSize);

  // Fader
  connect(fader_, SIGNAL(valueChanged(qreal)), SLOT(FaderValueChanged(qreal)));
  connect(fader_, SIGNAL(finished()), SLOT(FaderFinished()));

#ifdef Q_OS_WIN32
  set_fading_enabled(true);
#endif

  // Load the show edges and corners
  QImage shadow_edge(":osd_shadow_edge.png");
  QImage shadow_corner(":osd_shadow_corner.png");
  for (int i=0 ; i<4 ; ++i) {
    QTransform rotation = QTransform().rotate(90 * i);
    shadow_edge_[i] = QPixmap::fromImage(shadow_edge.transformed(rotation));
    shadow_corner_[i] = QPixmap::fromImage(shadow_corner.transformed(rotation));
  }
  background_ = QPixmap(":osd_background.png");

  // Set the margins to allow for the drop shadow
  QBoxLayout* l = static_cast<QBoxLayout*>(layout());
  int margin = l->margin() + kDropShadowSize;
  l->setMargin(margin);

  Load();
}

OSDPretty::~OSDPretty() {
  delete ui_;
}

bool OSDPretty::IsTransparencyAvailable() {
#ifdef Q_WS_X11
  return QX11Info::isCompositingManagerRunning();
#endif
  return true;
}

void OSDPretty::Load() {
  QSettings s;
  s.beginGroup(kSettingsGroup);

  foreground_color_ = QColor(s.value("foreground_color", 0).toInt());
  background_color_ = QColor(s.value("background_color", kPresetBlue).toInt());
  background_opacity_ = s.value("background_opacity", 0.85).toDouble();
  popup_display_ = s.value("popup_display", -1).toInt();
  popup_pos_ = s.value("popup_pos", QPoint(0, 0)).toPoint();

  set_foreground_color(foreground_color());
}

void OSDPretty::ReloadSettings() {
  Load();
  if (isVisible())
    update();
}

QRect OSDPretty::BoxBorder() const {
  return rect().adjusted(kDropShadowSize, kDropShadowSize,
                         -kDropShadowSize, -kDropShadowSize);
}

void OSDPretty::paintEvent(QPaintEvent *) {
  QPainter p(this);
  p.setRenderHint(QPainter::Antialiasing);
  p.setRenderHint(QPainter::HighQualityAntialiasing);

  QRect box(BoxBorder());

  // Shadow corners
  const int kShadowCornerSize = kDropShadowSize + kBorderRadius;
  p.drawPixmap(0, 0, shadow_corner_[0]);
  p.drawPixmap(width() - kShadowCornerSize, 0, shadow_corner_[1]);
  p.drawPixmap(width() - kShadowCornerSize, height() - kShadowCornerSize, shadow_corner_[2]);
  p.drawPixmap(0, height() - kShadowCornerSize, shadow_corner_[3]);

  // Shadow edges
  p.drawTiledPixmap(kShadowCornerSize, 0,
                    width() - kShadowCornerSize*2, kDropShadowSize,
                    shadow_edge_[0]);
  p.drawTiledPixmap(width() - kDropShadowSize, kShadowCornerSize,
                    kDropShadowSize, height() - kShadowCornerSize*2,
                    shadow_edge_[1]);
  p.drawTiledPixmap(kShadowCornerSize, height() - kDropShadowSize,
                    width() - kShadowCornerSize*2, kDropShadowSize,
                    shadow_edge_[2]);
  p.drawTiledPixmap(0, kShadowCornerSize,
                    kDropShadowSize, height() - kShadowCornerSize*2,
                    shadow_edge_[3]);

  // Box background
  p.setBrush(background_color_);
  p.setPen(QPen());
  p.setOpacity(background_opacity_);
  p.drawRoundedRect(box, kBorderRadius, kBorderRadius);

  // Background pattern
  QPainterPath background_path;
  background_path.addRoundedRect(box, kBorderRadius, kBorderRadius);
  p.setClipPath(background_path);
  p.setOpacity(1.0);
  p.drawPixmap(box.right() - background_.width(),
               box.bottom() - background_.height(), background_);
  p.setClipping(false);

  // Gradient overlay
  QLinearGradient gradient(0, 0, 0, height());
  gradient.setColorAt(0, QColor(255, 255, 255, 130));
  gradient.setColorAt(1, QColor(255, 255, 255, 50));
  p.setBrush(gradient);
  p.drawRoundedRect(box, kBorderRadius, kBorderRadius);

  // Box border
  p.setBrush(QBrush());
  p.setPen(QPen(background_color_.darker(150), 2));
  p.drawRoundedRect(box, kBorderRadius, kBorderRadius);
}

void OSDPretty::SetMessage(const QString& summary, const QString& message,
                           const QImage& image) {

  if (!image.isNull()) {
    QImage scaled_image =
        image.scaled(kMaxIconSize, kMaxIconSize,
                     Qt::KeepAspectRatio, Qt::SmoothTransformation);
    ui_->icon->setPixmap(QPixmap::fromImage(scaled_image));
    ui_->icon->show();
  } else {
    ui_->icon->hide();
  }

  ui_->summary->setText(summary);
  ui_->message->setText(message);

  if (isVisible())
    Reposition();

  if (isVisible() && mode_ == Mode_Popup)
    timeout_->start(); // Restart the timer
}

void OSDPretty::showEvent(QShowEvent* e) {
  setWindowOpacity(fading_enabled_ ? 0.0 : 1.0);

  QWidget::showEvent(e);

  Reposition();

  if (fading_enabled_) {
    fader_->setDirection(QTimeLine::Forward);
    fader_->start(); // Timeout will be started in FaderFinished
  }
  else if (mode_ == Mode_Popup)
    timeout_->start();
}

void OSDPretty::setVisible(bool visible) {
  if (!visible && fading_enabled_ && fader_->direction() == QTimeLine::Forward) {
    fader_->setDirection(QTimeLine::Backward);
    fader_->start();
  } else {
    QWidget::setVisible(visible);
  }
}

void OSDPretty::FaderFinished() {
  if (fader_->direction() == QTimeLine::Backward)
    hide();
  else if (mode_ == Mode_Popup)
    timeout_->start();
}

void OSDPretty::FaderValueChanged(qreal value) {
  setWindowOpacity(value);
}

void OSDPretty::Reposition() {
  QDesktopWidget* desktop = QApplication::desktop();

  // Make the OSD the proper size
  layout()->activate();
  resize(sizeHint());

  // Work out where to place the OSD.  -1 for x or y means "on the right or
  // bottom edge".
  QRect geometry(desktop->availableGeometry(popup_display_));

  int x = popup_pos_.x() < 0 ? geometry.right() - width()
                             : geometry.left() + popup_pos_.x();
  int y = popup_pos_.y() < 0 ? geometry.bottom() - height()
                             : geometry.top() + popup_pos_.y();

  move(qBound(0, x, geometry.right() - width()),
       qBound(0, y, geometry.bottom() - height()));

  // Create a mask for the actual area of the OSD
  QBitmap mask(size());
  mask.clear();

  QPainter p(&mask);
  p.setBrush(Qt::color1);
  p.drawRoundedRect(BoxBorder().adjusted(-1, -1, 0, 0), kBorderRadius, kBorderRadius);
  p.end();

  // If there's no compositing window manager running then we have to set an
  // XShape mask.
  if (IsTransparencyAvailable())
    clearMask();
  else {
    setMask(mask);
  }

#ifdef Q_OS_WIN32
  // On windows, enable blurbehind on the masked area
  QtWin::enableBlurBehindWindow(this, true, QRegion(mask));
#endif
}

void OSDPretty::enterEvent(QEvent *) {
  if (mode_ == Mode_Popup)
    setWindowOpacity(0.25);
}

void OSDPretty::leaveEvent(QEvent *) {
  setWindowOpacity(1.0);
}

void OSDPretty::mousePressEvent(QMouseEvent* e) {
  if (mode_ == Mode_Popup)
    hide();
  else {
    original_window_pos_ = pos();
    drag_start_pos_ = e->globalPos();
  }
}

void OSDPretty::mouseMoveEvent(QMouseEvent* e) {
  if (mode_ == Mode_Draggable) {
    QPoint delta = e->globalPos() - drag_start_pos_;
    QPoint new_pos = original_window_pos_ + delta;

    // Keep it to the bounds of the desktop
    QDesktopWidget* desktop = QApplication::desktop();
    QRect geometry(desktop->availableGeometry(e->globalPos()));

    new_pos.setX(qBound(geometry.left(), new_pos.x(), geometry.right() - width()));
    new_pos.setY(qBound(geometry.top(), new_pos.y(), geometry.bottom() - height()));

    // Snap to center
    int snap_x = geometry.center().x() - width() / 2;
    if (new_pos.x() > snap_x - kSnapProximity && new_pos.x() < snap_x + kSnapProximity) {
      new_pos.setX(snap_x);
    }

    move(new_pos);

    popup_display_ = current_display();
    popup_pos_ = current_pos();
  }
}

QPoint OSDPretty::current_pos() const {
  QDesktopWidget* desktop = QApplication::desktop();
  QRect geometry(desktop->availableGeometry(current_display()));

  int x = pos().x() >= geometry.right() - width()
          ? -1 : pos().x() - geometry.left();
  int y = pos().y() >= geometry.bottom() - height()
          ? -1 : pos().y() - geometry.top();

  return QPoint(x, y);
}

int OSDPretty::current_display() const {
  QDesktopWidget* desktop = QApplication::desktop();
  return desktop->screenNumber(pos());
}

void OSDPretty::set_background_color(QRgb color) {
  background_color_ = color;
  if (isVisible())
    update();
}

void OSDPretty::set_background_opacity(qreal opacity) {
  background_opacity_ = opacity;
  if (isVisible())
    update();
}

void OSDPretty::set_foreground_color(QRgb color) {
  foreground_color_ = QColor(color);

  QPalette p;
  p.setColor(QPalette::WindowText, foreground_color_);

  ui_->summary->setPalette(p);
  ui_->message->setPalette(p);
}

void OSDPretty::set_popup_duration(int msec) {
  timeout_->setInterval(msec);
}

void OSDPretty::mouseReleaseEvent(QMouseEvent *) {
  if (mode_ == Mode_Draggable) {
    popup_display_ = current_display();
    popup_pos_ = current_pos();
  }
}
