/* $BEGIN_LICENSE

This file is part of Musique.
Copyright 2013, Flavio Tordini <flavio.tordini@gmail.com>

Musique 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.

Musique 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 Musique.  If not, see <http://www.gnu.org/licenses/>.

$END_LICENSE */

#include "album.h"
#include "../constants.h"

#include "../database.h"
#include "../datautils.h"
#include <QtSql>
#include <utility>

#include "../httputils.h"
#include "http.h"

#include "../finderitemdelegate.h"

Album::Album() : year(0), artist(nullptr), listeners(0) {}

QHash<int, Album *> Album::cache;

Album *Album::forId(int albumId) {
    auto i = cache.constFind(albumId);
    if (i != cache.constEnd()) return i.value();

    QSqlDatabase db = Database::instance().getConnection();
    QSqlQuery query(db);
    query.prepare("select title, year, artist from albums where id=?");
    query.bindValue(0, albumId);
    bool success = query.exec();
    if (!success) qDebug() << query.lastQuery() << query.lastError().text();
    if (query.next()) {
        Album *album = new Album();
        album->setId(albumId);
        album->setTitle(query.value(0).toString());
        album->setYear(query.value(1).toInt());

        // relations
        int artistId = query.value(2).toInt();
        album->setArtist(Artist::forId(artistId));
        // if (!album->getArtist()) qWarning() << "no artist for" << album->getName();

        // put into cache
        cache.insert(albumId, album);
        return album;
    }
    cache.insert(albumId, nullptr);
    return nullptr;
}

int Album::idForHash(const QString &hash) {
    int id = -1;
    QSqlDatabase db = Database::instance().getConnection();
    QSqlQuery query(db);
    query.prepare("select id from albums where hash=?");
    query.bindValue(0, hash);
    bool success = query.exec();
    if (!success) qDebug() << query.lastError().text();
    if (query.next()) {
        id = query.value(0).toInt();
    }
    // qDebug() << "album id" << id;
    return id;
}

void Album::insert() {
    QSqlDatabase db = Database::instance().getConnection();
    QSqlQuery query(db);
    query.prepare("insert into albums (hash,title,year,artist,trackCount,listeners)"
                  " values (?,?,?,?,0,?)");
    query.bindValue(0, getHash());
    query.bindValue(1, name);
    query.bindValue(2, year);
    int artistId = artist ? artist->getId() : 0;
    query.bindValue(3, artistId);
    query.bindValue(4, listeners);
    bool success = query.exec();
    if (!success) qDebug() << query.lastError().text();
    id = query.lastInsertId().toInt();

    // increment artist's album count
    if (artist && artist->getId()) {
        QSqlQuery query(db);
        query.prepare("update artists set albumCount=albumCount+1 where id=?");
        query.bindValue(0, artist->getId());
        bool success = query.exec();
        if (!success) qDebug() << query.lastError().text();

        // for artists that have no yearFrom, use the earliest album year
        if (year > 0) {
            query = QSqlQuery(db);
            query.prepare(
                    "update artists set yearFrom=? where id=? and (yearFrom=0 or yearFrom>?)");
            query.bindValue(0, year);
            query.bindValue(1, artist->getId());
            query.bindValue(2, year);
            bool success = query.exec();
            if (!success) qDebug() << query.lastError().text();
        }
    }
}

void Album::update() {
    // qDebug() << "Album::update";
    QSqlDatabase db = Database::instance().getConnection();
    QSqlQuery query(db);
    query.prepare("update albums set title=?, year=?, artist=? where hash=?");
    query.bindValue(0, name);
    query.bindValue(1, year);
    int artistId = artist ? artist->getId() : 0;
    query.bindValue(2, artistId);
    query.bindValue(3, getHash());
    bool success = query.exec();
    if (!success) qDebug() << query.lastError().text();
}

QString Album::getHash(const QString &name, Artist *artist) {
    QString h;
    if (artist)
        h = artist->getHash() + QLatin1Char('/');
    else
        h = "_unknown/";
    h += DataUtils::normalizeTag(name);
    return h;
}

const QString &Album::getHash() {
    if (hash.isNull()) hash = getHash(name, artist);
    return hash;
}

QString Album::getStatusTip() {
    QString tip;
    Artist *artist = getArtist();
    if (artist) tip += artist->getName() + " - ";
    tip += getTitle();
    if (year) tip += " (" + QString::number(year) + ")";
    tip += " - " + formattedDuration();
    return tip;
}

QString Album::formattedDuration() {
    int totalLength = Track::getTotalLength(getTracks());
    QString duration;
    if (totalLength > 3600)
        duration = QTime(0, 0).addSecs(totalLength).toString("h:mm:ss");
    else
        duration = QTime(0, 0).addSecs(totalLength).toString("m:ss");
    return duration;
}

void Album::fetchInfo() {
    // an artist name is needed in order to fix the album title
    // also workaround last.fm bug with selftitled albums
    /*
    if (artist && artist->getName() != name) {
        fetchLastFmSearch();
    } else */
    fetchLastFmInfo();
}

// *** Last.fm Photo ***

bool Album::hasPhoto() {
    return QFile::exists(getImageLocation());
}

QPixmap Album::getPhoto() {
    QPixmap p;
    QFile file(getImageLocation());
    if (file.open(QFile::ReadOnly)) {
        p.loadFromData(file.readAll());
        file.close();
    }
    return p;
}

QPixmap Album::getThumb(int width, int height, qreal pixelRatio) {
    if (pixmap.isNull() || pixmap.devicePixelRatio() != pixelRatio ||
        pixmap.width() != width * pixelRatio) {
        pixmap = getPhoto();
        if (pixmap.isNull()) return pixmap;
        const int pixelWidth = width * pixelRatio;
        const int pixelHeight = height * pixelRatio;
        const int wDiff = pixmap.width() - pixelWidth;
        const int hDiff = pixmap.height() - pixelHeight;
        if (wDiff || hDiff) {
            pixmap = pixmap.scaled(pixelWidth, pixelHeight, Qt::KeepAspectRatio,
                                   Qt::SmoothTransformation);
        }
        pixmap.setDevicePixelRatio(pixelRatio);
    }
    return pixmap;
}

void Album::fetchLastFmSearch() {
    QUrl url("http://ws.audioscrobbler.com/2.0/");
    QUrlQuery q;
    q.addQueryItem("method", "album.search");
    q.addQueryItem("api_key", Constants::LASTFM_API_KEY);
    q.addQueryItem("artist", artist->getName());
    q.addQueryItem("album", name);
    q.addQueryItem("limit", "5");
    url.setQuery(q);

    QObject *reply = HttpUtils::lastFm().get(url);
    connect(reply, SIGNAL(data(QByteArray)), SLOT(parseLastFmSearch(QByteArray)));
    connect(reply, SIGNAL(error(QString)), SIGNAL(gotInfo()));
}

void Album::parseLastFmSearch(const QByteArray &bytes) {
    QXmlStreamReader xml(bytes);

    QString artistName;
    QString albumName;

    while (!xml.atEnd() && !xml.hasError()) {
        xml.readNext();
        if (xml.isStartElement()) {
            if (xml.name() == QLatin1String("artist")) {
                artistName = xml.readElementText();
            } else if (xml.name() == QLatin1String("name")) {
                albumName = xml.readElementText();
            }

        } else if (xml.isEndElement()) {
            if (xml.name() == QLatin1String("album")) {
                // qDebug() << "Comparing artist name" << artist->getName() << artistName;
                if (artist->getName() == artistName) {
                    if (name != albumName) {
                        qDebug() << "Fixed album name" << name << "=>" << albumName;
                        name = albumName;
                        hash.clear();
                    }
                    break;
                }
            }
        }
    }

    /* Error handling. */
    if (xml.hasError()) {
        qDebug() << xml.errorString();
    }

    fetchLastFmInfo();
}

void Album::parseLastFmRedirectedName(QNetworkReply *reply) {
    QString location = reply->header(QNetworkRequest::LocationHeader).toString();
    if (!location.isEmpty()) {
        int slashIndex = location.lastIndexOf('/');
        if (slashIndex > 0) {
            name = location.mid(slashIndex);
            hash.clear();
            // qDebug() << "*** Redirected name is" << name;
            fetchLastFmSearch();
            return;
        }
    }
    emit gotInfo();
}

void Album::fetchLastFmInfo() {
    /*
    if (QFile::exists(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/albums/" +
    getHash())) { qDebug() << "Album" << name << "has a photo"; emit gotInfo(); return;
    } */

    if (!artist) {
        qDebug() << "Album" << name << "has no artist";
        emit gotInfo();
        return;
    }

    QUrl url("http://ws.audioscrobbler.com/2.0/");
    QUrlQuery q;
    q.addQueryItem("method", "album.getinfo");
    q.addQueryItem("api_key", Constants::LASTFM_API_KEY);
    q.addQueryItem("autocorrect", "1");
    if (mbid.isEmpty()) {
        q.addQueryItem("artist", artist->getName());
        q.addQueryItem("album", name);
    } else {
        q.addQueryItem("mbid", mbid);
    }
    url.setQuery(q);
    QObject *reply = HttpUtils::lastFm().get(url);
    connect(reply, SIGNAL(data(QByteArray)), SLOT(parseLastFmInfo(QByteArray)));
    connect(reply, SIGNAL(error(QString)), SIGNAL(gotInfo()));
}

void Album::parseLastFmInfo(const QByteArray &bytes) {
    QXmlStreamReader xml(bytes);

    QMap<QString, QVariant> trackNames;

    while (xml.readNextStartElement()) {
        if (xml.name() == QLatin1String("album")) {
            while (xml.readNextStartElement()) {
                const auto n = xml.name();

                if (n == QLatin1String("track")) {
                    QString number = xml.attributes().value("rank").toString();
                    if (trackNames.contains(number))
                        xml.skipCurrentElement();
                    else
                        while (xml.readNextStartElement()) {
                            if (xml.name() == QLatin1String("name")) {
                                QString title = xml.readElementText();
                                trackNames.insert(number, title);
                            } else
                                xml.skipCurrentElement();
                        }
                }

                else if (n == QLatin1String("name")) {
                    QString albumTitle = xml.readElementText();
                    if (name != albumTitle) {
                        QString newHash = getHash(albumTitle, artist);
                        if (getHash() != newHash) {
                            qDebug() << "Fixed album name" << name << "->" << albumTitle;
                            name = albumTitle;
                            hash.clear();
                        }
                    }
                }

                else if (n == QLatin1String("image") &&
                         xml.attributes().value("size") == QLatin1String("extralarge")) {
                    bool imageAlreadyPresent = property("localCover").toBool();
                    if (!imageAlreadyPresent)
                        imageAlreadyPresent = QFile::exists(getImageLocation());
                    if (!imageAlreadyPresent) {
                        QString imageUrl = xml.readElementText();
                        if (!imageUrl.isEmpty()) setProperty("imageUrl", imageUrl);
                    } else
                        xml.skipCurrentElement();
                }

                else if (n == QLatin1String("listeners")) {
                    listeners = xml.readElementText().toUInt();
                }

                else if (n == QLatin1String("releasedate") && year < 1600) {
                    QString releasedateString = xml.readElementText().simplified();
                    if (!releasedateString.isEmpty()) {
                        // Something like "6 Apr 1999, 00:00"
                        QDateTime releaseDate =
                                QDateTime::fromString(releasedateString, "d MMM yyyy, hh:mm");
                        int releaseYear = releaseDate.date().year();
                        if (releaseYear > 0) year = releaseDate.date().year();
                    }
                }

                // wiki
                else if (n == QLatin1String("wiki")) {
                    while (xml.readNextStartElement()) {
                        if (xml.name() == QLatin1String("content")) {
                            QString wiki = xml.readElementText();
                            static const QRegularExpression re("User-contributed text.*");
                            wiki.remove(re);
                            wiki = wiki.trimmed();
                            if (!wiki.isEmpty()) {
                                QString wikiLocation = getWikiLocation();
                                QDir().mkpath(QFileInfo(wikiLocation).absolutePath());
                                QFile file(wikiLocation);
                                if (!file.open(QIODevice::WriteOnly))
                                    qWarning()
                                            << "Error opening file for writing" << file.fileName();
                                QTextStream stream(&file);
                                stream << wiki;
                            }
                        } else
                            xml.skipCurrentElement();
                    }
                }

                else
                    xml.skipCurrentElement();
            }
        }
    }

    setProperty("trackNames", trackNames);

    if (xml.hasError()) qWarning() << xml.errorString();

    emit gotInfo();
}

QString Album::getBaseLocation() {
    return Database::getFilesLocation() + getHash();
}

QString Album::getImageLocation() {
    return getBaseLocation() + QLatin1String("/_cover");
}

QString Album::getWikiLocation() {
    return getBaseLocation() + QLatin1String("/_wiki");
}

void Album::setPhoto(const QByteArray &bytes) {
    qDebug() << "Storing photo for" << name;

    // store photo
    QString storageLocation = getImageLocation();

    QFileInfo info(storageLocation);
    QDir().mkpath(info.absolutePath());

    QFile file(storageLocation);
    if (!file.open(QIODevice::WriteOnly)) {
        qWarning() << "Error opening file for writing" << file.fileName();
    }
    QDataStream stream(&file);
    stream.writeRawData(bytes.constData(), bytes.size());

    emit gotPhoto();
}

QList<Track *> Album::getTracks() {
    QSqlDatabase db = Database::instance().getConnection();
    QSqlQuery query(db);
    query.prepare("select id from tracks where album=? order by disk, track, path");
    query.bindValue(0, id);
    bool success = query.exec();
    if (!success) qDebug() << query.lastQuery() << query.lastError();
    QList<Track *> tracks;
    tracks.reserve(query.size());
    while (query.next()) {
        int trackId = query.value(0).toInt();
        Track *track = Track::forId(trackId);
        tracks << track;
    }
    return tracks;
}

QString Album::getWiki() {
    QFile file(getWikiLocation());
    if (!file.exists()) return QString();
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qDebug() << "Cannot open file" << file.fileName();
        return QString();
    }
    QByteArray bytes = file.readAll();
    return QString::fromUtf8(bytes.data());
}
