#include <cppunit/extensions/HelperMacros.h>

#include "edit.h"

#include <kinstance.h>
#include <kdebug.h>

class PresetTest : public CppUnit::TestFixture
{
  CPPUNIT_TEST_SUITE( PresetTest );
  CPPUNIT_TEST( testOperatorEqual );
  CPPUNIT_TEST( testOperatorNotEqual );
  CPPUNIT_TEST_SUITE_END();

public:
  void setUp()
  {
  }

  void tearDown()
  {
  }

  void testOperatorEqual()
  {
    Sonik::Edit::Preset a;
    Sonik::Edit::Preset b;

    a["param1"] = 1.0;
    b["param1"] = 2.0;
    CPPUNIT_ASSERT(!(a == b));
    b["param1"] = 1.0;
    CPPUNIT_ASSERT(a == b);

    a["param2"] = 2.0;
    CPPUNIT_ASSERT(!(a == b));
    b["param2"] = 2.0;
    CPPUNIT_ASSERT(a == b);

    a.erase("param1");
    CPPUNIT_ASSERT(!(a == b));
    b.erase("param1");
    CPPUNIT_ASSERT(a == b);
  }

  void testOperatorNotEqual()
  {
    Sonik::Edit::Preset a;
    Sonik::Edit::Preset b;

    a["param1"] = 1.0;
    b["param1"] = 2.0;
    CPPUNIT_ASSERT(a != b);
    b["param1"] = 1.0;
    CPPUNIT_ASSERT(!(a != b));

    a["param2"] = 2.0;
    CPPUNIT_ASSERT(a != b);
    b["param2"] = 2.0;
    CPPUNIT_ASSERT(!(a != b));

    a.erase("param1");
    CPPUNIT_ASSERT(a != b);
    b.erase("param1");
    CPPUNIT_ASSERT(!(a != b));
  }
};

CPPUNIT_TEST_SUITE_REGISTRATION(PresetTest);

class TestPresetManager : public Sonik::Edit::PresetManager
{
public:
  TestPresetManager(const QString& pluginId, KInstance* instance)
    : PresetManager(pluginId, instance)
  {
  }

  QStringList presetFiles()
  {
    return PresetManager::presetFiles();
  }

  QString userFile()
  {
    return PresetManager::userFile();
  }

  void loadPluginPresetFile(const QString& filename,
                            Presets& pluginPresets)
  {
    PresetManager::loadPresetFile(filename, pluginPresets);
  }

  void savePluginPresetFile(const QString& filename,
                            const Presets& pluginPresets,
                            const QStringList& deletedPresets)
  {
    PresetManager::savePresetFile(filename, pluginPresets, deletedPresets);
  }

  void generateDeltas(const Presets& current,
                      const Presets& base,
                      Presets&       changed,
                      QStringList&   deleted)
  {
    PresetManager::generateDeltas(current, base,
                                  changed, deleted);
  }

  // TODO: override file selection fns to test load/save
};

class PresetManagerTest : public CppUnit::TestFixture
{
  CPPUNIT_TEST_SUITE( PresetManagerTest );
  CPPUNIT_TEST( testAccess );
  CPPUNIT_TEST( testLoadFile );
  CPPUNIT_TEST( testSaveFile );
  CPPUNIT_TEST_SUITE_END();

public:
  void setUp()
  {
    mInstance = new KInstance("presetTestPlugin");
    mPresetManager = new TestPresetManager("testPreset", mInstance);

    QString srcPath(getenv("SONIK_SRC"));
    CPPUNIT_ASSERT(!srcPath.isNull());
    mPresetFiles.push_back(srcPath + "/part/test/test1.preset");
    mPresetFiles.push_back(srcPath + "/part/test/test2.preset");
    mPresetFiles.push_back(srcPath + "/part/test/test3.preset");
  }

  void tearDown()
  {
    mPresetFiles.clear();

    delete mPresetManager;
    delete mInstance;
  }

  void testAccess()
  {
    CPPUNIT_ASSERT_EQUAL(0U, mPresetManager->presetNames().size());

    Sonik::Edit::Preset preset = mPresetManager->preset("testPreset");
    preset["param1"] = 1.0;

    Sonik::Edit::Preset preset2 = mPresetManager->preset("testPreset");
    CPPUNIT_ASSERT(preset != preset2);

    mPresetManager->setPreset("testPreset", preset);
    preset2 = mPresetManager->preset("testPreset");
    CPPUNIT_ASSERT(preset == preset2);
  }

  void testLoadFile()
  {
    TestPresetManager::Presets presets;
    TestPresetManager::Presets::const_iterator preset;
    Sonik::Edit::Preset::const_iterator param;

    mPresetManager->loadPluginPresetFile(mPresetFiles[0], presets);

    CPPUNIT_ASSERT_EQUAL(2u, presets.size());

    preset = presets.find("testPreset1");
    CPPUNIT_ASSERT(preset != presets.end());
    CPPUNIT_ASSERT_EQUAL(2u, (*preset).size());
    param = (*preset).find("param1");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, (*param), 0.0001);
    param = (*preset).find("param2");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(2.5, (*param), 0.0001);

    preset = presets.find("testPreset2");
    CPPUNIT_ASSERT(preset != presets.end());
    CPPUNIT_ASSERT_EQUAL(3u, (*preset).size());
    param = (*preset).find("param1");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(98765.4, (*param), 0.01);
    param = (*preset).find("param2");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(300, (*param), 0.0001);
    param = (*preset).find("param3");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(-10, (*param), 0.0001);

    // test overlaying
    mPresetManager->loadPluginPresetFile(mPresetFiles[1], presets);

    CPPUNIT_ASSERT_EQUAL(3u, presets.size());

    preset = presets.find("testPreset1");
    CPPUNIT_ASSERT(preset != presets.end());
    CPPUNIT_ASSERT_EQUAL(2u, (*preset).size());
    param = (*preset).find("param1");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(11.0, (*param), 0.0001);
    param = (*preset).find("param2");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0, (*param), 0.0001);

    // testPreset2 is unchanged
    preset = presets.find("testPreset2");
    CPPUNIT_ASSERT(preset != presets.end());
    CPPUNIT_ASSERT_EQUAL(3u, (*preset).size());
    param = (*preset).find("param1");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(98765.4, (*param), 0.01);
    param = (*preset).find("param2");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(300, (*param), 0.0001);
    param = (*preset).find("param3");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(-10, (*param), 0.0001);

    preset = presets.find("testPreset3");
    CPPUNIT_ASSERT(preset != presets.end());
    CPPUNIT_ASSERT_EQUAL(3u, (*preset).size());
    param = (*preset).find("param1");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(123.456, (*param), 0.0001);
    param = (*preset).find("param2");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(20, (*param), 0.0001);
    param = (*preset).find("param3");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.123456, (*param), 0.0000001);

    // deletion
    mPresetManager->loadPluginPresetFile(mPresetFiles[2], presets);
    CPPUNIT_ASSERT_EQUAL(2u, presets.size());
    CPPUNIT_ASSERT(presets.find("testPreset1") != presets.end());
    CPPUNIT_ASSERT(presets.find("testPreset3") != presets.end());
  }

  void testSaveFile()
  {
    TestPresetManager::Presets presets;

    QString srcPath(getenv("SONIK_SRC"));
    CPPUNIT_ASSERT(!srcPath.isNull());

    Sonik::Edit::Preset p;
    p["param1"] = 1.0;
    p["param2"] = 2.5;
    presets["testPreset1"] = p;
    p["param1"] = 98765.4;
    p["param2"] = 300;
    p["param3"] = -10;
    presets["testPreset2"] = p;

    QStringList deletions;
    mPresetManager->savePluginPresetFile("out.preset", presets, deletions);

    // diff against expected
    CPPUNIT_ASSERT_EQUAL(0, system(QString("diff %1 %2").
                                   arg(mPresetFiles[0]).
                                   arg("out.preset")));

    // check delta algorithms
    TestPresetManager::Presets basePresets = presets;

    presets["testPreset1"]["param1"] = 11.0;
    presets["testPreset1"]["param2"] = 5.0;
    p["param1"] = 123.456;
    p["param2"] = 20;
    p["param3"] = 0.123456;
    presets["testPreset3"] = p;

    TestPresetManager::Presets changedPresets;
    QStringList deletedPresets;

    mPresetManager->generateDeltas(presets, basePresets,
                                   changedPresets, deletedPresets);

    TestPresetManager::Presets::const_iterator preset;
    Sonik::Edit::Preset::const_iterator param;

    CPPUNIT_ASSERT_EQUAL(2u, changedPresets.size());

    preset = changedPresets.find("testPreset1");
    CPPUNIT_ASSERT(preset != changedPresets.end());
    CPPUNIT_ASSERT_EQUAL(2u, (*preset).size());
    param = (*preset).find("param1");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(11.0, (*param), 0.0001);
    param = (*preset).find("param2");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0, (*param), 0.0001);

    preset = changedPresets.find("testPreset3");
    CPPUNIT_ASSERT(preset != changedPresets.end());
    CPPUNIT_ASSERT_EQUAL(3u, (*preset).size());
    param = (*preset).find("param1");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(123.456, (*param), 0.0001);
    param = (*preset).find("param2");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(20, (*param), 0.0001);
    param = (*preset).find("param3");
    CPPUNIT_ASSERT(param != (*preset).end());
    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.123456, (*param), 0.0000001);

    CPPUNIT_ASSERT_EQUAL(0u, deletedPresets.size());

    mPresetManager->savePluginPresetFile("out.preset",
                                         changedPresets, deletedPresets);

    CPPUNIT_ASSERT_EQUAL(0, system(QString("diff %1 %2").
                                   arg(mPresetFiles[1]).
                                   arg("out.preset")));

    // deletions
    basePresets = presets;
    presets.erase("testPreset2");

    mPresetManager->generateDeltas(presets, basePresets,
                                   changedPresets, deletedPresets);
    mPresetManager->savePluginPresetFile("out.preset",
                                         changedPresets, deletedPresets);

    CPPUNIT_ASSERT_EQUAL(0, system(QString("diff %1 %2").
                                   arg(mPresetFiles[2]).
                                   arg("out.preset")));
  }

private:
  KInstance*         mInstance;
  TestPresetManager* mPresetManager;
  QStringList        mPresetFiles;
};

CPPUNIT_TEST_SUITE_REGISTRATION(PresetManagerTest);
