/* This file is part of Clementine.
   Copyright 2011-2013, Alan Briolat <alan.briolat@gmail.com>
   Copyright 2013, Ross Wolfson <ross.wolfson@gmail.com>
   Copyright 2013, David Sansome <me@davidsansome.com>
   Copyright 2013-2014, John Maguire <john.maguire@gmail.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/>.
*/

#ifndef INTERNET_SUBSONIC_SUBSONICSERVICE_H_
#define INTERNET_SUBSONIC_SUBSONICSERVICE_H_

#include <QQueue>
#include <memory>

#include "internet/core/internetmodel.h"
#include "internet/core/internetservice.h"
#include "internet/subsonic/subsonicdynamicplaylist.h"

class QNetworkAccessManager;
class QNetworkReply;
class QSortFilterProxyModel;
class QXmlStreamReader;

class SubsonicUrlHandler;
class SubsonicLibraryScanner;

class SubsonicService : public InternetService {
  Q_OBJECT

 public:
  SubsonicService(Application* app, InternetModel* parent);
  ~SubsonicService();

  enum LoginState {
    LoginState_Loggedin,
    LoginState_BadServer,
    LoginState_OutdatedClient,
    LoginState_OutdatedServer,
    LoginState_BadCredentials,
    LoginState_Unlicensed,
    LoginState_OtherError,
    LoginState_Unknown,
    LoginState_ConnectionRefused,
    LoginState_HostNotFound,
    LoginState_Timeout,
    LoginState_SslError,
    LoginState_IncompleteCredentials,
    LoginState_RedirectLimitExceeded,
    LoginState_RedirectNoUrl,
  };
  Q_ENUM(LoginState)

  enum ApiError {
    ApiError_Generic = 0,
    ApiError_ParameterMissing = 10,
    ApiError_OutdatedClient = 20,
    ApiError_OutdatedServer = 30,
    ApiError_BadCredentials = 40,
    ApiError_Unauthorized = 50,
    ApiError_Unlicensed = 60,
    ApiError_NotFound = 70,
  };
  Q_ENUM(ApiError)

  enum Type {
    Type_Artist = InternetModel::TypeCount,
    Type_Album,
    Type_Track,
  };

  enum Role {
    Role_Id = InternetModel::RoleCount,
  };

  typedef QMap<QString, QString> RequestOptions;

  bool IsConfigured() const;
  bool IsAmpache() const;

  QStandardItem* CreateRootItem();
  void LazyPopulate(QStandardItem* item);
  void ShowContextMenu(const QPoint& global_pos);
  QWidget* HeaderWidget() const;
  void ReloadSettings();

  void Login();
  void Login(const QString& server, const QString& username,
             const QString& password, const bool& usesslv3,
             const bool& verifycert);

  LoginState login_state() const { return login_state_; }

  bool ConfigRequired() override {
    return login_state() != LoginState_Loggedin;
  }

  // Subsonic API methods
  void Ping();

  QUrl BuildRequestUrl(const QString& view) const;
  // Scrubs the part of the path that we re-add in BuildRequestUrl().
  static QUrl ScrubUrl(const QUrl& url);
  // Convenience function to reduce QNetworkRequest/QSslConfiguration
  // boilerplate.
  QNetworkReply* Send(const QUrl& url);

  Song ReadSong(QXmlStreamReader& reader);

  friend PlaylistItemList SubsonicDynamicPlaylist::GenerateMoreAlbums(int);
  friend PlaylistItemList SubsonicDynamicPlaylist::GenerateMoreSongs(int);

  static const char* kServiceName;
  static const char* kSettingsGroup;
  static const char* kApiVersion;
  static const char* kApiClientName;

  static const char* kSongsTable;
  static const char* kFtsTable;

  static const int kMaxRedirects;
  static const int kCoverArtSize;

 signals:
  void LoginStateChanged(SubsonicService::LoginState newstate);

 private:
  void EnsureMenuCreated();
  // Update configured and working server state
  void UpdateServer(const QString& server);

  QNetworkAccessManager* network_;
  SubsonicUrlHandler* url_handler_;

  SubsonicLibraryScanner* scanner_;
  int load_database_task_id_;

  QStandardItem* root_;

  std::shared_ptr<LibraryBackend> library_backend_;
  LibraryModel* library_model_;
  LibraryFilterWidget* library_filter_;
  QSortFilterProxyModel* library_sort_model_;
  int total_song_count_;

  // Configuration
  // The server that shows up in the GUI (use UpdateServer() to update)
  QString configured_server_;
  QString username_;
  QString password_;
  bool usesslv3_;
  bool verifycert_;

  LoginState login_state_;
  QString working_server_;  // The actual server, possibly post-redirect
  int redirect_count_;
  bool is_ampache_;

 private slots:
  void UpdateTotalSongCount(int count);
  void ReloadDatabase();
  void ReloadDatabaseFinished();
  void OnLoginStateChanged(SubsonicService::LoginState newstate);
  void OnPingFinished(QNetworkReply* reply);

  void ShowConfig();
};

class SubsonicLibraryScanner : public QObject {
  Q_OBJECT

 public:
  explicit SubsonicLibraryScanner(SubsonicService* service,
                                  QObject* parent = nullptr);
  ~SubsonicLibraryScanner();

  void Scan();
  const SongList& GetSongs() const { return songs_; }

  static const int kAlbumChunkSize;
  static const int kConcurrentRequests;
  static const int kCoverArtSize;

 signals:
  void ScanFinished();

 private slots:
  // Step 1: use getAlbumList2 type=alphabeticalByName to list all albums
  void OnGetAlbumListFinished(QNetworkReply* reply, int offset);
  // Step 2: use getAlbum id=? to list all songs for each album
  void OnGetAlbumFinished(QNetworkReply* reply);

 private:
  void GetAlbumList(int offset);
  void GetAlbum(const QString& id);
  void ParsingError(const QString& message);

  SubsonicService* service_;
  bool scanning_;
  QQueue<QString> album_queue_;
  QSet<QNetworkReply*> pending_requests_;
  SongList songs_;
};

#endif  // INTERNET_SUBSONIC_SUBSONICSERVICE_H_
