/* 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 "magnatunedownloaddialog.h"
#include "magnatuneservice.h"
#include "radiomodel.h"
#include "ui_magnatunedownloaddialog.h"
#include "core/network.h"
#include "widgets/progressitemdelegate.h"

#include <QCloseEvent>
#include <QDir>
#include <QFileDialog>
#include <QMessageBox>
#include <QMetaEnum>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QSet>
#include <QSettings>
#include <QXmlStreamReader>

MagnatuneDownloadDialog::MagnatuneDownloadDialog(MagnatuneService* service,
                                                 QWidget *parent)
  : QDialog(parent),
    ui_(new Ui_MagnatuneDownloadDialog),
    service_(service),
    network_(new NetworkAccessManager(this)),
    current_reply_(NULL),
    next_row_(0)
{
  ui_->setupUi(this);
  ui_->albums->header()->setResizeMode(QHeaderView::ResizeToContents);
  ui_->albums->header()->setResizeMode(1, QHeaderView::Fixed);
  ui_->albums->header()->resizeSection(1, 150);
  ui_->albums->setItemDelegateForColumn(1, new ProgressItemDelegate(this));

  ui_->button_box->button(QDialogButtonBox::Cancel)->hide();

  connect(ui_->browse, SIGNAL(clicked()), SLOT(Browse()));

  QSettings s;
  s.beginGroup(MagnatuneService::kSettingsGroup);
  ui_->directory->setText(s.value("download_dir", QDir::homePath()).toString());
}

MagnatuneDownloadDialog::~MagnatuneDownloadDialog() {
  delete ui_;
  delete current_reply_;
}

void MagnatuneDownloadDialog::Show(const SongList& songs) {
  ui_->albums->clear();

  QSet<QString> sku_codes;
  foreach (const Song& song, songs) {
    if (!sku_codes.contains(song.comment())) {
      sku_codes.insert(song.comment());

      QTreeWidgetItem* item = new QTreeWidgetItem(ui_->albums);
      item->setText(0, song.artist() + " - " + song.album());
      item->setData(0, Qt::UserRole, song.comment());
    }
  }

  ui_->button_box->button(QDialogButtonBox::Ok)->show();
  ui_->format->setCurrentIndex(service_->preferred_format());

  show();
}

void MagnatuneDownloadDialog::accept() {
  QFileInfo dir(ui_->directory->text());
  if (!dir.exists() || !dir.isDir() || !dir.isWritable()) {
    ShowError(tr("The directory %1 is not valid").arg(ui_->directory->text()));
    ui_->directory->setFocus();
    return;
  }

  ui_->button_box->button(QDialogButtonBox::Ok)->hide();
  ui_->button_box->button(QDialogButtonBox::Close)->hide();
  ui_->button_box->button(QDialogButtonBox::Cancel)->show();
  ui_->options->setEnabled(false);

  // Reset all the progress bars
  for (int i=0 ; i<ui_->albums->topLevelItemCount() ; ++i) {
    ui_->albums->topLevelItem(i)->setData(1, Qt::DisplayRole, QVariant());
  }

  next_row_ = 0;
  DownloadNext();
}

void MagnatuneDownloadDialog::DownloadNext() {
  // We have to download an XML file first that gives us the URLs of the actual
  // zipped album.

  if (next_row_ >= ui_->albums->topLevelItemCount()) {
    AllFinished(false);
    return;
  }

  QTreeWidgetItem* item = ui_->albums->topLevelItem(next_row_);
  QString sku = item->data(0, Qt::UserRole).toString();
  item->setData(1, Qt::DisplayRole, tr("Starting..."));

  QUrl url = QString(MagnatuneService::kDownloadUrl);
  url.setUserName(service_->username());
  url.setPassword(service_->password());
  url.addQueryItem("id", MagnatuneService::kPartnerId);
  url.addQueryItem("sku", sku);

  current_reply_ = network_->get(QNetworkRequest(url));

  connect(current_reply_, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(Error(QNetworkReply::NetworkError)));
  connect(current_reply_, SIGNAL(finished()), SLOT(MetadataFinished()));
}

void MagnatuneDownloadDialog::Error(QNetworkReply::NetworkError e) {
  QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
  reply->deleteLater();

  QUrl url = reply->url();
  url.setPassword(QString());

  QMetaEnum error_enum = QNetworkReply::staticMetaObject.enumerator(
      QNetworkReply::staticMetaObject.indexOfEnumerator("NetworkError"));

  QString message = tr("Unable to download %1 (%2)")
                        .arg(url.toString()).arg(error_enum.valueToKey(e));
  ShowError(message);
}

void MagnatuneDownloadDialog::MetadataFinished() {
  QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
  reply->deleteLater();

  // The reply isn't valid XML so we can't use QtXML to parse it :(
  QString data = QString::fromUtf8(reply->readAll());

  // Check for errors
  if (data.contains("<ERROR>")) {
    ShowError(tr("There was a problem fetching the metadata from Magnatune"));
    return;
  }

  // Work out what format we want
  QString type;
  switch (ui_->format->currentIndex()) {
    case MagnatuneService::Format_Ogg:     type = "URL_OGGZIP";    break;
    case MagnatuneService::Format_Flac:    type = "URL_FLACZIP";   break;
    case MagnatuneService::Format_Wav:     type = "URL_WAVZIP";    break;
    case MagnatuneService::Format_MP3_VBR: type = "URL_VBRZIP";    break;
    case MagnatuneService::Format_MP3_128: type = "URL_128KMP3ZIP"; break;
  }

  // Parse the XML (lol) to find the URL
  QRegExp re(QString("<%1>([^<]+)</%2>").arg(type, type));
  if (re.indexIn(data) == -1) {
    ShowError(tr("This album is not available in the requested format"));
    return;
  }

  // Munge the URL a bit
  QString url_text = re.cap(1);
  url_text.replace("&amp;", "&");

  QUrl url = QUrl(url_text);
  url.setUserName(service_->username());
  url.setPassword(service_->password());

  // Start the actual download
  current_reply_ = network_->get(QNetworkRequest(url));

  connect(current_reply_, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(Error(QNetworkReply::NetworkError)));
  connect(current_reply_, SIGNAL(finished()), SLOT(DownloadFinished()));
  // No DownloadProgress connection because the first one is a redirect
}

void MagnatuneDownloadDialog::DownloadFinished() {
  QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
  reply->deleteLater();

  // Close any open file
  download_file_.reset();

  QUrl redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
  if (redirect.isValid()) {
    // Open the output file
    QString output_filename = GetOutputFilename();
    download_file_.reset(new QFile(output_filename));
    if (!download_file_->open(QIODevice::WriteOnly)) {
      ShowError(tr("Couldn't open output file %1").arg(output_filename));
      return;
    }

    // Round and round we go
    redirect.setUserName(service_->username());
    redirect.setPassword(service_->password());

    current_reply_ = network_->get(QNetworkRequest(redirect));

    connect(current_reply_, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(Error(QNetworkReply::NetworkError)));
    connect(current_reply_, SIGNAL(finished()), SLOT(DownloadFinished()));
    connect(current_reply_, SIGNAL(downloadProgress(qint64,qint64)), SLOT(DownloadProgress(qint64,qint64)));
    connect(current_reply_, SIGNAL(readyRead()), SLOT(DownloadReadyRead()));
    return;
  }

  next_row_ ++;
  DownloadNext();
}

void MagnatuneDownloadDialog::DownloadProgress(qint64 received, qint64 total) {
  int percent = float(received) / total * 100;
  ui_->albums->topLevelItem(next_row_)->setData(1, Qt::DisplayRole, percent);
}

void MagnatuneDownloadDialog::DownloadReadyRead() {
  QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
  download_file_->write(reply->readAll());
}

void MagnatuneDownloadDialog::ShowError(const QString &message) {
  QMessageBox::critical(this, tr("Error"), message, QMessageBox::Close);
  AllFinished(true);
}

void MagnatuneDownloadDialog::AllFinished(bool error) {
  current_reply_ = NULL;

  if (error)
    ui_->button_box->button(QDialogButtonBox::Ok)->show();
  ui_->button_box->button(QDialogButtonBox::Close)->show();
  ui_->button_box->button(QDialogButtonBox::Cancel)->hide();
  ui_->options->setEnabled(true);

  // Make the taskbar flash
  QApplication::alert(this);

  if (!error) {
    QStringList albums;
    for (int i=0 ; i<ui_->albums->topLevelItemCount() ; ++i) {
      albums << ui_->albums->topLevelItem(i)->text(0);
    }
    emit Finished(albums);
  }
}

void MagnatuneDownloadDialog::Browse() {
  QString dir = QFileDialog::getExistingDirectory(
      this, tr("Download directory"), ui_->directory->text());

  QSettings s;
  s.beginGroup(MagnatuneService::kSettingsGroup);
  s.setValue("download_dir", dir);

  ui_->directory->setText(dir);
}

QString MagnatuneDownloadDialog::GetOutputFilename() {
  QString album = ui_->albums->topLevelItem(next_row_)->text(0);
  QString extension;

  switch (MagnatuneService::PreferredFormat(ui_->format->currentIndex())) {
    case MagnatuneService::Format_Ogg:     extension = "ogg";  break;
    case MagnatuneService::Format_Flac:    extension = "flac"; break;
    case MagnatuneService::Format_Wav:     extension = "wav";  break;
    case MagnatuneService::Format_MP3_VBR: extension = "vbr";  break;
    case MagnatuneService::Format_MP3_128: extension = "mp3";  break;
  }

  return QString("%1/%2-%3.zip").arg(ui_->directory->text(), album, extension);
}

void MagnatuneDownloadDialog::closeEvent(QCloseEvent* e) {
  if (current_reply_ && current_reply_->isRunning()) {
    boost::scoped_ptr<QMessageBox> message_box(new QMessageBox(
        QMessageBox::Question, tr("Really cancel?"),
        tr("Closing this window will cancel the download."),
        QMessageBox::Abort, this));
    message_box->addButton(tr("Don't stop!"), QMessageBox::AcceptRole);

    if (message_box->exec() != QMessageBox::Abort) {
      e->ignore();
    }
  }
}
