#include "Base/Axis/MakeScale.h"
#include "Device/Data/Datafield.h"
#include "Device/IO/IOFactory.h"
#include "GUI/Model/Data/DataItem.h"
#include "GUI/Model/Device/BackgroundItems.h"
#include "GUI/Model/Device/InstrumentItems.h"
#include "GUI/Model/Device/InstrumentModel.h"
#include "GUI/Model/Device/RealItem.h"
#include "GUI/Model/Model/RealModel.h"
#include "Tests/GTestWrapper/google_test.h"
#include "Tests/Unit/GUI/Utils.h"
#include <QTest>
#include <fstream>

const QString dir_test_real_model = "TestRealModel";

TEST(TestRealModel, removeNativeData)
{
    RealModel model;

    // add RealItem
    auto* item = model.insertDataItem(1);
    EXPECT_EQ(item->nativeDataItem(), nullptr);

    // add native data
    item->initNativeData();
    EXPECT_NE(item->nativeDataItem(), nullptr);
    EXPECT_FALSE(item->hasNativeData());
    EXPECT_EQ(item->nativeDataItem()->c_field(), nullptr);

    // call remove while no output data set yet
    item->removeNativeData();
    EXPECT_NE(item->nativeDataItem(), nullptr);
    EXPECT_FALSE(item->hasNativeData());
    EXPECT_EQ(item->nativeDataItem()->c_field(), nullptr);

    // set datafield
    auto* oData = new Datafield{{newListScan("qVector", std::vector<double>({1, 2}))}};
    oData->setVector(std::vector<double>({3, 4}));
    item->nativeDataItem()->setDatafield(oData); // takes ownership of odata
    EXPECT_TRUE(item->hasNativeData());
    EXPECT_NE(item->nativeDataItem()->c_field(), nullptr);

    // call remove with existing output data
    item->removeNativeData();
    EXPECT_NE(item->nativeDataItem(), nullptr);
    EXPECT_FALSE(item->hasNativeData());
    EXPECT_EQ(item->nativeDataItem()->c_field(), nullptr);
}

TEST(TestRealModel, saveNonXMLData)
{
    if (!QFile::exists(dir_test_real_model))
        QDir(".").mkdir(dir_test_real_model);

    const QString dir = dir_test_real_model + "/saveNonXMLData";
    UTest::GUI::create_dir(dir);

    const double value1(1.0), value2(2.0), value3(3.0);

    RealModel model;
    RealItem* item1 = UTest::GUI::createRealData("data1", model, value1);
    RealItem* item2 = UTest::GUI::createRealData("data2", model, value2);

    item1->updateDataFileName();
    item2->updateDataFileName();

    // do saving in the main thread
    item1->dataItem()->setSaveInBackground(false);
    item2->dataItem()->setSaveInBackground(false);

    // save first time
    model.writeDataFiles(dir);
    QTest::qSleep(10);

    // check existence of data on disk
    QString fname1 = dir + "/realdata_data1_0.int.gz";
    QString fname2 = dir + "/realdata_data2_0.int.gz";
    EXPECT_TRUE(QFile::exists(fname1));
    EXPECT_TRUE(QFile::exists(fname2));

    // read data from disk, checking it is the same
    std::unique_ptr<Datafield> dataOnDisk1(IO::readData2D(fname1.toStdString()));
    std::unique_ptr<Datafield> dataOnDisk2(IO::readData2D(fname2.toStdString()));
    EXPECT_TRUE(UTest::GUI::isTheSame(*dataOnDisk1, *item1->dataItem()->c_field()));
    EXPECT_TRUE(UTest::GUI::isTheSame(*dataOnDisk2, *item2->dataItem()->c_field()));

    // modify data and save the project
    item2->setDatafield(UTest::GUI::createData(value3).release());
    model.writeDataFiles(dir);
    QTest::qSleep(10);

    EXPECT_TRUE(UTest::GUI::isTheSame(*dataOnDisk1, *item1->dataItem()->c_field()));
    EXPECT_FALSE(UTest::GUI::isTheSame(*dataOnDisk2, *item2->dataItem()->c_field()));

    // check that data on disk has changed
    dataOnDisk2.reset(IO::readData2D(fname2.toStdString()));
    EXPECT_TRUE(UTest::GUI::isTheSame(*dataOnDisk2, *item2->dataItem()->c_field()));

    // rename RealData and check that file on disk changed the name
    item2->setRealItemName("data2new");
    item2->updateDataFileName();
    model.writeDataFiles(dir);
    QTest::qSleep(10);

    QString fname2new = "./" + dir + "/realdata_data2new_0.int.gz";
    EXPECT_TRUE(QFile::exists(fname2new));
    EXPECT_TRUE(UTest::GUI::isTheSame(fname2new, *item2->dataItem()->c_field()));

    // check that file with old name was removed
    EXPECT_FALSE(QFile::exists(fname2));
}

TEST(TestRealModel, saveXMLData)
{
    if (!QFile::exists(dir_test_real_model))
        QDir(".").mkdir(dir_test_real_model);

    const QString dir = dir_test_real_model + "/saveXMLData";
    UTest::GUI::create_dir(dir);

    InstrumentModel instrument_model;
    RealModel model1(&instrument_model);

    // add specular RealItems with non-default parameters
    auto* spec1 = model1.insertDataItem(1);
    spec1->setRealItemName("spec1");
    spec1->setNativeFileName("spec1_file_name");
    spec1->setNativeDataUnits("Radians");
    spec1->setPresentationType("Reflectometry (Graph only)");
    spec1->linkToInstrument(instrument_model.addInstrumentItem<SpecularInstrumentItem>());

    // add second specular RealItem
    auto* spec2 = model1.insertDataItem(1);
    spec2->setRealItemName("spec2");

    // add 2D RealItems with non-default parameters
    auto* intensity1 = model1.insertDataItem(2);
    auto* intensity2 = model1.insertDataItem(2);
    intensity1->setRealItemName("GISAS");
    intensity2->setRealItemName("OffSpec");
    intensity1->linkToInstrument(instrument_model.addInstrumentItem<GISASInstrumentItem>());
    intensity2->linkToInstrument(instrument_model.addInstrumentItem<OffspecInstrumentItem>());

    // set non-default top-level model parameters
    model1.setSelectedRank(2);
    model1.setSelectedIndex(1);

    // save data to project files
    const QString file1 = dir + "/Project_1.ba";
    const QString file2 = dir + "/Project_2.ba";

    // write changed model to disk
    const QString tag = "RealModel";
    UTest::GUI::writeXMLFile<RealModel>(file1, model1, tag);
    EXPECT_TRUE(QFile::exists(file1));

    // read data to the second model
    RealModel model2(&instrument_model);
    UTest::GUI::readXMLFile<RealModel>(file1, model2, tag);

    // write the second model back to disk
    UTest::GUI::writeXMLFile<RealModel>(file2, model2, tag);
    EXPECT_TRUE(QFile::exists(file2));

    // compare files byte-by-byte
    std::ifstream f1(file1.toStdString());
    std::ifstream f2(file2.toStdString());

    typedef std::istreambuf_iterator<char> it;

    std::vector<char> contents1((it(f1)), it());
    std::vector<char> contents2((it(f2)), it());

    EXPECT_EQ(contents1, contents2);
}
