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

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

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

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

#ifndef DVPARSER_H
#define DVPARSER_H

#include"util.h"
#include"units.h"

#include<map>
#include<set>
#include<cstdint>
#include<time.h>
#include<functional>
#include<vector>

#define LIST_OF_VIF_RANGES \
    X(Volume,0x10,0x17,Quantity::Volume,Unit::M3) \
    X(OnTime,0x20,0x23, Quantity::Time, Unit::Hour)  \
    X(OperatingTime,0x24,0x27, Quantity::Time, Unit::Hour)  \
    X(VolumeFlow,0x38,0x3F, Quantity::Flow, Unit::M3H) \
    X(FlowTemperature,0x58,0x5B, Quantity::Temperature, Unit::C) \
    X(ReturnTemperature,0x5C,0x5F, Quantity::Temperature, Unit::C) \
    X(TemperatureDifference,0x60,0x63, Quantity::Temperature, Unit::C) \
    X(ExternalTemperature,0x64,0x67, Quantity::Temperature, Unit::C) \
    X(Pressure,0x68,0x6B, Quantity::Pressure, Unit::BAR) \
    X(HeatCostAllocation,0x6E,0x6E, Quantity::HCA, Unit::HCA) \
    X(Date,0x6C,0x6C, Quantity::PointInTime, Unit::DateLT) \
    X(DateTime,0x6D,0x6D, Quantity::PointInTime, Unit::DateTimeLT) \
    X(EnergyMJ,0x08,0x0F, Quantity::Energy, Unit::MJ) \
    X(EnergyWh,0x00,0x07, Quantity::Energy, Unit::KWH) \
    X(PowerW,0x28,0x2f, Quantity::Power, Unit::KW) \
    X(PowerJh,0x30,0x37, Quantity::Power, Unit::MJH) \
    X(ActualityDuration,0x74,0x77, Quantity::Time, Unit::Hour) \
    X(FabricationNo,0x78,0x78, Quantity::Text, Unit::TXT) \
    X(EnhancedIdentification,0x79,0x79, Quantity::Text, Unit::TXT) \
    X(EnergyMWh,0x7B00,0x7B01, Quantity::Energy, Unit::KWH) \
    X(EnergyGJ,0x7B08,0x7B09, Quantity::Energy, Unit::MJ) \
    X(RelativeHumidity,0x7B1A,0x7B1B, Quantity::RH, Unit::RH) \
    X(AccessNumber,0x7D08,0x7D08, Quantity::Counter, Unit::COUNTER) \
    X(Medium,0x7D09,0x7D09, Quantity::Text, Unit::TXT) \
    X(Manufacturer,0x7D0A,0x7D0A, Quantity::Text, Unit::TXT) \
    X(ParameterSet,0x7D0B,0x7D0B, Quantity::Text, Unit::TXT) \
    X(ModelVersion,0x7D0C,0x7D0C, Quantity::Text, Unit::TXT) \
    X(HardwareVersion,0x7D0D,0x7D0D, Quantity::Text, Unit::TXT) \
    X(FirmwareVersion,0x7D0E,0x7D0E, Quantity::Text, Unit::TXT) \
    X(SoftwareVersion,0x7D0F,0x7D0F, Quantity::Text, Unit::TXT) \
    X(Location,0x7D10,0x7D10, Quantity::Text, Unit::TXT) \
    X(Customer,0x7D11,0x7D11, Quantity::Text, Unit::TXT) \
    X(ErrorFlags,0x7D17,0x7D17, Quantity::Text, Unit::TXT) \
    X(DigitalOutput,0x7D1A,0x7D1A, Quantity::Text, Unit::TXT) \
    X(DigitalInput,0x7D1B,0x7D1B, Quantity::Text, Unit::TXT) \
    X(DurationSinceReadout,0x7D2c,0x7D2f, Quantity::Time, Unit::Hour) \
    X(DurationOfTariff,0x7D31,0x7D33, Quantity::Time, Unit::Hour) \
    X(Dimensionless,0x7D3A,0x7D3A, Quantity::Counter, Unit::COUNTER) \
    X(Voltage,0x7D40,0x7D4F, Quantity::Voltage, Unit::Volt) \
    X(Amperage,0x7D50,0x7D5F, Quantity::Amperage, Unit::Ampere) \
    X(ResetCounter,0x7D60,0x7D60, Quantity::Counter, Unit::COUNTER) \
    X(CumulationCounter,0x7D61,0x7D61, Quantity::Counter, Unit::COUNTER) \
    X(SpecialSupplierInformation,0x7D67,0x7D67, Quantity::Text, Unit::TXT) \
    X(RemainingBattery,0x7D74,0x7D74, Quantity::Time, Unit::Day) \
    X(AnyVolumeVIF,0x00,0x00, Quantity::Volume, Unit::Unknown) \
    X(AnyEnergyVIF,0x00,0x00, Quantity::Energy, Unit::Unknown)  \
    X(AnyPowerVIF,0x00,0x00, Quantity::Power, Unit::Unknown)


enum class VIFRange
{
    None,
    Any,
#define X(name,from,to,quantity,unit) name,
LIST_OF_VIF_RANGES
#undef X
};

struct VIFRaw {
    VIFRaw(uint16_t v) : value(v) {}
    uint16_t value;
};

const char *toString(VIFRange v);
VIFRange toVIFRange(const char *s);
Unit toDefaultUnit(VIFRange v);
VIFRange toVIFRange(int i);
bool isInsideVIFRange(int i, VIFRange range);

#define LIST_OF_VIF_COMBINABLES \
    X(Reserved,0x00,0x11) \
    X(Average,0x12,0x12) \
    X(InverseCompactProfile,0x13,0x13) \
    X(RelativeDeviation,0x14,0x14) \
    X(RecordErrorCodeMeterToController,0x15,0x1c) \
    X(StandardConformantDataContent,0x1d,0x1d) \
    X(CompactProfileWithRegister,0x1e,0x1e) \
    X(CompactProfile,0x1f,0x1f) \
    X(PerSecond,0x20,0x20) \
    X(PerMinute,0x21,0x21) \
    X(PerHour,0x22,0x22) \
    X(PerDay,0x23,0x23) \
    X(PerWeek,0x24,0x24) \
    X(PerMonth,0x25,0x25) \
    X(PerYear,0x26,0x26) \
    X(PerRevolutionMeasurement,0x27,0x27) \
    X(IncrPerInputPulseChannel0,0x28,0x28) \
    X(IncrPerInputPulseChannel1,0x29,0x29) \
    X(IncrPerOutputPulseChannel0,0x2a,0x2a) \
    X(IncrPerOutputPulseChannel1,0x2b,0x2b) \
    X(PerLitre,0x2c,0x2c) \
    X(PerM3,0x2d,0x2d) \
    X(PerKg,0x2e,0x2e) \
    X(PerKelvin,0x2f,0x2f) \
    X(PerKWh,0x30,0x30) \
    X(PerGJ,0x31,0x31) \
    X(PerKW,0x32,0x32) \
    X(PerKelvinLitreW,0x33,0x33) \
    X(PerVolt,0x34,0x34) \
    X(PerAmpere,0x35,0x35) \
    X(MultipliedByS,0x36,0x36) \
    X(MultipliedBySDivV,0x37,0x37) \
    X(MultipliedBySDivA,0x38,0x38) \
    X(StartDateTimeOfAB,0x39,0x39) \
    X(UncorrectedMeterUnit,0x3a,0x3a) \
    X(ForwardFlow,0x3b,0x3b) \
    X(BackwardFlow,0x3c,0x3c) \
    X(ReservedNonMetric,0x3d,0x3d) \
    X(ValueAtBaseCondC,0x3e,0x3e) \
    X(ObisDeclaration,0x3f,0x3f) \
    X(LowerLimit,0x40,0x40) \
    X(ExceedsLowerLimit,0x41,0x41) \
    X(DateTimeExceedsLowerFirstBegin, 0x42,0x42) \
    X(DateTimeExceedsLowerFirstEnd, 0x43,0x43) \
    X(DateTimeExceedsLowerLastBegin, 0x46,0x46) \
    X(DateTimeExceedsLowerLastEnd, 0x47,0x47) \
    X(UpperLimit,0x48,0x48) \
    X(ExceedsUpperLimit,0x49,0x49) \
    X(DateTimeExceedsUpperFirstBegin, 0x4a,0x4a) \
    X(DateTimeExceedsUpperFirstEnd, 0x4b,0x4b) \
    X(DateTimeExceedsUpperLastBegin, 0x4d,0x4d) \
    X(DateTimeExceedsUpperLastEnd, 0x4e,0x4e) \
    X(DurationExceedsLowerFirst,0x50,0x53) \
    X(DurationExceedsLowerLast,0x54,0x57) \
    X(DurationExceedsUpperFirst,0x58,0x5b) \
    X(DurationExceedsUpperLast,0x5c,0x5f) \
    X(DurationOfDFirst,0x60,0x63) \
    X(DurationOfDLast,0x64,0x67) \
    X(ValueDuringLowerLimitExceeded,0x68,0x68) \
    X(LeakageValues,0x69,0x69) \
    X(OverflowValues,0x6a,0x6a) \
    X(ValueDuringUpperLimitExceeded,0x6c,0x6c) \
    X(DateTimeOfDEFirstBegin,0x6a,0x6a) \
    X(DateTimeOfDEFirstEnd,0x6b,0x6b) \
    X(DateTimeOfDELastBegin,0x6e,0x6e) \
    X(DateTimeOfDELastEnd,0x6f,0x6f) \
    X(MultiplicativeCorrectionFactorForValue,0x70,0x77) \
    X(AdditiveCorrectionConstant,0x78,0x7b) \
    X(CombinableVIFExtension,0x7c,0x7c) \
    X(MultiplicativeCorrectionFactorForValue103,0x7d,0x7d) \
    X(FutureValue,0x7e,0x7e) \
    X(MfctSpecific,0x7f,0x7f) \
    X(AtPhase1,0x7c01,0x7c01) \
    X(AtPhase2,0x7c02,0x7c02) \
    X(AtPhase3,0x7c03,0x7c03) \
    X(AtNeutral,0x7c04,0x7c04) \
    X(BetweenPhaseL1AndL2,0x7c05,0x7c05) \
    X(BetweenPhaseL2AndL3,0x7c06,0x7c06) \
    X(BetweenPhaseL3AndL1,0x7c07,0x7c07) \
    X(AtQuadrantQ1,0x7c08,0x7c08) \
    X(AtQuadrantQ2,0x7c09,0x7c09) \
    X(AtQuadrantQ3,0x7c0a,0x7c0a) \
    X(AtQuadrantQ4,0x7c0b,0x7c0b) \
    X(DeltaBetweenImportAndExport,0x7c0c,0x7c0c) \
    X(AccumulationOfAbsoluteValue,0x7c10,0x7c10) \
    X(DataPresentedWithTypeC,0x7c11,0x7c11) \
    X(DataPresentedWithTypeD,0x7c12,0x7c12) \
    X(Mfct00,0x7f00,0x7f00) \
    X(Mfct01,0x7f01,0x7f01) \
    X(Mfct02,0x7f02,0x7f02) \
    X(Mfct03,0x7f03,0x7f03) \
    X(Mfct04,0x7f04,0x7f04) \
    X(Mfct05,0x7f05,0x7f05) \
    X(Mfct06,0x7f06,0x7f06) \
    X(Mfct07,0x7f07,0x7f07) \
    X(Mfct08,0x7f08,0x7f08) \
    X(Mfct21,0x7f21,0x7f21) \

enum class VIFCombinable
{
    None,
    Any,
#define X(name,from,to) name,
LIST_OF_VIF_COMBINABLES
#undef X
};

struct VIFCombinableRaw {
    VIFCombinableRaw(uint16_t v) : value(v) {}
    uint16_t value;
};

VIFCombinable toVIFCombinable(const char *s);
VIFCombinable toVIFCombinable(int i);
const char *toString(VIFCombinable v);

enum class MeasurementType
{
    Any,
    Instantaneous,
    Minimum,
    Maximum,
    AtError,
    Unknown
};

const char *toString(MeasurementType mt);
MeasurementType toMeasurementType(const char *s);

void extractDV(std::string &s, uchar *dif, int *vif, bool *has_difes, bool *has_vifes);

struct DifVifKey
{
    DifVifKey(std::string key) : key_(key) {
        extractDV(key, &dif_, &vif_, &has_difes_, &has_vifes_);
    }
    std::string str() { return key_; }
    bool operator==(DifVifKey &dvk) { return key_ == dvk.key_; }
    uchar dif() { return dif_; }
    int vif() { return vif_; }
    bool hasDifes() { return has_difes_; }
    bool hasVifes() { return has_vifes_; }

private:

    std::string key_;
    uchar dif_;
    int vif_;
    bool has_difes_;
    bool has_vifes_;
};

void extractDV(DifVifKey &s, uchar *dif, int *vif, bool *has_difes, bool *has_vifes);

static DifVifKey NoDifVifKey = DifVifKey("");

struct Vif
{
    Vif(int n) : nr_(n) {}
    int intValue() { return nr_; }
    bool operator==(Vif s) { return nr_ == s.nr_; }

private:
    int nr_;
};

Unit toDefaultUnit(Vif v);

enum class DVEntryCounterType
{
    UNKNOWN,
    STORAGE_COUNTER,
    TARIFF_COUNTER,
    SUBUNIT_COUNTER
};

DVEntryCounterType toDVEntryCounterType(const std::string &s);
const char *toString(DVEntryCounterType ct);

struct StorageNr
{
    StorageNr(int n) : nr_(n) {}
    int intValue() { return nr_; }
    bool operator==(StorageNr s) { return nr_ == s.nr_; }
    bool operator!=(StorageNr s) { return nr_ != s.nr_; }
    bool operator>=(StorageNr s) { return nr_ >= s.nr_; }
    bool operator<=(StorageNr s) { return nr_ <= s.nr_; }

private:
    int nr_;
};

static StorageNr AnyStorageNr = StorageNr(-1);

struct TariffNr
{
    TariffNr(int n) : nr_(n) {}
    int intValue() { return nr_; }
    bool operator==(TariffNr s) { return nr_ == s.nr_; }
    bool operator!=(TariffNr s) { return nr_ != s.nr_; }
    bool operator>=(TariffNr s) { return nr_ >= s.nr_; }
    bool operator<=(TariffNr s) { return nr_ <= s.nr_; }

private:
    int nr_;
};

static TariffNr AnyTariffNr = TariffNr(-1);

struct SubUnitNr
{
    SubUnitNr(int n) : nr_(n) {}
    int intValue() { return nr_; }
    bool operator==(SubUnitNr s) { return nr_ == s.nr_; }
    bool operator!=(SubUnitNr s) { return nr_ != s.nr_; }
    bool operator>=(SubUnitNr s) { return nr_ >= s.nr_; }
    bool operator<=(SubUnitNr s) { return nr_ <= s.nr_; }

private:
    int nr_;
};

struct IndexNr
{
    IndexNr(int n) : nr_(n) {}
    int intValue() { return nr_; }
    bool operator==(IndexNr s) { return nr_ == s.nr_; }
    bool operator!=(IndexNr s) { return nr_ != s.nr_; }

private:
    int nr_;
};

static IndexNr AnyIndexNr = IndexNr(-1);

struct FieldInfo;

struct DVEntry
{
    int offset {}; // Where in the telegram this dventry was found.
    DifVifKey dif_vif_key;
    MeasurementType measurement_type;
    Vif vif;
    std::set<VIFCombinable> combinable_vifs;
    std::set<uint16_t> combinable_vifs_raw;
    StorageNr storage_nr;
    TariffNr tariff_nr;
    SubUnitNr subunit_nr;
    std::string value;

    DVEntry(int off,
            DifVifKey dvk,
            MeasurementType mt,
            Vif vi,
            std::set<VIFCombinable> vc,
            std::set<uint16_t> vc_raw,
            StorageNr st,
            TariffNr ta,
            SubUnitNr su,
            std::string &val) :
        offset(off),
        dif_vif_key(dvk),
        measurement_type(mt),
        vif(vi),
        combinable_vifs(vc),
        combinable_vifs_raw(vc_raw),
        storage_nr(st),
        tariff_nr(ta),
        subunit_nr(su),
        value(val)
    {
    }

    DVEntry() :
        offset(999999),
        dif_vif_key("????"),
        measurement_type(MeasurementType::Instantaneous),
        vif(0),
        storage_nr(0),
        tariff_nr(0),
        subunit_nr(0),
        value("x")
    {
    }

    bool extractDouble(double *out, bool auto_scale, bool force_unsigned);
    bool extractLong(uint64_t *out);
    bool extractDate(struct tm *out);
    bool extractReadableString(std::string *out);
    void addFieldInfo(FieldInfo *fi) { field_infos_.insert(fi); }
    bool hasFieldInfo(FieldInfo *fi) { return field_infos_.count(fi) > 0; }
    std::string str();

    double getCounter(DVEntryCounterType ct);

private:
    std::set<FieldInfo*> field_infos_; // The field infos selected to decode this entry.
};

struct FieldMatcher
{
    // If not actually used, this remains false.
    bool active = false;

    // Exact difvif hex string matching all other checks are ignored.
    bool match_dif_vif_key = false;
    DifVifKey dif_vif_key { "" };

    // Match the measurement_type.
    bool match_measurement_type = false;
    MeasurementType measurement_type { MeasurementType::Instantaneous };

    // Match the value information range. See dvparser.h
    bool match_vif_range = false;
    VIFRange vif_range { VIFRange::Any };

    // Match the vif exactly, used for manufacturer specific vifs.
    bool match_vif_raw = false;
    uint16_t vif_raw {};

    // Match any vif combinables.
    std::set<VIFCombinable> vif_combinables;
    std::set<uint16_t> vif_combinables_raw;

    // Match the storage nr. If no storage is specified, default to match only 0.
    bool match_storage_nr = true;
    StorageNr storage_nr_from { 0 };
    StorageNr storage_nr_to { 0 };

    // Match the tariff nr. If no tariff is specified, default to match only 0.
    bool match_tariff_nr = true;
    TariffNr tariff_nr_from { 0 };
    TariffNr tariff_nr_to { 0 };

    // Match the subunit. If no subunit is specified, default to match only 0.
    bool match_subunit_nr = true;
    SubUnitNr subunit_nr_from { 0 };
    SubUnitNr subunit_nr_to { 0 };

    // If the telegram has multiple identical difvif entries matching this field
    // and you want to catch the second matching entry, then set the index nr to 2.
    // The default is 1.
    IndexNr index_nr { 1 };

    FieldMatcher() : active(false) { }
    FieldMatcher(bool act) : active(act) { }
    static FieldMatcher build() { return FieldMatcher(true); }
    static FieldMatcher noMatcher() { return FieldMatcher(false); }
    FieldMatcher &set(DifVifKey k) {
        dif_vif_key = k;
        match_dif_vif_key = (k.str() != ""); return *this; }
    FieldMatcher &set(MeasurementType mt) {
        measurement_type = mt;
        match_measurement_type = (mt != MeasurementType::Any);
        return *this; }
    FieldMatcher &set(VIFRange v) {
        vif_range = v;
        match_vif_range = (v != VIFRange::Any);
        return *this; }
    FieldMatcher &set(VIFRaw v) {
        vif_raw = v.value;
        match_vif_raw = true;
        return *this; }
    FieldMatcher &add(VIFCombinable v) {
        vif_combinables.insert(v);
        return *this; }
    FieldMatcher &add(VIFCombinableRaw v) {
        vif_combinables_raw.insert(v.value);
        return *this; }
    FieldMatcher &set(StorageNr s) {
        storage_nr_from = storage_nr_to = s;
        match_storage_nr = (s != AnyStorageNr);
        return *this; }
    FieldMatcher &set(StorageNr from, StorageNr to) {
        storage_nr_from = from;
        storage_nr_to = to;
        match_storage_nr = true;
        return *this; }
    FieldMatcher &set(TariffNr s) {
        tariff_nr_from = tariff_nr_to = s;
        match_tariff_nr = (s != AnyTariffNr);
        return *this; }
    FieldMatcher &set(TariffNr from, TariffNr to) {
        tariff_nr_from = from;
        tariff_nr_to = to;
        match_tariff_nr = true;
        return *this; }
    FieldMatcher &set(SubUnitNr s) {
        subunit_nr_from = subunit_nr_to = s;
        match_subunit_nr = true;
        return *this; }
    FieldMatcher &set(SubUnitNr from, SubUnitNr to) {
        subunit_nr_from = from;
        subunit_nr_to = to;
        match_subunit_nr = true; return *this; }

    FieldMatcher &set(IndexNr i) { index_nr = i; return *this; }

    bool matches(DVEntry &dv_entry);

    // Returns true of there is any range for storage, tariff, subunit nrs.
    // I.e. this matcher is expected to match against multiple dv entries!
    bool expectedToMatchAgainstMultipleEntries()
    {
        return (match_storage_nr && storage_nr_from != storage_nr_to)
            || (match_tariff_nr && tariff_nr_from != tariff_nr_to)
            || (match_subunit_nr && subunit_nr_from != subunit_nr_to);
    }

    std::string str();
};

bool loadFormatBytesFromSignature(uint16_t format_signature, std::vector<uchar> *format_bytes);

struct Telegram;

bool parseDV(Telegram *t,
             std::vector<uchar> &databytes,
             std::vector<uchar>::iterator data,
             size_t data_len,
             std::map<std::string,std::pair<int,DVEntry>> *dv_entries,
             std::vector<uchar>::iterator *format = NULL,
             size_t format_len = 0,
             uint16_t *format_hash = NULL);

// Instead of using a hardcoded difvif as key in the extractDV... below,
// find an existing difvif entry in the values based on the desired value information type.
// Like: Volume, VolumeFlow, FlowTemperature, ExternalTemperature etc
// in combination with the storagenr. (Later I will add tariff/subunit)
bool findKey(MeasurementType mt, VIFRange vi, StorageNr storagenr, TariffNr tariffnr,
             std::string *key, std::map<std::string,std::pair<int,DVEntry>> *values);
// Some meters have multiple identical DIF/VIF values! Meh, they are not using storage nrs or tariff nrs.
// So here we can pick for example nr 2 of an identical set if DIF/VIF values.
// Nr 1 means the first found value.
bool findKeyWithNr(MeasurementType mt, VIFRange vi, StorageNr storagenr, TariffNr tariffnr, int indexnr,
                   std::string *key, std::map<std::string,std::pair<int,DVEntry>> *values);

bool hasKey(std::map<std::string,std::pair<int,DVEntry>> *values, std::string key);

bool extractDVuint8(std::map<std::string,std::pair<int,DVEntry>> *values,
                    std::string key,
                    int *offset,
                    uchar *value);

bool extractDVuint16(std::map<std::string,std::pair<int,DVEntry>> *values,
                     std::string key,
                     int *offset,
                     uint16_t *value);

bool extractDVuint24(std::map<std::string,std::pair<int,DVEntry>> *values,
                     std::string key,
                     int *offset,
                     uint32_t *value);

bool extractDVuint32(std::map<std::string,std::pair<int,DVEntry>> *values,
                     std::string key,
                     int *offset,
                     uint32_t *value);

// All values are scaled according to the vif and wmbusmeters scaling defaults.
bool extractDVdouble(std::map<std::string,std::pair<int,DVEntry>> *values,
                     std::string key,
                     int *offset,
                     double *value,
                     bool auto_scale = true,
                     bool force_unsigned = false);

// Extract a value without scaling. Works for 8bits to 64 bits, binary and bcd.
bool extractDVlong(std::map<std::string,std::pair<int,DVEntry>> *values,
                   std::string key,
                   int *offset,
                   uint64_t *value);

// Just copy the raw hex data into the string, not reversed or anything.
bool extractDVHexString(std::map<std::string,std::pair<int,DVEntry>> *values,
                        std::string key,
                        int *offset,
                        std::string *value);

// Read the content and attempt to reverse and transform it into a readble string
// based on the dif information.
bool extractDVReadableString(std::map<std::string,std::pair<int,DVEntry>> *values,
                             std::string key,
                             int *offset,
                             std::string *value);

bool extractDVdate(std::map<std::string,std::pair<int,DVEntry>> *values,
                   std::string key,
                   int *offset,
                   struct tm *value);


const std::string &availableVIFRanges();
const std::string &availableVIFCombinables();

#endif
