/* This file is part of Clementine.
   Copyright 2012, 2014, John Maguire <john.maguire@gmail.com>
   Copyright 2012, 2014, David Sansome <me@davidsansome.com>
   Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.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 "googledriveservice.h"

#include <QDesktopServices>
#include <QEventLoop>
#include <QMenu>
#include <QScopedPointer>
#include <QSortFilterProxyModel>
#include <QUrlQuery>

#include "core/application.h"
#include "core/closure.h"
#include "core/database.h"
#include "core/mergedproxymodel.h"
#include "core/player.h"
#include "core/timeconstants.h"
#include "globalsearch/globalsearch.h"
#include "globalsearch/librarysearchprovider.h"
#include "googledriveclient.h"
#include "googledriveurlhandler.h"
#include "internet/core/internetmodel.h"
#include "library/librarybackend.h"
#include "library/librarymodel.h"
#include "playlist/playlist.h"
#include "ui/albumcovermanager.h"
#include "ui/iconloader.h"

const char* GoogleDriveService::kServiceName = "Google Drive";
const char* GoogleDriveService::kSettingsGroup = "GoogleDrive";

namespace {

static const char* kDriveEditFileUrl = "https://docs.google.com/file/d/%1/edit";
static const char* kServiceId = "google_drive";
}  // namespace

GoogleDriveService::GoogleDriveService(Application* app, InternetModel* parent)
    : CloudFileService(app, parent, kServiceName, kServiceId,
                       IconLoader::Load("googledrive", IconLoader::Provider),
                       SettingsDialog::Page_GoogleDrive),
      client_(new google_drive::Client(this)),
      open_in_drive_action_(nullptr),
      update_action_(nullptr),
      full_rescan_action_(nullptr) {
  app->player()->RegisterUrlHandler(new GoogleDriveUrlHandler(this, this));
}

bool GoogleDriveService::has_credentials() const {
  return !refresh_token().isEmpty();
}

QString GoogleDriveService::refresh_token() const {
  QSettings s;
  s.beginGroup(kSettingsGroup);

  return s.value("refresh_token").toString();
}

void GoogleDriveService::Connect() {
  google_drive::ConnectResponse* response = client_->Connect(refresh_token());
  NewClosure(response, SIGNAL(Finished()), this,
             SLOT(ConnectFinished(google_drive::ConnectResponse*)), response);
}

void GoogleDriveService::ForgetCredentials() {
  client_->ForgetCredentials();

  QSettings s;
  s.beginGroup(kSettingsGroup);

  s.remove("refresh_token");
  s.remove("user_email");
}

void GoogleDriveService::ListChanges(const QString& cursor) {
  google_drive::ListChangesResponse* changes_response =
      client_->ListChanges(cursor);
  connect(changes_response, SIGNAL(FilesFound(QList<google_drive::File>)),
          SLOT(FilesFound(QList<google_drive::File>)));
  connect(changes_response, SIGNAL(FilesDeleted(QList<QUrl>)),
          SLOT(FilesDeleted(QList<QUrl>)));
  NewClosure(changes_response, SIGNAL(Finished()), this,
             SLOT(ListChangesFinished(google_drive::ListChangesResponse*)),
             changes_response);
}

void GoogleDriveService::ListChangesFinished(
    google_drive::ListChangesResponse* changes_response) {
  changes_response->deleteLater();

  const QString cursor = changes_response->next_cursor();
  if (is_indexing()) {
    // Only save the cursor after all the songs have been indexed - that way if
    // Clementine is closed it'll resume next time.
    NewClosure(this, SIGNAL(AllIndexingTasksFinished()), this,
               SLOT(SaveCursor(QString)), cursor);
  } else {
    SaveCursor(cursor);
  }
}

void GoogleDriveService::SaveCursor(const QString& cursor) {
  QSettings s;
  s.beginGroup(kSettingsGroup);
  s.setValue("cursor", cursor);
}

void GoogleDriveService::ConnectFinished(
    google_drive::ConnectResponse* response) {
  response->deleteLater();

  // Save the refresh token
  QSettings s;
  s.beginGroup(kSettingsGroup);
  s.setValue("refresh_token", response->refresh_token());

  if (!response->user_email().isEmpty()) {
    // We only fetch the user's email address the first time we authenticate.
    s.setValue("user_email", response->user_email());
  }

  emit Connected();

  // Find all the changes since the last check.
  CheckForUpdates();
}

void GoogleDriveService::EnsureConnected() {
  if (client_->is_authenticated()) {
    return;
  }

  QEventLoop loop;
  connect(client_, SIGNAL(Authenticated()), &loop, SLOT(quit()));
  Connect();
  loop.exec();
}

void GoogleDriveService::FilesFound(const QList<google_drive::File>& files) {
  for (const google_drive::File& file : files) {
    if (!IsSupportedMimeType(file.mime_type())) {
      continue;
    }

    QUrl url;
    url.setScheme("googledrive");
    url.setPath("/" + file.id());

    Song song;
    // Add some extra tags from the Google Drive metadata.
    song.set_etag(file.etag().remove('"'));
    song.set_mtime(file.modified_date().toTime_t());
    song.set_ctime(file.created_date().toTime_t());
    song.set_comment(file.description());
    song.set_directory_id(0);
    song.set_url(QUrl(url));
    song.set_filesize(file.size());

    // Use the Google Drive title if we couldn't read tags from the file.
    if (song.title().isEmpty()) {
      song.set_title(file.title());
    }

    MaybeAddFileToDatabase(song, file.mime_type(), file.download_url(),
                           QString("Bearer %1").arg(client_->access_token()));
  }
}

void GoogleDriveService::FilesDeleted(const QList<QUrl>& files) {
  for (const QUrl& url : files) {
    Song song = library_backend_->GetSongByUrl(url);
    qLog(Debug) << "Deleting:" << url << song.title();
    if (song.is_valid()) {
      library_backend_->DeleteSongs(SongList() << song);
    }
  }
}

QUrl GoogleDriveService::GetStreamingUrlFromSongId(const QString& id) {
  EnsureConnected();
  QScopedPointer<google_drive::GetFileResponse> response(client_->GetFile(id));

  QEventLoop loop;
  connect(response.data(), SIGNAL(Finished()), &loop, SLOT(quit()));
  loop.exec();

  if (response->had_error()) {
    app_->AddError(tr("Could not find Google Drive file."));
    return QUrl();
  }

  return QUrl(response->file().download_url());
}

void GoogleDriveService::PopulateContextMenu() {
  context_menu_->addActions(GetPlaylistActions());
  open_in_drive_action_ = context_menu_->addAction(
      IconLoader::Load("googledrive", IconLoader::Provider),
      tr("Open in Google Drive"), this, SLOT(OpenWithDrive()));
  context_menu_->addSeparator();
  update_action_ = context_menu_->addAction(
      IconLoader::Load("view-refresh", IconLoader::Base),
      tr("Check for updates"), this, SLOT(CheckForUpdates()));
  full_rescan_action_ = context_menu_->addAction(
      IconLoader::Load("view-refresh", IconLoader::Base),
      tr("Do a full rescan..."), this, SLOT(FullRescanRequested()));
  context_menu_->addSeparator();
  context_menu_->addAction(IconLoader::Load("download", IconLoader::Base),
                           tr("Cover Manager"), this, SLOT(ShowCoverManager()));
  context_menu_->addAction(IconLoader::Load("configure", IconLoader::Base),
                           tr("Configure..."), this, SLOT(ShowConfig()));
}

void GoogleDriveService::UpdateContextMenu() {
  // Only show some actions if there are real songs selected
  bool songs_selected = false;
  for (const QModelIndex& index : model()->selected_indexes()) {
    const int type = index.data(LibraryModel::Role_Type).toInt();
    if (type == LibraryItem::Type_Song || type == LibraryItem::Type_Container) {
      songs_selected = true;
      break;
    }
  }

  open_in_drive_action_->setEnabled(songs_selected);
  update_action_->setEnabled(!is_indexing());
  full_rescan_action_->setEnabled(!is_indexing());
}

void GoogleDriveService::OpenWithDrive() {
  // Map indexes to the actual library model.
  QModelIndexList library_indexes;
  for (const QModelIndex& index : model()->selected_indexes()) {
    if (index.model() == library_sort_model_) {
      library_indexes << library_sort_model_->mapToSource(index);
    }
  }

  // Ask the library for the songs for these indexes.
  for (const Song& song : library_model_->GetChildSongs(library_indexes)) {
    QDesktopServices::openUrl(
        QUrl(QString(kDriveEditFileUrl).arg(song.url().path())));
  }
}

void GoogleDriveService::DoFullRescan() {
  QSettings s;
  s.beginGroup(kSettingsGroup);
  s.remove("cursor");

  library_backend_->DeleteAll();

  ListChanges(QString());
}

void GoogleDriveService::CheckForUpdates() {
  QSettings s;
  s.beginGroup(kSettingsGroup);
  ListChanges(s.value("cursor").toString());
}
