/*
 Copyright (C) 2018-2024 Fredrik Öhrström (gpl-3.0-or-later)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include"address.h"
#include"aes.h"
#include"aescmac.h"
#include"cmdline.h"
#include"config.h"
#include"formula_implementation.h"
#include"meters.h"
#include"printer.h"
#include"serial.h"
#include"translatebits.h"
#include"util.h"
#include"wmbus.h"
#include"dvparser.h"
#include"xmq.h"

#include<string.h>
#include<set>

using namespace std;

// This is test specific verbose.
bool verbose_ = false;

#define LIST_OF_TESTS \
    X(addresses) \
    X(dynamic_loading)                        \
    X(crc)            \
    X(dvparser)       \
    X(ixmlparser)      \
    X(devices)        \
    X(linkmodes)      \
    X(ids)            \
    X(kdf)            \
    X(periods)        \
    X(device_parsing) \
    X(meters)         \
    X(months)         \
    X(aes)            \
    X(sbc)            \
    X(hex)            \
    X(translate)                                \
    X(slip)                                     \
    X(dvs)                                      \
    X(ascii_detection)                          \
    X(status_join)                              \
    X(status_sort)                              \
    X(field_matcher)                            \
    X(units_extraction)                         \
    X(si_units_siexp)                           \
    X(si_units_basic)                           \
    X(si_units_conversion)                      \
    X(formulas_building_consts)                 \
    X(formulas_building_meters)                 \
    X(formulas_datetimes)                       \
    X(formulas_parsing_1)                       \
    X(formulas_parsing_2)                       \
    X(formulas_multiply_constants)              \
    X(formulas_divide_constants)                \
    X(formulas_sqrt_constants)                  \
    X(formulas_errors)                          \
    X(formulas_dventries)                       \
    X(formulas_stringinterpolation)             \

#define X(t) void test_##t();
LIST_OF_TESTS
#undef X

// Test if we should run this test based on the command line pattern.
bool test(const char *test_name, const char *pattern)
{
    if (pattern == NULL)
    {
        if (verbose_) printf("Test %s\n", test_name);
        return true;
    }
    bool ok = strstr(test_name, pattern) != NULL;
    if (ok) info("Test %s\n", test_name);
    return ok;
}

int main(int argc, char **argv)
{
    const char *pattern = NULL;

    int i = 1;
    while (i < argc)
    {
        if (!strcmp(argv[i], "--verbose"))
        {
            verbose_ = true;
        }
        else
        if (!strcmp(argv[i], "--debug"))
        {
            verbose_ = true;
            debugEnabled(true);
        }
        else
        if (!strcmp(argv[i], "--trace"))
        {
            verbose_ = true;
            debugEnabled(true);
            traceEnabled(true);
        }
        else
        {
            pattern = argv[i];
        }
        i++;
    }
    onExit([](){});

#define X(x) if (test(#x, pattern)) test_##x();
LIST_OF_TESTS
#undef X

    return 0;
}

void test_crc()
{
    unsigned char data[4];
    data[0] = 0x01;
    data[1] = 0xfd;
    data[2] = 0x1f;
    data[3] = 0x01;

    uint16_t crc = crc16_EN13757(data, 4);
    if (crc != 0xcc22) {
        printf("ERROR! %4x should be cc22\n", crc);
    }
    data[3] = 0x00;

    crc = crc16_EN13757(data, 4);
    if (crc != 0xf147) {
        printf("ERROR! %4x should be f147\n", crc);
    }

    uchar block[10];
    block[0]=0xEE;
    block[1]=0x44;
    block[2]=0x9A;
    block[3]=0xCE;
    block[4]=0x01;
    block[5]=0x00;
    block[6]=0x00;
    block[7]=0x80;
    block[8]=0x23;
    block[9]=0x07;

    crc = crc16_EN13757(block, 10);

    if (crc != 0xaabc) {
        printf("ERROR! %4x should be aabc\n", crc);
    }

    block[0]='1';
    block[1]='2';
    block[2]='3';
    block[3]='4';
    block[4]='5';
    block[5]='6';
    block[6]='7';
    block[7]='8';
    block[8]='9';

    crc = crc16_EN13757(block, 9);

    if (crc != 0xc2b7) {
        printf("ERROR! %4x should be c2b7\n", crc);
    }
}

bool tst_parse(const char *data, std::map<std::string,std::pair<int,DVEntry>> *dv_entries, int testnr)
{
    debug("\n\nTest nr %d......\n\n", testnr);
    bool b;
    Telegram t;
    vector<uchar> databytes;
    hex2bin(data, &databytes);
    vector<uchar>::iterator i = databytes.begin();

    b = parseDV(&t, databytes, i, databytes.size(), dv_entries);

    return b;
}

bool tst_ixmlparse(const char *hex, const char *grammar, std::map<std::string,std::pair<int,DVEntry>> *dv_entries, int testnr)
{
    debug("\n\nTest ixml parse nr %d......\n\n", testnr);
    bool b;
    XMQDoc *ixml = xmqNewDoc();
    b = xmqParseBufferWithType(ixml, grammar, NULL, NULL, XMQ_CONTENT_IXML, 0);
    if (!b) {
        fprintf(stderr, "Failed to parse IXML grammar: %s\n", grammar);
        return false;
    }

    string data(hex);
    Telegram t;
    b = parseWithIXML(&t, 0, data, ixml, dv_entries);
    if (!b) {
        fprintf(stderr, "Failed to parse %s using IXML grammar: %s\n", hex, grammar);
        return false;
    }
    xmqFreeDoc(ixml);

    return b;
}

void tst_double(map<string,pair<int,DVEntry>> &values, const char *key, double v, int testnr)
{
    int offset;
    double value;
    bool b =  extractDVdouble(&values,
                              key,
                              &offset,
                              &value);

    if (!b || value != v) {
        fprintf(stderr, "Error in dvparser testnr %d: got %lf but expected value %lf for key %s\n", testnr, value, v, key);
    }
}

void tst_string(map<string,pair<int,DVEntry>> &values, const char *key, const char *v, int testnr)
{
    int offset;
    string value;
    bool b = extractDVHexString(&values,
                                key,
                                &offset,
                                &value);
    if (!b || value != v) {
        fprintf(stderr, "Error in dvparser testnr %d: got \"%s\" but expected value \"%s\" for key %s\n", testnr, value.c_str(), v, key);
    }
}

void tst_date(map<string,pair<int,DVEntry>> &values, const char *key, string date_expected, int testnr)
{
    int offset;
    struct tm value;
    bool b = extractDVdate(&values,
                           key,
                           &offset,
                           &value);

    char buf[256];
    strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &value);
    string date_got = buf;
    if (!b || date_got != date_expected) {
        fprintf(stderr, "Error in dvparser testnr %d:\ngot >%s< but expected >%s< for key %s\n\n", testnr, date_got.c_str(), date_expected.c_str(), key);
    }
}

void test_dvparser()
{
    map<string,pair<int,DVEntry>> dv_entries;

    int testnr = 1;
    tst_parse("2F 2F 0B 13 56 34 12 8B 82 00 93 3E 67 45 23 0D FD 10 0A 30 31 32 33 34 35 36 37 38 39 0F 88 2F", &dv_entries, testnr);
    tst_double(dv_entries, "0B13", 123.456, testnr);
    tst_double(dv_entries, "8B8200933E", 234.567, testnr);
    tst_string(dv_entries, "0DFD10", "30313233343536373839", testnr);

    testnr++;
    dv_entries.clear();
    tst_parse("82046C 5f1C", &dv_entries, testnr);
    tst_date(dv_entries, "82046C", "2010-12-31 00:00:00", testnr); // 2010-dec-31

    testnr++;
    dv_entries.clear();
    tst_parse("0C1348550000426CE1F14C130000000082046C21298C0413330000008D04931E3A3CFE3300000033000000330000003300000033000000330000003300000033000000330000003300000033000000330000004300000034180000046D0D0B5C2B03FD6C5E150082206C5C290BFD0F0200018C4079678885238310FD3100000082106C01018110FD610002FD66020002FD170000", &dv_entries, testnr);
    tst_double(dv_entries, "0C13", 5.548, testnr);
    tst_date(dv_entries, "426C", "2127-01-01 00:00:00", testnr); // 2127-jan-1
    tst_date(dv_entries, "82106C", "2000-01-01 00:00:00", testnr); // 2000-jan-1

    testnr++;
    dv_entries.clear();
    tst_parse("426C FE04", &dv_entries, testnr);
    tst_date(dv_entries, "426C", "2007-04-30 00:00:00", testnr); // 2010-dec-31
}

void test_ixmlparser()
{
    map<string,pair<int,DVEntry>> dv_entries;

    int testnr = 1;
    tst_ixmlparse("10351F0400",
                  "decode = total. -hex  = ['A'-'F';'0'-'9']. -byte = hex, hex. -word = byte, byte. -quad = byte, byte, byte, byte. total     = -'10', quad, @DV_0413. DV_0413>dvk=+'0413'.",
                  &dv_entries, testnr);

    tst_double(dv_entries, "0413", 270.133, testnr);
}

void test_devices()
{
    shared_ptr<SerialCommunicationManager> manager = createSerialCommunicationManager(0, false);

    shared_ptr<SerialDevice> serial1 = manager->createSerialDeviceSimulator();

    /*
    shared_ptr<BusDevice> wmbus_im871a = openIM871A("", manager, serial1);
    manager->stop();*/
}

void test_linkmodes()
{
    /*
    LinkModeCalculationResult lmcr;
    auto manager = createSerialCommunicationManager(0, false);

    auto serial1 = manager->createSerialDeviceSimulator();
    auto serial2 = manager->createSerialDeviceSimulator();
    auto serial3 = manager->createSerialDeviceSimulator();
    auto serial4 = manager->createSerialDeviceSimulator();
    auto serial5 = manager->createSerialDeviceSimulator();

    vector<string> no_meter_shells, no_meter_jsons;
    Detected de;
    SpecifiedDevice sd;
    shared_ptr<BusDevice> wmbus_im871a = openIM871A(de, manager, serial1);
    shared_ptr<BusDevice> wmbus_amb8465 = openAMB8465(de, manager, serial2);
    shared_ptr<BusDevice> wmbus_rtlwmbus = openRTLWMBUS(de, "", false, manager, serial3);
    shared_ptr<BusDevice> wmbus_rawtty = openRawTTY(de, manager, serial4);
    shared_ptr<BusDevice> wmbus_amb3665 = openAMB3665(de, manager, serial5);

    Configuration nometers_config;
    // Check that if no meters are supplied then you must set a link mode.
    lmcr = calculateLinkModes(&nometers_config, wmbus_im871a.get());
    if (lmcr.type != LinkModeCalculationResultType::NoMetersMustSupplyModes)
    {
        printf("ERROR! Expected failure due to automatic deduction! Got instead:\n%s\n", lmcr.msg.c_str());
    }
    debug("test0 OK\n\n");

    nometers_config.default_device_linkmodes.addLinkMode(LinkMode::T1);
    lmcr = calculateLinkModes(&nometers_config, wmbus_im871a.get());
    if (lmcr.type != LinkModeCalculationResultType::Success)
    {
        printf("ERROR! Expected succcess! Got instead:\n%s\n", lmcr.msg.c_str());
    }
    debug("test0.0 OK\n\n");

    Configuration apator_config;
    string apator162 = "apator162";
    vector<string> ids = { "12345678" };
    apator_config.meters.push_back(MeterInfo("", // bus
                                             "m1", // name
                                             MeterDriver::APATOR162, // driver/type
                                             "", // extras
                                             ids, // ids
                                             "", // Key
                                             toMeterLinkModeSet(apator162), // link mode set
                                             0, // baud
                                             no_meter_shells, // shells
                                             no_meter_jsons)); // jsons

    // Check that if no explicit link modes are provided to apator162, then
    // automatic deduction will fail, since apator162 can be configured to transmit
    // either C1 or T1 telegrams.
    lmcr = calculateLinkModes(&apator_config, wmbus_im871a.get());
    if (lmcr.type != LinkModeCalculationResultType::AutomaticDeductionFailed)
    {
        printf("ERROR! Expected failure due to automatic deduction! Got instead:\n%s\n", lmcr.msg.c_str());
    }
    debug("test1 OK\n\n");

    // Check that if we supply the link mode T1 when using an apator162, then
    // automatic deduction will succeeed.
    apator_config.default_device_linkmodes = LinkModeSet();
    apator_config.default_device_linkmodes.addLinkMode(LinkMode::T1);
    apator_config.default_device_linkmodes.addLinkMode(LinkMode::C1);
    lmcr = calculateLinkModes(&apator_config, wmbus_im871a.get());
    if (lmcr.type != LinkModeCalculationResultType::DongleCannotListenTo)
    {
        printf("ERROR! Expected dongle cannot listen to! Got instead:\n%s\n", lmcr.msg.c_str());
    }
    debug("test2 OK\n\n");

    lmcr = calculateLinkModes(&apator_config, wmbus_rtlwmbus.get());
    if (lmcr.type != LinkModeCalculationResultType::Success)
    {
        printf("ERROR! Expected success! Got instead:\n%s\n", lmcr.msg.c_str());
    }
    debug("test3 OK\n\n");

    Configuration multical21_and_supercom587_config;
    string multical21 = "multical21";
    string supercom587 = "supercom587";

    multical21_and_supercom587_config.meters.push_back(MeterInfo("", "m1", MeterDriver::MULTICAL21, "", ids, "",
                                                                 toMeterLinkModeSet(multical21),
                                                                 0,
                                                                 no_meter_shells,
                                                                 no_meter_jsons));
    multical21_and_supercom587_config.meters.push_back(MeterInfo("", "m2", MeterDriver::UNKNOWN, "supercom587", ids, "",
                                                                 toMeterLinkModeSet(supercom587),
                                                                 0,
                                                                 no_meter_shells,
                                                                 no_meter_jsons));

    // Check that meters that transmit on two different link modes cannot be listened to
    // at the same time using im871a.
    lmcr = calculateLinkModes(&multical21_and_supercom587_config, wmbus_im871a.get());
    if (lmcr.type != LinkModeCalculationResultType::AutomaticDeductionFailed)
    {
        printf("ERROR! Expected failure due to automatic deduction! Got instead:\n%s\n", lmcr.msg.c_str());
    }
    debug("test4 OK\n\n");

    // Explicitly set T1
    multical21_and_supercom587_config.default_device_linkmodes = LinkModeSet();
    multical21_and_supercom587_config.default_device_linkmodes.addLinkMode(LinkMode::T1);
    lmcr = calculateLinkModes(&multical21_and_supercom587_config, wmbus_im871a.get());
    if (lmcr.type != LinkModeCalculationResultType::MightMissTelegrams)
    {
        printf("ERROR! Expected might miss telegrams! Got instead:\n%s\n", lmcr.msg.c_str());
    }
    debug("test5 OK\n\n");

    // Explicitly set N1a, but the meters transmit on C1 and T1.
    multical21_and_supercom587_config.default_device_linkmodes = LinkModeSet();
    multical21_and_supercom587_config.default_device_linkmodes.addLinkMode(LinkMode::N1a);
    lmcr = calculateLinkModes(&multical21_and_supercom587_config, wmbus_im871a.get());
    if (lmcr.type != LinkModeCalculationResultType::MightMissTelegrams)
    {
        printf("ERROR! Expected no meter can be heard! Got instead:\n%s\n", lmcr.msg.c_str());
    }
    debug("test6 OK\n\n");

    // Explicitly set N1a, but it is an amber dongle.
    multical21_and_supercom587_config.default_device_linkmodes = LinkModeSet();
    multical21_and_supercom587_config.default_device_linkmodes.addLinkMode(LinkMode::N1a);
    lmcr = calculateLinkModes(&multical21_and_supercom587_config, wmbus_amb8465.get());
    if (lmcr.type != LinkModeCalculationResultType::DongleCannotListenTo)
    {
        printf("ERROR! Expected dongle cannot listen to! Got instead:\n%s\n", lmcr.msg.c_str());
    }
    debug("test7 OK\n\n");

    manager->stop();
    */
}

void test_valid_match_expression(string s, bool expected)
{
    bool b = isValidSequenceOfAddressExpressions(s);
    if (b == expected) return;
    if (expected == true)
    {
        printf("ERROR! Expected \"%s\" to be valid! But it was not!\n", s.c_str());
    }
    else
    {
        printf("ERROR! Expected \"%s\" to be invalid! But it was reported as valid!\n", s.c_str());
    }
}

void test_does_id_match_expression(string id, string mes, bool expected, bool expected_uw)
{
    Address a;
    a.id = id;
    vector<Address> as;
    as.push_back(a);
    vector<AddressExpression> expressions = splitAddressExpressions(mes);
    bool uw = false;
    bool b = doesTelegramMatchExpressions(as, expressions, &uw);
    if (b != expected)
    {
        if (expected == true)
        {
            printf("ERROR! Expected \"%s\" to match \"%s\" ! But it did not!\n", id.c_str(), mes.c_str());
        }
        else
        {
            printf("ERROR! Expected \"%s\" to NOT match \"%s\" ! But it did!\n", id.c_str(), mes.c_str());
        }
    }
    if (expected_uw != uw)
    {
        printf("ERROR! Matching \"%s\" \"%s\" and expecte used_wildcard %d but got %d!\n",
               id.c_str(), mes.c_str(), expected_uw, uw);
    }
}

void test_ids()
{
    test_valid_match_expression("12345678", true);
    test_valid_match_expression("*", true);
    test_valid_match_expression("!12345678", true);
    test_valid_match_expression("12345*", true);
    test_valid_match_expression("!123456*", true);

    test_valid_match_expression("1234567", false);
    test_valid_match_expression("", false);
    test_valid_match_expression("z1234567", false);
    test_valid_match_expression("123456789", false);
    test_valid_match_expression("!!12345678", false);
    test_valid_match_expression("12345678*", false);
    test_valid_match_expression("**", false);
    test_valid_match_expression("123**", false);

    test_valid_match_expression("2222*,!22224444", true);

    test_does_id_match_expression("12345678", "12345678", true, false);
    test_does_id_match_expression("12345678", "*", true, true);
    test_does_id_match_expression("12345678", "2*", false, false);

    test_does_id_match_expression("12345678", "*,!2*", true, true);

    test_does_id_match_expression("22222222", "22*,!22222222", false, false);
    test_does_id_match_expression("22222223", "22*,!22222222", true, true);
    test_does_id_match_expression("22222223", "*,!22*", false, false);
    test_does_id_match_expression("12333333", "123*,!1234*,!1235*,!1236*", true, true);
    test_does_id_match_expression("12366666", "123*,!1234*,!1235*,!1236*", false, false);
    test_does_id_match_expression("11223344", "22*,33*,44*,55*", false, false);
    test_does_id_match_expression("55223344", "22*,33*,44*,55*", true, true);

    test_does_id_match_expression("78563413", "78563412,78563413", true, false);
    test_does_id_match_expression("78563413", "*,!00156327,!00048713", true, true);
}

void tst_address(string s, bool valid,
                 string id, bool has_wildcard,
                 string mfct,
                 uchar version,
                 uchar type,
                 bool mbus_primary,
                 bool filter_out)
{
    AddressExpression a;
    bool ok = a.parse(s);

    if (ok != valid)
    {
        printf("Expected parse of address \"%s\" to return %s, but returned %s\n",
               s.c_str(),
               valid?"valid":"bad", ok?"valid":"bad");
    }
    if (ok)
    {
        string smfct = manufacturerFlag(a.mfct);
        if (id != a.id ||
            has_wildcard != a.has_wildcard ||
            mfct != smfct ||
            version != a.version ||
            type != a.type ||
            mbus_primary != a.mbus_primary ||
            filter_out != a.filter_out)
        {
            printf("Expected parse of address \"%s\" to return\n"
                   "(id=%s haswild=%d mfct=%s version=%02x type=%02x mbus=%d negt=%d)\n"
                   "but got\n"
                   "(id=%s haswild=%d mfct=%s version=%02x type=%02x mbus=%d negt=%d)\n",
                   s.c_str(),
                   id.c_str(),
                   has_wildcard,
                   mfct.c_str(),
                   version,
                   type,
                   mbus_primary,
                   filter_out,
                   a.id.c_str(),
                   a.has_wildcard,
                   smfct.c_str(),
                   a.version,
                   a.type,
                   a.mbus_primary,
                   a.filter_out);
        }
    }
}

void tst_address_match(string expr, string id, uint16_t m, uchar v, uchar t, bool match, bool filter_out)
{
    AddressExpression e;
    bool ok = e.parse(expr);
    assert(ok);

    bool test = e.match(id, m, v, t);

    if (test != match)
    {
        printf("Expected address %s %04x %02x %02x to %smatch expression %s\n",
               id.c_str(),
               m, v, t,
               match?"":"not ",
               expr.c_str());
    }
    if (match && e.filter_out != filter_out)
    {
        printf("Expected %s from match expression %s\n",
               filter_out?"FILTERED OUT":"NOT filtered",
               expr.c_str());
    }
}

void tst_telegram_match(string addresses, string expressions, bool match, bool uw)
{
    vector<AddressExpression> exprs = splitAddressExpressions(expressions);
    vector<AddressExpression> as = splitAddressExpressions(addresses);
    vector<Address> addrs;

    for (auto &ad : as)
    {
        Address a;
        a.id = ad.id;
        a.mfct = ad.mfct;
        a.version = ad.version;
        a.type = ad.type;

        addrs.push_back(a);
    }

    bool used_wildcard = false;
    bool m = doesTelegramMatchExpressions(addrs, exprs, &used_wildcard);

    if (m != match)
    {
        printf("Expected addresses %s to %smatch expressions %s\n",
               addresses.c_str(),
               match?"":"NOT ",
               expressions.c_str());
    }
    if (uw != used_wildcard)
    {
        printf("Expected addresses %s from match expression %s %susing wildcard\n",
               addresses.c_str(),
               expressions.c_str(),
               uw?"":"NOT ");
    }
}

void test_addresses()
{
    tst_address("12345678",
                true,
                "12345678", // id
                false, // has wildcard
                "___", // mfct
                0xff, // type
                0xff,  // version
                false, // mbus primary found
                false // negate test
        );
    tst_address("123k45678", false, "", false, "", 0xff, 0xff, false, false);
    tst_address("1234", false, "", false, "", 0xff, 0xff, false, false);
    tst_address("p0", true, "p0", false, "___", 0xff, 0xff, true, false);
    tst_address("p250", true, "p250", false, "___", 0xff, 0xff, true, false);
    tst_address("p251", false, "", false, "", 0xff, 0xff, false, false);
    tst_address("p0.M=PII.V=01.T=1b", true, "p0", false, "PII", 0x01, 0x1b, true, false);
    tst_address("p123.V=11.M=FOO.T=ff", true, "p123", false, "FOO", 0x11, 0xff, true, false);
    tst_address("p123.M=FOO", true, "p123", false, "FOO", 0xff, 0xff, true, false);
    tst_address("p123.M=FOO.V=33", true, "p123", false, "FOO", 0x33, 0xff, true, false);
    tst_address("p123.T=33", true, "p123", false, "___", 0xff, 0x33, true, false);
    tst_address("p1.V=33", true, "p1", false, "___", 0x33, 0xff, true, false);
    tst_address("p16.M=BAR", true, "p16", false, "BAR", 0xff, 0xff, true, false);

    tst_address("12345678.M=ABB.V=66.T=16", true, "12345678",  false, "ABB", 0x66, 0x16, false, false);
    tst_address("!12345678.M=ABB.V=66.T=16", true, "12345678", false, "ABB", 0x66, 0x16, false, true);
    tst_address("!*.M=ABB", true, "*", true, "ABB", 0xff, 0xff, false, true);
    tst_address("!*.V=66.T=06", true, "*", true, "___", 0x66, 0x06, false, true);

    tst_address("12*", true, "12*", true, "___", 0xff, 0xff, false, false);
    tst_address("!1234567*", true, "1234567*", true, "___", 0xff, 0xff, false, true);

    tst_address_match("12345678", "12345678", 1, 1, 1, true, false);
    tst_address_match("12345678.M=ABB.V=77", "12345678", MANUFACTURER_ABB, 0x77, 88, true, false);
    tst_address_match("1*.V=77", "12345678", MANUFACTURER_ABB, 0x77, 1, true, false);
    tst_address_match("12345678.M=ABB.V=67.T=06", "12345678", MANUFACTURER_ABB, 0x67, 0x06, true, false);
    tst_address_match("12345678.M=ABB.V=67.T=06", "12345678", MANUFACTURER_ABB, 0x68, 0x06, false, false);
    tst_address_match("12345678.M=ABB.V=67.T=06", "12345678", MANUFACTURER_ABB, 0x67, 0x07, false, false);
    tst_address_match("12345678.M=ABB.V=67.T=06", "12345678", MANUFACTURER_ABB+1, 0x67, 0x06, false, false);
    tst_address_match("12345678.M=ABB.V=67.T=06", "12345677", MANUFACTURER_ABB, 0x67, 0x06, false, false);

    // Now verify filter out ! character. The filter out does notchange the test. It is still the same
    // test, but the match will be used as a filter out. Ie if the match triggers, then the telegram will be filtered out.
    tst_address_match("!12345678", "12345677", 1, 1, 1, false, false);
    tst_address_match("!*.M=ABB", "99999999", MANUFACTURER_ABB, 1, 1, true, true);
    tst_address_match("*.M=ABB", "99999999", MANUFACTURER_ABB, 1, 1, true, false);

    // Test that both id wildcard matches and the version.
    tst_address_match("9*.V=06", "99999999", MANUFACTURER_ABB, 0x06, 1, true, false);
    tst_address_match("9*.V=06", "89999999", MANUFACTURER_ABB, 0x06, 1, false, false);
    tst_address_match("9*.V=06", "99999999", MANUFACTURER_ABB, 0x07, 1, false, false);
    tst_address_match("9*.V=06", "89999999", MANUFACTURER_ABB, 0x07, 1, false, false);

    // Test the same, expect same answers but check that filtered out is set.
    tst_address_match("!9*.V=06", "99999999", MANUFACTURER_ABB, 0x06, 1, true, true);
    tst_address_match("!9*.V=06", "89999999", MANUFACTURER_ABB, 0x06, 1, false, true);
    tst_address_match("!9*.V=06", "99999999", MANUFACTURER_ABB, 0x07, 1, false, true);
    tst_address_match("!9*.V=06", "89999999", MANUFACTURER_ABB, 0x07, 1, false, true);

    tst_telegram_match("12345678", "12345678", true, false);
    tst_telegram_match("11111111,22222222", "12345678,22*", true, true);
    tst_telegram_match("11111111,22222222", "12345678,22222222", true, false);
    tst_telegram_match("11111111.M=KAM,22222222.M=PII", "11111111.M=KAM", true, false);
    tst_telegram_match("11111111.M=KAF", "11111111.M=KAM", false, false);

    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAM", true, false);
    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAF", false, false);
    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111", true, false);
    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAM", true, false);
    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.V=1b", true, false);
    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.T=16", true, false);
    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAM.T=16", true, false);
    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAM.V=1b", true, false);
    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.T=16.V=1b", true, false);

    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAL", false, false);
    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.V=1c", false, false);
    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.T=17", false, false);
    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAM.T=17", false, false);
    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.M=KAL.V=1b", false, false);
    tst_telegram_match("11111111.M=KAM.V=1b.T=16", "11111111.T=17.V=1b", false, false);

    // Test * matches both 11111111 and 2222222 but the only the 111111 matches the filter out V=1b.
    // Verify that the filter out !1*.V=1b will override successfull match (with no filter out) * for 22222222.
    tst_telegram_match("11111111.M=KAM.V=1b.T=16,22222222.M=XXX.V=aa.T=99", "*,!1*.V=1b", false, true);
}

void eq(string a, string b, const char *tn)
{
    if (a != b)
    {
        printf("ERROR in test %s expected \"%s\" to be equal to \"%s\"\n", tn, a.c_str(), b.c_str());
    }
}

void eqn(int a, int b, const char *tn)
{
    if (a != b)
    {
        printf("ERROR in test %s expected %d to be equal to %d\n", tn, a, b);
    }
}

void test_kdf()
{
    vector<uchar> key;
    vector<uchar> input;
    vector<uchar> mac;

    hex2bin("2b7e151628aed2a6abf7158809cf4f3c", &key);
    mac.resize(16);

    AES_CMAC(safeButUnsafeVectorPtr(key),
             safeButUnsafeVectorPtr(input), 0,
             safeButUnsafeVectorPtr(mac));
    string s = bin2hex(mac);
    string ex = "BB1D6929E95937287FA37D129B756746";
    if (s != ex)
    {
        printf("ERROR in aes-cmac expected \"%s\" but got \"%s\"\n", ex.c_str(), s.c_str());
    }


    input.clear();
    hex2bin("6bc1bee22e409f96e93d7e117393172a", &input);
    AES_CMAC(safeButUnsafeVectorPtr(key),
             safeButUnsafeVectorPtr(input), 16,
             safeButUnsafeVectorPtr(mac));
    s = bin2hex(mac);
    ex = "070A16B46B4D4144F79BDD9DD04A287C";

    if (s != ex)
    {
        printf("ERROR in aes-cmac expected \"%s\" but got \"%s\"\n", ex.c_str(), s.c_str());
    }
}

void testp(time_t now, string period, bool expected)
{
    bool rc = isInsideTimePeriod(now, period);

    char buf[256];
    struct tm now_tm {};
    localtime_r(&now, &now_tm);
    strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M %A", &now_tm);
    string nows = buf;

    if (expected == true && rc == false)
    {
        printf("ERROR in period test is \"%s\" in period \"%s\"? Expected true but got false!\n", nows.c_str(), period.c_str());
    }
    if (expected == false && rc == true)
    {
        printf("ERROR in period test is \"%s\" in period \"%s\"? Expected false but got true!\n", nows.c_str(), period.c_str());
    }
}

void test_periods()
{
    // 3600*24*7+3600 means 1970-01-08 01:00 Thursday in Greenwich.
    // However your local time is adjusted with your timezone.
    // Get your timezone offset tm_gmtoff into the value.
    time_t t = 3600*24*7+3600;
    struct tm value {};
    localtime_r(&t, &value);

    // if tm_gmtoff is zero, then we are in Greenwich!
    // if tm_gmtoff is 3600 then we are in Stockholm!

    t -= value.tm_gmtoff;

    // We have now adjusted the t so that it should be thursday at 1 am.
    // The following test should therefore work inependently on
    // where in the world this test is run.
    testp(t, "mon-sun(00-23)", true);
    testp(t, "mon(00-23)", false);
    testp(t, "thu-fri(01-01)", true);
    testp(t, "mon-wed(00-23),thu(02-23),fri-sun(00-23)", false);
    testp(t, "mon-wed(00-23),thu(01-23),fri-sun(00-23)", true);
    testp(t, "thu(00-00)", false);
    testp(t, "thu(01-01)", true);
}

void testd(string arg, bool xok, string xalias, string xfile, string xtype, string xid, string xextras,
           string xfq, string xbps, string xlm, string xcmd)
{
    SpecifiedDevice d;
    bool ok = d.parse(arg);
    if (ok != xok)
    {
        printf("ERROR in device parse test \"%s\" expected %s but got %s\n", arg.c_str(), xok?"OK":"FALSE", ok?"OK":"FALSE");
        return;
    }
    if (ok == false) return;

    if (d.bus_alias != xalias ||
        d.file != xfile ||
        toString(d.type) != xtype ||
        d.id != xid ||
        d.extras != xextras ||
        d.fq != xfq ||
        d.bps != xbps ||
        d.linkmodes.hr() != xlm ||
        d.command != xcmd)
    {
        printf("ERROR in bus device parsing parts \"%s\" - got\n"
               "alias: \"%s\", file: \"%s\", type: \"%s\", id: \"%s\", extras: \"%s\", fq: \"%s\", bps: \"%s\", lm: \"%s\", cmd: \"%s\"\n"
               "but expected:\n"
               "alias: \"%s\", file: \"%s\", type: \"%s\", id: \"%s\", extras: \"%s\", fq: \"%s\", bps: \"%s\", lm: \"%s\", cmd: \"%s\"\n",

               arg.c_str(),
               d.bus_alias.c_str(),
               d.file.c_str(),
               toString(d.type),
               d.id.c_str(),
               d.extras.c_str(),
               d.fq.c_str(),
               d.bps.c_str(),
               d.linkmodes.hr().c_str(),
               d.command.c_str(),

               xalias.c_str(),
               xfile.c_str(),
               xtype.c_str(),
               xid.c_str(),
               xextras.c_str(),
               xfq.c_str(),
               xbps.c_str(),
               xlm.c_str(),
               xcmd.c_str());

    }
}

void test_device_parsing()
{
    testd("Bus_4711=/dev/ttyUSB0:im871a[12345678]:9600:868.95M:c1,t1", true,
          "Bus_4711", // alias
          "/dev/ttyUSB0", // file
          "im871a", // type
          "12345678", // id
          "", // extras
          "868.95M", // fq
          "9600", // bps
          "t1,c1", // linkmodes
          ""); // command

    testd("/dev/ttyUSB0:im871a:c1", true,
          "", // alias
          "/dev/ttyUSB0", // file
          "im871a", // type
          "", // id
          "", // extras
          "", // fq
          "", // bps
          "c1", // linkmodes
          ""); // command

    testd("im871a[12345678]:c1", true,
          "", // alias
          "", // file
          "im871a", // type
          "12345678", // id
          "", // extras
          "", // fq
          "", // bps
          "c1", // linkmodes
          ""); // command

    testd("im871a(track=7,pi=3.14):c1", true,
          "", // alias
          "", // file
          "im871a", // type
          "", // id
          "track=7,pi=3.14", // extras
          "", // fq
          "", // bps
          "c1", // linkmodes
          ""); // command

    testd("rtlwmbus:c1,t1:CMD(gurka)", true,
          "", // alias
          "", // file
          "rtlwmbus", // type
          "", // id
          "", // extras
          "", // fq
          "", // bps
          "t1,c1", // linkmodes
          "gurka"); // command

    testd("rtlwmbus[plast]:c1,t1", true,
          "", // alias
          "", // file
          "rtlwmbus", // type
          "plast", // id
          "", // extras
          "", // fq
          "", // bps
          "t1,c1", // linkmodes
          ""); // command

    testd("ANTENNA1=rtlwmbus[plast](ppm=5):c1,t1", true,
          "ANTENNA1", // alias
          "", // file
          "rtlwmbus", // type
          "plast", // id
          "ppm=5", // extras
          "", // fq
          "", // bps
          "t1,c1", // linkmodes
          ""); // command

    testd("stdin:rtlwmbus", true,
          "", // alias
          "stdin", // file
          "rtlwmbus", // type
          "", // id
          "", // extras
          "", // fq
          "", // bps
          "none", // linkmodes
          ""); // command

    testd("/dev/ttyUSB0:rawtty:9600", true,
          "", // alias
          "/dev/ttyUSB0", // file
          "rawtty", // type
          "", // id
          "", // extras
          "", // fq
          "9600", // bps
          "none", // linkmodes
          ""); // command

    // testinternals must be run from a location where
    // there is a Makefile.
    testd("Makefile:simulation", true,
          "", // alias
          "Makefile", // file
          "simulation", // type
          "", // id
          "", // extras
          "", // fq
          "", // bps
          "none", // linkmodes
          ""); // command

    testd("auto:c1,t1", true,
          "", // alias
          "", // file
          "auto", // type
          "", // id
          "", // extras
          "", // fq
          "", // bps
          "t1,c1", // linkmodes
          ""); // command

    testd("auto:Makefile:c1,t1", false,
          "", // alias
          "", // file
          "", // type
          "", // id
          "", // extras
          "", // fq
          "", // bps
          "none", // linkmodes
          ""); // command

    testd("Vatten", false,
          "", // alias
          "", // file
          "", // type
          "", // id
          "", // extras
          "", // fq
          "", // bps
          "none", // linkmodes
          ""); // command

    testd("main=/dev/ttyUSB0:mbus:2400", true,
          "main", // alias
          "/dev/ttyUSB0", // file
          "mbus", // type
          "", // id
          "", // extras
          "", // fq
          "2400", // bps
          "none", // linkmodes
          ""); // command

    // Support : inside the command.
    testd("cul:c1:CMD(socat TCP:CUNO:2323 STDIO)", true,
          "", // alias
          "", // file
          "cul", // type
          "", // id
          "", // extras
          "", // fq
          "", // bps
          "c1", // linkmodes
          "socat TCP:CUNO:2323 STDIO"); // command
}

void test_month(int y, int m, int day, int mdiff, string from, string to)
{
    struct tm date {};
    date.tm_year  = y-1900;
    date.tm_mon   = m-1;
    date.tm_mday  = day;

    string s = strdate(&date);

    struct tm d;
    d = date;
    addMonths(&d, mdiff);

    string os = strdate(&d);

    if (s != from ||
        os != to)
    {
        printf("ERROR! Expected %s + %d months should be %s\n"
               "But got %s - 11 = %s\n",
               from.c_str(), mdiff, to.c_str(),
               s.c_str(), os.c_str());
    }
}

void test_months()
{
    test_month(2020,12,31, 2, "2020-12-31", "2021-02-28");
    test_month(2020,12,31,-10, "2020-12-31", "2020-02-29");
    test_month(2021,01,31,-1,  "2021-01-31", "2020-12-31");
    test_month(2021,01,31,-2,  "2021-01-31", "2020-11-30");
    test_month(2021,01,31,-24, "2021-01-31", "2019-01-31");
    test_month(2021,01,31, 24, "2021-01-31", "2023-01-31");
    test_month(2021,01,31, 22, "2021-01-31", "2022-11-30");

    // 2020 was a leap year.
    test_month(2021,02,28, -12, "2021-02-28", "2020-02-29");
    // 2000 was a leap year %100=0 but %400=0 overrides.
    test_month(2001,02,28, -12, "2001-02-28", "2000-02-29");
    // 2100 is not a leap year since %100=0 and not overriden %400 != 0.
    test_month(2000,02,29, 12*100, "2000-02-29", "2100-02-28");
}

// Vatten    multical21:BUS1:c1 12345678 KEY
// Tempmeter piigth(info=123):BUS2:2400   0        NOKEY

void testm(string arg, bool xok,
           string xdriver, string xextras, string xbus, string xbps, string xlm)
{
    MeterInfo mi;
    bool ok = mi.parse("", arg, "12345678", "");
    if (ok != xok)
    {
        printf("ERROR in meter parse test \"%s\" expected %s but got %s\n", arg.c_str(), xok?"OK":"FALSE", ok?"OK":"FALSE");
        return;
    }
    if (ok == false) return;

    bool driver_ok = mi.driverName().str() == xdriver;
    bool extras_ok = mi.extras == xextras;
    bool bus_ok = mi.bus == xbus;
    bool bps_ok = to_string(mi.bps) == xbps;
    bool link_modes_ok =  mi.link_modes.hr() == xlm;

    if (!driver_ok || !extras_ok || !bus_ok || !bps_ok || !link_modes_ok)
    {
        printf("ERROR in meterc parsing parts \"%s\" got\n"
               "driver: \"%s\", extras: \"%s\", bus: \"%s\", bbps: \"%s\", linkmodes: \"%s\"\n"
               "but expected\n"
               "driver: \"%s\", extras: \"%s\", bus: \"%s\", bbps: \"%s\", linkmodes: \"%s\"\n",

               arg.c_str(),

               mi.driverName().str().c_str(),
               mi.extras.c_str(),
               mi.bus.c_str(),
               to_string(mi.bps).c_str(),
               mi.link_modes.hr().c_str(),

               xdriver.c_str(),
               xextras.c_str(),
               xbus.c_str(),
               xbps.c_str(),
               xlm.c_str()
            );
    }
}

void testc(string file, string file_content,
    string xdriver, string xextras, string xbus, string xbps, string xlm)
{
    MeterInfo mi;
    Configuration c;

    vector<char> meter_conf(file_content.begin(), file_content.end());
    meter_conf.push_back('\n');

    parseMeterConfig(&c, meter_conf, file);

    assert(c.meters.size() > 0);

    mi = c.meters.back();

    if (mi.driverName().str() != xdriver ||
        mi.extras != xextras ||
        mi.bus != xbus ||
        to_string(mi.bps) != xbps ||
        mi.link_modes.hr() != xlm)
    {
        printf("ERROR in meterc parsing parts \"%s\" got\n"
               "driver: \"%s\", extras: \"%s\", bus: \"%s\", bbps: \"%s\", linkmodes: \"%s\"\n"
               "but expected\n"
               "driver: \"%s\", extras: \"%s\", bus: \"%s\", bbps: \"%s\", linkmodes: \"%s\"\n",

               file.c_str(),

               mi.driverName().str().c_str(),
               mi.extras.c_str(),
               mi.bus.c_str(),
               to_string(mi.bps).c_str(),
               mi.link_modes.hr().c_str(),

               xdriver.c_str(),
               xextras.c_str(),
               xbus.c_str(),
               xbps.c_str(),
               xlm.c_str()
            );
    }
}

void test_meters()
{
    string config_content;

    testm("piigth:BUS1:2400", true,
          "piigth", // driver
          "", // extras
          "BUS1", // bus
          "2400", // bps
          "none"); // linkmodes

    testm("c5isf:MAINO:9600:mbus", true,
          "c5isf", // driver
          "", // extras
          "MAINO", // bus
          "9600", // bps
          "mbus"); // linkmodes

    testm("c5isf:DONGLE:t1", true,
          "c5isf", // driver
          "", // extras
          "DONGLE", // bus
          "0", // bps
          "t1"); // linkmodes

    testm("c5isf:t1,c1,mbus", true,
          "c5isf", // driver
          "", // extras
          "", // bus
          "0", // bps
          "mbus,t1,c1"); // linkmodes

    /*
    config_content =
        "name=test\n"
        "driver=piigth:BUS1:2400:mbus\n"
        "id=01234567\n";

    testc("meter/piigth:BUS1:2400", config_content,
          "piigth", // driver
          "", // extras
          "BUS1", // bus
          "2400", // bps
          "mbus"); // linkmodes)
    */

    testm("multical21:c1", true,
          "multical21", // driver
          "", // extras
          "", // bus
          "0", // bps
          "c1"); // linkmodes

    config_content =
        "name=test\n"
        "driver=multical21:c1\n"
        "id=01234567\n";
    testc("meter/multical21:c1", config_content,
          "multical21", // driver
          "", // extras
          "", // bus
          "0", // bps
          "c1"); // linkmodes)


    testm("unknown(offset=162)", true,
          "unknown", // driver
          "offset=162", // extras
          "", // bus
          "0", // bps
          "none"); // linkmodes

    config_content =
        "name=test\n"
        "driver=unknown(offset=99)\n"
        "id=01234567\n"
        "key=00000000000000000000000000000000\n";
    testc("meter/apatortest", config_content,
          "unknown", // driver
          "offset=99", // extras
          "", // bus
          "0", // bps
          "none"); // linkmodes)

}

void tests(string arg, bool expect, LinkMode link_mode, TelegramFormat format, string bus, string content)
{
    SendBusContent sbc;
    bool rc = sbc.parse(arg);
    if (rc != expect && rc == false)
    {
        printf("ERROR could not parse send bus content \"%s\"\n", arg.c_str());
        return;
    }
    if (rc != expect && rc == true)
    {
        printf("ERROR could parse send bus content \"%s\" but expected failure!\n", arg.c_str());
        return;
    }

    if (expect == false && rc == false) return; // It failed, which was expected.

    if (sbc.link_mode != link_mode ||
        sbc.format != format ||
        sbc.bus != bus ||
        sbc.content != content)
    {
        printf("ERROR in parsing send bus content \"%s\"\n"
               "got      (link_mode: %s format: %s bus: %s, data: %s)\n"
               "expected (link_mode: %s format: %s bus: %s, data: %s)\n", arg.c_str(),
               toString(sbc.link_mode), toString(sbc.format), sbc.bus.c_str(), sbc.content.c_str(),
               toString(link_mode), toString(format), bus.c_str(), content.c_str());
    }
}

void test_sbc()
{
    tests("send:t1:wmbus_c_field:BUS1:11223344", true,
          LinkMode::T1,
          TelegramFormat::WMBUS_C_FIELD,
          "BUS1", // bus
          "11223344"); // content

    tests("send:c1:wmbus_ci_field:alfa:11", true,
          LinkMode::C1,
          TelegramFormat::WMBUS_CI_FIELD,
          "alfa", // bus
          "11"); // content

    tests("send:t2:wmbus_c_field:OUTBUS:1122334455", true,
          LinkMode::T2,
          TelegramFormat::WMBUS_C_FIELD,
          "OUTBUS", // bus
          "1122334455"); // content

    tests("alfa:t1", false, LinkMode::UNKNOWN, TelegramFormat::UNKNOWN, "", "");
    tests("send", false, LinkMode::UNKNOWN, TelegramFormat::UNKNOWN, "", "");
    tests("send:::::::::::", false, LinkMode::UNKNOWN, TelegramFormat::UNKNOWN, "", "");
    tests("send:foo", false, LinkMode::UNKNOWN, TelegramFormat::UNKNOWN, "", "");
    tests("send:t2:wmbus_c_field:OUT:", false, LinkMode::UNKNOWN, TelegramFormat::UNKNOWN, "", "");
    tests("send:t2:wmbus_c_field:OUT:1", false, LinkMode::UNKNOWN, TelegramFormat::UNKNOWN, "", "");

    tests("send:mbus:mbus_short_frame:out:5b00", true,
          LinkMode::MBUS,
          TelegramFormat::MBUS_SHORT_FRAME,
          "out", // bus
          "5b00"); // content

    tests("send:mbus:mbus_long_frame:mbus2:1122334455", true,
          LinkMode::MBUS,
          TelegramFormat::MBUS_LONG_FRAME,
          "mbus2", // bus
          "1122334455"); // content*/
}

void test_aes()
{
    vector<uchar> key;

    hex2bin("0123456789abcdef0123456789abcdef", &key);
    string poe =
        "Once upon a midnight dreary, while I pondered, weak and weary,\n"
        "Over many a quaint and curious volume of forgotten lore\n";

    while (poe.length() % 16 != 0)
    {
        poe += ".";
    }

    uchar iv[16];
    memset(iv, 0xaa, 16);

    uchar in[poe.length()];
    memcpy(in, &poe[0], poe.size());

    debug("(aes) input: \"%s\"\n", poe.c_str());

    uchar out[sizeof(in)];
    AES_CBC_encrypt_buffer(out, in, sizeof(in), safeButUnsafeVectorPtr(key), iv);

    vector<uchar> outv(out, out+sizeof(out));
    string s = bin2hex(outv);
    debug("(aes) encrypted: \"%s\"\n", s.c_str());

    uchar back[sizeof(in)];
    AES_CBC_decrypt_buffer(back, out, sizeof(in), safeButUnsafeVectorPtr(key), iv);

    string b = string(back, back+sizeof(back));
    debug("(aes) decrypted: \"%s\"\n", b.c_str());

    if (poe != b)
    {
        printf("ERROR! aes with IV encrypt decrypt failed!\n");
    }

    AES_ECB_encrypt(in, safeButUnsafeVectorPtr(key), out, sizeof(in));
    AES_ECB_decrypt(out, safeButUnsafeVectorPtr(key), back, sizeof(in));

    if (memcmp(back, in, sizeof(in)))
    {
        printf("ERROR! aes encrypt decrypt (no iv) failed!\n");
    }
}

void test_is_hex(const char *hex, bool expected_ok, bool expected_invalid, bool strict)
{
    bool got_invalid;
    bool got_ok;
    if (strict) got_ok = isHexStringStrict(hex, &got_invalid);
    else got_ok = isHexStringFlex(hex, &got_invalid);

    if (got_ok != expected_ok || got_invalid != expected_invalid)
    {
        printf("ERROR! hex string %s was expected to be %d (invalid %d) but got %d (invalid %d)\n",
               hex,
               expected_ok, expected_invalid, got_ok, got_invalid);
    }
}

void test_hex()
{
    test_is_hex("00112233445566778899aabbccddeeff", true, false, true);
    test_is_hex("00112233445566778899AABBCCDDEEFF", true, false, true);
    test_is_hex("00112233445566778899AABBCCDDEEF", true, true, true);
    test_is_hex("00112233445566778899AABBCCDDEEFG", false, false, true);

    test_is_hex("00 11 22 33#44|55#66 778899aabbccddeeff", true, false, false);
    test_is_hex("00 11 22 33#4|55#66 778899aabbccddeeff", true, true, false);
}

void test_translate()
{
    Translate::Lookup lookup1 =
        Translate::Lookup()
        .add(Translate::Rule("ACCESS_BITS", Translate::MapType::BitToString)
             .set(MaskBits(0xf0))
             .add(Translate::Map(0x10, "NO_ACCESS", TestBit::Set))
             .add(Translate::Map(0x20, "ALL_ACCESS", TestBit::Set))
             .add(Translate::Map(0x40, "TEMP_ACCESS", TestBit::Set))
            )
        .add(Translate::Rule("ACCESSOR_TYPE", Translate::MapType::IndexToString)
             .set(MaskBits(0x0f))
             .add(Translate::Map(0x00, "ACCESSOR_RED", TestBit::Set))
             .add(Translate::Map(0x07, "ACCESSOR_GREEN", TestBit::Set))
            )
        ;

   Translate::Lookup lookup2 =
        {
            {
                {
                    "FLOW_FLAGS",
                    Translate::MapType::BitToString,
                    AlwaysTrigger,
                    MaskBits(0x3f),
                    "OOOK",
                    {
                        { 0x01, "BACKWARD_FLOW" },
                        { 0x02, "DRY" },
                        { 0x10, "TRIG" },
                        { 0x20, "COS" },
                    }
                },
            },
        };

   Translate::Lookup lookup3 =
       {
            {
                {
                    "NO_FLAGS",
                    Translate::MapType::BitToString,
                    AlwaysTrigger,
                    MaskBits(0x03),
                    "OK",
                    {
                        // Test that 0x01 is set, means OK (ie installed)
                        // Not set means not installed.
                        { 0x01, "NOT_INSTALLED", TestBit::NotSet },
                        { 0x02, "FOO" },
                    }
                },
            },
        };

    string s, e;
    uint8_t bits;

    bits = 0xa0;
    s = sortStatusString(lookup1.translate(bits));
    e = sortStatusString("ALL_ACCESS ACCESS_BITS_80 ACCESSOR_RED");
    if (s != e)
    {
        printf("ERROR lookup1 0x%02x expected \"%s\" but got \"%s\"\n", bits, e.c_str(), s.c_str());
    }

    bits = 0x35;
    s = sortStatusString(lookup1.translate(bits));
    e = sortStatusString("NO_ACCESS ALL_ACCESS ACCESSOR_TYPE_5");
    if (s != e)
    {
        printf("ERROR lookup1 0x%02x expected \"%s\" but got \"%s\"\n", bits, e.c_str(), s.c_str());
    }

    bits = 0x02;
    s = lookup2.translate(0x02);
    e = "DRY";
    if (s != e)
    {
        printf("ERROR lookup2 0x%02x expected \"%s\" but got \"%s\"\n", bits, e.c_str(), s.c_str());
    }

    bits = 0x00;
    s = lookup2.translate(bits);
    e = "OOOK";
    if (s != e)
    {
        printf("ERROR lookup2 0x%02x expected \"%s\" but got \"%s\"\n", bits, e.c_str(), s.c_str());
    }

    // Verify that the not set 0x01 bit translates to NOT_INSTALLED
    // The set bit 0x02 translates to FOO.
    bits = 0x02;
    s = sortStatusString(lookup3.translate(0x02));
    e = sortStatusString("NOT_INSTALLED FOO");
    if (s != e)
    {
        printf("ERROR lookup3 0x%02x expected \"%s\" but got \"%s\"\n", bits, e.c_str(), s.c_str());
    }

    bits = 0x01;
    s = lookup3.translate(bits);
    e = "OK";
    if (s != e)
    {
        printf("ERROR lookup3 0x%02x expected \"%s\" but got \"%s\"\n", bits, e.c_str(), s.c_str());
    }

}

void test_slip()
{
    vector<uchar> from = { 1, 0xc0, 3, 4, 5, 0xdb };
    vector<uchar> expected_to = { 0xc0, 1, 0xdb, 0xdc, 3, 4, 5, 0xdb, 0xdd, 0xc0 };
    vector<uchar> to;
    vector<uchar> back;

    addSlipFraming(from, to);

    if (expected_to != to)
    {
        printf("ERROR slip 1\n");
    }

    size_t frame_length = 0;
    removeSlipFraming(to, &frame_length, back);

    if (back != from)
    {
        printf("ERROR slip 2\n");
    }

    if (to.size() != frame_length)
    {
        printf("ERROR slip 3\n");
    }

    vector<uchar> more = { 0xc0, 0xc0, 0xc0, 1, 2, 3, 4, 5, 6, 7, 8 };
    addSlipFraming(more, to);

    frame_length = 0;
    removeSlipFraming(to, &frame_length, back);

    if (back != from)
    {
        printf("ERROR slip 4\n");
    }

    to.erase(to.begin(), to.begin()+frame_length);
    removeSlipFraming(to, &frame_length, back);

    if (back != more)
    {
        printf("ERROR slip 5\n");
    }

    vector<uchar> again = { 0xc0 };
    removeSlipFraming(again, &frame_length, back);

    if (frame_length != 0)
    {
        printf("ERROR slip 6\n");
    }

    vector<uchar> againn = { 0xc0, 1, 2, 3, 4, 5 };
    removeSlipFraming(againn, &frame_length, back);

    if (frame_length != 0)
    {
        printf("ERROR slip 7\n");
    }

}

void test_dvs()
{
    DifVifKey dvk("0B2B");

    if (dvk.dif() != 0x0b || dvk.vif() != 0x2b || dvk.hasDifes() || dvk.hasVifes())
    {
        printf("ERROR test_dvs 1\n");
    }
}

void test_ascii_detection()
{
    string s = "000008";
    if (isLikelyAscii(s))
    {
        printf("ERROR >%s< should not be likely ascii\n", s.c_str());
    }
    s = "41424344";
    if (!isLikelyAscii(s))
    {
        printf("ERROR >%s< should be likely ascii\n", s.c_str());
    }
    s = "000041424344";
    if (!isLikelyAscii(s))
    {
        printf("ERROR >%s< should be likely ascii\n", s.c_str());
    }
    s = "000041194300";
    if (isLikelyAscii(s))
    {
        printf("ERROR >%s< should not be likely ascii\n", s.c_str());
    }
}

void test_join(string a, string b, string s)
{
    string t = joinStatusOKStrings(a, b);
    if (t != s)
    {
        printf("Expected joinStatusString(\"%s\",\"%s\") to be \"%s\" but got \"%s\"\n",
               a.c_str(), b.c_str(), s.c_str(), t.c_str());
    }

}

void test_status_join()
{
    test_join("OK", "OK", "OK");
    test_join("", "", "OK");
    test_join("OK", "", "OK");
    test_join("", "OK", "OK");
    test_join("null", "OK", "OK");
    test_join("null", "null", "OK");
    test_join("ERROR FLOW", "OK", "ERROR FLOW");
    test_join("ERROR FLOW", "", "ERROR FLOW");
    test_join("OK", "ERROR FLOW", "ERROR FLOW");
    test_join("", "ERROR FLOW", "ERROR FLOW");
    test_join("ERROR", "FLOW", "ERROR FLOW");
    test_join("ERROR", "null", "ERROR");
    test_join("A B C", "D E F G", "A B C D E F G");
}

void test_sort(string in, string out)
{
    string t = sortStatusString(in);
    if (t != out)
    {
        printf("Expected sortStatusString(\"%s\") to be \"%s\" but got \"%s\"\n",
               in.c_str(), out.c_str(), t.c_str());
    }
}

void test_status_sort()
{
    test_sort("C B A", "A B C");
    test_sort("ERROR BUSY FLOW ERROR", "BUSY ERROR FLOW");
    test_sort("X X X Y Y Z A B C A A AAAA AA AAA", "A AA AAA AAAA B C X Y Z");
}

void test_field_matcher()
{
    // 04 dif (32 Bit Integer/Binary Instantaneous value)
    // 13 vif (Volume l)
    // 2F4E0000 ("total_m3":20.015)

    FieldMatcher m1 = FieldMatcher::build()
        .set(MeasurementType::Instantaneous)
        .set(VIFRange::Volume);

    string v1 = "2F4E0000";
    DVEntry e1(0,
               DifVifKey("0413"),
               MeasurementType::Instantaneous,
               Vif(0x13),
               { },
               { },
               StorageNr(0),
               TariffNr(0),
               SubUnitNr(0),
               v1);

    if (!m1.matches(e1))
    {
        printf("ERROR expected match for field matcher test 1 !\n");
    }

    // 81 dif (8 Bit Integer/Binary Instantaneous value)
    // 01 dife (subunit=0 tariff=0 storagenr=2)
    // 10 vif (Volume)
    // FC combinable vif (Extension)
    // 0C combinable vif (DeltaBetween...)
    // 03 ("external_temperature_c":3)

    FieldMatcher m2 = FieldMatcher::build()
        .set(MeasurementType::Instantaneous)
        .set(StorageNr(2))
        .set(VIFRange::Volume)
        .add(VIFCombinable::Any);

    string v2 = "03";
    DVEntry e2(0,
               DifVifKey("810110FC0C"),
               MeasurementType::Instantaneous,
               Vif(0x10),
               { VIFCombinable::DeltaBetweenImportAndExport },
               { },
               StorageNr(2),
               TariffNr(0),
               SubUnitNr(0),
               v2);

    if (!m2.matches(e2))
    {
        printf("ERROR expected match for field matcher test 2 !\n");
    }

    FieldMatcher m3 = FieldMatcher::build()
        .set(MeasurementType::Instantaneous)
        .set(StorageNr(2))
        .set(VIFRange::Volume)
        .add(VIFCombinable::DeltaBetweenImportAndExport);

    if (!m3.matches(e2))
    {
        printf("ERROR expected match for field matcher test 3 !\n");
    }

    FieldMatcher m4 = FieldMatcher::build()
        .set(MeasurementType::Instantaneous)
        .set(StorageNr(2))
        .set(VIFRange::Volume)
        .add(VIFCombinable::ValueDuringUpperLimitExceeded);

    if (m4.matches(e2))
    {
        printf("ERROR expected NO match for field matcher test 4 !\n");
    }

}

void test_unit(string in, bool expected_ok, string expected_vname, Unit expected_unit)
{
    Unit unit;
    string vname;

    bool ok = extractUnit(in, &vname, &unit);

    if (ok != expected_ok ||
        vname != expected_vname ||
        unit != expected_unit)
    {
        printf("ERROR expected ok=%d vname=%s unit=%s but got\n"
               "      but got  ok=%d vname=%s unit=%s\n",
               expected_ok, expected_vname.c_str(), unitToStringUpperCase(expected_unit).c_str(),
               ok, vname.c_str(), unitToStringUpperCase(unit).c_str());
    }
}

void test_units_extraction()
{
    test_unit("total_kwh", true, "total", Unit::KWH);
    test_unit("total_", false, "", Unit::Unknown);
    test_unit("total", false, "", Unit::Unknown);
    test_unit("", false, "", Unit::Unknown);
    test_unit("_c", false, "", Unit::Unknown);

    test_unit("work__c", true, "work_", Unit::C);

    test_unit("water_c", true, "water", Unit::C);
    test_unit("walk_counter", true, "walk", Unit::COUNTER);
    test_unit("work_kvarh", true, "work", Unit::KVARH);

    test_unit("now_mjh", true, "now", Unit::MJH);

    test_unit("current_power_consumption_phase1_kw", true, "current_power_consumption_phase1", Unit::KW);
}

void test_expected_failed_si_convert(Unit from_unit,
                                     Unit to_unit,
                                     Quantity q)
{
    SIUnit from_si_unit(from_unit);
    SIUnit to_si_unit(to_unit);
    string fu = unitToStringLowerCase(from_si_unit.asUnit());
    string tu = unitToStringLowerCase(to_si_unit.asUnit());

    if (q != from_si_unit.quantity() || q != to_si_unit.quantity())
    {
        printf("ERROR! Not the expected quantities!\n");
    }
    if (from_si_unit.convertTo(0, to_si_unit, NULL))
    {
        printf("ERROR! Should not be able to convert from %s to %s !\n", fu.c_str(), tu.c_str());
    }
}

void test_si_convert(double from_value, double expected_value,
                     Unit from_unit,
                     string expected_from_unit,
                     Unit to_unit,
                     string expected_to_unit,
                     Quantity q,
                     set<Unit> *from_set,
                     set<Unit> *to_set)
{
    debug("test_si_convert from %.17g %s to %.17g %s\n",
          from_value, expected_from_unit.c_str(),
          expected_value, expected_to_unit.c_str());

    string evs = tostrprintf("%.15g", expected_value);

    SIUnit from_si_unit(from_unit);
    SIUnit to_si_unit(to_unit);
    string fu = unitToStringLowerCase(from_si_unit.asUnit(q));
    string tu = unitToStringLowerCase(to_si_unit.asUnit(q));

    from_set->erase(from_unit);
    to_set->erase(to_unit);

    double e {};
    from_si_unit.convertTo(from_value, to_si_unit, &e);
    string es = tostrprintf("%.15g", e);

    if (canConvert(from_unit, to_unit))
    {
        // Test if conversion was the same using 16 significant digits.
        // I.e. slightly less than the maximum 17 significant digits.
        // Takes up the slack between the old style conversion and the new style conversion
        // which can introduce minor changes in the final digit.
        double ee = convert(from_value, from_unit, to_unit);
        string ees = tostrprintf("%.15g", ee);
        if (es != ees)
        {
            printf("ERROR! SI unit conversion %.15g (%s) from %.15g differs from unit conversion %.15g (%s)! \n",
                   e, es.c_str(), from_value, ee, ees.c_str());
        }
    }
    if (fu != expected_from_unit)
    {
        printf("ERROR! Expected from unit %s (but got %s) when converting si unit %s\n",
               expected_from_unit.c_str(), fu.c_str(), from_si_unit.str().c_str());
    }
    if (tu != expected_to_unit)
    {
        printf("ERROR! Expected to unit %s (but got %s) when converting si unit %s\n",
               expected_to_unit.c_str(), tu.c_str(), to_si_unit.str().c_str());
    }
    if (es != evs)
    {
        printf("ERROR! Expected %.17g [%s] (but got %.17g [%s]) when converting %.17g from %s (%s) to %s (%s)\n",
               expected_value, evs.c_str(), e, es.c_str(), from_value,
               from_si_unit.str().c_str(),
               fu.c_str(),
               to_si_unit.str().c_str(),
               tu.c_str());
    }
}

void test_si_units_siexp()
{
    // m3/s
    SIExp e = SIExp::build().s(-1).m(3);
    if (e.str() != "m³s⁻¹") { printf("ERROR Expected m³s⁻¹ but got \"%s\"\n", e.str().c_str()); }

    SIExp f = SIExp::build().s(1);
    if (f.str() != "s") { printf("ERROR Expected s but got \"%s\"\n", f.str().c_str()); }

    SIExp g = e.mul(f);
    if (g.str() != "m³") { printf("ERROR Expected m³ but got \"%s\"\n", g.str().c_str()); }

    SIExp h = SIExp::build().s(127);

    // Test overflow of exponent for seconds!
    SIExp i = h.mul(f);
    if (i.str() != "!s⁻¹²⁸-Invalid!") { printf("ERROR Expected !s⁻¹²⁸-Invalid! but got \"%s\"\n", i.str().c_str()); }

    SIExp j = e.div(e);
    if (j.str() != "") { printf("ERROR Expected \"\" but got \"%s\"\n", j.str().c_str()); }

    SIExp bad = SIExp::build().k(1).c(1);
    if (bad.str() != "!kc-Invalid!") { printf("ERROR Expected !kc-Invalid! but got \"%s\"\n", bad.str().c_str()); }

}

void test_si_units_basic()
{
    // A kilowatt unit generated from scratch:
    SIUnit kwh(Quantity::Energy, 3.6E6, SIExp().kg(1).m(2).s(-2));

    string expected = "3.6×10⁶kgm²s⁻²";
    if (kwh.str() != expected) printf("ERROR expected kwh to be %s but got %s\n", expected.c_str(), kwh.str().c_str());

    // A kilowatt unit from the unit lookup table.
    SIUnit kwh2(Unit::KWH);

    if (kwh2.str() != expected) printf("ERROR expected second kwh to be %s but got %s\n", expected.c_str(), kwh2.str().c_str());

    // A Celsius unit generated from scratch:
    SIUnit celsius(Quantity::Temperature, 1, SIExp().c(1));

    expected = "1c";
    if (celsius.str() != expected) printf("ERROR expected celsius to be %s but got %s\n", expected.c_str(), celsius.str().c_str());

    // A celsius unit from the Unit.
    SIUnit celsius2(Unit::C);

    if (celsius2.str() != expected) printf("ERROR expected second celsius to be %s but got %s\n", expected.c_str(), celsius2.str().c_str());
}

void fill_with_units_from(Quantity q, set<Unit> *s)
{
    s->clear();
#define X(cname,lcname,hrname,quantity,explanation) if (q == Quantity::quantity) s->insert(Unit::cname);
LIST_OF_UNITS
#undef X
}

void check_units_tested(set<Unit> &from_set, set<Unit> &to_set, Quantity q)
{
    if (from_set.size() > 0)
    {
        printf("ERROR not all units as source in quantity %s tested! Remaining: ", toString(q));
        for (Unit u : from_set) printf("%s ", unitToStringLowerCase(u).c_str());
        printf("\n");
    }
    if (to_set.size() > 0)
    {
        printf("ERROR not all units as targets in quantity %s tested! Remaining: ", toString(q));
        for (Unit u : to_set) printf("%s ", unitToStringLowerCase(u).c_str());
        printf("\n");
    }
}

void check_quantities_tested(set<Quantity> &s)
{
    if (s.size() > 0)
    {
        printf("ERROR not all quantities tested! Remaining: ");
        for (Quantity q : s) printf("%s ", toString(q));
        printf("\n");
    }
}

void test_si_units_conversion()
{
    set<Quantity> q_set;
    set<Unit> from_set;
    set<Unit> to_set;

#define X(quantity,default_unit) q_set.insert(Quantity::quantity);
LIST_OF_QUANTITIES
#undef X

    // Test time units: s, min, h, d, y
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Time);
    fill_with_units_from(Quantity::Time, &from_set);
    fill_with_units_from(Quantity::Time, &to_set);

    // 60 seconds is one minute.
    test_si_convert(60.0, 1.0, Unit::Second, "s", Unit::Minute, "min", Quantity::Time, &from_set, &to_set);
    // 3600 seconds is one hour.
    test_si_convert(3600.0, 1.0, Unit::Second, "s", Unit::Hour, "h", Quantity::Time, &from_set, &to_set);
    // 3600 seconds is 1/24 of a day which is 0.041666666666666664.
    test_si_convert(3600.0, 0.041666666666666664, Unit::Second, "s", Unit::Day, "d", Quantity::Time, &from_set, &to_set);
    // Same test again.
    test_si_convert(3600.0, 1.0/24.0, Unit::Second, "s", Unit::Day, "d", Quantity::Time, &from_set, &to_set);
    // 1 min is 60 seconds.
    test_si_convert(1.0, 60.0, Unit::Minute, "min", Unit::Second, "s", Quantity::Time, &from_set, &to_set);
    // 1 day is 24 hours
    test_si_convert(1.0, 24, Unit::Day, "d", Unit::Hour, "h", Quantity::Time, &from_set, &to_set);
    // 1 month is 1 month.
    test_si_convert(1.0, 1.0, Unit::Month, "month", Unit::Month, "month", Quantity::Time, &from_set, &to_set);
    test_si_convert(1.0, 1.0, Unit::Year, "y", Unit::Year, "y", Quantity::Time, &from_set, &to_set);
    // 100 hours is 100/24 days.
    test_si_convert(100.0, 100.0/24.0, Unit::Hour, "h", Unit::Day, "d", Quantity::Time, &from_set, &to_set);
    // 1 year is 365.2425 days.
//    test_si_convert(1.0, 365.2425, Unit::Year, "y", Unit::Day, "d", Quantity::Time, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Time);

    // Test length units: m
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Length);
    fill_with_units_from(Quantity::Length, &from_set);
    fill_with_units_from(Quantity::Length, &to_set);

    test_si_convert(111.1, 111.1, Unit::M, "m", Unit::M, "m", Quantity::Length, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Length);

    // Test mass units: kg
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Mass);
    fill_with_units_from(Quantity::Mass, &from_set);
    fill_with_units_from(Quantity::Mass, &to_set);

    test_si_convert(222.1, 222.1, Unit::KG, "kg", Unit::KG, "kg", Quantity::Mass, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Mass);

    // Test electrical current units: a
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Amperage);
    fill_with_units_from(Quantity::Amperage, &from_set);
    fill_with_units_from(Quantity::Amperage, &to_set);

    test_si_convert(999.9, 999.9, Unit::Ampere, "a", Unit::Ampere, "a", Quantity::Amperage, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Amperage);

    // Test temperature units: c k f
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Temperature);
    fill_with_units_from(Quantity::Temperature, &from_set);
    fill_with_units_from(Quantity::Temperature, &to_set);

    test_si_convert(0, 273.15, Unit::C, "c", Unit::K, "k", Quantity::Temperature, &from_set, &to_set);
    test_si_convert(10.85, 284.0, Unit::C, "c", Unit::K, "k", Quantity::Temperature, &from_set, &to_set);
    test_si_convert(100.0, -173.15, Unit::K, "k", Unit::C, "c", Quantity::Temperature, &from_set, &to_set);
    test_si_convert(100.0, -279.67, Unit::K, "k", Unit::F, "f", Quantity::Temperature, &from_set, &to_set);
    test_si_convert(100.0, 37.77777777777777, Unit::F, "f", Unit::C, "c", Quantity::Temperature, &from_set, &to_set);
    test_si_convert(0.0, -17.7777777777778, Unit::F, "f", Unit::C, "c", Quantity::Temperature, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Temperature);


    // Test energy units: kwh, mj, gj
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Energy);
    fill_with_units_from(Quantity::Energy, &from_set);
    fill_with_units_from(Quantity::Energy, &to_set);

    // 1 kwh is 1000 wh
    test_si_convert(1.0, 1000, Unit::KWH, "kwh", Unit::WH, "wh", Quantity::Energy, &from_set, &to_set);
    // 2000 wh is 2 wh
    test_si_convert(2000.0, 2.0, Unit::WH, "wh", Unit::KWH, "kwh", Quantity::Energy, &from_set, &to_set);
    // 1 kwh is 3.6 mj
    test_si_convert(1.0, 3.6, Unit::KWH, "kwh", Unit::MJ, "mj", Quantity::Energy, &from_set, &to_set);
    // 1 kwh is 0.0036 gj
    test_si_convert(1.0, 0.0036, Unit::KWH, "kwh", Unit::GJ, "gj", Quantity::Energy, &from_set, &to_set);
    // 1 gj is 1000 mj
    test_si_convert(1.0, 1000.0, Unit::GJ, "gj", Unit::MJ, "mj", Quantity::Energy, &from_set, &to_set);
    // 10 mj is 2.77777 kwh
    test_si_convert(10, 2.7777777777777777, Unit::MJ, "mj", Unit::KWH, "kwh", Quantity::Energy, &from_set, &to_set);
    // 1 ws = 1/3600000 kwh is 1 j = 0.000001 MJ
    test_si_convert(1.0/3600000.0, 0.000001, Unit::KWH, "kwh", Unit::MJ, "mj", Quantity::Energy, &from_set, &to_set);

    // 99 m3c = 99 m3c this is the only test we can do with the m3c energy unit,
    // which cannot be converted into other energy units since we lack the density of the water etc.
    test_si_convert(99.0, 99.0, Unit::M3C, "m3c", Unit::M3C, "m3c", Quantity::Energy, &from_set, &to_set);

    test_expected_failed_si_convert(Unit::M3C, Unit::KWH, Quantity::Energy);

    check_units_tested(from_set, to_set, Quantity::Energy);

    // Test reactive energy kvarh unit: kvarh
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Reactive_Energy);
    fill_with_units_from(Quantity::Reactive_Energy, &from_set);
    fill_with_units_from(Quantity::Reactive_Energy, &to_set);

    // 1 kvarh is 1kwh
    test_si_convert(1.0, 1.0, Unit::KVARH, "kvarh", Unit::KWH, "kvarh", Quantity::Reactive_Energy, &from_set, &to_set);
    test_si_convert(1.0, 1.0, Unit::KWH, "kvarh", Unit::KVARH, "kvarh", Quantity::Reactive_Energy, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Reactive_Energy);

    // Test apparent energy kvah unit: kvah
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Apparent_Energy);
    fill_with_units_from(Quantity::Apparent_Energy, &from_set);
    fill_with_units_from(Quantity::Apparent_Energy, &to_set);

    // 1 kvah is 1kwh
    test_si_convert(1.0, 1.0, Unit::KVAH, "kvah", Unit::KWH, "kvah", Quantity::Apparent_Energy, &from_set, &to_set);
    test_si_convert(1.0, 1.0, Unit::KWH, "kvah", Unit::KVAH, "kvah", Quantity::Apparent_Energy, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Apparent_Energy);

    // Test volume units: m3 l
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Volume);
    fill_with_units_from(Quantity::Volume, &from_set);
    fill_with_units_from(Quantity::Volume, &to_set);

    test_si_convert(1, 1000.0, Unit::M3, "m3", Unit::L, "l", Quantity::Volume, &from_set, &to_set);
    test_si_convert(1, 1.0/1000.0, Unit::L, "l", Unit::M3, "m3", Quantity::Volume, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Volume);

    // Test voltage unit: v
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Voltage);
    fill_with_units_from(Quantity::Voltage, &from_set);
    fill_with_units_from(Quantity::Voltage, &to_set);

    test_si_convert(1, 1, Unit::Volt, "v", Unit::Volt, "v", Quantity::Voltage, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Voltage);

    // Test power unit: kw
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Power);
    fill_with_units_from(Quantity::Power, &from_set);
    fill_with_units_from(Quantity::Power, &to_set);

    test_si_convert(1, 1000, Unit::KW, "kw", Unit::W, "w", Quantity::Power, &from_set, &to_set);
    test_si_convert(1000, 1, Unit::W, "w", Unit::KW, "kw", Quantity::Power, &from_set, &to_set);

    test_si_convert(1, double(1.0)/3600.0, Unit::JH, "jh", Unit::W, "w", Quantity::Power, &from_set, &to_set);
    test_si_convert(double(1.0)/3600.0, 1, Unit::W, "w", Unit::JH, "jh", Quantity::Power, &from_set, &to_set);

    test_si_convert(1, double(1000.0)/3600.0, Unit::MJH, "mjh", Unit::KW, "kw", Quantity::Power, &from_set, &to_set);
    test_si_convert(double(1000.0)/3600.0, 1, Unit::KW, "kw", Unit::MJH, "mjh", Quantity::Power, &from_set, &to_set);

    // Test the non-linear conversion between dBm and W.
    test_si_convert(-1.2, -1.2, Unit::DBM, "dbm", Unit::DBM, "dbm", Quantity::Power, &from_set, &to_set);
    test_si_convert(0, 0.001, Unit::DBM, "dbm", Unit::W, "w", Quantity::Power, &from_set, &to_set);
    test_si_convert(0.001, 0, Unit::W, "w", Unit::DBM, "dbm", Quantity::Power, &from_set, &to_set);

    test_si_convert(0, 0.000001, Unit::DBM, "dbm", Unit::KW, "kw", Quantity::Power, &from_set, &to_set);
    test_si_convert(0.000001, 0, Unit::KW, "kw", Unit::DBM, "dbm", Quantity::Power, &from_set, &to_set);

    test_si_convert(0.003, 34.771212547196626, Unit::KW, "kw", Unit::DBM, "dbm", Quantity::Power, &from_set, &to_set);

    // The power variant is m3ch.
    test_si_convert(99.0, 99.0, Unit::M3CH, "m3ch", Unit::M3CH, "m3ch", Quantity::Power, &from_set, &to_set);

    test_expected_failed_si_convert(Unit::M3CH, Unit::KW, Quantity::Power);

    check_units_tested(from_set, to_set, Quantity::Power);

    // Test reactive power kvar unit: kvar
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Reactive_Power);
    fill_with_units_from(Quantity::Reactive_Power, &from_set);
    fill_with_units_from(Quantity::Reactive_Power, &to_set);

    // 1 kvar is 1kw
    test_si_convert(1.0, 1.0, Unit::KVAR, "kvar", Unit::KW, "kvar", Quantity::Reactive_Power, &from_set, &to_set);
    test_si_convert(1.0, 1.0, Unit::KW, "kvar", Unit::KVAR, "kvar", Quantity::Reactive_Power, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Reactive_Power);

    // Test apparent power kva unit: kva
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Apparent_Power);
    fill_with_units_from(Quantity::Apparent_Power, &from_set);
    fill_with_units_from(Quantity::Apparent_Power, &to_set);

    // 1 kva is 1kw
    test_si_convert(1.0, 1.0, Unit::KVA, "kva", Unit::KW, "kva", Quantity::Apparent_Power, &from_set, &to_set);
    test_si_convert(1.0, 1.0, Unit::KW, "kva", Unit::KVA, "kva", Quantity::Apparent_Power, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Apparent_Power);

    // Test volume flow units: m3h lh
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Flow);
    fill_with_units_from(Quantity::Flow, &from_set);
    fill_with_units_from(Quantity::Flow, &to_set);

    test_si_convert(1, 1000.0, Unit::M3H, "m3h", Unit::LH, "lh", Quantity::Flow, &from_set, &to_set);
    test_si_convert(1000.0, 1.0, Unit::LH, "lh", Unit::M3H, "m3h", Quantity::Flow, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Flow);

    // Test amount of substance: mol
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::AmountOfSubstance);
    fill_with_units_from(Quantity::AmountOfSubstance, &from_set);
    fill_with_units_from(Quantity::AmountOfSubstance, &to_set);

    test_si_convert(1.1717, 1.1717, Unit::MOL, "mol", Unit::MOL, "mol", Quantity::AmountOfSubstance, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::AmountOfSubstance);

    // Test luminous intensity: cd
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::LuminousIntensity);
    fill_with_units_from(Quantity::LuminousIntensity, &from_set);
    fill_with_units_from(Quantity::LuminousIntensity, &to_set);

    test_si_convert(1.1717, 1.1717, Unit::CD, "cd", Unit::CD, "cd", Quantity::LuminousIntensity, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::LuminousIntensity);

    // Test relative humidity: rh
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::RelativeHumidity);
    fill_with_units_from(Quantity::RelativeHumidity, &from_set);
    fill_with_units_from(Quantity::RelativeHumidity, &to_set);

    test_si_convert(1.1717, 1.1717, Unit::RH, "rh", Unit::RH, "rh", Quantity::RelativeHumidity, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::RelativeHumidity);

    // Test heat cost allocation: hca
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::HCA);
    fill_with_units_from(Quantity::HCA, &from_set);
    fill_with_units_from(Quantity::HCA, &to_set);

    test_si_convert(11717, 11717, Unit::HCA, "hca", Unit::HCA, "hca", Quantity::HCA, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::HCA);

    // Test pressure: bar pa
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Pressure);
    fill_with_units_from(Quantity::Pressure, &from_set);
    fill_with_units_from(Quantity::Pressure, &to_set);

    test_si_convert(1.1717, 117170, Unit::BAR, "bar", Unit::PA, "pa", Quantity::Pressure, &from_set, &to_set);
    test_si_convert(1.1717, 1.1717e-05, Unit::PA, "pa", Unit::BAR, "bar", Quantity::Pressure, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Pressure);

    // Test frequency: hz
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Frequency);
    fill_with_units_from(Quantity::Frequency, &from_set);
    fill_with_units_from(Quantity::Frequency, &to_set);

    test_si_convert(440, 440, Unit::HZ, "hz", Unit::HZ, "hz", Quantity::Frequency, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Frequency);

    // Test counter: counter
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Dimensionless);
    fill_with_units_from(Quantity::Dimensionless, &from_set);
    fill_with_units_from(Quantity::Dimensionless, &to_set);

    test_si_convert(2211717, 2211717, Unit::COUNTER, "counter", Unit::FACTOR, "counter", Quantity::Dimensionless, &from_set, &to_set);
    test_si_convert(2211717, 2211717, Unit::FACTOR, "counter", Unit::COUNTER, "counter", Quantity::Dimensionless, &from_set, &to_set);
    test_si_convert(2211717, 2211717, Unit::NUMBER, "counter", Unit::COUNTER, "counter", Quantity::Dimensionless, &from_set, &to_set);
    test_si_convert(2211717, 2211717, Unit::FACTOR, "counter", Unit::NUMBER, "counter", Quantity::Dimensionless, &from_set, &to_set);
    test_si_convert(22, 0.22, Unit::PERCENTAGE, "pct", Unit::NUMBER, "counter", Quantity::Dimensionless, &from_set, &to_set);
    test_si_convert(0.22, 22, Unit::NUMBER, "counter", Unit::PERCENTAGE, "pct", Quantity::Dimensionless, &from_set, &to_set);
    test_si_convert(222, 0.000222, Unit::PPM, "ppm", Unit::NUMBER, "counter", Quantity::Dimensionless, &from_set, &to_set);
    test_si_convert(1.234, 1234000.0, Unit::NUMBER, "counter", Unit::PPM, "ppm", Quantity::Dimensionless, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Dimensionless);

    // Test angles: deg rad
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    q_set.erase(Quantity::Angle);
    fill_with_units_from(Quantity::Angle, &from_set);
    fill_with_units_from(Quantity::Angle, &to_set);

    test_si_convert(180, 3.1415926535897931l, Unit::DEGREE, "deg", Unit::RADIAN, "rad", Quantity::Angle, &from_set, &to_set);
    test_si_convert(3.1415926535897931l, 180, Unit::RADIAN, "rad", Unit::DEGREE, "deg", Quantity::Angle, &from_set, &to_set);

    check_units_tested(from_set, to_set, Quantity::Angle);

    // Test point in time units: ut utc lt
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    // I do not know how to handle the point in time units yet.
    // Mark them as tested....
    q_set.erase(Quantity::PointInTime);

    // Test text unit: text
    /////////////////////////////////////////////////////////////////////////////////////////////////////

    // I do not know how to handle the text unit yet.
    // Mark it as tested....
    q_set.erase(Quantity::Text);

    check_quantities_tested(q_set);
}


void test_formulas_building_consts()
{
    unique_ptr<FormulaImplementation> f = unique_ptr<FormulaImplementation>(new FormulaImplementation());
    double v, expected;

    f->doConstant(Unit::KWH, 17);
    f->doConstant(Unit::KWH, 1);
    f->doAddition(SI_KWH);
    v = f->calculate(Unit::KWH);
    if (v != 18.0)
    {
        printf("ERROR in test formula 1 expected 18.0 but got %lf\n", v);
    }

    f->clear();
    f->doConstant(Unit::KWH, 10);
    v = f->calculate(Unit::MJ);
    if (v != 36.0)
    {
        printf("ERROR in test formula 2 expected 36.0 but got %lf\n", v);
    }

    f->clear();
    f->doConstant(Unit::GJ, 10);
    f->doConstant(Unit::MJ, 10);
    f->doAddition(SI_GJ);
    v = f->calculate(Unit::GJ);
    if (v != 10.01)
    {
        printf("ERROR in test formula 3 expected 10.01 but got %lf\n", v);
    }

    f->clear();
    f->doConstant(Unit::C, 10);
    f->doConstant(Unit::C, 20);
    f->doAddition(SI_C);
    f->doConstant(Unit::C, 22);
    f->doAddition(SI_C);
    v = f->calculate(Unit::C);
    if (v != 52)
    {
        printf("ERROR in test formula 4 expected 52 but got %lf\n", v);
    }

    f->clear();
    f->doConstant(Unit::Month, 2);
    f->doConstant(Unit::COUNTER, 3);
    f->doMultiplication();
    v = f->calculate(Unit::Month);
    if (v != 6)
    {
        printf("ERROR in test formula 5 expected 6 but got %g\n", v);
    }

    f->clear();
    f->doConstant(Unit::UnixTimestamp, 3600*24*11); // mon 12 jan 1970 01:00:00 CET
    f->doConstant(Unit::Second, 9);
    f->doAddition(SIUnit(Unit::UnixTimestamp));
    v = f->calculate(Unit::UnixTimestamp);
    expected = 3600*24*11+9;
    if (v != expected)
    {
        printf("ERROR in test formula 6 expected %g but got %g\n", expected, v);
    }

    f->clear();
    f->doConstant(Unit::UnixTimestamp, 3600*24*11); // mon 12 jan 1970 01:00:00 CET
    f->doConstant(Unit::Month, 1);
    f->doAddition(SIUnit(Unit::UnixTimestamp));
    v = f->calculate(Unit::UnixTimestamp);
    expected = 3600*24*(31+11); // mon 12 feb 1970 01:00:00 CET
    if (v != expected)
    {
        printf("ERROR in test formula 7 expected %g but got %g\n", expected, v);
    }
}

void test_formulas_building_meters()
{
    unique_ptr<FormulaImplementation> f = unique_ptr<FormulaImplementation>(new FormulaImplementation());

    ////////////////////////////////////////////////////////////////////////////////////////////////////

    {
        MeterInfo mi;
        assert(lookupDriverInfo("multical21"));
        mi.parse("testur", "multical21", "12345678", "");
        shared_ptr<Meter> meter = createMeter(&mi);
        FieldInfo *fi_flow = meter->findFieldInfo("flow_temperature", Quantity::Temperature);
        FieldInfo *fi_ext = meter->findFieldInfo("external_temperature", Quantity::Temperature);
        assert(fi_flow != NULL);
        assert(fi_ext != NULL);

        vector<uchar> frame;
        hex2bin("2a442d2c785634121B168d2091d37cac217f2d7802ff207100041308190000441308190000615B1f616713", &frame);

        Telegram t;
        MeterKeys mk;
        t.parse(frame, &mk, true);

        vector<Address> addresses;
        bool match;
        meter->handleTelegram(t.about, frame, true, &addresses, &match, &t);

        f->clear();
        f->setMeter(meter.get());

        f->doMeterField(Unit::C, fi_flow);
        double v = f->calculate(Unit::C);
        if (v != 31)
        {
            printf("ERROR in test formula 5 expected 31 but got %lf\n", v);
        }

        f->clear();
        f->setMeter(meter.get());

        f->doMeterField(Unit::C, fi_flow);
        f->doMeterField(Unit::C, fi_ext);
        f->doAddition(SIUnit(Unit::C));
        v = f->calculate(Unit::C);
        if (v != 50)
        {
            printf("ERROR in test formula 6 expected 50 but got %lf\n", v);
        }

        // Check that trying to add a field reference expecting a non-convertible unit, will fail!
//        f->clear();
//        assert(false == f->doField(Unit::M3, meter.get(), fi_flow));
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////

    {
        MeterInfo mi;
        mi.parse("testur", "ebzwmbe", "22992299", "");
        shared_ptr<Meter> meter = createMeter(&mi);
        FieldInfo *fi_p1 = meter->findFieldInfo("current_power_consumption_phase1", Quantity::Power);
        FieldInfo *fi_p2 = meter->findFieldInfo("current_power_consumption_phase2", Quantity::Power);
        FieldInfo *fi_p3 = meter->findFieldInfo("current_power_consumption_phase3", Quantity::Power);
        assert(fi_p1 != NULL);
        assert(fi_p2 != NULL);
        assert(fi_p3 != NULL);

        vector<uchar> frame;
        hex2bin("5B445a149922992202378c20f6900f002c25Bc9e0000BBBBBBBBBBBBBBBB72992299225a140102f6003007102f2f040330f92a0004a9ff01ff24000004a9ff026a29000004a9ff03460600000dfd11063132333435362f2f2f2f2f2f", &frame);

        Telegram t;
        MeterKeys mk;
        t.parse(frame, &mk, true);

        vector<Address> id;
        bool match;
        meter->handleTelegram(t.about, frame, true, &id, &match, &t);

        unique_ptr<FormulaImplementation> f = unique_ptr<FormulaImplementation>(new FormulaImplementation());

        f->clear();
        f->setMeter(meter.get());

        f->doMeterField(Unit::KW, fi_p1);
        f->doMeterField(Unit::KW, fi_p2);
        f->doAddition(SI_KW);
        f->doMeterField(Unit::KW, fi_p3);
        f->doAddition(SI_KW);

        double v = f->calculate(Unit::KW);
        if (v != 0.21679)
        {
            printf("ERROR in test formula 7 expected 0.21679 but got %lf\n", v);
        }
    }
}

void test_formula_tree(FormulaImplementation *f, Meter *m, string formula, string tree)
{
    f->clear();
    f->parse(m, formula);
    string t = f->tree();
    if (t != tree)
    {
        printf("ERROR when parsing \"%s\" expected tree to be \"%s\"\nbut got \"%s\"\n",
               formula.c_str(), tree.c_str(), t.c_str());
    }
}

void test_formula_value(FormulaImplementation *f, Meter *m, string formula, double val, Unit unit)
{
    f->clear();

    bool ok = f->parse(m, formula);
    assert(ok);

    double v = f->calculate(unit);
    debug("(formula) %s\n", f->tree().c_str());

    if (v != val)
    {
        printf("ERROR when evaluating \"%s\"\nERROR expected %.17g but got %.17g\n", formula.c_str(), val, v);
    }
}

void test_formula_error(FormulaImplementation *f, Meter *m, string formula, Unit unit, string errors)
{
    f->clear();

    bool ok = f->parse(m, formula);
    string es = f->errors();
    if (es != errors)
    {
        printf("ERROR when parsing \"%s\"\nExpected errors:\n%sBut got errors:\n%s",
               formula.c_str(),
               errors.c_str(),
               f->errors().c_str());
    }
    assert(!ok);
}

double totime(int year, int month = 1, int day = 1, int hour = 0, int min = 0, int sec = 0)
{
    struct tm date {};

    date.tm_year  = year-1900;
    date.tm_mon   = month-1;
    date.tm_mday  = day;
    date.tm_hour = hour;
    date.tm_min = min;
    date.tm_sec = sec;

    // This t timestamp is dependent on the local time zone.
    time_t t = mktime(&date);
    /*
    // Extract the local time zone.
    struct tm tz_adjust {};
    localtime_r(&t, &tz_adjust);

    // if tm_gmtoff is zero, then we are in Greenwich!
    // if tm_gmtoff is 3600 then we are in Stockholm!
    // Now adjust the t timestamp so that we execute this this, as if we are in Greenwich.
    // This way, the test will work wherever in the world we run it.
    t -= tz_adjust.tm_gmtoff;
    */
    return (double)t;
}

void test_datetime(FormulaImplementation *f, string formula, int year, int month=1, int day=1, int hour=0, int min=0, int sec=0)
{
    f->clear();
    double expected = totime(year,month,day,hour,min,sec);
    f->parse(NULL, formula);
    if (!f->valid())
    {
        printf("%s\n", f->errors().c_str());
    }

    double v = f->calculate(Unit::UnixTimestamp);
    if (v != expected)
    {
        time_t t = v;
        struct tm time {};
        localtime_r(&t, &time);
        string gs = strdatetimesec(&time);

        printf("ERROR Expected datetime %.17g %04d-%02d-%02d %02d:%02d:%02d "
               "but got %.17g (%s) when testing \"%s\"\n",
               expected, year, month, day, hour, min, sec,
               v, gs.c_str(), formula.c_str());
    }
}

void test_time(FormulaImplementation *f, string formula, int hour=0, int min=0, int sec=0)
{
    f->clear();
    double expected = hour*3600+min*60+sec;
    f->parse(NULL, formula);
    if (!f->valid())
    {
        printf("%s\n", f->errors().c_str());
    }

    double v = f->calculate(Unit::Second);
    if (v != expected)
    {
        printf("ERROR Expected time %.17g but got %.17g when testing %s %02d:%02d.%02d\n",
               expected, v, formula.c_str(), hour, min, sec);
    }
}

void test_formulas_datetimes()
{
    unique_ptr<FormulaImplementation> f = unique_ptr<FormulaImplementation>(new FormulaImplementation());

    test_datetime(f.get(), "'2022-02-02'", 2022, 02, 02);
    test_datetime(f.get(), "'2021-02-28'", 2021, 02, 28);

    test_datetime(f.get(), "'1970-01-01 01:00:00'", 1970, 01, 01, 01, 00, 00);
    test_datetime(f.get(), "'1970-01-01 01:00'", 1970, 01, 01, 01, 00);
    test_datetime(f.get(), "'1970-01-01'", 1970, 01, 01);

    test_time(f.get(), "'00:15'", 0, 15, 0);
    test_time(f.get(), "'00:00:16'", 0, 0, 16);

    test_datetime(f.get(), "'2022-01-01 00:00:00' + 1s", 2022,1,1,0,0,1);
    test_datetime(f.get(), "'1971-10-01 02:17' +7d+1h+2min+1s", 1971, 10, 8, 3, 19, 1);

    test_datetime(f.get(), "'2000-01-01' + 1month", 2000, 2, 1);
    test_datetime(f.get(), "'2020-12-31' + 2month", 2021, 2, 28);
    test_datetime(f.get(), "'2020-12-31' - 10month", 2020, 2, 29);
    test_datetime(f.get(), "'2021-01-31' - 1month",  2020, 12,31);
    test_datetime(f.get(), "'2021-01-31' - 2month",  2020, 11, 30);
    test_datetime(f.get(), "'2021-01-31' - 24month", 2019, 1, 31);
    test_datetime(f.get(), "'2021-01-31' + 24month", 2023, 1, 31);
    test_datetime(f.get(), "'2021-01-31' + 22month", 2022, 11, 30);

    // 2020 was a leap year.
    test_datetime(f.get(), "'2021-02-28' -12month", 2020,2,29);
    // 2000 was a leap year %100=0 but %400=0 overrides.
    test_datetime(f.get(), "'2001-02-28' -12month", 2000, 2, 29);
    // 2100 is not a leap year since %100=0 and not overriden %400 != 0.
    test_datetime(f.get(), "'2000-02-29' +(12month * 100counter)", 2100,2,28);
}

void test_formulas_parsing_1()
{
    MeterInfo mi;
    mi.parse("testur", "ebzwmbe", "22992299", "");
    shared_ptr<Meter> meter = createMeter(&mi);

    vector<uchar> frame;
    hex2bin("5B445a149922992202378c20f6900f002c25Bc9e0000BBBBBBBBBBBBBBBB72992299225a140102f6003007102f2f040330f92a0004a9ff01ff24000004a9ff026a29000004a9ff03460600000dfd11063132333435362f2f2f2f2f2f", &frame);

    Telegram t;
    MeterKeys mk;
    t.parse(frame, &mk, true);

    vector<Address> id;
    bool match;
    meter->handleTelegram(t.about, frame, true, &id, &match, &t);

    unique_ptr<FormulaImplementation> f = unique_ptr<FormulaImplementation>(new FormulaImplementation());

    test_formula_value(f.get(), meter.get(),
                       "10 kwh + 100 kwh",
                       110,
                       Unit::KWH);

    test_formula_value(f.get(), meter.get(),
                       "current_power_consumption_phase1_kw + "
                       "current_power_consumption_phase2_kw + "
                       "current_power_consumption_phase3_kw + "
                       "100 kw",
                       100.21679,
                       Unit::KW);

    test_formula_tree(f.get(), meter.get(),
                      "5 c + 7 c + 10 c",
                      "<ADD <ADD <CONST 5 c[1c]Temperature> <CONST 7 c[1c]Temperature> > <CONST 10 c[1c]Temperature> >");

    test_formula_tree(f.get(), meter.get(),
                      "(5 c + 7 c) + 10 c",
                      "<ADD <ADD <CONST 5 c[1c]Temperature> <CONST 7 c[1c]Temperature> > <CONST 10 c[1c]Temperature> >");

    test_formula_tree(f.get(), meter.get(),
                      "5 c + (7 c + 10 c)",
                      "<ADD <CONST 5 c[1c]Temperature> <ADD <CONST 7 c[1c]Temperature> <CONST 10 c[1c]Temperature> > >");

    test_formula_tree(f.get(), meter.get(),
                      "sqrt(22 m * 22 m)",
                      "<SQRT <TIMES <CONST 22 m[1m]Length> <CONST 22 m[1m]Length> > >");

}

void test_formulas_parsing_2()
{
    MeterInfo mi;
    mi.parse("testur", "em24", "66666666", "");
    shared_ptr<Meter> meter = createMeter(&mi);

    vector<uchar> frame;
    hex2bin("35442D2C6666666633028D2070806A0520B4D378_0405F208000004FB82753F00000004853C0000000004FB82F53CCA01000001FD1722", &frame);

    Telegram t;
    MeterKeys mk;
    t.parse(frame, &mk, true);

    vector<Address> id;
    bool match;
    meter->handleTelegram(t.about, frame, true, &id, &match, &t);

    unique_ptr<FormulaImplementation> f = unique_ptr<FormulaImplementation>(new FormulaImplementation());

    test_formula_value(f.get(), meter.get(),
                       "total_energy_consumption_kwh + 18 kwh",
                       247,
                       Unit::KWH);
}

void test_formulas_multiply_constants()
{
    FormulaImplementation fi;

    test_formula_value(&fi, NULL, "100.5 counter * 22 kwh", 2211, Unit::KWH);
    test_formula_value(&fi, NULL, "5 kw * 10 h", 50, Unit::KWH);
    test_formula_value(&fi, NULL, "5000 v * 10 a * 700 h", 35000, Unit::KVAH);
}

void test_formulas_divide_constants()
{
    FormulaImplementation fi;

    test_formula_value(&fi, NULL, "22 kwh / 11 h", 2, Unit::KW);
}

void test_formulas_sqrt_constants()
{
    FormulaImplementation fi;

    test_formula_value(&fi, NULL, "sqrt(22 m * 22 m)", 22, Unit::M);
    test_formula_value(&fi, NULL, "sqrt((2 kwh * 2 kwh) + (3 kvarh * 3 kvarh))", 3.6055512754639891, Unit::KVAH);
}

void test_formulas_date_constants()
{
    FormulaImplementation fi;

//    test_formula_value(&fi, NULL, "2022-01-01 + 1 month", "2022-02-01");
}

void test_formulas_errors()
{
    {
        MeterInfo mi;
        mi.parse("testur", "em24", "66666666", "");

        auto meter = createMeter(&mi);
        auto formula = unique_ptr<FormulaImplementation>(new FormulaImplementation());

        test_formula_error(formula.get(), meter.get(),
                           "10 kwh + 20 kw", Unit::KWH,
                           "Cannot add [kwh|Energy|3.6×10⁶kgm²s⁻²] to [kw|Power|1000kgm²s⁻³]!\n"
                           "10 kwh + 20 kw\n"
                           "       ^~~~~\n");
    }

}

void test_formulas_dventries()
{
    FormulaImplementation fi;

    DVEntry dve;
    dve.storage_nr = 17;
    dve.tariff_nr = 3;
    dve.subunit_nr = 2;

    unique_ptr<FormulaImplementation> f = unique_ptr<FormulaImplementation>(new FormulaImplementation());

    string s = "(storage_counter - 12 counter) *  tariff_counter - subunit_counter";
    f->parse(NULL, s);

    f->setDVEntry(&dve);
    double v = f->calculate(Unit::COUNTER);

    if (v != 13.0)
    {
        printf("ERROR when calculating:\n%s\nExpected: %g but got: %g\n",
               s.c_str(),
               13.0,
               v);
    }

    dve.storage_nr = 18;
    dve.tariff_nr = 0;
    dve.subunit_nr = 0;

    s = "(storage_counter - 8counter) / 2counter";
    f->parse(NULL, s);

    f->setDVEntry(&dve);
    v = f->calculate(Unit::COUNTER);

    if (v != 5.0)
    {
        printf("ERROR when calculating:\n%s\nExpected: %g but got: %g\n",
               s.c_str(),
               5.0,
               v);
    }

}

void test_formulas_stringinterpolation()
{
    DVEntry dve;
    dve.storage_nr = 17;
    dve.tariff_nr = 3;
    dve.subunit_nr = 2;

    unique_ptr<StringInterpolator> f = unique_ptr<StringInterpolator>(new StringInterpolatorImplementation());

    string p = "history_{storage_counter-12counter}_value";
    f->parse(NULL, p);
    string s = f->apply(NULL, &dve);
    string e = "history_5_value";

    if (s != e)
    {
        printf("ERROR when interpolating\n%s\nExpected: %s but got: %s\n",
               p.c_str(),
               e.c_str(),
               s.c_str());
    }

    p = "{storage_counter}_{tariff_counter}_{2counter*subunit_counter}";
    f->parse(NULL, p);
    s = f->apply(NULL, &dve);
    e = "17_3_4";

    if (s != e)
    {
        printf("ERROR when interpolating\n%s\nExpected: %s but got: %s\n",
               p.c_str(),
               e.c_str(),
               s.c_str());
    }

}

void test_dynamic_loading()
{
    VIFRange vr = toVIFRange("Date");

    if (vr != VIFRange::Date)
    {
        printf("ERROR in dynamic loading got %s but expected %s!\n",
               toString(vr), toString(VIFRange::Date));
    }

    vr = toVIFRange("DateTime");

    if (vr != VIFRange::DateTime)
    {
        printf("ERROR in dynamic loading got %s but expected %s!\n",
               toString(vr), toString(VIFRange::DateTime));
    }
}
