text:
#include <moja/flint/configuration/externalvariable.h>
#include <moja/flint/configuration/json2configurationprovider.h>
#include <moja/flint/configuration/localdomain.h>
#include <moja/flint/configuration/module.h>
#include <moja/flint/configuration/pool.h>
#include <moja/flint/configuration/provider.h>
#include <moja/flint/configuration/spinup.h>
#include <moja/flint/configuration/transform.h>
#include <moja/flint/configuration/variable.h>

#include <moja/datetime.h>

#include <Poco/FileStream.h>
#include <Poco/TemporaryFile.h>

#include <boost/format.hpp>
#include <boost/test/unit_test.hpp>

using moja::flint::configuration::JSON2ConfigurationProvider;
using moja::flint::configuration::LibraryType;
using moja::flint::configuration::LocalDomainType;
using Poco::FileOutputStream;
using Poco::TemporaryFile;

template <class T>
struct EnumMapping {
   std::string asString;
   T asEnum;
};

struct Library2ConfigurationTestsFixture {
   const std::string configTemplate =
       "{\n"
       "    \"LocalDomain\": {\n"
       "%1%\n"
       "    },\n"
       "    \"Libraries\": {\n"
       "%2%\n"
       "    },\n"
       "    \"Spinup\": {\n"
       "%3%\n"
       "    },\n"
       "    \"SpinupModules\": {\n"
       "%4%\n"
       "    },\n"
       "    \"SpinupVariables\": {\n"
       "%5%\n"
       "    },\n"
       "    \"Pools\": {\n"
       "%6%\n"
       "    },\n"
       "    \"Variables\": {\n"
       "%7%\n"
       "    },\n"
       "    \"Modules\": {\n"
       "%8%\n"
       "    }\n"
       "}";

   const std::string configTemplate2 =  // Pools in Array
       "{\n"
       "    \"LocalDomain\": {\n"
       "%1%\n"
       "    },\n"
       "    \"Libraries\": {\n"
       "%2%\n"
       "    },\n"
       "    \"Spinup\": {\n"
       "%3%\n"
       "    },\n"
       "    \"SpinupModules\": {\n"
       "%4%\n"
       "    },\n"
       "    \"SpinupVariables\": {\n"
       "%5%\n"
       "    },\n"
       "    \"Pools\": [\n"
       "%6%\n"
       "    ],\n"
       "    \"Variables\": {\n"
       "%7%\n"
       "    },\n"
       "    \"Modules\": {\n"
       "%8%\n"
       "    }\n"
       "}";

   const std::string configTemplate3 =  // Variables in array
       "{\n"
       "    \"LocalDomain\": {\n"
       "%1%\n"
       "    },\n"
       "    \"Libraries\": {\n"
       "%2%\n"
       "    },\n"
       "    \"Spinup\": {\n"
       "%3%\n"
       "    },\n"
       "    \"SpinupModules\": {\n"
       "%4%\n"
       "    },\n"
       "    \"SpinupVariables\": {\n"
       "%5%\n"
       "    },\n"
       "    \"Pools\": {\n"
       "%6%\n"
       "    },\n"
       "    \"Variables\": [\n"
       "%7%\n"
       "    ],\n"
       "    \"Modules\": {\n"
       "%8%\n"
       "    }\n"
       "}";

   const std::string configProviderTemplate =
       "{\n"
       "    \"Providers\": {\n"
       "%1%\n"
       "    }\n"
       "}";

   const std::string validLocalDomain =
       "        \"type\": \"spatial_tiled\",\n"
       "        \"start_date\": \"2001/01/01\",\n"
       "        \"end_date\": \"2013/12/31\",\n"
       "        \"sequencer_library\": \"internal.flint\",\n"
       "        \"sequencer\": \"CalendarAndEventSequencer\",\n"
       //"        \"landunitcontroller\": \"Eigen\",\n"
       "        \"simulateLandUnit\": \"Dummy\",\n"
       "        \"landUnitBuildSuccess\": \"Dummy\",\n"
       "        \"landscape\": {\n"
       "            \"provider\": \"Test\",\n"
       "            \"tile_size_x\": 1.0,\n"
       "            \"tile_size_y\": 1.0,\n"
       "            \"x_pixels\": 4000,\n"
       "            \"y_pixels\": 4000,\n"
       "            \"tiles\": [\n"
       "                { \"x\": 34, \"y\": 0 }\n"
       "            ]\n"
       "        }";

   const std::string validLibraries = "        \"moja.modules.sleek\": \"external\"";

   const std::string validLibrariesCBM = "        \"moja.modules.cbm\": \"external\"";

   const std::string validSpinupEnabled =
       "        \"enabled\": true,\n"
       "        \"simulateLandUnit\": \"Dummy\",\n"
       "        \"landUnitBuildSuccess\": \"Dummy\",\n"
       "        \"sequencer_library\": \"internal.flint\",\n"
       "        \"sequencer\": \"CalendarAndEventSequencer\"\n";

   const std::string validSpinupDisabled = "        \"enabled\": false";

   const std::string validSpinupVariables = "        \"TestSpinUpVariable\": 1";

   const std::string validSpinupModules =
       "        \"CalendarAndEventSequencer\" : {\n"
       "            \"library\": \"moja.modules.sleek\",\n"
       "            \"order\": 1,\n"
       "            \"create_new\": true\n"
       "        }";

   const std::string validSpinupModulesCBM =
       "        \"CBMSequencer\" : {\n"
       "            \"library\": \"moja.modules.cbm\",\n"
       "            \"order\": 2,\n"
       "            \"create_new\": true\n"
       "        }";

   const std::string validProviders =
       "        \"raster\" : {\n"
       "            \"type\": \"raster_tiled\"\n"
       "        }";

   const std::string validPools = "        \"atmosphere\": 100.0";

   const std::string validPoolsPeastland = "        \"Catotelm_O\": 10.0";

   const std::string validPoolsArray =
       "        { \"C_atmosphere\": 101.0 },"
       "        { \"A_atmosphere\": 102.0 },"
       "        { \"B_atmosphere\": 103.0 }";

   const std::string validPoolsArray2 =
       "        {\n"
       "            \"name\": \"C_atmosphere\",\n"
       "            \"value\": 100.0,\n"
       "            \"description\": \"testDesc\",\n"
       "            \"units\": \"testUnits\",\n"
       "            \"scale\": 1.0,\n"
       "            \"order\": 1\n"
       "		 },\n"
       "        {\n"
       "            \"name\": \"A_atmosphere\",\n"
       "            \"value\": 100.0,\n"
       "            \"description\": \"testDesc\",\n"
       "            \"units\": \"testUnits\",\n"
       "            \"scale\": 1.0,\n"
       "            \"order\": 1\n"
       "		 },\n"
       "        {\n"
       "            \"name\": \"B_atmosphere\",\n"
       "            \"value\": 100.0,\n"
       "            \"description\": \"testDesc\",\n"
       "            \"units\": \"testUnits\",\n"
       "            \"scale\": 1.0,\n"
       "            \"order\": 1\n"
       "		 }\n";

   const std::string validPoolsArray3 =
       "        \"C_atmosphere\","
       "        \"A_atmosphere\","
       "        \"B_atmosphere\"";

   const std::string validVariables = "        \"localDomainId\": 1";

   const std::string validVariablesD = "        \"D_variable\": 102.2";

   const std::string validVariablesArray1 =
       "        { \"C_variable\": 101.0 },"
       "        { \"A_variable\": 102.0 },"
       "        { \"B_variable\": 103.0 }";

   const std::string validVariablesArray2 = "        { \"localDomainId\": 1 }";

   const std::string validVariablesArray3 = "        \"localDomainId\"";

   const std::string validModules =
       "        \"CalendarAndEventSequencer\" : {\n"
       "            \"library\": \"moja.modules.sleek\",\n"
       "            \"order\": 1,\n"
       "            \"settings\": { }\n"
       "        }";

   std::shared_ptr<TemporaryFile> writeConfigFile(const std::string& confTemplate, const std::string& localDomain,
                                                  const std::string& libraries, const std::string& spinup,
                                                  const std::string& spinupModules, const std::string& spinupVariables,
                                                  const std::string& pools, const std::string& variables,
                                                  const std::string& modules) const {
      auto configFile = std::make_shared<TemporaryFile>();
      if (configFile->exists()) {
         configFile->remove();
      }

      configFile->createFile();
      FileOutputStream configWriter{{configFile->path()}};
      configWriter << boost::format(confTemplate) % localDomain % libraries % spinup % spinupModules % spinupVariables %
                          pools % variables % modules;

      configWriter.close();
      return configFile;
   }

   std::shared_ptr<TemporaryFile> writeProviderConfigFile(const std::string& providers) const {
      auto configFile = std::make_shared<TemporaryFile>();
      if (configFile->exists()) {
         configFile->remove();
      }

      configFile->createFile();
      FileOutputStream configWriter{{configFile->path()}};
      configWriter << boost::format(configProviderTemplate) % providers;
      configWriter.close();
      return configFile;
   }
};

BOOST_FIXTURE_TEST_SUITE(Library2ConfigurationTests, Library2ConfigurationTestsFixture);

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_ConstructorThrowsExceptionIfConfigFileNotFound) {
   BOOST_CHECK_THROW(JSON2ConfigurationProvider({"notfound.json", "notfound.json"}, {"notfound.json", "notfound.json"}),
                     std::runtime_error);
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedConfiguration) {
   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, validVariables, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   BOOST_CHECK_EQUAL(config->startDate().year(), 2001);
   BOOST_CHECK_EQUAL(config->startDate().month(), 1);
   BOOST_CHECK_EQUAL(config->startDate().day(), 1);
   BOOST_CHECK_EQUAL(config->endDate().year(), 2013);
   BOOST_CHECK_EQUAL(config->endDate().month(), 12);
   BOOST_CHECK_EQUAL(config->endDate().day(), 31);
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedConfiguration2_PoolArray) {
   auto configFile =
       writeConfigFile(configTemplate2, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPoolsArray, validVariables, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();

   BOOST_CHECK_EQUAL(config->startDate().year(), 2001);
   BOOST_CHECK_EQUAL(config->startDate().month(), 1);
   BOOST_CHECK_EQUAL(config->startDate().day(), 1);
   BOOST_CHECK_EQUAL(config->endDate().year(), 2013);
   BOOST_CHECK_EQUAL(config->endDate().month(), 12);
   BOOST_CHECK_EQUAL(config->endDate().day(), 31);

   // Check order of Pools
   auto pools = config->pools();

   auto pool01 = pools[0];
   auto pool02 = pools[1];
   auto pool03 = pools[2];

   BOOST_CHECK_EQUAL(pool01->name(), "C_atmosphere");
   BOOST_CHECK_EQUAL(pool02->name(), "A_atmosphere");
   BOOST_CHECK_EQUAL(pool03->name(), "B_atmosphere");
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedConfiguration3_PoolArrayObjects) {
   auto configFile =
       writeConfigFile(configTemplate2, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPoolsArray2, validVariables, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   BOOST_CHECK_EQUAL(config->startDate().year(), 2001);
   BOOST_CHECK_EQUAL(config->startDate().month(), 1);
   BOOST_CHECK_EQUAL(config->startDate().day(), 1);
   BOOST_CHECK_EQUAL(config->endDate().year(), 2013);
   BOOST_CHECK_EQUAL(config->endDate().month(), 12);
   BOOST_CHECK_EQUAL(config->endDate().day(), 31);

   // Check order of Pools
   auto pools = config->pools();

   auto pool01 = pools[0];
   auto pool02 = pools[1];
   auto pool03 = pools[2];

   BOOST_CHECK_EQUAL(pool01->name(), "C_atmosphere");
   BOOST_CHECK_EQUAL(pool02->name(), "A_atmosphere");
   BOOST_CHECK_EQUAL(pool03->name(), "B_atmosphere");
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedConfiguration4_PoolArrayObjects) {
   auto configFile =
       writeConfigFile(configTemplate2, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPoolsArray3, validVariables, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   BOOST_CHECK_EQUAL(config->startDate().year(), 2001);
   BOOST_CHECK_EQUAL(config->startDate().month(), 1);
   BOOST_CHECK_EQUAL(config->startDate().day(), 1);
   BOOST_CHECK_EQUAL(config->endDate().year(), 2013);
   BOOST_CHECK_EQUAL(config->endDate().month(), 12);
   BOOST_CHECK_EQUAL(config->endDate().day(), 31);

   // Check order of Pools
   auto pools = config->pools();

   auto pool01 = pools[0];
   auto pool02 = pools[1];
   auto pool03 = pools[2];

   BOOST_CHECK_EQUAL(pool01->name(), "C_atmosphere");
   BOOST_CHECK_EQUAL(pool02->name(), "A_atmosphere");
   BOOST_CHECK_EQUAL(pool03->name(), "B_atmosphere");
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedLocalDomainConfiguration) {
   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, validVariables, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto localDomain = config->localDomain();

   auto expectedType = LocalDomainType::SpatialTiled;
   auto actualType = localDomain->type();
   BOOST_CHECK(expectedType == actualType);

   auto expectedSequencer = "CalendarAndEventSequencer";
   auto actualSequencer = localDomain->sequencer();
   BOOST_CHECK_EQUAL(expectedSequencer, actualSequencer);

   BOOST_CHECK(localDomain->landscapeObject()->iterationLandscapeTiles()->tileSizeX() == 1.0);
   BOOST_CHECK(localDomain->landscapeObject()->iterationLandscapeTiles()->tileSizeY() == 1.0);
   BOOST_CHECK(localDomain->landscapeObject()->iterationLandscapeTiles()->xPixels() == 4000);
   BOOST_CHECK(localDomain->landscapeObject()->iterationLandscapeTiles()->yPixels() == 4000);

   auto& tiles = localDomain->landscapeObject()->iterationLandscapeTiles()->landscapeTiles();
   auto tile = tiles[0];
   BOOST_CHECK(tile.xIndex() == 34);
   BOOST_CHECK(tile.yIndex() == 0);
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedProviderConfiguration) {
   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, validVariables, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto dataProvider = config->providers()[0];
   BOOST_CHECK_EQUAL(dataProvider->name(), "raster");
   auto dataProviderSettings = dataProvider->settings();
   BOOST_CHECK(dataProviderSettings["type"].convert<std::string>() == "raster_tiled");
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedPoolConfiguration) {
   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, validVariables, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto pool = config->pools()[0];
   BOOST_CHECK_EQUAL(pool->name(), "atmosphere");
   BOOST_CHECK_EQUAL(pool->initValue(), 100.0);
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedInternalVariableConfiguration) {
   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, validVariables, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto variable = config->variables()[0];
   BOOST_CHECK_EQUAL(variable->name(), "localDomainId");
   BOOST_CHECK(variable->value() == 1);
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedModuleConfiguration) {
   auto moduleConfig =
       "\"testmodule\": {\n"
       "    \"library\": \"moja.modules.sleek\",\n"
       "    \"order\": 1,\n"
       "    \"settings\": {\n"
       "        \"foo\": \"bar\"\n"
       "    }\n"
       "}";

   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, validVariables, moduleConfig);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto module = config->modules()[0];
   BOOST_CHECK_EQUAL(module->name(), "testmodule");
   auto settings = module->settings();
   BOOST_CHECK(settings["foo"].convert<std::string>() == "bar");
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedModuleConfiguration2) {
   auto moduleConfig =
       "\"testmoduleNotEnabled\": {\n"
       "    \"enabled\": false,\n"
       "    \"library\": \"moja.modules.sleek\",\n"
       "    \"order\": 1,\n"
       "    \"settings\": {\n"
       "        \"foo\": \"bar\"\n"
       "    }\n"
       "},\n"
       "\"testmodule\": {\n"
       "    \"enabled\": true,\n"
       "    \"library\": \"moja.modules.sleek\",\n"
       "    \"order\": 1,\n"
       "    \"settings\": {\n"
       "        \"foo\": \"bar\"\n"
       "    }\n"
       "}";

   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, validVariables, moduleConfig);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto module = config->modules()[0];
   BOOST_CHECK_EQUAL(module->name(), "testmodule");
   auto settings = module->settings();
   BOOST_CHECK(settings["foo"].convert<std::string>() == "bar");
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedModuleConfiguration3) {
   auto moduleConfig =
       "\"testmoduleEnabled\": {\n"
       "    \"enabled\": true,\n"
       "    \"library\": \"moja.modules.sleek\",\n"
       "    \"order\": 1,\n"
       "    \"settings\": {\n"
       "        \"foo\": \"bar\"\n"
       "    }\n"
       "},\n"
       "\"testmodule\": {\n"
       "    \"enabled\": false,\n"
       "    \"library\": \"moja.modules.sleek\",\n"
       "    \"order\": 1,\n"
       "    \"settings\": {\n"
       "        \"foo\": \"bar\"\n"
       "    }\n"
       "}";

   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, validVariables, moduleConfig);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto module = config->modules()[0];
   BOOST_CHECK_EQUAL(module->name(), "testmoduleEnabled");
   auto settings = module->settings();
   BOOST_CHECK(settings["foo"].convert<std::string>() == "bar");
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedModuleConfiguration4) {
   auto moduleConfig =
       "\"testmoduleNotEnabled\": {\n"
       "    \"enabled\": false,\n"
       "    \"library\": \"moja.modules.sleek\",\n"
       "    \"order\": 1,\n"
       "    \"settings\": {\n"
       "        \"foo\": \"bar\"\n"
       "    }\n"
       "},\n"
       "\"testmodule\": {\n"
       "    \"enabled\": false,\n"
       "    \"library\": \"moja.modules.sleek\",\n"
       "    \"order\": 1,\n"
       "    \"settings\": {\n"
       "        \"foo\": \"bar\"\n"
       "    }\n"
       "}";

   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, validVariables, moduleConfig);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   BOOST_CHECK_EQUAL(config->modules().size(), 0);  // all modules were set enabled = false
}

BOOST_AUTO_TEST_CASE(
    flint_configuration_JSON2ConfigurationProvider_BuildsExpectedMultiValueInternalVariableConfiguration) {
   auto variableConfig =
       "\"soil\": {\n"
       "    \"fracBlah\": 0.32,\n"
       "    \"fracWhatevs\": 0.54\n"
       "}";

   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, variableConfig, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto variable = config->variables()[0];
   BOOST_CHECK_EQUAL(variable->name(), "soil");
   auto variableTable = variable->value();
   BOOST_CHECK(variableTable["fracBlah"] == 0.32);
   BOOST_CHECK(variableTable["fracWhatevs"] == 0.54);
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedVectorInternalVariableConfiguration) {
   auto variableConfig = "\"stuff\": [1, 7]\n";
   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, variableConfig, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto variable = config->variables()[0];
   BOOST_CHECK_EQUAL(variable->name(), "stuff");
   auto values = variable->value();
   BOOST_CHECK(values[0] == 1);
   BOOST_CHECK(values[1] == 7);
}

BOOST_AUTO_TEST_CASE(
    flint_configuration_JSON2ConfigurationProvider_BuildsExpectedVectorOfStructInternalVariableConfiguration) {
   auto variableConfig =
       "\"stuff\": [\n"
       "    {\n"
       "        \"thing\": 1\n"
       "    },\n"
       "    {\n"
       "        \"thing\": 2\n"
       "    }\n"
       "]";

   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, variableConfig, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto variable = config->variables()[0];
   BOOST_CHECK_EQUAL(variable->name(), "stuff");
   auto values = variable->value();
   BOOST_CHECK(values.isVector());

   int expectedValue = 1;
   for (auto row : values) {
      BOOST_CHECK(row["thing"].convert<int>() == expectedValue);
      expectedValue++;
   }
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedDateVariable) {
   auto variableConfig = "\"testDate\": { \"$date\": \"2000/10/25\" }\n";

   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, variableConfig, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto variable = config->variables()[0];
   BOOST_CHECK_EQUAL(variable->name(), "testDate");
   const auto& test = variable->value().extract<moja::DateTime>();
   BOOST_CHECK_EQUAL(test.year(), 2000);
   BOOST_CHECK_EQUAL(test.month(), 10);
   BOOST_CHECK_EQUAL(test.day(), 25);
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedDateArrayVariable) {
   auto variableConfig =
       "\"testDateArray\": [\n"
       "    { \"$date\": \"2000/10/25\" },\n"
       "    { \"$date\": \"2001/02/01\" },\n"
       "    { \"$date\": \"2002/12/05\" },\n"
       "    { \"$date\": \"2003/11/15\" }\n"
       "]";

   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, variableConfig, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto variable = config->variables()[0];

   BOOST_CHECK_EQUAL(variable->name(), "testDateArray");
   auto dynamicArray = variable->value().extract<moja::DynamicVector>();

   for (auto& dateDynamic : dynamicArray) {
      moja::DateTime test = dateDynamic.extract<moja::DateTime>();
   }
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedDateVariable_inArray) {
   auto variableConfig = "{ \"testDate\": { \"$date\": \"2000/10/25\" }}\n";

   auto configFile =
       writeConfigFile(configTemplate3, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, variableConfig, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto variable = config->variables()[0];
   BOOST_CHECK_EQUAL(variable->name(), "testDate");
   moja::DateTime test = variable->value().extract<moja::DateTime>();
   BOOST_CHECK_EQUAL(test.year(), 2000);
   BOOST_CHECK_EQUAL(test.month(), 10);
   BOOST_CHECK_EQUAL(test.day(), 25);
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedDateArrayVariable_inArray) {
   auto variableConfig =
       "{\"testDateArray\": [\n"
       "    { \"$date\": \"2000/10/25\" },\n"
       "    { \"$date\": \"2001/02/01\" },\n"
       "    { \"$date\": \"2002/12/05\" },\n"
       "    { \"$date\": \"2003/11/15\" }\n"
       "]}";

   auto configFile =
       writeConfigFile(configTemplate3, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, variableConfig, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto variable = config->variables()[0];

   BOOST_CHECK_EQUAL(variable->name(), "testDateArray");
   auto dynamicArray = variable->value().extract<moja::DynamicVector>();

   for (const auto& dateDynamic : dynamicArray) {
      moja::DateTime test = dateDynamic.extract<moja::DateTime>();
   }
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedConfiguration2_VariableArray) {
   auto configFile =
       writeConfigFile(configTemplate3, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, validVariablesArray1, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();

   // Check order of Variables
   auto variables = config->variables();

   auto variables01 = variables[0];
   auto variables02 = variables[1];
   auto variables03 = variables[2];

   BOOST_CHECK_EQUAL(variables01->name(), "C_variable");
   BOOST_CHECK_EQUAL(variables02->name(), "A_variable");
   BOOST_CHECK_EQUAL(variables03->name(), "B_variable");
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_CheckModuleOrderAfterLoadIsSameAsDefinitionInJSON) {
   const int moduleCount = 9;
   std::string moduleName[moduleCount] = {"TestModule0", "TestModule1", "TestModule2", "TestModule3", "AABB",
                                          "AAAA",        "ZZZZZZ",      "aaaaaaa",     "12345"};

   auto moduleConfig = "\"" + moduleName[0] +
                       "\" : {\n"
                       "    \"library\": \"moja.modules.sleek\",\n"
                       "    \"order\": 1,\n"
                       "    \"settings\": { }\n"
                       "},\n"
                       "\"" +
                       moduleName[1] +
                       "\" : {\n"
                       "    \"library\": \"moja.modules.sleek\",\n"
                       "    \"order\": 2,\n"
                       "    \"settings\": { }\n"
                       "},\n"
                       "\"" +
                       moduleName[2] +
                       "\" : {\n"
                       "    \"library\": \"moja.modules.sleek\",\n"
                       "    \"order\": 3,\n"
                       "    \"settings\": { }\n"
                       "},\n"
                       "\"" +
                       moduleName[3] +
                       "\" : {\n"
                       "    \"library\": \"moja.modules.sleek\",\n"
                       "    \"order\": 4,\n"
                       "    \"settings\": { }\n"
                       "},\n"
                       "\"" +
                       moduleName[4] +
                       "\" : {\n"
                       "    \"library\": \"moja.modules.sleek\",\n"
                       "    \"order\": 5,\n"
                       "    \"settings\": { }\n"
                       "},\n"
                       "\"" +
                       moduleName[5] +
                       "\" : {\n"
                       "    \"library\": \"moja.modules.sleek\",\n"
                       "    \"order\": 6\n"
                       "},\n"
                       "\"" +
                       moduleName[6] +
                       "\" : {\n"
                       "    \"library\": \"moja.modules.sleek\",\n"
                       "    \"order\": 7\n"
                       "},\n"
                       "\"" +
                       moduleName[7] +
                       "\" : {\n"
                       "    \"library\": \"moja.modules.sleek\",\n"
                       "    \"order\": 8\n"
                       "},\n"
                       "\"" +
                       moduleName[8] +
                       "\" : {\n"
                       "    \"library\": \"moja.modules.sleek\",\n"
                       "    \"order\": 9\n"
                       "}";

   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, validVariables, moduleConfig);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();
   auto modules = config->modules();

   BOOST_CHECK_EQUAL(modules.size(), moduleCount);

   for (int i = 0; i < moduleCount; i++) {
      auto module = modules[i];
      BOOST_CHECK_EQUAL(module->name(), moduleName[i]);
      BOOST_CHECK_EQUAL(module->order(), i + 1);
   }
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedSpinupConfiguration) {
   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupEnabled, validSpinupModules,
                       validSpinupVariables, validPools, validVariables, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);
   JSON2ConfigurationProvider provider{{configFile->path()}, {configProviderFile->path()}};
   auto config = provider.createConfiguration();

   auto variable = config->variables()[0];
   BOOST_CHECK_EQUAL(config->spinup()->enabled(), true);
   BOOST_CHECK_EQUAL(config->spinup()->sequencerName(), "CalendarAndEventSequencer");
   BOOST_CHECK_EQUAL(config->spinup()->sequencerLibrary(), "internal.flint");
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedConfigurationMergeOne) {
   auto moduleConfig =
       "		\"testmoduleEnabled\": {\n"
       "			\"enabled\": true,\n"
       "			\"library\": \"moja.modules.sleek\",\n"
       "			\"order\": 1,\n"
       "			\"settings\": {\n"
       "				\"foo1\": \"bar1\"\n"
       "			}\n"
       "		},\n"
       "		\"testmodule\": {\n"
       "			\"enabled\": true,\n"
       "			\"library\": \"moja.modules.cbm\",\n"
       "			\"order\": 2,\n"
       "			\"settings\": {\n"
       "				\"foo\": \"bar\"\n"
       "			}\n"
       "		}";

   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, validVariables, moduleConfig);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path(), configFile->path()}, {configProviderFile->path()}};

   auto config = provider.createConfiguration();

   auto moduesSize = config->modules().size();
   BOOST_CHECK_EQUAL(moduesSize, 2);

   auto module = config->modules()[0];
   BOOST_CHECK_EQUAL(module->name(), "testmoduleEnabled");
   auto settings = module->settings();
   BOOST_CHECK(settings["foo1"].convert<std::string>() == "bar1");
}

BOOST_AUTO_TEST_CASE(flint_configuration_JSON2ConfigurationProvider_BuildsExpectedConfigurationMergeTwo) {
   auto configFile =
       writeConfigFile(configTemplate, validLocalDomain, validLibraries, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPools, validVariables, validModules);

   auto configFile1 =
       writeConfigFile(configTemplate, validLocalDomain, validLibrariesCBM, validSpinupDisabled, validSpinupModules,
                       validSpinupVariables, validPoolsPeastland, validVariablesD, validModules);

   auto configProviderFile = writeProviderConfigFile(validProviders);

   JSON2ConfigurationProvider provider{{configFile->path(), configFile1->path()}, {configProviderFile->path()}};

   auto config = provider.createConfiguration();

   auto moduesSize = config->modules().size();
   BOOST_CHECK_EQUAL(moduesSize, 1);

   auto module = config->modules()[0];
   BOOST_CHECK_EQUAL(module->name(), "CalendarAndEventSequencer");

   auto variables = config->variables();
   auto var1Name = variables[0]->name();
   auto var2Name = variables[1]->name();
   BOOST_CHECK_EQUAL(var2Name, "localDomainId");
   BOOST_CHECK_EQUAL(var1Name, "D_variable");

   auto pools = config->pools();
   BOOST_CHECK_EQUAL(pools[0]->name(), "Catotelm_O");
   BOOST_CHECK_EQUAL(pools[1]->name(), "atmosphere");

   auto librarieSize = config->libraries().size();
   BOOST_CHECK_EQUAL(librarieSize, 2);
}

BOOST_AUTO_TEST_SUITE_END();

max_stars_repo_name: YevenLourance/FLINT

max_issues_repo_name: YevenLourance/FLINT

max_forks_repo_name: YevenLourance/FLINT


text:
#ifndef BOOST_SMART_PTR_DETAIL_SP_COUNTED_BASE_SOLARIS_HPP_INCLUDED
#define BOOST_SMART_PTR_DETAIL_SP_COUNTED_BASE_SOLARIS_HPP_INCLUDED

//
//  detail/sp_counted_base_solaris.hpp
//   based on: detail/sp_counted_base_w32.hpp
//
//  Copyright (c) 2001, 2002, 2003 Peter Dimov and Multi Media Ltd.
//  Copyright 2004-2005 Peter Dimov
//  Copyright 2006 Michael van der Westhuizen
//
//  Distributed under the Boost Software License, Version 1.0. (See
//  accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)
//
//
//  Lock-free algorithm by Alexander Terekhov
//
//  Thanks to Ben Hitchings for the #weak + (#shared != 0)
//  formulation
//

#include <boost/smart_ptr/detail/sp_typeinfo_.hpp>
#include <boost/config.hpp>
#include <atomic.h>

namespace boost
{

namespace detail
{

class BOOST_SYMBOL_VISIBLE sp_counted_base
{
private:

    sp_counted_base( sp_counted_base const & );
    sp_counted_base & operator= ( sp_counted_base const & );

    uint32_t use_count_;        // #shared
    uint32_t weak_count_;       // #weak + (#shared != 0)

public:

    sp_counted_base(): use_count_( 1 ), weak_count_( 1 )
    {
    }

    virtual ~sp_counted_base() // nothrow
    {
    }

    // dispose() is called when use_count_ drops to zero, to release
    // the resources managed by *this.

    virtual void dispose() = 0; // nothrow

    // destroy() is called when weak_count_ drops to zero.

    virtual void destroy() // nothrow
    {
        delete this;
    }

    virtual void * get_deleter( sp_typeinfo_ const & ti ) = 0;
    virtual void * get_local_deleter( sp_typeinfo_ const & ti ) = 0;
    virtual void * get_untyped_deleter() = 0;

    void add_ref_copy()
    {
        atomic_inc_32( &use_count_ );
    }

    bool add_ref_lock() // true on success
    {
        for( ;; )
        {
            uint32_t tmp = static_cast< uint32_t const volatile& >( use_count_ );
            if( tmp == 0 ) return false;
            if( atomic_cas_32( &use_count_, tmp, tmp + 1 ) == tmp ) return true;
        }
    }

    void release() // nothrow
    {
        if( atomic_dec_32_nv( &use_count_ ) == 0 )
        {
            dispose();
            weak_release();
        }
    }

    void weak_add_ref() // nothrow
    {
        atomic_inc_32( &weak_count_ );
    }

    void weak_release() // nothrow
    {
        if( atomic_dec_32_nv( &weak_count_ ) == 0 )
        {
            destroy();
        }
    }

    long use_count() const // nothrow
    {
        return static_cast<long const volatile &>( use_count_ );
    }
};

} // namespace detail

} // namespace boost

#endif  // #ifndef BOOST_SMART_PTR_DETAIL_SP_COUNTED_BASE_SOLARIS_HPP_INCLUDED

ext: hpp

max_stars_repo_name: cpp-pm/boost

max_issues_repo_name: cpp-pm/boost

max_forks_repo_name: cpp-pm/boost


text:
/// \file   quantity.hpp
///
/// \brief  A class used to enforce conversion without numerical error when
///         counting property, goods and other items.
///
/// \authors    Maarten P. Scholl
/// \date       2018-02-05
/// \copyright  Copyright 2017-2019 The Institute for New Economic Thinking,
///             Oxford Martin School, University of Oxford
///
///             Licensed under the Apache License, Version 2.0 (the "License");
///             you may not use this file except in compliance with the License.
///             You may obtain a copy of the License at
///
///                 http://www.apache.org/licenses/LICENSE-2.0
///
///             Unless required by applicable law or agreed to in writing,
///             software distributed under the License is distributed on an "AS
///             IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
///             express or implied. See the License for the specific language
///             governing permissions and limitations under the License.
///
///             You may obtain instructions to fulfill the attribution
///             requirements in CITATION.cff
///
#ifndef ESL_AMOUNT_HPP
#define ESL_AMOUNT_HPP

#include <array>
#include <cstdint>
#include <vector>

#include <iostream>

#include <boost/serialization/nvp.hpp>

#ifndef EXCEPTION_ON_BASIS_CHANGE
#define EXCEPTION_ON_BASIS_CHANGE true
#endif

namespace esl {
    ///
    /// \brief  The quantity class is used to losslessly deal with quantities of
    ///         economic goods in large quantities. A quantity is associated
    ///         with a basis (the number of parts that make up a unit amount of
    ///         the object).
    ///
    /// \details    The quantity is not a fraction: a property that dividies
    ///             into single parts, is not the same as a property that
    ///             divides into 100 parts. Hence, quantity(1,1) is not the same
    ///             as quantity(100,100). Implicit conversion or combination of
    ///             `quantity` with different `basis` is not encouraged, and the
    ///             preprocessor option EXCEPTION_ON_BASIS_CHANGE can be used to
    ///             disallow this.
    ///
    struct quantity
    {
    private:
    public:
        ///
        /// \note   We explicitly delete the division into quantity operator as
        /// this
        ///         loses the remainder. Instead, use
        ///         quantity::divide()
        template<typename divisor_type_>
        quantity &operator/=(divisor_type_) const = delete;

        ///
        ///`\brief  The number of parts
        ///
        std::uint64_t amount;

        ///
        /// \brief  The number of sub-units to make one part
        ///
        std::uint64_t basis;

        ///
        /// \param amount   The number of parts of something, e.g. 23 cents.
        ///                 Default: 0
        /// \param basis    The number of parts to make one unit, e.g.
        ///                 100 cents to 1 dollar. Default: 1
        explicit constexpr quantity(std::uint64_t amount = 0,
                                    std::uint64_t basis  = 1)
        : amount(amount), basis(basis)
        {
            if(basis < 1) {
                throw std::logic_error("quantity with 0 basis");
            }
        }

        ///
        /// \param q
        constexpr quantity(const quantity &q)
        : quantity(q.amount, q.basis)
        {

        }

        ///
        /// \param operand
        /// \return
        constexpr void assert_equal_basis(const quantity &operand) const
        {
            if(basis != operand.basis) {
                throw std::logic_error("assert_equal_basis");
            }
        }

        ///
        /// \tparam divisor_
        /// \return
        template<uint64_t divisor_>
        constexpr std::array<quantity, divisor_> divide() const
        {
            static_assert(divisor_ != 0, "division by zero");

            std::uint64_t quotient_  = amount / divisor_;
            std::uint64_t remainder_ = amount % divisor_;

            std::array<quantity, divisor_> result_;

            auto iterator_ = result_.begin();
            for(; iterator_ != result_.begin() + remainder_; ++iterator_) {
                iterator_->amount = quotient_ + 1;
                iterator_->basis  = basis;
            }

            for(; iterator_ != result_.end(); ++iterator_) {
                iterator_->amount = quotient_;
                iterator_->basis  = basis;
            }

            return result_;
        }

        [[nodiscard]] constexpr quantity
        operator*(const std::uint64_t &operand) const
        {
            return quantity(amount * operand, basis);
        }

        [[nodiscard]]
#if !EXCEPTION_ON_BASIS_CHANGE
        constexpr
#endif
        quantity
        operator*(const quantity &operand) const
        {
            auto numerator_ = amount * operand.amount;
            auto remainder_ = numerator_ % operand.basis;

            if(!remainder_) {
                return quantity(numerator_ / operand.basis, basis);
            }

            if(EXCEPTION_ON_BASIS_CHANGE) {
                throw std::logic_error("EXCEPTION_ON_BASIS_CHANGE");
            }

            auto coremainder_ = numerator_ % basis;

            if(!coremainder_) {
                return quantity(numerator_ / basis, operand.basis);
            }

            return quantity(numerator_, basis * operand.basis);
        }

        ///
        /// \param operand
        /// \return
        quantity &operator*=(const std::uint64_t &operand)
        {
            (*this) = (*this) * operand;
            return *this;
        }

        ///
        /// \brief  Add together two quantities.
        ///
        /// \param operand
        /// \return
        [[nodiscard]]
#if !EXCEPTION_ON_BASIS_CHANGE
        constexpr
#endif
        quantity operator+(const quantity &operand) const
        {
            auto numerator_ = amount * operand.basis + operand.amount * basis;
            auto remainder_ = numerator_ % operand.basis;

            if(!remainder_) {
                return quantity(numerator_ / operand.basis, basis);
            }

            if(EXCEPTION_ON_BASIS_CHANGE) {
                throw std::logic_error("EXCEPTION_ON_BASIS_CHANGE");
            }

            auto coremainder_ = numerator_ % basis;

            if(!coremainder_) {
                return quantity(numerator_ / basis, operand.basis);
            }

            return quantity(numerator_, basis * operand.basis);
        }

        ///
        /// \param operand
        /// \return
        quantity &operator+=(const quantity &operand)
        {
            (*this) = (*this) + operand;
            return *this;
        }

        ///
        /// \brief  subtract two quantities.
        ///
        /// \param operand
        /// \return
        [[nodiscard]]
#if !EXCEPTION_ON_BASIS_CHANGE
        constexpr
#endif
        quantity operator-(const quantity &operand) const
        {
            auto left_  = amount * operand.basis;
            auto right_ = operand.amount * basis;
            if(right_ > left_) {
                throw std::logic_error(
                    "subtraction results in negative quantity");
            }
            auto numerator_ = left_ - right_;
            auto remainder_ = numerator_ % operand.basis;

            if(!remainder_) {
                return quantity(numerator_ / operand.basis, basis);
            }

            if(EXCEPTION_ON_BASIS_CHANGE) {
                throw std::logic_error("EXCEPTION_ON_BASIS_CHANGE");
            }

            auto coremainder_ = numerator_ % basis;

            if(!coremainder_) {
                return quantity(numerator_ / basis, operand.basis);
            }

            return quantity(numerator_, basis * operand.basis);
        }

        quantity &operator=(const quantity &operand)
        {
            amount = operand.amount;
            basis  = operand.basis;
            return *this;
        }

        ///
        /// \param operand
        /// \return
        quantity &operator-=(const quantity &operand)
        {
            (*this) = (*this) - operand;
            return *this;
        }

        ///
        /// \brief  true if equal to the truncated double equivalent of this
        ///         quantity
        ///
        /// \param operand
        /// \return
        [[nodiscard]] constexpr bool operator==(double operand) const
        {
            return (amount == std::uint64_t(operand * basis));
        }

        ///
        /// \param operand
        /// \return
        [[nodiscard]] constexpr bool operator==(const quantity &operand) const
        {
            return (amount == operand.amount && basis == operand.basis);
        }

        [[nodiscard]] constexpr bool operator!=(double operand) const
        {
            return !((*this) == operand);
        }

        [[nodiscard]] constexpr bool operator<(double operand) const
        {
            return (amount < operand * basis);
        }

        [[nodiscard]] constexpr bool operator<(const quantity &operand) const
        {
            return (amount * basis < operand.amount * operand.basis);
        }

        [[nodiscard]] constexpr bool operator>(double operand) const
        {
            return (amount > operand * basis);
        }

        [[nodiscard]] constexpr bool operator>(const quantity &operand) const
        {
            return (amount * basis > operand.amount * operand.basis);
        }

        [[nodiscard]] constexpr bool operator<=(double operand) const
        {
            return (*this) < operand || (*this) == operand;
        }

        [[nodiscard]] constexpr bool operator<=(const quantity &operand) const
        {
            return (*this) < operand || (*this) == operand;
        }

        [[nodiscard]] constexpr bool operator>=(double operand) const
        {
            return (*this) > operand || (*this) == operand;
        }

        [[nodiscard]] constexpr bool operator>=(const quantity & operand) const
        {
            return (*this) > operand || (*this) == operand;
        }

        ///
        /// \brief  Division into `divisor` parts, allocating the remainder
        /// starting at the first quotient.
        ///
        /// \param divisor
        /// \return
        [[nodiscard]] std::vector<quantity>
        operator/(std::uint64_t divisor) const
        {
            std::uint64_t quotient_  = amount / divisor;
            std::uint64_t remainder_ = amount % divisor;

            if(divisor >= (2 * remainder_)) {
                std::vector<quantity> result_(divisor,
                                              quantity(quotient_, basis));
                for(auto iterator_ = result_.begin();
                    iterator_ != result_.begin() + remainder_;
                    ++iterator_) {
                    iterator_->amount = quotient_ + 1;
                }
                return result_;
            }

            std::vector<quantity> result_(divisor,
                                          quantity(quotient_ + 1, basis));
            for(auto iterator_ = result_.begin() + remainder_;
                iterator_ != result_.end();
                ++iterator_) {
                iterator_->amount = quotient_;
            }
            return result_;
        }

        ///
        /// \return amount/basis as a double precision floating point number.
        ///
        [[nodiscard]] explicit operator double() const
        {
            return double(amount) / basis;
        }

        friend std::ostream &operator<<(std::ostream &stream, const quantity &q)
        {
            stream << q.amount;
            if(q.basis != 1) {
                stream << '/';
                stream << q.basis;
            }
            return stream;
        }

        template<class archive_t>
        void serialize(archive_t &archive, const unsigned int version)
        {
            (void)version;
            archive &BOOST_SERIALIZATION_NVP(amount);
            archive &BOOST_SERIALIZATION_NVP(basis);
        }
    };

}  // namespace esl

#endif  // ESL_AMOUNT_HPP


text:
#include "lue/translate/format/geo_eas.hpp"
#include "lue/hl/time_series.hpp"
#include <boost/format.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <boost/tokenizer.hpp>
#include <filesystem>
#include <fstream>

namespace lue {
namespace utility {
namespace {

class GeoEASHeader
{

public:

    using Variables = std::vector<std::string>;

    GeoEASHeader()=default;

    GeoEASHeader(
        std::string const& title,
        Variables const& variables)
        : _title{title},
          _variables{variables}
    {
    }

    GeoEASHeader(GeoEASHeader const&)=delete;

    GeoEASHeader(GeoEASHeader&&)=default;

    ~GeoEASHeader()=default;

    GeoEASHeader& operator=(GeoEASHeader const&)=delete;

    GeoEASHeader& operator=(GeoEASHeader&&)=default;

    std::string const& title() const
    {
        return _title;
    }

    Variables const& variables() const
    {
        return _variables;
    }

    std::size_t nr_variables() const
    {
        return _variables.size();
    }

private:

    std::string    _title;

    Variables      _variables;

};

// template<
//     typename T>
// struct TypeTraits
// {
//     std::string name();
// };

class ColumnDefinition
{

public:

    ColumnDefinition()=default;

    explicit ColumnDefinition(
        std::string const& value_type_name):

        _value_type_name{value_type_name}

    {
    }

    // template<
    //     typename T>
    // ColumnDefinition()
    //     : _value_type_name{TypeTraits<T>::name()}
    // {
    // }

    ~ColumnDefinition()=default;

    std::string const& value_type_name() const
    {
        return _value_type_name;
    }

private:

    std::string    _value_type_name;

};

class TableDefinition
{

public:

    using ColumnDefinitions = std::vector<ColumnDefinition>;

    TableDefinition()=default;

    TableDefinition(
        ColumnDefinitions const& column_definitions,
        char const separator)
        : _separator{separator},
          _column_definitions{column_definitions}
    {
    }

    ~TableDefinition()=default;

    char separator() const
    {
        return _separator;
    }

    ColumnDefinitions const& column_definitions() const
    {
        return _column_definitions;
    }

    std::size_t nr_columns() const
    {
        return _column_definitions.size();
    }

private:

    char           _separator;

    ColumnDefinitions _column_definitions;

};

class Table
{

public:

    template<
        typename T>
    using Column = std::vector<T>;
    using SomeColumn = boost::variant<Column<int>, Column<double>>;
    using Columns = std::vector<SomeColumn>;

    Table()=default;

    Table(Table const&)=delete;

    Table(Table&&)=default;

    Table(
        TableDefinition const& definition,
        Columns&& columns)
        : _definition{definition},
          _columns(std::move(columns))
    {
        assert(_columns.size() == _definition.nr_columns());
    }

    ~Table()=default;

    Table& operator=(Table const&)=delete;

    Table& operator=(Table&&)=default;

    TableDefinition const& definition() const
    {
        return _definition;
    }

    template<
        typename T>
    Column<T> const& column(std::size_t const idx) const
    {
        assert(idx < _columns.size());

        return boost::get<Column<T>>(_columns[idx]);
    }

    std::size_t nr_columns() const
    {
        return _columns.size();
    }

    std::size_t nr_records() const
    {
        assert(_definition.nr_columns() > 0);
        assert(!_columns.empty());

        auto const& column_definition = _definition.column_definitions()[0];
        std::size_t result = 0;

        if(column_definition.value_type_name() == "int") {
            result = column<int>(0).size();
        }
        else if(column_definition.value_type_name() == "double") {
            result = column<double>(0).size();
        }
        else {
            assert(false);
        }

        return result;
    }

private:

    TableDefinition _definition;

    Columns        _columns;

};

template<
    typename Iterator>
bool parse_header(
    Iterator& first,
    Iterator last,
    GeoEASHeader& header)
{
    namespace qi = boost::spirit::qi;
    namespace phoenix = boost::phoenix;

    bool status;

    // First line contains the title
    qi::rule<Iterator, std::string()> rule = +(qi::alnum | qi::punct | qi::blank);
    std::string title;
    status = qi::parse(first, last, rule >> qi::eol, title);

    if(!status) {
        return false;
    }

    // Second line contains nr_variables
    std::size_t nr_variables;
    status = qi::parse(first, last, qi::uint_ >> qi::eol, nr_variables);

    if(!status) {
        return false;
    }

    // Next nr_variables lines contain the description of the variables
    rule = +(qi::alnum | qi::punct | qi::blank);
    GeoEASHeader::Variables variables;
    status = qi::parse(
        first, last,
        qi::repeat(nr_variables)[rule >> qi::eol],
        variables);

    if(!status) {
        return false;
    }

    header = GeoEASHeader(title, variables);

    return true;
}

bool token_is_int(
        std::string const& token)
{
    namespace qi = boost::spirit::qi;

    auto first = token.begin();
    auto last = token.end();

    return qi::parse(first, last, qi::int_) && first == last;
}

bool token_is_double(
        std::string const& token)
{
    namespace qi = boost::spirit::qi;

    auto first = token.begin();
    auto last = token.end();

    return qi::parse(first, last, qi::double_) && first == last;
}

template<
    typename Iterator>
bool parse_table_definition(
    Iterator first,
    Iterator last,
    TableDefinition& table_definition)
{
    // For now, assume separator is a space
    // Figure out:
    // - Number of columns
    // - Value type of columns

    auto eol = std::find(first, last, '\n');

    if(eol == last) {
        return false;
    }

    std::string line(first, eol);

    using Tokenizer = boost::tokenizer<boost::char_separator<char>>;
    using Separator = boost::char_separator<char>;

    char separator_character = ' ';
    Separator separator{" "};
    Tokenizer tokens{line, separator};

    TableDefinition::ColumnDefinitions column_definitions;

    for(auto const& token: tokens) {
        if(token_is_int(token)) {
            column_definitions.emplace_back("int");
        }
        else if(token_is_double(token)) {
            column_definitions.emplace_back("double");
        }
        else {
            assert(false);
        }
    }

    table_definition = TableDefinition(column_definitions, separator_character);

    return true;
}

template<
    typename Iterator>
bool parse_table(
    Iterator& first,
    Iterator last,
    TableDefinition const& definition,
    Table& table)
{
    namespace qi = boost::spirit::qi;
    namespace phoenix = boost::phoenix;

    Table::Columns columns;
    columns.reserve(definition.nr_columns());

    using Rule = qi::rule<Iterator>;
    std::vector<Rule> column_parsers;
    column_parsers.reserve(definition.nr_columns());

    for(auto const& column_definition: definition.column_definitions()) {
        // - Add empty column to table
        // - Update parser
        // - Make it dump values in this specific column

        if(column_definition.value_type_name() == "int") {
            using Column = Table::Column<int>;

            columns.emplace_back(Column{});
            auto& column = boost::get<Column>(columns.back());

            column_parsers.emplace_back(
                qi::int_[phoenix::push_back(phoenix::ref(column), qi::_1)]);
        }
        else if(column_definition.value_type_name() == "double") {
            using Column = Table::Column<double>;

            columns.emplace_back(Column{});
            auto& column = boost::get<Column>(columns.back());

            column_parsers.emplace_back(
                qi::double_[phoenix::push_back(phoenix::ref(column), qi::_1)]);
        }
        else {
            assert(false);
        }
    }

    // Parse table rows
    Rule row_parser = column_parsers[0].copy();

    for(std::size_t i = 1; i < column_parsers.size(); ++i) {
        row_parser =
            row_parser.copy() >> +qi::blank >> column_parsers[i].copy();
    }

    row_parser = *qi::blank >> row_parser.copy() >> *qi::blank >> qi::eol;

    bool status = qi::parse(first, last, +row_parser >> qi::eoi);

    if(!status) {
        return false;
    }

    table = Table(definition, std::move(columns));

    return true;
}

}  // Anonymous namespace

void translate_geo_eas_to_lue(
    std::string const& geo_eas_pathname,
    std::string const& lue_pathname,
    Metadata const& metadata)
{
    std::ifstream stream(geo_eas_pathname);

    if(!stream) {
        throw std::runtime_error("Cannot open " + geo_eas_pathname);
    }

    stream.unsetf(std::ios::skipws);

    boost::spirit::istream_iterator begin(stream);
    boost::spirit::istream_iterator end;

    GeoEASHeader geo_eas_header;

    if(!parse_header(begin, end, geo_eas_header)) {
        throw std::runtime_error("Cannot parse Geo-EAS header");
    }

    TableDefinition table_definition;

    if(!parse_table_definition(begin, end, table_definition)) {
        throw std::runtime_error("Cannot parse Geo-EAS table definition");
    }

    assert(table_definition.nr_columns() > 1);
    assert(table_definition.nr_columns() == geo_eas_header.nr_variables());
    assert(table_definition.column_definitions()[0].value_type_name() == "int");
    for(std::size_t idx = 1; idx < table_definition.nr_columns(); ++idx) {
        assert(
            table_definition.column_definitions()[idx].value_type_name() ==
            "double");
    }

    Table table;

    if(!parse_table(begin, end, table_definition, table) || begin != end) {
        throw std::runtime_error("Cannot parse Geo-EAS table body");
    }

    // - First column represents time steps of discretized time box
    // - Subsequent columns represent temporal property values for
    //   nr_columns-1 items

    // Create phenomenon
    // - Name of phenomenon is obtained from supplied meta-data

    // Create property-set
    // - Name of property-set is obtained from supplied meta-data

    std::string const geo_eas_dataset_name =
        std::filesystem::path(geo_eas_pathname).stem().string();
    std::string phenomenon_name = geo_eas_dataset_name;
    std::string property_set_name = "point";

    auto const& root_json = metadata.object();

    auto const dataset_json = json::object(
        root_json, JSONPointer{"/datasets"}, "name",
        geo_eas_dataset_name);

    if(json::has_key(dataset_json, "phenomenon")) {
        auto const phenomenon_pointer =
            json::pointer(dataset_json, "phenomenon");
        auto const phenomenon_json =
            json::object(root_json, phenomenon_pointer);
        phenomenon_name = json::string(phenomenon_json, "name");

        if(json::has_key(phenomenon_json, "property_set")) {
            property_set_name = json::string(phenomenon_json,
                    JSON::json_pointer("/property_set/name"));
        }
    }

    auto lue_dataset = create_dataset(lue_pathname);

    // Create time domain
    // - TODO: Verify first column is monotonically increasing
    // - Set-up time domain and discretization
    // - Clock, units and time period is obtained from supplied meta-data

    auto const time_json = json::object(dataset_json, "time");
    auto const clock_json = json::object(time_json, "clock");
    auto const clock = json::clock(clock_json);
    auto const box_json = json::object(time_json, "box");
    time::DurationCount const start_time_point = box_json.at(0);
    time::DurationCount const end_time_point = box_json.at(1);
    auto const nr_steps = table.nr_records();

    // Create space domain
    // - Space points are obtained from supplied meta-data
    // - Number of space points must equal nr_columns-1
    auto const space_json = json::object(dataset_json, "space");
    auto const point_json = json::object(space_json, "point");
    auto const nr_points = point_json.size();
    auto const nr_items = table.nr_columns() - 1;

    if(nr_points != nr_items) {
        throw std::runtime_error(boost::str(
            boost::format(
                "Number of space points in meta-data file (%1%) must equal the "
                "number of items (number of columns - 1) in the GeoEAS dataset "
                "(%2%)") %
            nr_points % nr_items));
    }

    auto const crs = json::string(space_json, "crs");
    hl::TimeSeries::SpaceDomain::Coordinates coordinates;
    coordinates.reserve(2 * nr_points);

    for(auto point: point_json) {
        coordinates.emplace_back(point[0]);
        coordinates.emplace_back(point[1]);
    }

    assert(coordinates.size() == 2 * nr_points);

    hl::TimeSeries::SpaceDomain const space_domain{crs, std::move(coordinates)};

    auto const property_name = json::string(
        dataset_json, JSONPointer{"/property/name"});

    hl::TimeSeriesDomain const time_domain{
        clock, start_time_point, end_time_point};
    hl::TimeSeriesDiscretization const time_discretization{nr_steps};

    auto time_series = hl::create_time_series(
        lue_dataset, phenomenon_name, property_set_name,
        time_domain, space_domain, property_name, time_discretization);

    // Create name property
    // - Name is 'name'. Use increment in case such property already exists
    // - Value is column name from table header

    // auto memory_datatype = hdf5::create_string_datatype();
    // time_series.add_property("name", memory_datatype);

    // TODO
    // time_series.add_names(geo_eas_header.variables());

    // Create value property
    for(std::size_t item_idx = 0; item_idx < nr_items; ++item_idx) {
        time_series.write(item_idx, table.column<double>(item_idx + 1).data());
    }
}

}  // namespace utility
}  // namespace lue

ext: cpp

max_stars_repo_name: computationalgeography/lue

max_issues_repo_name: pcraster/lue

max_forks_repo_name: computationalgeography/lue


text:
/*
 * Copyright: 2018 Gerard Choinka (gerard.choinka@gmail.com)
 *
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or
 * copy at http://www.boost.org/LICENSE_1_0.txt)
 */
#pragma once

#include <boost/variant.hpp>
#include <string_view>

namespace gpm {

template <typename StringT>
struct Printer : public boost::static_visitor<StringT> {
  template <typename T>
  StringT operator()(T const& node) const {
    char const* delimiter = "";
    char const* begin_delimiter = "";
    char const* end_delimiter = "";
    StringT children;
    if constexpr (std::tuple_size<decltype(node.children)>::value != 0)
      for (auto const& n : node.children) {
        children += delimiter + boost::apply_visitor(*this, n);
        delimiter = " , ";
        begin_delimiter = "( ";
        end_delimiter = " )";
      }
    return StringT{T::name} + begin_delimiter + children + end_delimiter;
  }
};

template <typename StringT>
struct RPNPrinter : public boost::static_visitor<StringT> {
  template <typename T>
  StringT operator()(T const& node) const {
    StringT children;
    if constexpr (std::tuple_size<decltype(node.children)>::value != 0)
      for (auto const& n : node.children) {
        children = boost::apply_visitor(*this, n) + " " + children;
      }
    return children + T::name;
  }
};

template <typename StringT>
struct PNPrinter : public boost::static_visitor<StringT> {
  template <typename T>
  StringT operator()(T const& node) const {
    StringT children;
    if constexpr (std::tuple_size<decltype(node.children)>::value != 0)
      for (auto const& n : node.children) {
        children = children + " " + boost::apply_visitor(*this, n);
      }
    return T::name + children;
  }
};

class RPNTokenCursor {
  std::string_view sv_;
  std::string_view::size_type tokenBegin_;
  std::string_view::size_type tokenEnd_;

 public:
  RPNTokenCursor(std::string_view sv) : sv_{sv} {
    tokenBegin_ = sv_.size();
    --tokenBegin_;
    for (; tokenBegin_ > 0; --tokenBegin_) {
      if (sv_[tokenBegin_] == ' ') {
        tokenBegin_++;
        break;
      }
    }

    tokenEnd_ = tokenBegin_ + 1;
    for (; tokenEnd_ != sv_.size(); ++tokenEnd_) {
      if (sv_[tokenEnd_] == ' ') {
        break;
      }
    }
  }

  std::string_view token() {
    return sv_.substr(tokenBegin_, tokenEnd_ - tokenBegin_);
  }

  RPNTokenCursor& next() {
    --tokenBegin_;
    if (tokenBegin_ > 0) {
      --tokenBegin_;
      for (; tokenBegin_ > 0; --tokenBegin_) {
        if (sv_[tokenBegin_] == ' ') {
          ++tokenBegin_;
          break;
        }
      }
    }
    tokenEnd_ = tokenBegin_ + 1;
    for (; tokenEnd_ != sv_.size(); ++tokenEnd_) {
      if (sv_[tokenEnd_] == ' ') {
        break;
      }
    }
    return *this;
  }
};

class PNTokenCursor {
  std::string_view sv_;
  std::string_view::size_type tokenBegin_;
  std::string_view::size_type tokenEnd_;

 public:
  PNTokenCursor(std::string_view sv) : sv_{sv}, tokenBegin_{0} {
    tokenEnd_ = tokenBegin_ + 1;
    while (tokenEnd_ < sv_.size() && sv_[tokenEnd_] != ' ') ++tokenEnd_;
  }

  std::string_view token() const {
    return sv_.substr(tokenBegin_, tokenEnd_ - tokenBegin_);
  }

  PNTokenCursor& next() {
    while (tokenBegin_ < sv_.size() && sv_[tokenBegin_] != ' ') ++tokenBegin_;
    ++tokenBegin_;
    tokenEnd_ = tokenBegin_ + 1;
    while (tokenEnd_ < sv_.size() && sv_[tokenEnd_] != ' ') ++tokenEnd_;
    return *this;
  }
};

}  // namespace gpm

max_stars_repo_name: gchoinka/gpm

max_issues_repo_name: gchoinka/gpm

max_forks_repo_name: gchoinka/gpm