/* 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 <memory>

#include <QBuffer>
#include <QDir>
#include <QEventLoop>
#include <QSignalSpy>
#include <QtDebug>

#include <cstdlib>

#include "test_utils.h"
#include "gmock/gmock-matchers.h"
#include "gtest/gtest.h"
#include "mock_librarybackend.h"

#include "core/songloader.h"
#include "engines/gstengine.h"

using ::testing::_;
using ::testing::Return;

class SongLoaderTest : public ::testing::Test {
public:
  static void SetUpTestCase() {
    sGstEngine = new GstEngine;
    ASSERT_TRUE(sGstEngine->Init());
    sGstEngine->EnsureInitialised();
  }

  static void TearDownTestCase() {
    delete sGstEngine;
    sGstEngine = nullptr;
  }

protected:
  void SetUp() {
    library_.reset(new MockLibraryBackend);
    loader_.reset(new SongLoader(library_.get(), nullptr));
    loader_->set_timeout(20000);

    // the thing we return is not really important
    EXPECT_CALL(*library_.get(), GetSongByUrl(_, _)).WillRepeatedly(Return(Song()));
  }

  void LoadLocalDirectory(const QString& dir);

  static const char* kRemoteUrl;
  static GstEngine* sGstEngine;

  std::unique_ptr<SongLoader> loader_;
  std::unique_ptr<MockLibraryBackend> library_;
};

const char* SongLoaderTest::kRemoteUrl = "http://remotetestdata.clementine-player.org";
GstEngine* SongLoaderTest::sGstEngine = nullptr;

TEST_F(SongLoaderTest, LoadLocalMp3) {
  TemporaryResource file(":/testdata/beep.mp3");
  SongLoader::Result ret = loader_->Load(QUrl::fromLocalFile(file.fileName()));

  ASSERT_EQ(SongLoader::Success, ret);
  ASSERT_EQ(1, loader_->songs().count());
  EXPECT_TRUE(loader_->songs()[0].is_valid());
  EXPECT_EQ("Beep mp3", loader_->songs()[0].title());
}

TEST_F(SongLoaderTest, LoadLocalFileQueryExecutesButEmpty) {
  EXPECT_CALL(*library_.get(), ExecQuery(_)).WillOnce(Return(true));
  TemporaryResource file(":/testdata/beep.mp3");
  SongLoader::Result ret = loader_->Load(QUrl::fromLocalFile(file.fileName()));

  ASSERT_EQ(SongLoader::Success, ret);
  ASSERT_EQ(1, loader_->songs().count());
  EXPECT_TRUE(loader_->songs()[0].is_valid());
  EXPECT_EQ("Beep mp3", loader_->songs()[0].title());
}

TEST_F(SongLoaderTest, LoadLocalPls) {
  TemporaryResource file(":/testdata/pls_one.pls");
  SongLoader::Result ret = loader_->Load(QUrl::fromLocalFile(file.fileName()));

  ASSERT_EQ(SongLoader::WillLoadAsync, ret);
  QSignalSpy spy(loader_.get(), SIGNAL(LoadFinished(bool)));

  // Start an event loop to wait for gstreamer to do its thing
  QEventLoop loop;
  QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
                   &loop, SLOT(quit()));
  loop.exec(QEventLoop::ExcludeUserInputEvents);

  // Check the signal was emitted with Success
  ASSERT_EQ(1, spy.count());
  EXPECT_EQ(true, spy[0][0].toBool());

  // Check the song got loaded
  ASSERT_EQ(1, loader_->songs().count());
  EXPECT_EQ("Title", loader_->songs()[0].title());
  EXPECT_EQ(123 * kNsecPerSec, loader_->songs()[0].length_nanosec());
}

TEST_F(SongLoaderTest, LoadLocalM3U) {
  TemporaryResource file(":/testdata/test.m3u");
  SongLoader::Result ret = loader_->Load(QUrl::fromLocalFile(file.fileName()));

  QSignalSpy spy(loader_.get(), SIGNAL(LoadFinished(bool)));

  QEventLoop loop;
  QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
                   &loop, SLOT(quit()));
  loop.exec(QEventLoop::ExcludeUserInputEvents);

  // Check the signal was emitted with Success
  ASSERT_EQ(1, spy.count());
  EXPECT_EQ(true, spy[0][0].toBool());

  ASSERT_EQ(SongLoader::WillLoadAsync, ret);
  ASSERT_EQ(239, loader_->songs().count());
}

TEST_F(SongLoaderTest, LoadLocalXSPF) {
  TemporaryResource file(":/testdata/test.xspf");
  SongLoader::Result ret = loader_->Load(QUrl::fromLocalFile(file.fileName()));

  QSignalSpy spy(loader_.get(), SIGNAL(LoadFinished(bool)));

  QEventLoop loop;
  QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
                   &loop, SLOT(quit()));
  loop.exec(QEventLoop::ExcludeUserInputEvents);

  // Check the signal was emitted with Success
  ASSERT_EQ(1, spy.count());
  EXPECT_EQ(true, spy[0][0].toBool());

  ASSERT_EQ(SongLoader::WillLoadAsync, ret);
  ASSERT_EQ(1, loader_->songs().count());
  EXPECT_EQ("Foo", loader_->songs()[0].title());
}

TEST_F(SongLoaderTest, LoadLocalASX) {
  TemporaryResource file(":/testdata/test.asx");
  SongLoader::Result ret = loader_->Load(QUrl::fromLocalFile(file.fileName()));

  QSignalSpy spy(loader_.get(), SIGNAL(LoadFinished(bool)));

  QEventLoop loop;
  QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
                   &loop, SLOT(quit()));
  loop.exec(QEventLoop::ExcludeUserInputEvents);

  // Check the signal was emitted with Success
  ASSERT_EQ(1, spy.count());
  EXPECT_EQ(true, spy[0][0].toBool());

  ASSERT_EQ(SongLoader::WillLoadAsync, ret);
  ASSERT_EQ(1, loader_->songs().count());
  EXPECT_EQ("Foo", loader_->songs()[0].title());
}

TEST_F(SongLoaderTest, LoadRemoteMp3) {
  SongLoader::Result ret = loader_->Load(QString(kRemoteUrl) + "/beep.mp3");
  ASSERT_EQ(SongLoader::WillLoadAsync, ret);

  QSignalSpy spy(loader_.get(), SIGNAL(LoadFinished(bool)));

  // Start an event loop to wait for gstreamer to do its thing
  QEventLoop loop;
  QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
                   &loop, SLOT(quit()));
  loop.exec(QEventLoop::ExcludeUserInputEvents);

  // Check the signal was emitted with Success
  ASSERT_EQ(1, spy.count());
  EXPECT_EQ(true, spy[0][0].toBool());

  // Check the song got loaded
  ASSERT_EQ(1, loader_->songs().count());
  EXPECT_EQ(QUrl(QString(kRemoteUrl) + "/beep.mp3"), loader_->songs()[0].url());
}

TEST_F(SongLoaderTest, LoadRemote404) {
  SongLoader::Result ret = loader_->Load(QString(kRemoteUrl) + "/404.mp3");
  ASSERT_EQ(SongLoader::WillLoadAsync, ret);

  QSignalSpy spy(loader_.get(), SIGNAL(LoadFinished(bool)));

  // Start an event loop to wait for gstreamer to do its thing
  QEventLoop loop;
  QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
                   &loop, SLOT(quit()));
  loop.exec(QEventLoop::ExcludeUserInputEvents);

  // Check the signal was emitted with Error
  ASSERT_EQ(1, spy.count());
  EXPECT_EQ(false, spy[0][0].toBool());
}

TEST_F(SongLoaderTest, LoadRemotePls) {
  SongLoader::Result ret = loader_->Load(QString(kRemoteUrl) + "/pls_somafm.pls");
  ASSERT_EQ(SongLoader::WillLoadAsync, ret);

  QSignalSpy spy(loader_.get(), SIGNAL(LoadFinished(bool)));

  // Start an event loop to wait for gstreamer to do its thing
  QEventLoop loop;
  QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
                   &loop, SLOT(quit()));
  loop.exec(QEventLoop::ExcludeUserInputEvents);

  // Check the signal was emitted with Success
  ASSERT_EQ(1, spy.count());
  EXPECT_EQ(true, spy[0][0].toBool());

  // Check some metadata
  ASSERT_EQ(4, loader_->songs().count());
  EXPECT_EQ("SomaFM: Groove Salad (#3 128k mp3): A nicely chilled plate of ambient beats and grooves.",
            loader_->songs()[2].title());
  EXPECT_EQ(QUrl("http://ice.somafm.com/groovesalad"), loader_->songs()[3].url());
}

TEST_F(SongLoaderTest, LoadRemotePlainText) {
  SongLoader::Result ret = loader_->Load(QString(kRemoteUrl) + "/notaplaylist.txt");
  ASSERT_EQ(SongLoader::WillLoadAsync, ret);

  QSignalSpy spy(loader_.get(), SIGNAL(LoadFinished(bool)));

  // Start an event loop to wait for gstreamer to do its thing
  QEventLoop loop;
  QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
                   &loop, SLOT(quit()));
  loop.exec(QEventLoop::ExcludeUserInputEvents);

  // Check the signal was emitted with Error
  ASSERT_EQ(1, spy.count());
  EXPECT_EQ(false, spy[0][0].toBool());
}

TEST_F(SongLoaderTest, LoadRemotePlainM3U) {
  SongLoader::Result ret = loader_->Load(QString(kRemoteUrl) + "/plainm3u.m3u");
  ASSERT_EQ(SongLoader::WillLoadAsync, ret);

  QSignalSpy spy(loader_.get(), SIGNAL(LoadFinished(bool)));

  // Start an event loop to wait for gstreamer to do its thing
  QEventLoop loop;
  QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
                   &loop, SLOT(quit()));
  loop.exec(QEventLoop::ExcludeUserInputEvents);

  // Check the signal was emitted with Success
  ASSERT_EQ(1, spy.count());
  EXPECT_EQ(true, spy[0][0].toBool());

  ASSERT_EQ(2, loader_->songs().count());
  EXPECT_EQ(QUrl("http://www.example.com/one.mp3"), loader_->songs()[0].url());
  EXPECT_EQ(QUrl("http://www.example.com/two.mp3"), loader_->songs()[1].url());
}

TEST_F(SongLoaderTest, LoadLocalDirectory) {
  // Make a directory and shove some files in it
  QString dir;
  {
    QTemporaryFile ffffuuuuuuuu;
    ffffuuuuuuuu.open();
    dir = ffffuuuuuuuu.fileName();
  }

  QDir d;
  ASSERT_TRUE(d.mkdir(dir));

  QFile resource(":/testdata/beep.mp3");
  resource.open(QIODevice::ReadOnly);
  QByteArray data(resource.readAll());

  // Write 3 MP3 files
  for (int i=0 ; i<3 ; ++i) {
    QFile mp3(QString("%1/%2.mp3").arg(dir).arg(i));
    mp3.open(QIODevice::WriteOnly);
    mp3.write(data);
  }

  // And one file that isn't an MP3
  QFile somethingelse(QString(dir) + "/somethingelse.foo");
  somethingelse.open(QIODevice::WriteOnly);
  somethingelse.write("I'm not an MP3!");
  somethingelse.close();

  // The actual test happens in another function so we can always clean up if
  // it asserts
  LoadLocalDirectory(dir);

  QFile::remove(QString(dir) + "/0.mp3");
  QFile::remove(QString(dir) + "/1.mp3");
  QFile::remove(QString(dir) + "/2.mp3");
  QFile::remove(QString(dir) + "/somethingelse.foo");

  d.rmdir(dir);
}

void SongLoaderTest::LoadLocalDirectory(const QString &filename) {
  // Load the directory
  SongLoader::Result ret = loader_->Load(QUrl::fromLocalFile(filename));
  ASSERT_EQ(SongLoader::WillLoadAsync, ret);

  QSignalSpy spy(loader_.get(), SIGNAL(LoadFinished(bool)));

  // Start an event loop to wait for it to read the directory
  QEventLoop loop;
  QObject::connect(loader_.get(), SIGNAL(LoadFinished(bool)),
                   &loop, SLOT(quit()));
  loop.exec(QEventLoop::ExcludeUserInputEvents);
  loader_.get()->EffectiveSongsLoad();

  // Check the signal was emitted with Success
  ASSERT_EQ(1, spy.count());
  EXPECT_EQ(true, spy[0][0].toBool());

  // Check it loaded three files
  ASSERT_EQ(3, loader_->songs().count());
  EXPECT_EQ("Beep mp3", loader_->songs()[2].title());
}
