/*
 Copyright (C) 2017-2023 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"bus.h"
#include"config.h"
#include"drivers.h"
#include"driver_dynamic.h"
#include"meters.h"
#include"meters_common_implementation.h"
#include"units.h"
#include"wmbus.h"
#include"wmbus_utils.h"

#include<algorithm>
#include<cmath>
#include<limits>
#include<memory.h>
#include<numeric>
#include<stdexcept>
#include<time.h>

map<string, DriverInfo> *registered_drivers_ = NULL;
vector<DriverInfo*> *registered_drivers_list_ = NULL;
map<string, string> removed_driver_explanation_;

void verifyDriverLookupCreated()
{
    if (registered_drivers_ == NULL)
    {
        registered_drivers_ = new map<string,DriverInfo>;
    }
    if (registered_drivers_list_ == NULL)
    {
        registered_drivers_list_ = new vector<DriverInfo*>;
    }
}

// This function should return NULL if the name is not found.
DriverInfo *lookupDriver(string name)
{
    verifyDriverLookupCreated();

    // Check if we have a compiled/loaded driver available.
    if (registered_drivers_->count(name) == 1)
    {
        return &(*registered_drivers_)[name];
    }

    // No, ok lets look for driver aliases.
    for (DriverInfo *di : *registered_drivers_list_)
    {
        for (DriverName &dn : di->nameAliases())
        {
            if (dn.str() == name)
            {
                return di;
            }
        }
    }

    return NULL;
}

vector<DriverInfo*> &allDrivers()
{
    return *registered_drivers_list_;
}

void removeDriver(const string &name, string explanation)
{
    for (auto i = registered_drivers_list_->begin(); i != registered_drivers_list_->end(); i++)
    {
        if ((*i)->name().str() == name)
        {
            registered_drivers_list_->erase(i);
            break;
        }
    }

    registered_drivers_->erase(name);
    removeBuiltinDriver(name);
    removed_driver_explanation_[name] = explanation;
    assert(registered_drivers_->count(name) == 0);
}

string removedDriverExplanation(const string& name)
{
    if (removed_driver_explanation_.count(name) > 0)
    {
        return removed_driver_explanation_[name];
    }
    return "";
}

void addRegisteredDriver(DriverInfo di)
{
    verifyDriverLookupCreated();
    if (registered_drivers_->count(di.name().str()) != 0)
    {
        error("Two drivers trying to register the name \"%s\"\n", di.name().str().c_str());
        exit(1);
    }

    (*registered_drivers_)[di.name().str()] = di;
    // The list elements points into the map.
    (*registered_drivers_list_).push_back(lookupDriver(di.name().str()));
}

bool DriverInfo::detect(uint16_t mfct, uchar version, uchar type)
{
    for (auto &dd : mvts_)
    {
        if (dd.mfct == 0 && dd.type == 0 && dd.version == 0) continue; // Ignore drivers with no detection.
        // Some weird meters (aptor08 and itronheat) send a mfct where the first character is lower case,
        // which results in mfct which are bigger than 32767, therefore restrict mfct to correct range
        // and the normal check will work.
        if ((dd.mfct & 0x7fff) == (mfct & 0x7fff) && dd.version == version && dd.type == type ) return true;
    }
    return false;
}

void DriverInfo::setAliases(string a)
{
    if (a == "") return;

    auto as = splitString(a, ',');
    for (string& s : as)
    {
        addNameAlias(s);
    }
}

bool DriverInfo::isValidMedia(uchar type)
{
    for (auto &dd : mvts_)
    {
        if (dd.type == type) return true;
    }
    return false;
}

DriverInfo::~DriverInfo()
{
}

bool DriverInfo::isCloseEnoughMedia(uchar type)
{
    for (auto &dd : mvts_)
    {
        if (isCloseEnough(dd.type, type)) return true;
    }
    return false;
}

bool staticRegisterDriver(function<void(DriverInfo&)> setup)
{
    DriverInfo di;
    setup(di);

    // Check that the driver name has not been registered before!
    assert(lookupDriver(di.name().str()) == NULL);

    // Check that no other driver also triggers on the same detection values.
    for (auto &d : di.mvts())
    {
        for (DriverInfo *p : allDrivers())
        {
            bool foo = p->detect(d.mfct, d.version, d.type);
            if (foo)
            {
                error("Internal error: driver %s tried to register the same auto detect combo as driver %s alread has taken!\n",
                      di.name().str().c_str(), p->name().str().c_str());
            }
        }
    }

    // Everything looks, good install this driver.
    addRegisteredDriver(di);

    // This code is invoked from the static initializers of DriverInfos when starting
    // wmbusmeters. Thus we do not yet know if the user has supplied --debug or similar setting.
    // To debug this you have to uncomment the printf below.
    // fprintf(stderr, "(STATIC) added driver: %s\n", di.name().str().c_str());
    return true;
}

string loadDriver(const string &file, const char *content)
{
    bool loading_builtin = false;
    if (file != "")
    {
        debug("(driver) loading %s\n", file.c_str());
    }
    else
    {
        debug("(driver) loading builtin\n");
        loading_builtin = true;
    }

    DriverInfo di;
    // Populate the di struct with the loaded driver content.
    bool ok = DriverDynamic::load(&di, file, content);
    if (!ok)
    {
        error("Failed to load driver from file: %s\n", file.c_str());
    }

    // Check if the driver name has been registered before....
    DriverInfo *existing = lookupDriver(di.name().str());
    if (existing != NULL)
    {
        if (existing->getDynamicFileName() == file)
        {
            // The driver has already been loaded, typically during analyze.
            return di.name().str();
        }
        else if (existing->getDynamicFileName() == "builtin")
        {
            // We found a builtin xmq driver
            string exp = tostrprintf("new driver %s (%s) triggered a removal of the builtin driver with the same name.",
                                     di.name().str().c_str(),
                                     di.getDynamicFileName().c_str());
            debug("(driver) %s\n", exp.c_str());
            removeDriver(di.name().str(), exp);
        }
        else if (existing->getDynamicFileName() == "")
        {
            // We found a builtin c++ driver
            string exp = tostrprintf("new driver %s (%s) triggered a removal of the c++ driver with the same name.",
                                     di.name().str().c_str(),
                                     di.getDynamicFileName().c_str());
            debug("(driver) %s\n", exp.c_str());
            removeDriver(di.name().str(), exp);
        }
        else
        {
            // Ok, two xmq drivers in /etc/wmbusmeters.drivers.d that declare the same driver name are NOT ok.
            error("Conflicting driver names are not permitted. Plese change the driver name in file %s "
                  "to something that is different from %s since the existing driver file %s has already taken the name!\n",
                  file.c_str(), di.name().str().c_str(),
                  existing->getDynamicFileName().c_str());
        }
    }

    // For new drivers in xmq format from /etc/wmbusmeters.drivers.d check that no builtin mvt has been registered with the same mvt.
    if (!loading_builtin)
    {
        for (auto &d : di.mvts())
        {
            string mfct = manufacturerFlag(d.mfct);
            const char *old_name = findBuiltinDriver(d.mfct, d.version, d.type);
            if (old_name != NULL)
            {
                string exp = tostrprintf("new driver %s (%s) triggered a removal of the (not yet loaded) builtin driver %s because of the same mvt=%s,%02x,%02x",
                      di.name().str().c_str(),
                      di.getDynamicFileName().c_str(),
                      old_name,
                      mfct.c_str(),
                      d.version,
                      d.type);
                debug("(driver) %s\n", exp.c_str());
                removeDriver(old_name, exp);
            }
        }
    }

    // Check that no other driver also triggers on the same detection values.
    // If there is another driver (or more drivers) which would trigger on the same mvt, then remove those drivers entirely!
    for (auto &d : di.mvts())
    {
        // For each mvt triplet in the new driver.
        for (DriverInfo *old : allDrivers())
        {
            bool detect = old->detect(d.mfct, d.type, d.version);
            if (detect)
            {
                // Oups, the old driver as the same mvt as the new.
                string mfct = manufacturerFlag(d.mfct);
                if (old->getDynamicFileName() == "builtin")
                {
                    string exp = tostrprintf("new driver %s (%s) triggered a removal of the builtin driver %s because of the same mvt=%s,%02x,%02x",
                                             di.name().str().c_str(),
                                             di.getDynamicFileName().c_str(),
                                             old->name().str().c_str(),
                                             mfct.c_str(),
                                             d.version,
                                             d.type);
                    debug("(driver) %s\n", exp.c_str());
                    removeDriver(old->name().str(),exp);
                }
                else if (old->getDynamicFileName() == "")
                {
                    string exp = tostrprintf("new driver %s (%s) triggered a removal of the c++ driver %s because of the same mvt=%s,%02x,%02x",
                                             di.name().str().c_str(),
                                             di.getDynamicFileName().c_str(),
                                             old->name().str().c_str(),
                                             mfct.c_str(),
                                             d.version,
                                             d.type);
                    debug("(driver) %s\n", exp.c_str());
                    removeDriver(old->name().str(), exp);
                }
                else
                {
                    // It is not ok to override an previously dynamically loaded driver though!
                    error("Newly loaded driver %s (%s) tries to register the same "
                          "auto detect combo as driver %s (%s) alread has taken! mvt=%s,%02x,%02x\n",
                          di.name().str().c_str(),
                          di.getDynamicFileName().c_str(),
                          old->name().str().c_str(),
                          old->getDynamicFileName().c_str(),
                          mfct.c_str(),
                          d.version,
                          d.type);
                }
            }
        }
    }

    // Everything looks, good install this driver.
    addRegisteredDriver(di);

    return di.name().str();
}

bool lookupDriverInfo(const string& driver_name, DriverInfo *out_di)
{
    // Lookup an already loaded driver, it might be compiled in as well.
    DriverInfo *di = lookupDriver(driver_name);
    if (di)
    {
        if (out_di) *out_di = *di;
        return true;
    }

    // Lookup a dynamic text driver that can be loaded from memory.
    if (loadBuiltinDriver(driver_name))
    {
        // It is loaded, lets fetch the DriverInfo.
        di = lookupDriver(driver_name);
        if (di)
        {
            if (out_di) *out_di = *di;
            return true;
        }
    }

    string file_name = driver_name;

    // The driver name could be prefixed with @ (eg @iperl)
    // try to download the driver from https://wmbusmeters.org/drivers/iperl.xmq.
    if (file_name.length() > 1 && file_name[0] == '@')
    {
        // Drop the @.
        file_name = file_name.substr(1);
        char local_file[256];
        snprintf(local_file, 256, "%s/%s.xmq", downloadDir(), file_name.c_str());

        int rc = download(".xmq", file_name.c_str(), local_file);
        // 200 is good, 304 is good since the file is already downloaded.
        if (rc != 200 && rc != 304)
        {
            if (rc != -1) verbose("(download) driver not found (%d)\n", rc);
            // Nope, give up.
            return false;
        }
        if (rc == 200)
        {
            verbose("(driver) %s downloaded and cached.\n", driver_name.c_str());
        }
        if (rc == 304)
        {
            verbose("(driver) %s was up to date.\n", driver_name.c_str());
        }
        if (rc == -1304)
        {
            verbose("(driver) %s was up to date.\n", driver_name.c_str());
        }
        file_name = local_file;
    }

    // Is this a dynamic text driver file?
    if (!endsWith(file_name, ".xmq") || !checkFileExists(file_name.c_str()))
    {
        // Nope, give up.
        return false;
    }

    // Load the driver from the file.
    string new_name = loadDriver(file_name, NULL);

    // Check again if it was registered.
    di = lookupDriver(new_name);
    if (di)
    {
        if (out_di) *out_di = *di;
        return true;
    }

    return false;
}

MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
                                                     DriverInfo &di) :
    type_(di.type()),
    driver_name_(di.name()),
    driver_info_(&di),
    bus_(mi.bus),
    name_(mi.name),
    mfct_tpl_status_bits_(di.mfctTPLStatusBits()),
    has_process_content_(di.hasProcessContent()),
    waiting_for_poll_response_sem_("waiting_for_poll_response"),
    more_records_follow_(false)
{
    address_expressions_ = mi.address_expressions;
    identity_mode_ = mi.identity_mode;
    link_modes_ = mi.link_modes;
    poll_interval_= mi.poll_interval;

    if (mi.key.length() > 0)
    {
        hex2bin(mi.key, &meter_keys_.confidentiality_key);
    }
    for (auto s : mi.shells)
    {
        addShellMeterUpdated(s);
    }
    for (auto s : mi.new_meter_shells)
    {
        addShellMeterAdded(s);
    }
    for (auto j : mi.extra_constant_fields)
    {
        addExtraConstantField(j);
    }

    link_modes_.unionLinkModeSet(di.linkModes());
    force_mfct_index_ = di.forceMfctIndex();
}

void MeterCommonImplementation::addShellMeterAdded(string cmdline)
{
    shell_cmdlines_added_.push_back(cmdline);
}

void MeterCommonImplementation::addShellMeterUpdated(string cmdline)
{
    shell_cmdlines_updated_.push_back(cmdline);
}

void MeterCommonImplementation::addExtraConstantField(string ecf)
{
    extra_constant_fields_.push_back(ecf);
}

void MeterCommonImplementation::addExtraCalculatedField(string ecf)
{
    verbose("(meter) Adding calculated field: %s\n", ecf.c_str());

    vector<string> parts = splitString(ecf, '=');

    if (parts.size() != 2)
    {
        warning("Invalid formula for calculated field. %s\n", ecf.c_str());
        return;
    }

    string vname;
    Unit unit;

    bool ok = extractUnit(parts[0], &vname, &unit);
    if (!ok)
    {
        warning("Could not extract a valid unit from calculated field name %s\n", parts[0].c_str());
        return;
    }

    Quantity quantity = toQuantity(unit);

    FieldInfo *existing = findFieldInfo(vname, quantity);
    if (existing != NULL)
    {
        if (!canConvert(unit, existing->displayUnit()))
        {
            warning("Warning! Cannot add the calculated field: %s since it would conflict with the already declared field %s for quantity %s.\n",
                    parts[0].c_str(), vname.c_str(), toString(quantity));
            return;
        }
    }

    addNumericFieldWithCalculator(
        vname,
        "Calculated: "+ecf,
        DEFAULT_PRINT_PROPERTIES,
        quantity,
        parts[1],
        unit
        );
}

vector<string> &MeterCommonImplementation::shellCmdlinesMeterAdded()
{
    return shell_cmdlines_added_;
}

vector<string> &MeterCommonImplementation::shellCmdlinesMeterUpdated()
{
    return shell_cmdlines_updated_;
}

vector<string> &MeterCommonImplementation::meterExtraConstantFields()
{
    return extra_constant_fields_;
}

DriverName MeterCommonImplementation::driverName()
{
    return driver_name_;
}

DriverInfo *MeterCommonImplementation::driverInfo()
{
    return driver_info_;
}

void MeterCommonImplementation::setMeterType(MeterType mt)
{
    type_ = mt;
}

void MeterCommonImplementation::addLinkMode(LinkMode lm)
{
    link_modes_.addLinkMode(lm);
}

void MeterCommonImplementation::setMfctTPLStatusBits(Translate::Lookup &lookup)
{
    mfct_tpl_status_bits_ = lookup;
}

void MeterCommonImplementation::markLastFieldAsLibrary()
{
    field_infos_.back().markAsLibrary();
    num_driver_fields_--;
}

FieldInfo *MeterCommonImplementation::lastAddedField()
{
    return &field_infos_.back();
}

void MeterCommonImplementation::addNumericFieldWithExtractor(string vname,
                                                             string help,
                                                             PrintProperties print_properties,
                                                             Quantity vquantity,
                                                             VifScaling vif_scaling,
                                                             DifSignedness dif_signedness,
                                                             FieldMatcher matcher,
                                                             Unit display_unit,
                                                             double scale)
{
    size_t index = num_driver_fields_++;
    field_infos_.emplace_back(
        FieldInfo(index,
                  vname,
                  vquantity,
                  display_unit == Unit::Unknown ? defaultUnitForQuantity(vquantity) : display_unit,
                  vif_scaling,
                  dif_signedness,
                  scale,
                  matcher,
                  help,
                  print_properties,
                  NULL,
                  NULL,
                  NULL,
                  NULL,
                  NoLookup, /* Lookup table */
                  NULL, /* Formula */
                  this /* Meter */
            ));
}

void MeterCommonImplementation::addNumericFieldWithCalculator(string vname,
                                                              string help,
                                                              PrintProperties print_properties,
                                                              Quantity vquantity,
                                                              string formula,
                                                              Unit display_unit)
{
    Formula *f = newFormula();
    bool ok = f->parse(this, formula);
    if (!ok)
    {
        string err = f->errors();
        warning("Warning! Ignoring calculated field %s because parse failed:\n%s",
                vname.c_str(),
                err.c_str());
        delete f;
        return;
    }
    assert(ok);

    size_t index = num_driver_fields_++;
    field_infos_.push_back(
        FieldInfo(index,
                  vname,
                  vquantity,
                  display_unit == Unit::Unknown ? defaultUnitForQuantity(vquantity) : display_unit,
                  VifScaling::Auto,
                  DifSignedness::Signed,
                  1.0,
                  FieldMatcher::noMatcher(),
                  help,
                  print_properties,
                  NULL,
                  NULL,
                  NULL,
                  NULL,
                  NoLookup, /* Lookup table */
                  f, /* Formula */
                  this /* Meter */
            ));
}

void MeterCommonImplementation::addNumericFieldWithCalculatorAndMatcher(string vname,
                                                                        string help,
                                                                        PrintProperties print_properties,
                                                                        Quantity vquantity,
                                                                        string formula,
                                                                        FieldMatcher matcher,
                                                                        Unit display_unit)
{
    Formula *f = newFormula();
    bool ok = f->parse(this, formula);
    if (!ok)
    {
        string err = f->errors();
        warning("Warning! Ignoring calculated field %s because parse failed:\n%s",
                vname.c_str(),
                err.c_str());
        delete f;
        return;
    }
    assert(ok);

    size_t index = num_driver_fields_++;
    field_infos_.push_back(
        FieldInfo(index,
                  vname,
                  vquantity,
                  display_unit == Unit::Unknown ? defaultUnitForQuantity(vquantity) : display_unit,
                  VifScaling::Auto,
                  DifSignedness::Signed,
                  1.0,
                  matcher,
                  help,
                  print_properties,
                  NULL,
                  NULL,
                  NULL,
                  NULL,
                  NoLookup, /* Lookup table */
                  f, /* Formula */
                  this /* Meter */
            ));
}


void MeterCommonImplementation::addNumericField(
    string vname,
    Quantity vquantity,
    PrintProperties print_properties,
    string help,
    Unit display_unit)
{
    size_t index = num_driver_fields_++;
    field_infos_.emplace_back(
        FieldInfo(index,
                  vname,
                  vquantity,
                  display_unit == Unit::Unknown ? defaultUnitForQuantity(vquantity) : display_unit,
                  VifScaling::None,
                  DifSignedness::Signed,
                  1.0,
                  FieldMatcher::noMatcher(),
                  help,
                  print_properties,
                  NULL, // getValueFunc,
                  NULL,
                  NULL, // setValueFunc
                  NULL,
                  NoLookup, /* Lookup table */
                  NULL, /* Formula */
                  this /* Meter */
            ));
}

void MeterCommonImplementation::addStringFieldWithExtractor(string vname,
                                                            string help,
                                                            PrintProperties print_properties,
                                                            FieldMatcher matcher,
                                                            string ixml,
                                                            bool match_entire_payload)
{
    size_t index = num_driver_fields_++;
    field_infos_.emplace_back(
        FieldInfo(index,
                  vname,
                  Quantity::Text,
                  defaultUnitForQuantity(Quantity::Text),
                  VifScaling::None,
                  DifSignedness::Signed,
                  1.0,
                  matcher,
                  help,
                  print_properties,
                  NULL,
                  NULL,
                  NULL,
                  NULL,
                  NoLookup, /* Lookup table */
                  NULL, /* Formula */
                  this /* Meter */
            ));
    if (ixml != "")
    {
        field_infos_.back().useIXML(ixml);
    }
    if (match_entire_payload)
    {
        field_infos_.back().matchEntirePayload(true);
    }
}

void MeterCommonImplementation::addStringFieldWithExtractorAndLookup(string vname,
                                                                     string help,
                                                                     PrintProperties print_properties,
                                                                     FieldMatcher matcher,
                                                                     Translate::Lookup lookup)
{
    size_t index = num_driver_fields_++;
    field_infos_.emplace_back(
        FieldInfo(index,
                  vname,
                  Quantity::Text,
                  defaultUnitForQuantity(Quantity::Text),
                  VifScaling::None,
                  DifSignedness::Signed,
                  1.0,
                  matcher,
                  help,
                  print_properties,
                  NULL,
                  NULL,
                  NULL,
                  NULL,
                  lookup,
                  NULL, /* Formula */
                  this /* Meter */
            ));
}

void MeterCommonImplementation::addStringField(string vname,
                                               string help,
                                               PrintProperties print_properties)
{
    size_t index = num_driver_fields_++;
    field_infos_.emplace_back(
        FieldInfo(index,
                  vname,
                  Quantity::Text,
                  defaultUnitForQuantity(Quantity::Text),
                  VifScaling::None,
                  DifSignedness::Signed,
                  1.0,
                  FieldMatcher(),
                  help,
                  print_properties,
                  NULL,
                  NULL,
                  NULL,
                  NULL,
                  NoLookup, /* Lookup table */
                  NULL, /* Formula */
                  this /* Meter */
            ));
}

bool send_primary_poll(Meter *m, BusDevice *bus_device, AddressExpression *ae, bool next_telegram, uchar fcb)
{
    const char *again;

    again = "";
    if (next_telegram) again = "again ";

    int idnum = atoi(ae->id.c_str()+1);

    if (idnum < 0 || idnum > 250)
    {
        warning("(meter) not polling from bad id \"%s\"\n", ae->id.c_str());
        return false;
    }

    vector<uchar> buf;
    buf.resize(5);
    buf[0] = 0x10; // Start
    if (fcb == 0) buf[1] = 0x5b; // REQ_UD2 fcb==0
    else          buf[1] = 0x7b; // REQ_UD2 fcb==1
    buf[2] = idnum & 0xff;
    uchar cs = 0;
    for (int i=1; i<3; ++i) cs += buf[i];
    buf[3] = cs; // checksum
    buf[4] = 0x16; // Stop

    verbose("(meter) polling %s%s %s (primary) with req ud2 fcb=%u on bus %s\n",
            again,
            m->name().c_str(),
            ae->id.c_str(),
            fcb,
            bus_device->busAlias().c_str());
    bus_device->serial()->send(buf);

    return true;
}

bool send_secondary_poll(Meter *m, BusDevice *bus_device, AddressExpression *ae, bool next_telegram, uchar fcb)
{
    // A full secondary address 12345678 was specified.
    const char *again;

    again = "";
    if (next_telegram) again = "again ";

    if (!next_telegram)
    {
        // Only send the setup secondary address for the first UD_REQ2.
        vector<uchar> idhex;
        bool ok = hex2bin(ae->id, &idhex);

        if (!ok || idhex.size() != 4)
        {
            warning("(meter) not polling from bad id \"%s\"\n", ae->id.c_str());
            return false;
        }

        vector<uchar> buf;
        buf.resize(17);
        buf[0] = 0x68;
        buf[1] = 0x0b;
        buf[2] = 0x0b;
        buf[3] = 0x68;
        buf[4] = 0x73; // SND_UD
        buf[5] = 0xfd; // address 253
        buf[6] = 0x52; // ci 52
        // Assuming we send id 12345678
        buf[7] = idhex[3]; // id 78
        buf[8] = idhex[2]; // id 56
        buf[9] = idhex[1]; // id 34
        buf[10] = idhex[0]; // id 12
        buf[11] = ae->mfct & 0xff; // mfct
        buf[12] = (ae->mfct >> 8) & 0xff; // use 0xff as a wildcard
        buf[13] = ae->version; // version/generation
        buf[14] = ae->type; // type/media/device

        uchar cs = 0;
        for (int i=4; i<15; ++i) cs += buf[i];
        buf[15] = cs; // checksum
        buf[16] = 0x16; // Stop

        debug("(meter) secondary addressing bus %s to address %s\n",
              bus_device->busAlias().c_str(),
              ae->id.c_str());
        bus_device->serial()->send(buf);

        usleep(1000*500);
    }

    vector<uchar> buf;
    buf.resize(5);
    buf[0] = 0x10; // Start
    if (fcb == 0) buf[1] = 0x5b; // REQ_UD2 fcb==0
    else          buf[1] = 0x7b; // REQ_UD2 fcb==1
    buf[2] = 0xfd; // Address 253 previously primed with the secondary address.
    uchar cs = 0;
    for (int i=1; i<3; ++i) cs += buf[i];
    buf[3] = cs; // checksum
    buf[4] = 0x16; // Stop

    verbose("(meter) polling %s%s %s (secondary) with req ud2 fcb=%u bus %s\n",
            again,
            m->name().c_str(),
            ae->id.c_str(),
            fcb,
            bus_device->busAlias().c_str());
    bus_device->serial()->send(buf);

    return true;
}

void MeterCommonImplementation::poll(shared_ptr<BusManager> bus_manager)
{
    if (!usesPolling()) return;

    // An valid poll interval must have been set!
    if (pollInterval() <= 0) return;

    time_t now = time(NULL);
    time_t next_poll_time = datetime_of_poll_+pollInterval();
    if (now < next_poll_time)
    {
        // Not yet time to poll this meter.
        return;
    }

    BusDevice *bus_device = bus_manager->findBus(bus());

    if (!bus_device)
    {
        string aesc = AddressExpression::concat(addressExpressions());
        warning("(meter) warning! no bus specified for meter %s %s\n", name().c_str(), aesc.c_str());
        return;
    }

    if (addressExpressions().size() == 0)
    {
        warning("(meter) not polling from \"%s\" since no valid id\n", name().c_str());
        return;
    }

    AddressExpression &ae = addressExpressions().back();
    if (ae.has_wildcard)
    {
        warning("(meter) not polling from id \"%s\" since poll id must not have a wildcard\n", ae.id.c_str());
        return;
    }

    // Reading the mbus spec:
    // https://m-bus.com/documentation-wired/05-data-link-layer
    // https://m-bus.com/documentation-wired/07-network-layer
    // A valid secondary selection command will force the fcb bit in the meter to 0.
    // The next fcb bit should therefore be 1 to make sure we send a fresh telegram.
    uchar fcb = 1;
    bool next_telegram = false;
    int num_repolls = 0;
    for (;;)
    {
        if (ae.mbus_primary)
        {
            bool ok = send_primary_poll(this, bus_device, &ae, next_telegram, fcb);
            if (!ok) return;
        }
        else
        {
            bool ok = send_secondary_poll(this, bus_device, &ae, next_telegram, fcb);
            if (!ok) return;
        }

        bool ok = waiting_for_poll_response_sem_.wait();
        if (!ok)
        {
            warning("(meter) %s %s did not send a response!\n", name().c_str(), ae.id.c_str());
            break;
        }
        if (!more_records_follow_) break;
        next_telegram = true;
        // Toggle fcb
        if (fcb == 0) fcb = 1;
        else          fcb = 0;
        num_repolls++;
        debug("(meter) found 0x1f record, polling again (%d) with fcb=%u for more data\n",
              num_repolls,
              fcb);

        if (num_repolls > 10)
        {
            warning("(meter) repolling more than 10 times and no 0x0f found yet! Giving up!\n");
            break;
        }
        // Sleep 50ms before polling for the next telegram.
        usleep(1000*50);
    }
}

vector<AddressExpression>& MeterCommonImplementation::addressExpressions()
{
    return address_expressions_;
}

IdentityMode MeterCommonImplementation::identityMode()
{
    return identity_mode_;
}

vector<FieldInfo> &MeterCommonImplementation::fieldInfos()
{
    return field_infos_;
}

vector<string> &MeterCommonImplementation::extraConstantFields()
{
    return extra_constant_fields_;
}

string MeterCommonImplementation::name()
{
    return name_;
}

void MeterCommonImplementation::onUpdate(function<void(Telegram*,Meter*)> cb)
{
    on_update_.push_back(cb);
}

int MeterCommonImplementation::numUpdates()
{
    return num_updates_;
}

string MeterCommonImplementation::datetimeOfUpdateHumanReadable()
{
    char datetime[40];
    memset(datetime, 0, sizeof(datetime));
    strftime(datetime, 20, "%Y-%m-%d %H:%M:%S", localtime(&datetime_of_update_));
    return string(datetime);
}

string MeterCommonImplementation::datetimeOfUpdateRobot()
{
    char datetime[40];
    memset(datetime, 0, sizeof(datetime));
    // This is the date time in the Greenwich timezone (Zulu time), dont get surprised!
    time_t d = datetime_of_update_;
    struct tm ts;
    gmtime_r(&d, &ts);
    strftime(datetime, sizeof(datetime), "%FT%TZ", &ts);
    return string(datetime);
}

string MeterCommonImplementation::unixTimestampOfUpdate()
{
    char ut[40];
    memset(ut, 0, sizeof(ut));
    snprintf(ut, sizeof(ut)-1, "%lu", datetime_of_update_);
    return string(ut);
}

time_t MeterCommonImplementation::timestampLastUpdate()
{
    return datetime_of_update_;
}

void MeterCommonImplementation::setPollInterval(time_t interval)
{
    poll_interval_ = interval;
    if (usesPolling() && poll_interval_ == 0)
    {
        string aesc = AddressExpression::concat(addressExpressions());
        warning("(meter) %s %s needs polling but has no pollinterval set!\n",
                name().c_str(),
                aesc.c_str());
    }
}

time_t MeterCommonImplementation::pollInterval()
{
    return poll_interval_;
}

bool MeterCommonImplementation::usesPolling()
{
    return link_modes_.has(LinkMode::MBUS) ||
        link_modes_.has(LinkMode::C2) ||
        link_modes_.has(LinkMode::T2) ||
        link_modes_.has(LinkMode::S2);
}

bool driverNeedsPolling(DriverName& dn)
{
    DriverInfo *di = lookupDriver(dn.str());

    if (di == NULL) return false;

    // Return true for MBUS,S2,C2,T2 meters. Currently only mbus is implemented.
    return di->linkModes().has(LinkMode::MBUS) ||
        di->linkModes().has(LinkMode::C2) ||
        di->linkModes().has(LinkMode::T2) ||
        di->linkModes().has(LinkMode::S2);
}

const char *toString(MeterType type)
{
#define X(tname) if (type == MeterType::tname) return #tname;
LIST_OF_METER_TYPES
#undef X
    return "unknown";
}

MeterType toMeterType(string type)
{
#define X(tname) if (type == #tname) return MeterType::tname;
LIST_OF_METER_TYPES
#undef X
    return MeterType::UnknownMeter;
}

const char *toString(ReadableString rs)
{
    if (rs == ReadableString::Unknown) return "Unknown";
    if (rs == ReadableString::Normal) return "Normal";
    if (rs == ReadableString::Reversed) return "Reversedl";

    return "unknown";
}

ReadableString toReadableString(string rs)
{
    if (rs == "Normal") return ReadableString::Normal;
    if (rs == "Reversed") return ReadableString::Reversed;
    return ReadableString::Unknown;
}

string toString(DriverInfo &di)
{
    return di.name().str();
}

bool MeterCommonImplementation::isTelegramForMeter(Telegram *t, Meter *meter, MeterInfo *mi)
{
    string name;
    vector<AddressExpression> address_expressions;
    string driver_name;

    assert((meter && !mi) ||
           (!meter && mi));

    if (meter)
    {
        name = meter->name();
        address_expressions = meter->addressExpressions();
        driver_name = meter->driverName().str();
    }
    else
    {
        name = mi->name;
        address_expressions = mi->address_expressions;
        driver_name = mi->driver_name.str();
    }

    if (isDebugEnabled())
    {
        // Telegram addresses
        string t_idsc = Address::concat(t->addresses);
        // Meter/MeterInfo address expressions
        string m_idsc = AddressExpression::concat(address_expressions);
        debug("(meter) %s: for me? %s in %s\n", name.c_str(), t_idsc.c_str(), m_idsc.c_str());
    }

    bool used_wildcard = false;
    bool match = doesTelegramMatchExpressions(t->addresses,
                                              address_expressions,
                                              &used_wildcard);

    if (!match)
    {
        // The id must match.
        debug("(meter) %s: not for me: no match\n", name.c_str());
        return false;
    }

    debug("(meter) %s: yes for me\n", name.c_str());
    t->meter = meter;
    return true;
}

MeterKeys *MeterCommonImplementation::meterKeys()
{
    return &meter_keys_;
}

int MeterCommonImplementation::index()
{
    return index_;
}

void MeterCommonImplementation::setIndex(int i)
{
    index_ = i;
}

string MeterCommonImplementation::bus()
{
    return bus_;
}

void MeterCommonImplementation::triggerUpdate(Telegram *t)
{
    // Check if processContent has discarded this telegram.
    if (t->discard) return;

    datetime_of_poll_ = time(NULL);
    datetime_of_update_ = t->about.timestamp ? t->about.timestamp : datetime_of_poll_;
    num_updates_++;
    for (auto &cb : on_update_) if (cb) cb(t, this);
    t->handled = true;
}

string findField(string key, vector<string> *extra_constant_fields)
{
    key = key+"=";
    for (string ecf : *extra_constant_fields)
    {
        if (startsWith(ecf, key))
        {
            return ecf.substr(key.length());
        }
    }
    return "";
}

string build_id(Address &a, IdentityMode im)
{
    string id = a.id;
    if (im == IdentityMode::ID_MFCT ||
        im == IdentityMode::FULL)
    {
        id += string(".M=")+manufacturerFlag(a.mfct);
    }
    if (im == IdentityMode::FULL)
    {
        id += tostrprintf(".V=%02x.T=%02x", a.version, a.type);
    }
    return id;
}

// Is the desired field one of the fields common to all meters and telegrams?
bool checkCommonField(string *buf, string desired_field, Meter *m, Telegram *t, char c, bool human_readable)
{
    if (desired_field == "name")
    {
        *buf += m->name() + c;
        return true;
    }
    if (desired_field == "id")
    {
        string id = build_id(t->addresses.back(), m->identityMode());
        *buf += id + c;
        return true;
    }
    if (desired_field == "timestamp")
    {
        *buf += m->datetimeOfUpdateHumanReadable() + c;
        return true;
    }
    if (desired_field == "timestamp_lt")
    {
        *buf += m->datetimeOfUpdateHumanReadable() + c;
        return true;
    }
    if (desired_field == "timestamp_utc")
    {
        *buf += m->datetimeOfUpdateRobot() + c;
        return true;
    }
    if (desired_field == "timestamp_ut")
    {
        *buf += m->unixTimestampOfUpdate() + c;
        return true;
    }
    if (desired_field == "device")
    {
        *buf += t->about.device + c;
        return true;
    }
    if (desired_field == "rssi_dbm")
    {
        *buf += to_string(t->about.rssi_dbm) + c;
        return true;
    }

    return false;
}

// Is the desired field one of the meter printable fields?
bool checkPrintableField(string *buf, string desired_field, Meter *m, Telegram *t, char c,
                         vector<FieldInfo> &fields, bool human_readable)
{

    for (FieldInfo &fi : fields)
    {
        if (fi.xuantity() == Quantity::Text)
        {
            // Strings are simply just print them.
            if (desired_field == fi.vname())
            {
                *buf += m->getStringValue(&fi) + c;
                return true;
            }
        }
        else
        {
            string display_unit_s = unitToStringLowerCase(fi.displayUnit());
            string var = fi.vname()+"_"+display_unit_s;
            if (desired_field != var) continue;

            // We have the correc field.
            if (fi.displayUnit() == Unit::DateLT)
            {
                double d = m->getNumericValue(&fi, Unit::DateLT);
                *buf += strdate(d);
                *buf += c;
                return true;
            }
            else if (fi.displayUnit() == Unit::DateTimeLT)
            {
                double d = m->getNumericValue(&fi, Unit::DateTimeLT);
                *buf += strdatetime(d);
                *buf += c;
                return true;
            }
            else if (fi.displayUnit() == Unit::DateTimeUTC)
            {
                double d = m->getNumericValue(&fi, Unit::DateTimeUTC);
                *buf += strTimestampUTC(d);
                *buf += c;
                return true;
            }
            else
            {
                // Default unit.
                *buf += valueToString(m->getNumericValue(&fi, fi.displayUnit()), fi.displayUnit());
                if (human_readable)
                {
                    *buf += " ";
                    *buf += unitToStringHR(fi.displayUnit());
                }
                *buf += c;
                return true;
            }
        }
    }

    return false;
}

// Is the desired field one of the constant fields?
bool checkConstantField(string *buf, string field, char c, vector<string> *extra_constant_fields)
{
    // Ok, lets look for extra constant fields and print any such static information.
    string v = findField(field, extra_constant_fields);
    if (v != "")
    {
        *buf += v + c;
        return true;
    }

    return false;
}

string concatFields(Meter *m, Telegram *t, char c, vector<FieldInfo> &prints, bool human_readable,
                    vector<string> *selected_fields, vector<string> *extra_constant_fields)
{
    if (selected_fields == NULL || selected_fields->size() == 0)
    {
        selected_fields = &m->selectedFields();
    }

    string buf = "";

    for (string field : *selected_fields)
    {
        bool handled = checkCommonField(&buf, field, m, t, c, human_readable);
        if (handled) continue;

        handled = checkPrintableField(&buf, field, m, t, c, prints, human_readable);
        if (handled) continue;

        handled = checkConstantField(&buf, field, c, extra_constant_fields);
        if (handled) continue;

        if (!handled)
        {
            buf += "?"+field+"?"+c;
        }
    }
    if (buf.back() == c) buf.pop_back();
    return buf;
}

bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<uchar> input_frame,
                                               bool simulated, vector<Address> *addresses,
                                               bool *id_match, Telegram *out_analyzed)
{
    Telegram t;
    t.about = about;
    bool ok = t.parseHeader(input_frame);

    if (simulated) t.markAsSimulated();
    if (out_analyzed != NULL) t.markAsBeingAnalyzed();

    *addresses = t.addresses;

    if (!ok || !isTelegramForMeter(&t, this, NULL))
    {
        // This telegram is not intended for this meter.
        return false;
    }

    *id_match = true;
    if (isVerboseEnabled())
    {
        verbose("(meter) %s(%d) %s  handling telegram from %s\n",
                name().c_str(),
                index(),
                driverName().str().c_str(),
                t.addresses.back().str().c_str());
    }

    if (isDebugEnabled())
    {
        string msg = bin2hex(input_frame);
        debug("(meter) %s %s \"%s\"\n", name().c_str(), t.addresses.back().str().c_str(), msg.c_str());
    }

    // For older meters with manufacturer specific data without a nice 0f dif marker.
    if (force_mfct_index_ != -1)
    {
        t.force_mfct_index = force_mfct_index_;
    }

    ok = t.parse(input_frame, &meter_keys_, true);
    if (!ok)
    {
        if (out_analyzed != NULL) *out_analyzed = t;
        // Ignoring telegram since it could not be parsed.
        return false;
    }

    char log_prefix[256];
    snprintf(log_prefix, 255, "(%s) log", driverName().str().c_str());
    logTelegram(t.original, t.frame, t.header_size, t.suffix_size);

    if (usesPolling())
    {
        // Did the parser find a 0x1b record? If so, then we should poll again for the next
        // telegram using REQ_UD2 0x7b instead of 0x5b.
        more_records_follow_ = (t.mfct_1f_index != -1);
        waiting_for_poll_response_sem_.notify();
    }

    // Invoke ixml extractors!
    processFieldIXMLs(&t);

    // Invoke standardized field extractors!
    processFieldExtractors(&t);
    if (hasProcessContent())
    {
        // Invoke tailor made meter specific parsing!
        processContent(&t);
    }
    // Invoke any calculators working on the extracted fields.
    processFieldCalculators();

    // All done....

    if (isDebugEnabled())
    {
        char log_prefix[256];
        snprintf(log_prefix, 255, "(%s)", driverName().str().c_str());
        t.explainParse(log_prefix, 0);
    }

    triggerUpdate(&t);

    if (out_analyzed != NULL) *out_analyzed = t;
    return true;
}

void MeterCommonImplementation::processFieldIXMLs(Telegram *t)
{
    // Iterate over the fields with IXMLs.
    for (FieldInfo &fi : field_infos_)
    {
        if (fi.hasIXML())
        {
            if (fi.matchEntirePayload())
            {
                // Special case for mfct specific meters not compliant with difvif parsing.
                vector<uchar> content;
                t->extractPayload(&content);
                string value = bin2hex(content);
                debug("(ixml) parsing entire payload %s\n", value.c_str());
                bool ok = parseWithIXML(t, t->header_size, value, fi.ixmlGrammar(), &t->dv_entries);
                if (!ok)
                {
                    vector<uchar> frame;
                    t->extractFrame(&frame);
                    string hex = bin2hex(frame);

                    warning("(meters) meter: %s failed to decode ixml field: %s over entire payload\n"
                            "Please open an issue at https://github.com/wmbusmeters/wmbusmeters/\n"
                            "and report this telegram: %s\n",
                            name().c_str(),
                            fi.vname().c_str(),
                            hex.c_str());
                }
                continue;
            }

            if (!fi.hasMatcher())
            {
                debug("(meters) skipping IXML field without matcher %s(%s)[%d]...\n",
                      fi.vname().c_str(),
                      toString(fi.xuantity()),
                      fi.index());
                continue;
            }

            debug("(meters) IXML extracting for field %s(%s)[%d]\n",
                  fi.vname().c_str(),
                  toString(fi.xuantity()),
                  fi.index());

            for (auto &p : t->dv_entries)
            {
                DVEntry *dve = &p.second.second;
                if (fi.matches(dve))
                {
                    // Simpler match for ixml fields right now.
                    // No index test.
                    debug("(meters) IXML using field info %s(%s)[%d] to extract %s at offset %d\n",
                          fi.vname().c_str(),
                          toString(fi.xuantity()),
                          fi.index(),
                          dve->dif_vif_key.str().c_str(),
                          dve->offset);

                    dve->addFieldInfo(&fi);
                    fi.performExtraction(this, t, dve);
                    string value = getStringValue(&fi);
                    debug("(ixml) parsing field content %s\n", value.c_str());
                    bool ok = parseWithIXML(t, dve->offset, value, fi.ixmlGrammar(), &t->dv_entries);
                    if (!ok)
                    {
                        vector<uchar> frame;
                        t->extractFrame(&frame);
                        string hex = bin2hex(frame);

                        warning("(meters) meter: %s failed to decode ixml field: %s\n"
                                "Please open an issue at https://github.com/wmbusmeters/wmbusmeters/\n"
                                "and report this telegram: %s\n",
                                name().c_str(),
                                fi.vname().c_str(),
                                hex.c_str());
                    }
                }
            }
        }
    }
}

void MeterCommonImplementation::processFieldExtractors(Telegram *t)
{
    // Multiple dventries can be matched against a single wildcard FieldInfo.
    map<FieldInfo*,set<DVEntry*>> founds;

    // Sort the dv_entries based on their offset in the telegram.
    // I.e. restore the ordering that was implicit in the telegram.
    vector<DVEntry*> sorted_entries;

    for (auto &p : t->dv_entries)
    {
        sorted_entries.push_back(&p.second.second);
    }
    sort(sorted_entries.begin(), sorted_entries.end(),
         [](const DVEntry* a, const DVEntry *b) -> bool { return a->offset < b->offset; });

    // Now go through each field_info defined by the driver.
    for (FieldInfo &fi : field_infos_)
    {
        if (fi.hasIXML()) continue; // The IXML fields have already been handled.

        int current_match_nr = 0;

        if (!fi.hasMatcher())
        {
            debug("(meters) skipping field without matcher %s(%s)[%d]...\n",
                  fi.vname().c_str(),
                  toString(fi.xuantity()),
                  fi.index());
            continue;
        }

        debug("(meters) trying field info %s(%s)[%d]...\n",
              fi.vname().c_str(),
              toString(fi.xuantity()),
              fi.index());

        // Iterate through dv_entries in the telegram in the same order the telegram presented them.
        for (DVEntry *dve : sorted_entries)
        {
            if (fi.hasMatcher() && fi.matches(dve))
            {
                current_match_nr++;
                if (fi.matcher().index_nr != IndexNr(current_match_nr) &&
                    !fi.matcher().expectedToMatchAgainstMultipleEntries())
                {
                    // This field info did match, but requires another index nr!
                    // Increment the current index nr and look for the next match.
                }
                else if (founds[&fi].count(dve) == 0 || fi.matcher().expectedToMatchAgainstMultipleEntries())
                {
                    debug("(meters) using field info %s(%s)[%d] to extract %s at offset %d\n",
                          fi.vname().c_str(),
                          toString(fi.xuantity()),
                          fi.index(),
                          dve->dif_vif_key.str().c_str(),
                          dve->offset);

                    dve->addFieldInfo(&fi);
                    fi.performExtraction(this, t, dve);
                    founds[&fi].insert(dve);
                }
                else
                {
                    if (isVerboseEnabled())
                    {
                        set<DVEntry*> old = founds[&fi];
                        string olds;
                        for (DVEntry *dve : old)
                        {
                            olds += to_string(dve->offset)+",";
                        }
                        olds.pop_back();

                        verbose("(meter) while processing field extractors ignoring dventry %s at offset %d matching since "
                                "field %s was already matched against offsets %s !\n",
                                dve->dif_vif_key.str().c_str(),
                                dve->offset,
                                fi.vname().c_str(),
                                olds.c_str());
                    }
                }
            }
        }
    }

    // Iterate over the fields that has no matcher rule. Ie the field
    // itself does the searching and matching.
    for (FieldInfo &fi : field_infos_)
    {
        if (!fi.hasMatcher())
        {
            fi.performExtraction(this, t, NULL);
        }
        else if (founds.count(&fi) == 0 && fi.printProperties().hasINCLUDETPLSTATUS())
        {
            // This is a status field and it joins the tpl status but it also
            // has a potential dve match, which did not trigger. Now
            // force extraction to get the tpl status.
            fi.performExtraction(this, t, NULL);
        }
    }
}

void MeterCommonImplementation::processFieldCalculators()
{
    // Iterate over the fields with formulas but no matcher.
    for (FieldInfo &fi : field_infos_)
    {
        if (fi.hasFormula() && !fi.hasMatcher())
        {
            debug("(meters) calculating field %s(%s)[%d]\n",
                  fi.vname().c_str(),
                  toString(fi.xuantity()),
                  fi.index());
            fi.performCalculation(this);
        }
    }
}

string MeterCommonImplementation::getStatusField(FieldInfo *fi)
{
    string field_name_no_unit = fi->vname();
    if (string_values_.count(field_name_no_unit) == 0)
    {
        return "null"; // This is translated to a real(non-string) null in the json.
    }
    StringField &sf = string_values_[field_name_no_unit];
    string value = sf.value;

    // This is >THE< status field, only one is allowed.
    // Look for other fields with the JOIN_INTO_STATUS marker.
    // These other fields will not be printed, instead
    // joined into this status field.
    for (FieldInfo &f : field_infos_)
    {
        if (f.printProperties().hasINJECTINTOSTATUS())
        {
            //printf("NOW >%s<\n", value.c_str());
            string more = getStringValue(&f);
            //printf("MORE >%s<\n", more.c_str());
            string joined = joinStatusOKStrings(value, more);
            //printf("JOINED >%s<\n", joined.c_str());
            value = joined;
        }
    }
    // Sort all found flags and remove any duplicates. A well designed meter decoder
    // should not be able to generate duplicates.
    value = sortStatusString(value);
    // If it is empty, then translate to OK!
    if (value == "") value = "OK";
    return value;
}

void MeterCommonImplementation::processContent(Telegram *t)
{
}

bool MeterCommonImplementation::hasProcessContent()
{
    return has_process_content_;
}

void MeterCommonImplementation::setNumericValue(FieldInfo *fi, DVEntry *dve, Unit u, double v)
{
    string field_name_no_unit;

    if (dve == NULL)
    {
        string field_name_no_unit = fi->vname();
        numeric_values_[pair<string,Unit>(field_name_no_unit, fi->displayUnit())] = NumericField(u, v, fi);
    }
    else
    {
        field_name_no_unit = fi->generateFieldNameNoUnit(this, dve);
        numeric_values_[pair<string,Unit>(field_name_no_unit, fi->displayUnit())] = NumericField(u, v, fi, *dve);
    }
}

void MeterCommonImplementation::setNumericValue(string vname, Unit u, double v)
{
    Quantity q = toQuantity(u);
    FieldInfo *fi = findFieldInfo(vname, q);

    if (fi == NULL)
    {
        warning("(meter) cannot set numeric value %g %s for non-existant field \"%s\" %s\n", v, unitToStringLowerCase(u).c_str(), vname.c_str(), toString(q));
        return;
    }
    setNumericValue(fi, NULL, u, v);
}

bool MeterCommonImplementation::hasValue(FieldInfo *fi)
{
    return hasStringValue(fi) || hasNumericValue(fi);
}

bool MeterCommonImplementation::hasNumericValue(FieldInfo *fi)
{
    pair<string,Unit> key(fi->vname(),fi->displayUnit());

    return numeric_values_.count(key) != 0;
}

bool MeterCommonImplementation::hasStringValue(FieldInfo *fi)
{
    return string_values_.count(fi->vname()) != 0;
}

double MeterCommonImplementation::getNumericValue(FieldInfo *fi, Unit to)
{
    string field_name_no_unit = fi->vname();
    pair<string,Unit> key(field_name_no_unit,fi->displayUnit());
    if (numeric_values_.count(key) == 0)
    {
        return std::numeric_limits<double>::quiet_NaN(); // This is translated into a null in the json.
    }
    NumericField &nf = numeric_values_[key];
    return convert(nf.value, nf.unit, to);
}

double MeterCommonImplementation::getNumericValue(string vname, Unit to)
{
    pair<string,Unit> key(vname,to);
    if (numeric_values_.count(key) == 0)
    {
        return std::numeric_limits<double>::quiet_NaN(); // This is translated into a null in the json.
    }
    NumericField &nf = numeric_values_[key];
    return convert(nf.value, nf.unit, to);
}

void MeterCommonImplementation::setStringValue(FieldInfo *fi, string v, DVEntry *dve)
{
    string field_name_no_unit;

    if (dve == NULL)
    {
        string field_name_no_unit = fi->vname();
        string_values_[field_name_no_unit] = StringField(v, fi);
    }
    else
    {
        field_name_no_unit = fi->generateFieldNameNoUnit(this, dve);
        string_values_[field_name_no_unit] = StringField(v, fi);
    }
}

void MeterCommonImplementation::setStringValue(string vname, string v, DVEntry *dve)
{
    FieldInfo *fi = findFieldInfo(vname, Quantity::Text);

    if (fi == NULL)
    {
        warning("(meter) cannot set string value %s for non-existant field \"%s\"\n", v.c_str(), vname.c_str());
        return;
    }
    setStringValue(fi, v, dve);
}

string MeterCommonImplementation::getStringValue(FieldInfo *fi)
{
    string field_name_no_unit = fi->vname();
    if (string_values_.count(field_name_no_unit) == 0)
    {
        return "null"; // This is translated to a real(non-string) null in the json.
    }
    StringField &sf = string_values_[field_name_no_unit];
    string value = sf.value;

    if (fi->printProperties().hasSTATUS())
    {
        // This is >THE< status field, only one is allowed.
        // Look for other fields with the JOIN_INTO_STATUS marker.
        // These other fields will not be printed, instead
        // joined into this status field.
        for (FieldInfo &f : field_infos_)
        {
            if (f.printProperties().hasINJECTINTOSTATUS())
            {
                string more = getStringValue(&f);
                string joined = joinStatusOKStrings(value, more);
                value = joined;
            }
        }
        // Sort all found flags and remove any duplicates. A well designed meter decoder
        // should not be able to generate duplicates.
        value = sortStatusString(value);
        // If it is empty, then translate to OK!
        if (value == "") value = "OK";
    }

    return value;
}

string MeterCommonImplementation::decodeTPLStatusByte(uchar sts)
{
    return ::decodeTPLStatusByteWithMfct(sts, mfct_tpl_status_bits_);
}

FieldInfo *MeterCommonImplementation::findFieldInfo(string vname, Quantity xuantity)
{
    FieldInfo *found = NULL;
    for (FieldInfo &p : field_infos_)
    {
        if (p.vname() == vname &&
            p.xuantity() == xuantity)
        {
            found = &p;
            break;
        }
    }

    return found;
}

string MeterCommonImplementation::renderJsonOnlyDefaultUnit(string vname, Quantity xuantity)
{
    FieldInfo *fi = findFieldInfo(vname, xuantity);

    if (fi == NULL) return "unknown field "+vname;
    return fi->renderJsonOnlyDefaultUnit(this);
}

string MeterCommonImplementation::debugValues()
{
    string s;

    for (auto &p : numeric_values_)
    {
        string vname = p.first.first;
        string us = unitToStringLowerCase(p.first.second);
        NumericField& nf = p.second;

        s += tostrprintf("%s_%s = %g\n", vname.c_str(), us.c_str(), nf.value);
    }

    for (auto &p : string_values_)
    {
        string vname = p.first;
        StringField& nf = p.second;

        s += tostrprintf("%s = \"%s\"\n", vname.c_str(), nf.value.c_str());
    }

    return s;
}

FieldInfo::~FieldInfo()
{
}

FieldInfo::FieldInfo(int index,
                     string vname,
                     Quantity xuantity,
                     Unit display_unit,
                     VifScaling vif_scaling,
                     DifSignedness dif_signedness,
                     double scale,
                     FieldMatcher matcher,
                     string help,
                     PrintProperties print_properties,
                     function<double(Unit)> get_numeric_value_override,
                     function<string()> get_string_value_override,
                     function<void(Unit,double)> set_numeric_value_override,
                     function<void(string)> set_string_value_override,
                     Translate::Lookup lookup,
                     Formula *formula,
                     Meter *m
        ) :
        index_(index),
        vname_(vname),
        xuantity_(xuantity),
        display_unit_(display_unit),
        vif_scaling_(vif_scaling),
        dif_signedness_(dif_signedness),
        scale_(scale),
        matcher_(matcher),
        help_(help),
        print_properties_(print_properties),
        get_numeric_value_override_(get_numeric_value_override),
        get_string_value_override_(get_string_value_override),
        set_numeric_value_override_(set_numeric_value_override),
        set_string_value_override_(set_string_value_override),
        lookup_(lookup),
        formula_(formula),
        field_name_(newStringInterpolator()),
        valid_field_name_(field_name_->parse(m, vname))
{
    if (!valid_field_name_)
    {
        warning("(meter) field template \"%s\" could not be parsed!\n", vname.c_str());
    }
}

string FieldInfo::renderJsonOnlyDefaultUnit(Meter *m)
{
    return renderJson(m, NULL);
}

string FieldInfo::renderJsonText(Meter *m, DVEntry *dve)
{
    return renderJson(m, dve);
}

string FieldInfo::generateFieldNameNoUnit(Meter *m, DVEntry *dve)
{
    if (!valid_field_name_) return "bad_field_name";

    return field_name_->apply(m, dve);
}

string FieldInfo::generateFieldNameWithUnit(Meter *m, DVEntry *dve)
{
    if (!valid_field_name_) return "bad_field_name";

    if (xuantity_ == Quantity::Text)
    {
        return field_name_->apply(m, dve);
    }

    string display_unit_s = unitToStringLowerCase(displayUnit());
    string var = field_name_->apply(m, dve);

    return var+"_"+display_unit_s;
}


string FieldInfo::renderJson(Meter *m, DVEntry *dve)
{
    string s;

    string display_unit_s = unitToStringLowerCase(displayUnit());
    string field_name = generateFieldNameNoUnit(m, dve);

    if (xuantity() == Quantity::Text)
    {
        string v = m->getStringValue(this);
        if (v == "null")
        {
            // Yes, right now a meter cannot send a string value "something":"null" it will
            // be translated into "something":null in the json, indicating that there is no value.
            // This should not be a problem for now. Lets deal with it when a meter decides to send "null"
            // as its version string for example.
            s += "\""+field_name+"\":null";
        }
        else
        {
            // Normally the string values are quoted in json. TODO quote the value properly.
            // A well crafted meter could send a version string with " and break the json format.
            s += "\""+field_name+"\":\""+v+"\"";
        }
    }
    else
    {
        if (displayUnit() == Unit::DateLT)
        {
            s += "\""+field_name+"_"+display_unit_s+"\":\""+strdate(m->getNumericValue(field_name, Unit::DateLT))+"\"";
        }
        else if (displayUnit() == Unit::DateTimeLT)
        {
            s += "\""+field_name+"_"+display_unit_s+"\":\""+strdatetime(m->getNumericValue(field_name, Unit::DateTimeLT))+"\"";
        }
        else if (displayUnit() == Unit::DateTimeUTC)
        {
            s += "\""+field_name+"_"+display_unit_s+"\":\""+strTimestampUTC(m->getNumericValue(field_name, Unit::DateTimeUTC))+"\"";
        }
        else
        {
            // All numeric values.
            s += "\""+field_name+"_"+display_unit_s+"\":"+valueToString(m->getNumericValue(field_name, displayUnit()), displayUnit());
        }
    }

    return s;
}

void MeterCommonImplementation::createMeterEnv(string id,
                                               vector<string> *envs,
                                               vector<string> *extra_constant_fields)
{
    envs->push_back(string("METER_ID="+id));
    envs->push_back(string("METER_NAME=")+name());
    envs->push_back(string("METER_TYPE=")+driverName().str());
    envs->push_back(string("METER_DRIVER=")+driver_info_->getDynamicSource());

    // If the configuration has supplied json_address=Roodroad 123
    // then the env variable METER_address will available and have the content "Roodroad 123"
    for (string add_json : meterExtraConstantFields())
    {
        envs->push_back(string("METER_")+add_json);
    }
    for (string extra_field : *extra_constant_fields)
    {
        envs->push_back(string("METER_")+extra_field);
    }
}

void MeterCommonImplementation::printMeter(Telegram *t,
                                           string *human_readable,
                                           string *fields, char separator,
                                           string *json,
                                           vector<string> *envs,
                                           vector<string> *extra_constant_fields,
                                           vector<string> *selected_fields,
                                           bool pretty_print_json)
{
    bool first = !t->meter->hasReceivedFirstTelegram();

    *human_readable = concatFields(this, t, '\t', field_infos_, true, selected_fields, extra_constant_fields);
    *fields = concatFields(this, t, separator, field_infos_, false, selected_fields, extra_constant_fields);

    string media;
    if (t->tpl_id_found)
    {
        media = mediaTypeJSON(t->tpl_type, t->tpl_mfct);
    }
    else if (t->ell_id_found)
    {
        media = mediaTypeJSON(t->ell_type, t->ell_mfct);
    }
    else
    {
        media = mediaTypeJSON(t->dll_type, t->dll_mfct);
    }

    string id = "";
    if (t->addresses.size() > 0)
    {
        id = build_id(t->addresses.back(), identityMode());
    }

    string indent = "";
    string newline = "";

    if (pretty_print_json)
    {
        indent = "    ";
        newline ="\n";
    }

    string s;
    s += "{"+newline;
    s += indent+"\"_\":\"telegram\","+newline;
    s += indent+"\"media\":\""+media+"\","+newline;
    s += indent+"\"meter\":\""+driverName().str()+"\","+newline;
    s += indent+"\"name\":\""+name()+"\","+newline;
    s += indent+"\"id\":\""+id+"\","+newline;

    // Iterate over the meter field infos...
    map<FieldInfo*,set<DVEntry*>> founds; // Multiple dventries can match to a single field info.
    set<string> found_vnames;

    for (auto &p : numeric_values_)
    {
        string vname = p.first.first;
        NumericField& nf = p.second;
        if (nf.field_info->printProperties().hasHIDE()) continue;

        string out = nf.field_info->renderJson(this, &nf.dv_entry);
        s += indent+out+","+newline;

        if (first && getDetailedFirst())
        {
            size_t pos = out.find("\":");
            if (pos != string::npos)
            {
                string rule = out.substr(0, pos)+"_field\":"+to_string(nf.field_info->index());
                s += indent+rule+","+newline;
            }
        }
    }

    for (auto &p : string_values_)
    {
        string vname = p.first;
        StringField& sf = p.second;
        string out;

        if (sf.field_info->printProperties().hasHIDE()) continue;
        if (sf.field_info->printProperties().hasSTATUS())
        {
            string in = getStatusField(sf.field_info);
            out = tostrprintf("\"%s\":\"%s\"", vname.c_str(), in.c_str());
            s += indent+out+","+newline;
        }
        else
        {
            if (sf.value == "null")
            {
                // The string "null" translates to actual json null.
                out = tostrprintf("\"%s\":null", vname.c_str());
                s += indent+out+","+newline;
            }
            else
            {
                out = tostrprintf("\"%s\":\"%s\"", vname.c_str(), sf.value.c_str());
                s += indent+out+","+newline;
            }
        }
        if (first && getDetailedFirst())
        {
            size_t pos = out.find("\":");
            if (pos != string::npos)
            {
                string rule = out.substr(0, pos)+"_field\":"+to_string(sf.field_info->index());
                s += indent+rule+","+newline;
            }
        }
    }
    s += indent+"\"timestamp\":\""+datetimeOfUpdateRobot()+"\"";

    if (t->about.device != "")
    {
        s += ","+newline;
        s += indent+"\"device\":\""+t->about.device+"\","+newline;
        s += indent+"\"rssi_dbm\":"+to_string(t->about.rssi_dbm);
    }
    for (string extra_field : meterExtraConstantFields())
    {
        s += ","+newline;
        s += indent+makeQuotedJson(extra_field);
    }
    for (string extra_field : *extra_constant_fields)
    {
        s += ","+newline;
        s += indent+makeQuotedJson(extra_field);
    }
    s += newline;
    s += "}";
    *json = s;

    createMeterEnv(id, envs, extra_constant_fields);

    envs->push_back(string("METER_JSON=")+*json);
    envs->push_back(string("METER_MEDIA=")+media);
    envs->push_back(string("METER_TIMESTAMP=")+datetimeOfUpdateRobot());
    envs->push_back(string("METER_TIMESTAMP_UTC=")+datetimeOfUpdateRobot());
    envs->push_back(string("METER_TIMESTAMP_UT=")+unixTimestampOfUpdate());
    envs->push_back(string("METER_TIMESTAMP_LT=")+datetimeOfUpdateHumanReadable());

    for (FieldInfo& fi : field_infos_)
    {
        if (fi.printProperties().hasHIDE()) continue;

        string display_unit_s = unitToStringUpperCase(fi.displayUnit());
        string var = fi.vname();
        std::transform(var.begin(), var.end(), var.begin(), ::toupper);
        if (fi.xuantity() == Quantity::Text)
        {
            string envvar = "METER_"+var+"="+getStringValue(&fi);
            envs->push_back(envvar);
        }
        else
        {
            string envvar = "METER_"+var+"_"+display_unit_s+"="+valueToString(getNumericValue(&fi, fi.displayUnit()), fi.displayUnit());
            envs->push_back(envvar);
        }
    }

    if (t->about.device != "")
    {
        envs->push_back(string("METER_DEVICE=")+t->about.device);
        envs->push_back(string("METER_RSSI_DBM=")+to_string(t->about.rssi_dbm));
    }

}

void MeterCommonImplementation::setExpectedTPLSecurityMode(TPLSecurityMode tsm)
{
    expected_tpl_sec_mode_ = tsm;
}

void MeterCommonImplementation::setExpectedELLSecurityMode(ELLSecurityMode dsm)
{
    expected_ell_sec_mode_ = dsm;
}

TPLSecurityMode MeterCommonImplementation::expectedTPLSecurityMode()
{
    return expected_tpl_sec_mode_;
}

ELLSecurityMode MeterCommonImplementation::expectedELLSecurityMode()
{
    return expected_ell_sec_mode_;
}

void detectMeterDrivers(int manufacturer, int version, int type, vector<string> *drivers)
{
    bool found = false;
    for (DriverInfo *p : allDrivers())
    {
        if (p->detect(manufacturer, version, type))
        {
            drivers->push_back(p->name().str());
            found = true;
        }
    }
    if (!found)
    {
        const char *name = findBuiltinDriver(manufacturer, version, type);
        if (name) drivers->push_back(name);
    }
}

bool isMeterDriverReasonableForMedia(string driver_name, int media)
{
    if (media == 0x37) return false;  // Skip converter meter side since they do not give any useful information.

    for (DriverInfo *p : allDrivers())
    {
        if (p->name().str() == driver_name && p->isValidMedia(media))
        {
            return true;
        }
    }

    return false;
}

DriverInfo driver_unknown_;

DriverInfo pickMeterDriver(Telegram *t)
{
    int manufacturer = t->dll_mfct;
    int version = t->dll_version;
    int type = t->dll_type;

    if (t->tpl_id_found)
    {
        manufacturer = t->tpl_mfct;
        version = t->tpl_version;
        type = t->tpl_type;
    }

    for (DriverInfo *p : allDrivers())
    {
        if (p->detect(manufacturer, version, type))
        {
            return *p;
        }
    }

    return driver_unknown_;
}

shared_ptr<Meter> createMeter(MeterInfo *mi)
{
    const char *keymsg = (mi->key[0] == 0) ? "not-encrypted" : "encrypted";

    DriverInfo *di = lookupDriver(mi->driver_name.str());

    assert(di != NULL);

    shared_ptr<Meter> newm = di->construct(*mi);
    for (string &j : mi->extra_calculated_fields)
    {
        newm->addExtraCalculatedField(j);
    }
    newm->setPollInterval(mi->poll_interval);
    if (mi->selected_fields.size() > 0)
    {
        newm->setSelectedFields(mi->selected_fields);
    }
    else
    {
        newm->setSelectedFields(di->defaultFields());
    }
    if (isVerboseEnabled())
    {
        string aesc = AddressExpression::concat(mi->address_expressions);
        verbose("(meter) created %s %s %s %s\n",
                mi->name.c_str(),
                di->name().str().c_str(),
                aesc.c_str(),
                keymsg);
    }
    return newm;
}

bool is_driver_and_extras(const string& t, DriverName *out_driver_name, string *out_extras)
{
    // piigth(jump=foo)
    // multical21
    DriverInfo di;
    size_t ps = t.find('(');
    size_t pe = t.find(')');

    size_t te = 0; // Position after type end.

    bool found_parentheses = (ps != string::npos && pe != string::npos);

    if (!found_parentheses)
    {
        if (lookupDriverInfo(t, &di))
        {
            *out_driver_name = di.name();
            // We found a registered driver.
            *out_extras = "";
            return true;
        }
        else
        {
            error("No such driver %s %s\n", t.c_str(), removedDriverExplanation(t).c_str());
        }
        *out_extras = "";
        return true;
    }

    // Parentheses must be last.
    if (! (ps > 0 && ps < pe && pe == t.length()-1)) return false;
    te = ps;

    string type = t.substr(0, te);

    bool found = lookupDriverInfo(type, &di);

    if (found)
    {
        *out_driver_name = di.name();
    }
    else
    {
        error("No such driver %s %s\n", type.c_str(), removedDriverExplanation(type).c_str());
    }

    string extras = t.substr(ps+1, pe-ps-1);
    *out_extras = extras;

    return true;
}

string MeterInfo::str()
{
    string r;
    r += driver_name.str();
    if (extras != "")
    {
        r += "("+extras+")";
    }
    r += ":";
    if (bus != "") r += bus+":";
    if (bps != 0) r += to_string(bps)+":";
    if (!link_modes.empty()) r += link_modes.hr()+":";
    if (r.size() > 0) r.pop_back();

    return r;
}

bool MeterInfo::parse(string n, string d, string aes, string k)
{
    clear();

    name = n;
    address_expressions = splitAddressExpressions(aes);
    key = k;
    bool driverextras_checked = false;
    bool bus_checked = false;
    bool bps_checked = false;
    bool link_modes_checked = false;

    // The : colon is forbidden inside the parts.
    vector<string> parts = splitString(d, ':');

    // Example piigth:MAIN:2400 // it is an mbus meter.
    //         c5isf:MAIN:2400:mbus // attached to mbus instead of t1
    //         multical21:c1
    //         telco:BUS2:c2
    //         @iperl:t1
    // driver ( extras ) : bus_alias : bps : linkmodes

    for (auto& p : parts)
    {
        if (!driverextras_checked && is_driver_and_extras(p, &driver_name, &extras))
        {
            driverextras_checked = true;
        }
        else if (!bus_checked && isValidAlias(p) && !isValidBps(p) && !isValidLinkModes(p))
        {
            driverextras_checked = true;
            bus_checked = true;
            bus = p;
        }
        else if (!bps_checked && isValidBps(p) && !isValidLinkModes(p))
        {
            driverextras_checked = true;
            bus_checked = true;
            bps_checked = true;
            bps = atoi(p.c_str());
        }
        else if (!link_modes_checked && isValidLinkModes(p))
        {
            driverextras_checked = true;
            bus_checked = true;
            bps_checked = true;
            link_modes_checked = true;
            link_modes = parseLinkModes(p);
        }
        else
        {
            // Unknown part....
            return false;
        }
    }

    if (!link_modes_checked)
    {
        // No explicit link mode set, set to the default link modes
        // that the meter can transmit on.
        // link_modes = toMeterLinkModeSet(driver);
    }

    return true;
}

bool MeterInfo::usesPolling()
{
    return link_modes.has(LinkMode::MBUS) ||
        link_modes.has(LinkMode::C2) ||
        link_modes.has(LinkMode::T2) ||
        link_modes.has(LinkMode::S2);
}

bool isValidKey(const string& key, MeterInfo &mi)
{
    if (key.length() == 0) return true;
    if (key == "NOKEY") {
        return true;
    }
    if (mi.driver_name.str() == "izar" ||
        mi.driver_name.str() == "hydrus")
    {
        // These meters can either be OMS compatible 128 bit key (32 hex).
        // Or using an older proprietary encryption with 64 bit keys (16 hex)
        if (key.length() != 16 && key.length() != 32) return false;
    }
    else
    {
        // OMS compliant meters have 128 bit AES keys (32 hex).
        // There is a deprecated DES mode, but I have not yet
        // seen any telegram using that mode.
        if (key.length() != 32) return false;
    }
    vector<uchar> tmp;
    return hex2bin(key, &tmp);
}

void FieldInfo::performExtraction(Meter *m, Telegram *t, DVEntry *dve)
{
    if (xuantity_ == Quantity::Text)
    {
        // Extract a string.
        extractString(m, t, dve);
    }
    else if (hasFormula())
    {
        double value = formula_->calculate(displayUnit(), dve, m);
        m->setNumericValue(this, dve, displayUnit(), value);
    }
    else
    {
        // Extract a numeric.
        extractNumeric(m, t, dve);
    }
}

void FieldInfo::performCalculation(Meter *m)
{
    assert(hasFormula());

    double value = formula_->calculate(displayUnit());
    m->setNumericValue(this, NULL, displayUnit(), value);
}

bool FieldInfo::hasMatcher()
{
    return matcher_.active == true;
}

bool FieldInfo::hasFormula()
{
    return formula_ != NULL;
}

bool FieldInfo::hasIXML()
{
    return ixml_grammar_.get() != NULL;
}

bool FieldInfo::matches(DVEntry *dve)
{
    return matcher_.matches(*dve);
}

string FieldInfo::str()
{
    return tostrprintf("%d %s_%s (%s) %s [%s] \"%s\"",
                       index_,
                       vname_.c_str(),
                       unitToStringLowerCase(display_unit_).c_str(),
                       toString(xuantity_),
                       toString(vif_scaling_),
                       matcher_.str().c_str(),
                       help_.c_str());
}

void FieldInfo::useIXML(const string& ixml)
{
    XMQDoc *g = xmqNewDoc();
    bool b = xmqParseBufferWithType(g, ixml.c_str(), NULL, NULL, XMQ_CONTENT_IXML, 0);
    if (!b) {
        warning("(field) field %s failed to parse ixml grammar:\n--------------\n %s\n--------------\n", vname().c_str(), ixml.c_str());
        xmqFreeDoc(g);
        return;
    }
    ixml_grammar_ = shared_ptr<XMQDoc>(g, [](XMQDoc* d) { if (d) xmqFreeDoc(d); } );
}

DriverName MeterInfo::driverName()
{
    return driver_name;
}

bool FieldInfo::extractNumeric(Meter *m, Telegram *t, DVEntry *dve)
{
    bool found = false;
    string key = matcher_.dif_vif_key.str();

    if (dve == NULL)
    {
        if (key == "")
        {
            // Search for key.
            bool ok = findKeyWithNr(matcher_.measurement_type,
                                    matcher_.vif_range,
                                    matcher_.storage_nr_from.intValue(),
                                    matcher_.tariff_nr_from.intValue(),
                                    matcher_.index_nr.intValue(),
                                    &key,
                                    &t->dv_entries);
            // No entry was found.
            if (!ok) return false;
        }
        // No entry with this key was found.
        if (t->dv_entries.count(key) == 0) return false;
        dve = &t->dv_entries[key].second;
    }
    assert(dve != NULL);
    assert(key == "" || dve->dif_vif_key.str() == key);

    string field_name;
    if (isDebugEnabled())
    {
        field_name = generateFieldNameWithUnit(m, dve);
    }

    double extracted_double_value = NAN;

    bool auto_vif_scaling = vifScaling() == VifScaling::Auto;
    bool force_unsigned = difSignedness() == DifSignedness::Unsigned;

    if (dve->extractDouble(&extracted_double_value, auto_vif_scaling, force_unsigned))
    {
        Unit decoded_unit = displayUnit();
        if (matcher_.vif_range == VIFRange::DateTime)
        {
            struct tm datetime;
            dve->extractDate(&datetime);
            time_t tmp = mktime(&datetime);
            string bbb = strdatetime(tmp);
            extracted_double_value = tmp;
        }
        else if (matcher_.vif_range == VIFRange::Date)
        {
            struct tm date;
            dve->extractDate(&date);
            time_t tmp = mktime(&date);
            extracted_double_value = tmp;
        }
        else if (matcher_.vif_range == VIFRange::AnyEnergyVIF ||
                 matcher_.vif_range == VIFRange::AnyVolumeVIF ||
                 matcher_.vif_range == VIFRange::AnyPowerVIF)
        {
            // Find the actual unit used in the telegram.
            decoded_unit = toDefaultUnit(dve->vif);
        }
        else if (matcher_.vif_range != VIFRange::Any &&
                 matcher_.vif_range != VIFRange::None)
        {
            // Pick the default unit for this range.
            decoded_unit = toDefaultUnit(matcher_.vif_range);
        }

        debug("(meter) %s %s decoded %s default %s value %g (scale %g)\n",
              toString(matcher_.vif_range),
              field_name.c_str(),
              unitToStringLowerCase(decoded_unit).c_str(),
              unitToStringLowerCase(display_unit_).c_str(),
              extracted_double_value,
              scale());

        if (scale() != 1.0)
        {
            // Hardcoded scale factor for this field used for manufacturer specific values without vif units.
            extracted_double_value *= scale();
        }
        if (overrideConversion(decoded_unit, display_unit_))
        {
            // Special case! Transform the decoded unit into the display unit. I.e. kwh was replaced with kvarh.
            decoded_unit = display_unit_;
        }
        m->setNumericValue(this, dve, display_unit_, convert(extracted_double_value, decoded_unit, display_unit_));
        t->addMoreExplanation(dve->offset, renderJson(m, dve));
        found = true;
    }
    return found;
}

static string add_tpl_status(string existing_status, Meter *m, Telegram *t)
{
    string status = m->decodeTPLStatusByte(t->tpl_sts);
    t->addMoreExplanation(t->tpl_sts_offset, "(%s)", status.c_str());
    if (status != "OK")
    {
        if (existing_status != "OK")
        {
            // Join the statuses.
            if (existing_status != "")
            {
                existing_status += " ";
            }
            existing_status += status;
        }
        else
        {
            // Overwrite OK.
            existing_status = status;
        }
    }
    else
    {
        // No change to the existing_status
    }

    return existing_status;
}

bool FieldInfo::extractString(Meter *m, Telegram *t, DVEntry *dve)
{
    bool found = false;
    string key = matcher_.dif_vif_key.str();

    if (dve == NULL)
    {
        if (key == "")
        {
            if (!hasMatcher())
            {
                // There is no matcher, only use case is to capture JOIN_TPL_STATUS.
                if (print_properties_.hasINCLUDETPLSTATUS())
                {
                    string status = add_tpl_status("OK", m, t);
                    m->setStringValue(this, status, dve);
                    return true;
                }
            }
            else
            {
                // Search for key.
                bool ok = findKeyWithNr(matcher_.measurement_type,
                                        matcher_.vif_range,
                                        matcher_.storage_nr_from.intValue(),
                                        matcher_.tariff_nr_from.intValue(),
                                        matcher_.index_nr.intValue(),
                                        &key,
                                        &t->dv_entries);
                // No entry was found.
                if (!ok) {
                    // Nothing found, however check if capturing JOIN_TPL_STATUS.
                    if (print_properties_.hasINCLUDETPLSTATUS())
                    {
                        string status = add_tpl_status("OK", m, t);
                        m->setStringValue(this, status, dve);
                        return true;
                    }
                    return false;
                }
            }
        }
        // No entry with this key was found.
        if (t->dv_entries.count(key) == 0)
        {
            // Nothing found, however check if capturing JOIN_TPL_STATUS.
            if (print_properties_.hasINCLUDETPLSTATUS())
            {
                string status = add_tpl_status("OK", m, t);
                m->setStringValue(this, status, dve);
                return true;
            }
            return false;
        }
        dve = &t->dv_entries[key].second;
    }
    assert(dve != NULL);
    assert(key == "" || dve->dif_vif_key.str() == key);

    // Generate the json field name:
    string field_name = generateFieldNameNoUnit(m, dve);

    uint64_t extracted_bits {};
    if (lookup_.hasLookups() || (print_properties_.hasINCLUDETPLSTATUS()))
    {
        string translated_bits = "";
        // The field has lookups, or the print property JOIN_TPL_STATUS is set,
        // this means that we should create a string.
        if (lookup_.hasLookups() && dve->extractLong(&extracted_bits))
        {
            translated_bits = lookup().translate(extracted_bits);
            found = true;
        }

        if (print_properties_.hasINCLUDETPLSTATUS())
        {
            translated_bits = add_tpl_status(translated_bits, m, t);
        }

        if (found)
        {
            m->setStringValue(this, translated_bits, dve);
            t->addMoreExplanation(dve->offset, renderJsonText(m, dve));
        }
    }
    else if (matcher_.vif_range == VIFRange::DateTime)
    {
        struct tm datetime;
        dve->extractDate(&datetime);
        string extracted_device_date_time;

        if (dve->value.size() == 12)
        {
            // A long date time sec + timezone field. TODO add timezone data.
            extracted_device_date_time = strdatetimesec(&datetime);
        }
        else
        {
            extracted_device_date_time = strdatetime(&datetime);
        }
        m->setStringValue(this, extracted_device_date_time, dve);
        t->addMoreExplanation(dve->offset, renderJsonText(m, dve));
        found = true;
    }
    else if (matcher_.vif_range == VIFRange::Date)
    {
        struct tm date;
        dve->extractDate(&date);
        string extracted_device_date = strdate(&date);
        m->setStringValue(this, extracted_device_date, dve);
        t->addMoreExplanation(dve->offset, renderJsonText(m, dve));
        found = true;
    }
    else if (readable_string_ != ReadableString::Unknown ||
             matcher_.vif_range == VIFRange::EnhancedIdentification ||
             matcher_.vif_range == VIFRange::FabricationNo ||
             matcher_.vif_range == VIFRange::HardwareVersion ||
             matcher_.vif_range == VIFRange::FirmwareVersion ||
             matcher_.vif_range == VIFRange::Medium ||
             matcher_.vif_range == VIFRange::Manufacturer ||
             matcher_.vif_range == VIFRange::ModelVersion ||
             matcher_.vif_range == VIFRange::SoftwareVersion ||
             matcher_.vif_range == VIFRange::Customer ||
             matcher_.vif_range == VIFRange::Location ||
             matcher_.vif_range == VIFRange::SpecialSupplierInformation ||
             matcher_.vif_range == VIFRange::ParameterSet)
    {
        string extracted;
        if (readable_string_ == ReadableString::Reversed)
        {
            dve->extractReadableStringReversed(&extracted);
        }
        else
        {
            dve->extractReadableString(&extracted);
        }
        m->setStringValue(this, extracted, dve);
        t->addMoreExplanation(dve->offset, renderJsonText(m, dve));
        found = true;
    }
    else
    {
        string extracted;
        dve->extractHexString(&extracted);
        m->setStringValue(this, extracted, dve);
        if (!hasIXML()) t->addMoreExplanation(dve->offset, renderJsonText(m, dve));
        found = true;
    }
    return found;
}

bool checkIf(set<string> &fields, const char *s)
{
    if (fields.count(s) > 0)
    {
        fields.erase(s);
        return true;
    }

    return false;
}

bool checkFieldsEmpty(set<string> &fields, string driver_name)
{
    if (fields.size() > 0)
    {
        string info;
        for (auto &s : fields) { info += s+" "; }

        warning("(meter) when adding common fields to driver %s, these fields were not found: %s\n",
                driver_name.c_str(),
                info.c_str());
        return false;
    }
    return true;
}

bool MeterCommonImplementation::addOptionalLibraryFields(string field_names)
{
    // Old C++ driver passes fields like: "target_date,target_volume_m3"
    // New xmq driver passes single field like: "target_date|Special help for target date."
    // or just: "target_date"

    set<string> fields = splitStringIntoSet(field_names, ',');
    string help;
    vector<string> helps = splitString(field_names, '|');
    if (helps.size() == 2)
    {
        fields.clear();
        fields.insert(helps[0]);
        help = " "+helps[1];
    }
    if (helps.size() > 2)
    {
        error("Bad library field, only zero or one pipe | symbol is allowed: %s", field_names.c_str());
    }

    if (checkIf(fields,"status-tpl-only"))
    {
        addStringField(
            "status",
            "Status and error flags."+help,
            STATUS | INCLUDE_TPL_STATUS);
        markLastFieldAsLibrary();
    }

    if (checkIf(fields, "actuality_duration_s"))
    {
        addNumericFieldWithExtractor(
            "actuality_duration",
            "Lapsed time between measurement and transmission."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Time,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::ActualityDuration),
            Unit::Second
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields, "actuality_duration_h"))
    {
        addNumericFieldWithExtractor(
            "actuality_duration",
            "Lapsed time between measurement and transmission."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Time,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::ActualityDuration)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields, "fabrication_no"))
    {
        addStringFieldWithExtractor(
            "fabrication_no",
            "Fabrication number."+help,
            DEFAULT_PRINT_PROPERTIES,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::FabricationNo)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"enhanced_id"))
    {
        addStringFieldWithExtractor(
            "enhanced_id",
            "Enhanced identification number."+help,
            DEFAULT_PRINT_PROPERTIES,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::EnhancedIdentification)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"software_version"))
    {
        addStringFieldWithExtractor(
            "software_version",
            "Software version."+help,
            DEFAULT_PRINT_PROPERTIES,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::SoftwareVersion)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"manufacturer"))
    {
        addStringFieldWithExtractor(
            "manufacturer",
            "Meter manufacturer."+help,
            DEFAULT_PRINT_PROPERTIES,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::Manufacturer)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"model_version"))
    {
        addStringFieldWithExtractor(
            "model_version",
            "Meter model version."+help,
            DEFAULT_PRINT_PROPERTIES,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::ModelVersion)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"firmware_version"))
    {
        addStringFieldWithExtractor(
            "firmware_version",
            "Meter firmware version."+help,
            DEFAULT_PRINT_PROPERTIES,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::FirmwareVersion)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"parameter_set"))
    {
        addStringFieldWithExtractor(
            "parameter_set",
            "Parameter set for this meter."+help,
            DEFAULT_PRINT_PROPERTIES,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::ParameterSet)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"customer"))
    {
        addStringFieldWithExtractor(
            "customer",
            "Customer name."+help,
            DEFAULT_PRINT_PROPERTIES,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::Customer)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"location"))
    {
        addStringFieldWithExtractor(
            "location",
            "Meter installed at this customer location."+help,
            DEFAULT_PRINT_PROPERTIES,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::Location)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"operating_time_h"))
    {
        addNumericFieldWithExtractor(
            "operating_time",
            "How long the meter has been collecting data."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Time,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::OperatingTime)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"on_time_h"))
    {
        addNumericFieldWithExtractor(
            "on_time",
            "How long the meter has been powered up."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Time,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::OnTime)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"on_time_at_error_h"))
    {
        addNumericFieldWithExtractor(
            "on_time_at_error",
            "How long the meter has been in an error state while powered up."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Time,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::AtError)
            .set(VIFRange::OnTime)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"meter_date"))
    {
        addStringFieldWithExtractor(
            "meter_date",
            "Date when the meter sent the telegram."+help,
            DEFAULT_PRINT_PROPERTIES,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::Date)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"meter_date_at_error"))
    {
        addStringFieldWithExtractor(
            "meter_date_at_error",
            "Date when the meter was in error."+help,
            DEFAULT_PRINT_PROPERTIES,
            FieldMatcher::build()
            .set(MeasurementType::AtError)
            .set(VIFRange::Date)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"meter_datetime"))
    {
        addStringFieldWithExtractor(
            "meter_datetime",
            "Date and time when the meter sent the telegram."+help,
            DEFAULT_PRINT_PROPERTIES,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::DateTime)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"meter_datetime_at_error"))
    {
        addStringFieldWithExtractor(
            "meter_datetime_at_error",
            "Date and time when the meter was in error."+help,
            DEFAULT_PRINT_PROPERTIES,
            FieldMatcher::build()
            .set(MeasurementType::AtError)
            .set(VIFRange::DateTime)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"total_m3"))
    {
        addNumericFieldWithExtractor(
            "total",
            "The total media volume consumption recorded by this meter."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Volume,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::Volume)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"target_m3"))
    {
        addNumericFieldWithExtractor(
            "target",
            "The volume recorded by this meter at the target date."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Volume,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::Volume)
            .set(StorageNr(1))
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"total_kwh"))
    {
        addNumericFieldWithExtractor(
            "total",
            "The total energy consumption recorded by this meter."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Energy,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::AnyEnergyVIF)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"target_kwh"))
    {
        addNumericFieldWithExtractor(
            "target",
            "The energy recorded by this meter at the target date."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Volume,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::AnyEnergyVIF)
            .set(StorageNr(1))
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"target_date"))
    {
        addNumericFieldWithExtractor(
            "target",
            "The target date. Usually the end of the previous billing period."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::PointInTime,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::Date)
            .set(StorageNr(1)),
            Unit::DateLT
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"total_forward_m3"))
    {
        addNumericFieldWithExtractor(
            "total_forward",
            "The total media volume flowing forward."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Volume,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::Volume)
            .add(VIFCombinable::ForwardFlow)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"total_backward_m3"))
    {
        addNumericFieldWithExtractor(
            "total_backward",
            "The total media volume flowing backward."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Volume,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::Volume)
            .add(VIFCombinable::BackwardFlow)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"flow_temperature_c"))
    {
        addNumericFieldWithExtractor(
            "flow_temperature",
            "Forward media temperature."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Temperature,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::FlowTemperature)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"external_temperature_c"))
    {
        addNumericFieldWithExtractor(
            "external_temperature",
            "Temperature outside of meter."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Temperature,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::ExternalTemperature)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"return_temperature_c"))
    {
        addNumericFieldWithExtractor(
            "return_temperature",
            "Return media temperature.",
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Temperature,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::ReturnTemperature)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"flow_return_temperature_difference_c"))
    {
        addNumericFieldWithExtractor(
            "flow_return_temperature_difference",
            "The difference between flow and return media temperatures."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Temperature,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::TemperatureDifference)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"volume_flow_m3h"))
    {
        addNumericFieldWithExtractor(
            "volume_flow",
            "Media volume flow."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Flow,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::VolumeFlow)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"access_counter"))
    {
        addNumericFieldWithExtractor(
            "access",
            "Meter access counter."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::Dimensionless,
            VifScaling::None,
            DifSignedness::Unsigned,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::AccessNumber)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"consumption_hca"))
    {
        addNumericFieldWithExtractor(
            "consumption",
            "The current heat cost allocation for this meter."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::HCA,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::HeatCostAllocation)
            );
        markLastFieldAsLibrary();
    }

    if (checkIf(fields,"target_hca"))
    {
        addNumericFieldWithExtractor(
            "target",
            "The heat cost allocation recorded by this meter at the target date."+help,
            DEFAULT_PRINT_PROPERTIES,
            Quantity::HCA,
            VifScaling::Auto,
            DifSignedness::Signed,
            FieldMatcher::build()
            .set(MeasurementType::Instantaneous)
            .set(VIFRange::HeatCostAllocation)
            .set(StorageNr(1))
            );
        markLastFieldAsLibrary();
    }

    if (!checkFieldsEmpty(fields, name()))
    {
        return false;
    }
    return true;
}

const char *toString(VifScaling s)
{
    switch (s)
    {
    case VifScaling::Auto: return "Auto";
    case VifScaling::None: return "None";
    case VifScaling::Unknown: return "Unknown";
    }
    return "?";
}

VifScaling toVifScaling(const char *s)
{
    if (!s) return VifScaling::Unknown;
    if (!strcmp(s, "Auto")) return VifScaling::Auto;
    if (!strcmp(s, "None")) return VifScaling::None;
    if (!strcmp(s, "Unknown")) return VifScaling::Unknown;
    return VifScaling::Unknown;
}

const char *toString(DifSignedness s)
{
    switch (s)
    {
    case DifSignedness::Signed: return "Signed";
    case DifSignedness::Unsigned: return "Unsigned";
    case DifSignedness::Unknown: return "Unknown";
    }
    return "?";
}

DifSignedness toDifSignedness(const char *s)
{
    if (!s) return DifSignedness::Unknown;
    if (!strcmp(s, "Signed")) return DifSignedness::Signed;
    if (!strcmp(s, "Unsigned")) return DifSignedness::Unsigned;
    if (!strcmp(s, "Unknown")) return DifSignedness::Unknown;
    return DifSignedness::Unknown;
}

const char* toString(PrintProperty p)
{
    switch(p)
    {
    case PrintProperty::REQUIRED: return "REQUIRED";
    case PrintProperty::DEPRECATED: return "DEPRECATED";
    case PrintProperty::STATUS: return "STATUS";
    case PrintProperty::INCLUDE_TPL_STATUS: return "INCLUDE_TPL_STATUS";
    case PrintProperty::INJECT_INTO_STATUS: return "INJECT_INTO_STATUS";
    case PrintProperty::HIDE: return "HIDE";
    case PrintProperty::Unknown: return "Unknown";
    }

    return "Unknown";
}

PrintProperty toPrintProperty(const char *s)
{
    if (!strcmp(s, "REQUIRED")) return PrintProperty::REQUIRED;
    if (!strcmp(s, "DEPRECATED")) return PrintProperty::DEPRECATED;
    if (!strcmp(s, "STATUS")) return PrintProperty::STATUS;
    if (!strcmp(s, "INCLUDE_TPL_STATUS")) return PrintProperty::INCLUDE_TPL_STATUS;
    if (!strcmp(s, "INJECT_INTO_STATUS")) return PrintProperty::INJECT_INTO_STATUS;
    if (!strcmp(s, "HIDE")) return PrintProperty::HIDE;
    if (!strcmp(s, "Unknown")) return PrintProperty::Unknown;

    return PrintProperty::Unknown;
}

PrintProperties toPrintProperties(string s)
{
    auto fields = splitString(s, ',');

    int bits = 0;
    for (auto p : fields)
    {
        bits |= toPrintProperty(p.c_str());
    }

    return bits;
}

char available_meter_types_[2048];

const char *availableMeterTypes()
{
    if (available_meter_types_[0]) return available_meter_types_;

#define X(m) if (MeterType::m != MeterType::AutoMeter && MeterType::m != MeterType::UnknownMeter) {  \
        strcat(available_meter_types_, #m); strcat(available_meter_types_, "\n"); \
        assert(strlen(available_meter_types_) < 1024); }
LIST_OF_METER_TYPES
#undef X

    // Remove last ,
    available_meter_types_[strlen(available_meter_types_)-1] = 0;
    return available_meter_types_;
}

void MeterCommonImplementation::setMeterManager(MeterManager *mm)
{
    meter_manager_ = mm;
}

MeterManager *MeterCommonImplementation::meterManager()
{
    return meter_manager_;
}
