#include "Device/Coord/ICoordSystem.h"
#include "GUI/Model/Data/DataItemUtil.h"
#include "GUI/Model/Data/IntensityDataItem.h"
#include "GUI/Model/Device/InstrumentItems.h"
#include "GUI/Model/Device/RealItem.h"
#include "GUI/Model/Project/ProjectUtil.h"
#include "GUI/Support/Util/Path.h"
#include "GUI/View/Project/AutosaveController.h"
#include "GUI/View/Project/ProjectManager.h"
#include "Tests/GTestWrapper/google_test.h"
#include "Tests/Unit/GUI/Utils.h"
#include <QApplication>
#include <QSignalSpy>
#include <QUuid>

class TestAutosaveController : public ::testing::Test {
protected:
    // helper method to modify something in a model
    void modify_models(ProjectDocument& doc)
    {
        auto* instrument = doc.instrumentModel()->instrumentItems().front();
        doc.multiNotifier()->setInstrumentName(instrument, QUuid::createUuid().toString());
    }
    const int m_save_wait = 10000;
};

//! Testing AutosaveController. It watches ProjectDocument and sends autosaveRequest() when
//! number of document changes has been accumulated.

TEST_F(TestAutosaveController, autoSaveController)
{
    const QString projectDir("autoSaveController");
    UTest::GUI::create_dir(projectDir);
    const QString ext = QString(GUI::Project::Util::projectFileExtension);
    const QString projectFileName(projectDir + "/document" + ext);

    const int autosave_time(100);

    std::unique_ptr<ProjectDocument> document(new ProjectDocument);
    auto* instrument = document->instrumentModel()->addInstrumentItem<GISASInstrumentItem>();
    instrument->setInstrumentName("GISAS");
    document->saveProjectFileWithData(projectFileName);

    // setting up autosave
    AutosaveController autosave;
    autosave.setAutosaveTime(autosave_time);
    autosave.setDocument(document.get());

    // checking proposed autosave directory
    EXPECT_EQ(autosave.autosaveDir(), QString(projectDir + "/autosave"));
    EXPECT_EQ(autosave.autosaveFullPath(), QString(projectDir + "/autosave/document" + ext));

    QSignalSpy spyAutosave(&autosave, SIGNAL(autosaveRequest()));

    // modify document once and check that autosave directory was created
    modify_models(*document);
    EXPECT_TRUE(document->isModified());
    EXPECT_TRUE(spyAutosave.wait(m_save_wait));
    EXPECT_EQ(spyAutosave.count(), 1);
    EXPECT_TRUE(QFile::exists(autosave.autosaveDir()));

    // saving document and checking that autosave is not triggered
    document->saveProjectFileWithData(projectFileName);
    EXPECT_FALSE(document->isModified());
    EXPECT_EQ(spyAutosave.count(), 1);

    // modify several times and check that autosave was triggered only once
    for (size_t i = 0; i < 10; ++i)
        modify_models(*document);

    EXPECT_TRUE(spyAutosave.wait(m_save_wait));
    EXPECT_EQ(spyAutosave.count(), 2);

    // remove autosave dir
    autosave.removeAutosaveDir();
    EXPECT_FALSE(QFile::exists(autosave.autosaveDir()));
}

//! AutosaveController shouldn't trigger autosaveRequest() if document has no name.

TEST_F(TestAutosaveController, autoSaveControllerNewDocument)
{
    std::unique_ptr<ProjectDocument> document(new ProjectDocument);
    auto* instrument = document->instrumentModel()->addInstrumentItem<GISASInstrumentItem>();
    instrument->setInstrumentName("GISAS");

    const int autosave_time(100);

    AutosaveController autosave;
    autosave.setAutosaveTime(autosave_time);
    autosave.setDocument(document.get());

    QSignalSpy spyAutosave(&autosave, SIGNAL(autosaveRequest()));

    modify_models(*document);
    EXPECT_FALSE(spyAutosave.wait(autosave_time * 2));
    EXPECT_EQ(spyAutosave.count(), 0);
}

//! Testing autosave after changing document.

TEST_F(TestAutosaveController, autosaveEnabled)
{
    QApplication::setOrganizationName("BornAgain"); // autosave to directory ~/.config/BornAgain
    const QString projectDir("autosaveEnabled");
    UTest::GUI::create_dir(projectDir);
    const QString ext = QString(GUI::Project::Util::projectFileExtension);
    const QString projectFileName(projectDir + "/document" + ext);

    std::unique_ptr<ProjectManager> manager(new ProjectManager(nullptr));
    ProjectDocument* document = manager->newProject();
    EXPECT_TRUE(document != nullptr);

    document->setProjectFullPath(projectFileName);
    auto* instrument = document->instrumentModel()->addInstrumentItem<GISASInstrumentItem>();
    instrument->setInstrumentName("GISAS");

    RealItem* realData = UTest::GUI::createRealData("TestData", *document->realModel());
    DataItem* intensityItem = realData->dataItem();
    intensityItem->setSaveInBackground(false);
    const auto converter =
        document->instrumentModel()->instrument2DItems().front()->createCoordSystem();
    ASSERT_TRUE(converter);
    GUI::Model::DataItemUtil::createDefaultDetectorMap(intensityItem, *converter);
    document->clearModified();
    EXPECT_FALSE(document->isModified());

    manager->setAutosaveEnabled(true);
    const int autosave_time(200);
    manager->autosaveController()->setAutosaveTime(autosave_time);
    manager->autosaveController()->setDocument(document);

    QSignalSpy spyDocument(document, SIGNAL(projectSaved()));
    document->saveProjectFileWithData(projectFileName);

    spyDocument.wait(m_save_wait); // waiting saving in a thread is complete
    EXPECT_EQ(spyDocument.count(), 1);
    EXPECT_FALSE(document->isModified());
    EXPECT_TRUE(QFile::exists(projectDir + "/document" + ext));
    EXPECT_TRUE(QFile::exists(projectDir + "/realdata_TestData_0.int.gz"));
    spyDocument.clear();

    // modify several times and check SaveService signals
    for (size_t i = 0; i < 10; ++i)
        modify_models(*document);

    EXPECT_TRUE(document->isModified());

    spyDocument.wait(m_save_wait); // waiting saving in a thread is complete
    EXPECT_EQ(spyDocument.count(), 1);

    EXPECT_TRUE(QFile::exists(projectDir + "/autosave/document" + ext));
    EXPECT_TRUE(QFile::exists(projectDir + "/autosave/realdata_TestData_0.int.gz"));

    // after autosave the project has to be still in modified state
    EXPECT_TRUE(document->isModified());
    // after autosave, project file name should remain the same
    EXPECT_EQ(document->projectFullPath(), projectFileName);
}
