/*
 *  cdc.c
 *
 *  Helper functions for the dynamic creation of Common Data Classes (CDCs)
 *
 *  Copyright 2014-2023 Michael Zillgith
 *
 *  This file is part of libIEC61850.
 *
 *  libIEC61850 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.
 *
 *  libIEC61850 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 libIEC61850.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  See COPYING file for the complete license text.
 */

#include "iec61850_dynamic_model.h"
#include "iec61850_cdc.h"
#include "libiec61850_platform_includes.h"

/************************************************
 * Constructed Attribute Classes
 ***********************************************/

DataAttribute*
CAC_AnalogueValue_create(const char* name, ModelNode* parent, FunctionalConstraint fc, uint8_t triggerOptions,
        bool isIntegerNotFloat)
{
    DataAttribute* analogeValue = (name == NULL) ? (DataAttribute*)parent : DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, fc, triggerOptions, 0, 0);

    if (isIntegerNotFloat)
        DataAttribute_create("i", (ModelNode*) analogeValue, IEC61850_INT32, fc, triggerOptions, 0, 0);
    else
        DataAttribute_create("f", (ModelNode*) analogeValue, IEC61850_FLOAT32, fc, triggerOptions, 0, 0);

    return analogeValue;
}

DataAttribute*
CAC_ValWithTrans_create(const char* name, ModelNode* parent, FunctionalConstraint fc, uint8_t triggerOptions, bool hasTransientIndicator)
{
    DataAttribute* valWithTrans = (name == NULL) ? (DataAttribute*)parent : DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, fc, triggerOptions, 0, 0);

    DataAttribute_create("posVal", (ModelNode*) valWithTrans, IEC61850_INT8, fc, triggerOptions, 0, 0);

    if (hasTransientIndicator)
        DataAttribute_create("transInd", (ModelNode*) valWithTrans, IEC61850_BOOLEAN, fc, triggerOptions, 0, 0);

    return valWithTrans;
}

/**
 * CDC_OPTION_AC_CLC_O
 */
DataAttribute*
CAC_Vector_create(const char* name, ModelNode* parent, uint32_t options, FunctionalConstraint fc, uint8_t triggerOptions)
{
    DataAttribute* vector = (name == NULL) ? (DataAttribute*)parent : DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, fc, triggerOptions, 0, 0);

    CAC_AnalogueValue_create("mag", (ModelNode*) vector, fc, triggerOptions, false);

    if (options & CDC_OPTION_AC_CLC_O)
        CAC_AnalogueValue_create("ang", (ModelNode*) vector, fc, triggerOptions, false);

    return vector;
}

DataAttribute*
CAC_Point_create(const char* name, ModelNode* parent, FunctionalConstraint fc, uint8_t triggerOptions, bool hasZVal)
{
    DataAttribute* point = (name == NULL) ? (DataAttribute*)parent : DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, fc, triggerOptions, 0, 0);

    DataAttribute_create("xVal", (ModelNode*) point, IEC61850_FLOAT32, fc, triggerOptions, 0, 0);
    DataAttribute_create("yVal", (ModelNode*) point, IEC61850_FLOAT32, fc, triggerOptions, 0, 0);

    if (hasZVal)
        DataAttribute_create("zVal", (ModelNode*) point, IEC61850_FLOAT32, fc, triggerOptions, 0, 0);

    return point;
}

DataAttribute*
CAC_ScaledValueConfig_create(const char* name, ModelNode* parent)
{
    DataAttribute* scaling = (name == NULL) ? (DataAttribute*)parent : DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);

    DataAttribute_create("scaleFactor", (ModelNode*) scaling, IEC61850_FLOAT32, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);
    DataAttribute_create("offset", (ModelNode*) scaling, IEC61850_FLOAT32, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);

    return scaling;
}

DataAttribute*
CAC_Unit_create(const char* name, ModelNode* parent, bool hasMagnitude)
{
    DataAttribute* unit = (name == NULL) ? (DataAttribute*)parent : DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);

    DataAttribute_create("SIUnit", (ModelNode*) unit, IEC61850_ENUMERATED, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);

    if (hasMagnitude)
        DataAttribute_create("multiplier", (ModelNode*) unit, IEC61850_ENUMERATED, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);

    return unit;
}

/************************************************
 *  Control parameters
 ************************************************/

static void
addOriginator(char* name, ModelNode* parent, FunctionalConstraint fc)
{
    DataAttribute* origin = DataAttribute_create(name, parent, IEC61850_CONSTRUCTED, fc, 0 ,0 ,0);

    DataAttribute_create("orCat", (ModelNode*) origin, IEC61850_ENUMERATED, fc, 0, 0, 0);
    DataAttribute_create("orIdent", (ModelNode*) origin, IEC61850_OCTET_STRING_64, fc, 0, 0, 0);

}

static void
addGenericOperateElements(DataAttribute* oper, DataAttributeType type, bool isTimeActivated, bool hasCheck)
{
    DataAttribute_create("ctlVal", (ModelNode*) oper, type, IEC61850_FC_CO, 0, 0, 0);

    if (isTimeActivated)
        DataAttribute_create("operTm", (ModelNode*) oper, IEC61850_TIMESTAMP, IEC61850_FC_CO, 0, 0, 0);

    addOriginator("origin", (ModelNode*) oper, IEC61850_FC_CO);

    DataAttribute_create("ctlNum", (ModelNode*) oper, IEC61850_INT8U, IEC61850_FC_CO, 0, 0, 0);
    DataAttribute_create("T", (ModelNode*) oper, IEC61850_TIMESTAMP, IEC61850_FC_CO, 0, 0, 0);
    DataAttribute_create("Test", (ModelNode*) oper, IEC61850_BOOLEAN, IEC61850_FC_CO, 0, 0, 0);

    if (hasCheck)
        DataAttribute_create("Check", (ModelNode*) oper, IEC61850_CHECK, IEC61850_FC_CO, 0, 0, 0);
}

static void
addCommonOperateElements(DataAttribute* oper, bool isTimeActivated, bool hasCheck)
{
    if (isTimeActivated)
        DataAttribute_create("operTm", (ModelNode*) oper, IEC61850_TIMESTAMP, IEC61850_FC_CO, 0, 0, 0);

    addOriginator("origin", (ModelNode*) oper, IEC61850_FC_CO);

    DataAttribute_create("ctlNum", (ModelNode*) oper, IEC61850_INT8U, IEC61850_FC_CO, 0, 0, 0);
    DataAttribute_create("T", (ModelNode*) oper, IEC61850_TIMESTAMP, IEC61850_FC_CO, 0, 0, 0);
    DataAttribute_create("Test", (ModelNode*) oper, IEC61850_BOOLEAN, IEC61850_FC_CO, 0, 0, 0);

    if (hasCheck)
        DataAttribute_create("Check", (ModelNode*) oper, IEC61850_CHECK, IEC61850_FC_CO, 0, 0, 0);
}

static DataAttribute*
CDA_Oper(ModelNode* parent, DataAttributeType type, bool isTImeActivated)
{
    DataAttribute* oper =  DataAttribute_create("Oper", parent, IEC61850_CONSTRUCTED, IEC61850_FC_CO, 0, 0, 0);

    addGenericOperateElements(oper, type, isTImeActivated, true);

    return oper;
}

static DataAttribute*
CDA_SBOw(ModelNode* parent, DataAttributeType type, bool isTImeActivated)
{
    DataAttribute* oper =  DataAttribute_create("SBOw", parent, IEC61850_CONSTRUCTED, IEC61850_FC_CO, 0, 0, 0);

    addGenericOperateElements(oper, type, isTImeActivated, true);

    return oper;
}

static DataAttribute*
CDA_Cancel(ModelNode* parent, DataAttributeType type, bool isTImeActivated)
{
    DataAttribute* oper =  DataAttribute_create("Cancel", parent, IEC61850_CONSTRUCTED, IEC61850_FC_CO, 0, 0, 0);

    addGenericOperateElements(oper, type, isTImeActivated, false);

    return oper;
}

/************************************************
 * Common Data Classes - helper functions
 ***********************************************/

static void
CDC_addTimeQuality(DataObject* dataObject, FunctionalConstraint fc)
{
    DataAttribute_create("q", (ModelNode*) dataObject, IEC61850_QUALITY, fc, TRG_OPT_QUALITY_CHANGED, 0, 0);
    DataAttribute_create("t", (ModelNode*) dataObject, IEC61850_TIMESTAMP, fc, 0, 0, 0);
}

static void
CDC_addStatusToDataObject(DataObject* dataObject, DataAttributeType statusType)
{
    DataAttribute_create("stVal", (ModelNode*) dataObject, statusType, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED | TRG_OPT_DATA_UPDATE, 0, 0);
    CDC_addTimeQuality(dataObject, IEC61850_FC_ST);
}

static void
CDC_addOptionPicsSubst(DataObject* dataObject, DataAttributeType type)
{
    DataAttribute_create("subEna", (ModelNode*) dataObject, IEC61850_BOOLEAN, IEC61850_FC_SV, 0, 0, 0);
    DataAttribute_create("subVal", (ModelNode*) dataObject, type, IEC61850_FC_SV, 0, 0, 0);
    DataAttribute_create("subQ", (ModelNode*) dataObject, IEC61850_QUALITY, IEC61850_FC_SV, 0, 0, 0);
    DataAttribute_create("subID", (ModelNode*) dataObject, IEC61850_VISIBLE_STRING_64, IEC61850_FC_SV, 0, 0, 0);
}

static void
CDC_addOptionPicsSubstValWithTrans(DataObject* dataObject, bool hasTransientIndicator)
{
    DataAttribute_create("subEna", (ModelNode*) dataObject, IEC61850_BOOLEAN, IEC61850_FC_SV, 0, 0, 0);

    CAC_ValWithTrans_create("subVal", (ModelNode*) dataObject, IEC61850_FC_SV, 0, hasTransientIndicator);

    DataAttribute_create("subQ", (ModelNode*) dataObject, IEC61850_QUALITY, IEC61850_FC_SV, 0, 0, 0);
    DataAttribute_create("subID", (ModelNode*) dataObject, IEC61850_VISIBLE_STRING_64, IEC61850_FC_SV, 0, 0, 0);
}

/* Add optional attributes for extension (name spaces) and textual descriptions */
static void
CDC_addStandardOptions(DataObject* dataObject, uint32_t options)
{
    /* Standard options ? */
    if (options & CDC_OPTION_DESC)
        DataAttribute_create("d",(ModelNode*)  dataObject, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);

    if (options & CDC_OPTION_DESC_UNICODE)
        DataAttribute_create("dU", (ModelNode*) dataObject, IEC61850_UNICODE_STRING_255, IEC61850_FC_DC, 0, 0, 0);

    if (options & CDC_OPTION_AC_DLNDA) {
        DataAttribute_create("cdcNs", (ModelNode*) dataObject, IEC61850_VISIBLE_STRING_255, IEC61850_FC_EX, 0, 0, 0);
        DataAttribute_create("cdcName", (ModelNode*) dataObject, IEC61850_VISIBLE_STRING_255, IEC61850_FC_EX, 0, 0, 0);
    }

    if (options & CDC_OPTION_AC_DLN)
        DataAttribute_create("dataNs", (ModelNode*) dataObject, IEC61850_VISIBLE_STRING_255, IEC61850_FC_EX, 0, 0, 0);
}

/************************************************
 * Common Data Classes - constructors
 ***********************************************/

DataObject*
CDC_SPS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newSPS = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    CDC_addStatusToDataObject(newSPS, IEC61850_BOOLEAN);

    if (options & CDC_OPTION_PICS_SUBST)
        CDC_addOptionPicsSubst(newSPS, IEC61850_BOOLEAN);

    if (options & CDC_OPTION_BLK_ENA)
        DataAttribute_create("blkEna", (ModelNode*) newSPS, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0);

    CDC_addStandardOptions(newSPS, options);

    return newSPS;
}

DataObject*
CDC_DPS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newDPS = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    CDC_addStatusToDataObject(newDPS, IEC61850_CODEDENUM);

    if (options & CDC_OPTION_PICS_SUBST)
        CDC_addOptionPicsSubst(newDPS, IEC61850_CODEDENUM);

    if (options & CDC_OPTION_BLK_ENA)
        DataAttribute_create("blkEna", (ModelNode*) newDPS, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0);

    CDC_addStandardOptions(newDPS, options);

    return newDPS;
}

DataObject*
CDC_INS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newINS = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    CDC_addStatusToDataObject(newINS, IEC61850_INT32);

    if (options & CDC_OPTION_PICS_SUBST)
        CDC_addOptionPicsSubst(newINS, IEC61850_INT32);

    if (options & CDC_OPTION_BLK_ENA)
        DataAttribute_create("blkEna", (ModelNode*) newINS, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0);

    CDC_addStandardOptions(newINS, options);

    return newINS;
}


DataObject*
CDC_ENS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newENS = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    CDC_addStatusToDataObject(newENS, IEC61850_ENUMERATED);

    if (options & CDC_OPTION_PICS_SUBST)
        CDC_addOptionPicsSubst(newENS, IEC61850_ENUMERATED);

    if (options & CDC_OPTION_BLK_ENA)
        DataAttribute_create("blkEna", (ModelNode*) newENS, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0);

    CDC_addStandardOptions(newENS, options);

    return newENS;
}

DataObject*
CDC_BCR_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newBCR = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    DataAttribute_create("actVal", (ModelNode*) newBCR, IEC61850_INT64, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);

    if (options & CDC_OPTION_FROZEN_VALUE) {
        DataAttribute_create("frVal", (ModelNode*) newBCR, IEC61850_INT64, IEC61850_FC_ST, TRG_OPT_DATA_UPDATE, 0, 0);
        DataAttribute_create("frTm", (ModelNode*) newBCR, IEC61850_TIMESTAMP, IEC61850_FC_ST, 0, 0, 0);
    }

    CDC_addTimeQuality(newBCR, IEC61850_FC_ST);

    if (options & CDC_OPTION_UNIT)
        DataAttribute_create("units", (ModelNode*) newBCR, IEC61850_ENUMERATED, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);

    DataAttribute_create("pulsQty", (ModelNode*) newBCR, IEC61850_FLOAT32, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);

    if (options & CDC_OPTION_FROZEN_VALUE) {
        DataAttribute_create("frEna", (ModelNode*) newBCR, IEC61850_BOOLEAN, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);
        DataAttribute_create("strTm", (ModelNode*) newBCR, IEC61850_TIMESTAMP, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);
        DataAttribute_create("frPd", (ModelNode*) newBCR, IEC61850_INT32, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);
        DataAttribute_create("frRs", (ModelNode*) newBCR, IEC61850_BOOLEAN, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);
    }

    CDC_addStandardOptions(newBCR, options);

    return newBCR;
}

DataObject*
CDC_SEC_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newSEC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    DataAttribute_create("cnt", (ModelNode*) newSEC, IEC61850_INT32U, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
    DataAttribute_create("sev", (ModelNode*) newSEC, IEC61850_ENUMERATED, IEC61850_FC_ST, 0, 0, 0);
    DataAttribute_create("t", (ModelNode*) newSEC, IEC61850_TIMESTAMP, IEC61850_FC_ST, 0, 0, 0);

    if (options & CDC_OPTION_ADDR)
        DataAttribute_create("addr", (ModelNode*) newSEC, IEC61850_OCTET_STRING_64, IEC61850_FC_ST, 0, 0, 0);

    if (options & CDC_OPTION_ADDINFO)
        DataAttribute_create("addInfo", (ModelNode*) newSEC, IEC61850_VISIBLE_STRING_64, IEC61850_FC_ST, 0, 0, 0);

    CDC_addStandardOptions(newSEC, options);

    return newSEC;
}


DataObject*
CDC_VSS_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newSPS = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    CDC_addStatusToDataObject(newSPS, IEC61850_VISIBLE_STRING_255);

    if (options & CDC_OPTION_PICS_SUBST)
        CDC_addOptionPicsSubst(newSPS, IEC61850_BOOLEAN);

    if (options & CDC_OPTION_BLK_ENA)
        DataAttribute_create("blkEna", (ModelNode*) newSPS, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0);

    CDC_addStandardOptions(newSPS, options);

    return newSPS;
}

/**
 * CDC_OPTION_INST_MAG
 * CDC_OPTION_RANGE
 * CDC_OPTION_PICS_SUBST
 */
DataObject*
CDC_MV_create(const char* dataObjectName, ModelNode* parent, uint32_t options, bool isIntegerNotFloat)
{
    DataObject* newMV = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    if (options & CDC_OPTION_INST_MAG)
        CAC_AnalogueValue_create("instMag", (ModelNode*) newMV, IEC61850_FC_MX, 0, isIntegerNotFloat);

    CAC_AnalogueValue_create("mag", (ModelNode*) newMV, IEC61850_FC_MX, TRG_OPT_DATA_CHANGED | TRG_OPT_DATA_UPDATE, isIntegerNotFloat);

    if (options & CDC_OPTION_RANGE)
        DataAttribute_create("range", (ModelNode*) newMV, IEC61850_ENUMERATED, IEC61850_FC_MX, TRG_OPT_DATA_CHANGED, 0, 0);

    CDC_addTimeQuality(newMV, IEC61850_FC_MX);

/*
    if (options & CDC_OPTION_PICS_SUBST)
        CDC_addOptionPicsSubst(newMV, )
*/

    CDC_addStandardOptions(newMV, options);

    return newMV;
}

/**
 * CDC_OPTION_INST_MAG
 * CDC_OPTION_RANGE
 */
DataObject*
CDC_CMV_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newMV = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    if (options & CDC_OPTION_INST_MAG)
        CAC_Vector_create("instCVal", (ModelNode*) newMV, options, IEC61850_FC_MX, 0);

    CAC_Vector_create("cVal", (ModelNode*) newMV, options, IEC61850_FC_MX, TRG_OPT_DATA_CHANGED | TRG_OPT_DATA_UPDATE);

    if (options & CDC_OPTION_RANGE)
        DataAttribute_create("range", (ModelNode*) newMV, IEC61850_ENUMERATED, IEC61850_FC_MX, TRG_OPT_DATA_CHANGED, 0, 0);

    if (options & CDC_OPTION_RANGE_ANG)
           DataAttribute_create("rangeAng", (ModelNode*) newMV, IEC61850_ENUMERATED, IEC61850_FC_MX, TRG_OPT_DATA_CHANGED, 0, 0);

    CDC_addTimeQuality(newMV, IEC61850_FC_MX);

/*    if (options & CDC_OPTION_PICS_SUBST)
        CDC_addOptionPicsSubst(newMV, )*/

    CDC_addStandardOptions(newMV, options);

    return newMV;
}


/**
 * CDC_OPTION_UNIT
 * CDC_OPTION_AC_SCAV
 * CDC_OPTION_MIN
 * CDC_OPTION_MAX
 */
DataObject*
CDC_SAV_create(const char* dataObjectName, ModelNode* parent, uint32_t options, bool isIntegerNotFloat)
{
    DataObject* newSAV = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    CAC_AnalogueValue_create("instMag", (ModelNode*) newSAV, IEC61850_FC_MX, 0, isIntegerNotFloat);

    CDC_addTimeQuality(newSAV, IEC61850_FC_MX);

    if (options & CDC_OPTION_UNIT)
        CAC_Unit_create("units", (ModelNode*) newSAV, options & CDC_OPTION_UNIT_MULTIPLIER);

    if (options & CDC_OPTION_AC_SCAV)
        CAC_ScaledValueConfig_create("sVC", (ModelNode*) newSAV);

    if (options & CDC_OPTION_MIN)
        CAC_AnalogueValue_create("min", (ModelNode*) newSAV, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, isIntegerNotFloat);

    if (options & CDC_OPTION_MAX)
        CAC_AnalogueValue_create("max", (ModelNode*) newSAV, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, isIntegerNotFloat);

    CDC_addStandardOptions(newSAV, options);

    return newSAV;
}

DataObject*
CDC_HST_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint16_t maxPts)
{
    DataObject* newHST = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    DataAttribute_create("hstVal", (ModelNode*) newHST, IEC61850_INT32, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED | TRG_OPT_DATA_UPDATE, maxPts, 0);

    CDC_addTimeQuality(newHST, IEC61850_FC_ST);

    DataAttribute_create("numPts", (ModelNode*) newHST, IEC61850_INT16U, IEC61850_FC_CF, 0, 0, 0);

    /* TODO add mandatory attribute "hstRangeC" */

    CAC_Unit_create("units", (ModelNode*) newHST, options & CDC_OPTION_UNIT_MULTIPLIER);

    DataAttribute_create("maxPts", (ModelNode*) newHST, IEC61850_INT16U, IEC61850_FC_CF, 0, 0, 0);

    CDC_addStandardOptions(newHST, options);

    return newHST;
}


static void
addControls(DataObject* parent, DataAttributeType type, uint32_t controlOptions)
{
    DataAttribute* ctlModel =
            DataAttribute_create("ctlModel", (ModelNode*) parent, IEC61850_ENUMERATED, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);

    int controlModel = controlOptions & 0x07;

    ctlModel->mmsValue = MmsValue_newIntegerFromInt16(controlModel);

    if (controlModel > 0) {

        if (controlModel == CDC_CTL_MODEL_SBO_NORMAL)
            DataAttribute_create("SBO", (ModelNode*) parent, IEC61850_VISIBLE_STRING_129, IEC61850_FC_CO, 0, 0, 0);

        bool isTimeActivated = false;

        if (controlOptions & CDC_CTL_MODEL_IS_TIME_ACTIVATED)
            isTimeActivated = true;

        if (controlModel == CDC_CTL_MODEL_SBO_ENHANCED)
            CDA_SBOw((ModelNode*) parent, type, isTimeActivated);

        CDA_Oper((ModelNode*) parent, type, isTimeActivated);

        if (controlOptions & CDC_CTL_MODEL_HAS_CANCEL)
            CDA_Cancel((ModelNode*) parent, type, isTimeActivated);

    }
}

static void
addOriginatorAndCtlNumOptions(ModelNode* parent, uint32_t controlOptions)
{
    if (controlOptions & CDC_CTL_OPTION_ORIGIN)
        addOriginator("origin", parent, IEC61850_FC_ST);

    if (controlOptions & CDC_CTL_OPTION_CTL_NUM)
        DataAttribute_create("ctlNum", parent, IEC61850_INT8U, IEC61850_FC_ST, 0, 0, 0);
}

static void
addCommonControlAttributes(DataObject* dobj, uint32_t controlOptions)
{
    if (controlOptions & CDC_CTL_OPTION_OP_RCVD)
        DataAttribute_create("opRcvd", (ModelNode*) dobj, IEC61850_BOOLEAN, IEC61850_FC_OR, TRG_OPT_DATA_CHANGED, 0, 0);

    if (controlOptions & CDC_CTL_OPTION_OP_OK)
        DataAttribute_create("opOk", (ModelNode*) dobj, IEC61850_BOOLEAN, IEC61850_FC_OR, TRG_OPT_DATA_CHANGED, 0, 0);

    if (controlOptions & CDC_CTL_OPTION_T_OP_OK)
        DataAttribute_create("tOpOk", (ModelNode*) dobj, IEC61850_TIMESTAMP, IEC61850_FC_OR, TRG_OPT_DATA_CHANGED, 0, 0);
}

/**
 *
 * CDC_OPTION_IS_TIME_ACTICATED
 *
 * substitution options
 * CDC_OPTION_BLK_ENA
 * standard description and namespace options
 *
 */
DataObject*
CDC_SPC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions)
{
    DataObject* newSPC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    addOriginatorAndCtlNumOptions((ModelNode*) newSPC, controlOptions);

    CDC_addStatusToDataObject(newSPC, IEC61850_BOOLEAN);

    addControls(newSPC, IEC61850_BOOLEAN, controlOptions);

    if (controlOptions & CDC_CTL_OPTION_ST_SELD)
        DataAttribute_create("stSeld", (ModelNode*) newSPC, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);

    addCommonControlAttributes(newSPC, controlOptions);

    if (options & CDC_OPTION_PICS_SUBST)
        CDC_addOptionPicsSubst(newSPC, IEC61850_BOOLEAN);

    if (options & CDC_OPTION_BLK_ENA)
        DataAttribute_create("blkEna", (ModelNode*) newSPC, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0);

    CDC_addStandardOptions(newSPC, options);

    return newSPC;
}

/**
 *
 * CDC_OPTION_IS_TIME_ACTICATED
 *
 * substitution options
 * CDC_OPTION_BLK_ENA
 * standard description and namespace options
 *
 */
DataObject*
CDC_DPC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions)
{
    DataObject* newDPC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    addOriginatorAndCtlNumOptions((ModelNode*) newDPC, controlOptions);

    CDC_addStatusToDataObject(newDPC, IEC61850_CODEDENUM);

    addControls(newDPC, IEC61850_BOOLEAN, controlOptions);

    if (controlOptions & CDC_CTL_OPTION_ST_SELD)
        DataAttribute_create("stSeld", (ModelNode*) newDPC, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);

    addCommonControlAttributes(newDPC, controlOptions);

    if (options & CDC_OPTION_PICS_SUBST)
        CDC_addOptionPicsSubst(newDPC, IEC61850_CODEDENUM);

    if (options & CDC_OPTION_BLK_ENA)
        DataAttribute_create("blkEna", (ModelNode*) newDPC, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0);

    CDC_addStandardOptions(newDPC, options);

    return newDPC;
}

static void
addAnalogControls(DataObject* parent, uint32_t controlOptions, bool isIntegerNotFloat)
{
    DataAttribute* ctlModel =
            DataAttribute_create("ctlModel", (ModelNode*) parent, IEC61850_ENUMERATED, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);

    int controlModel = controlOptions & 0x07;

    ctlModel->mmsValue = MmsValue_newIntegerFromInt16(controlModel);

    if (controlModel != CDC_CTL_MODEL_NONE) {

        if (controlModel == CDC_CTL_MODEL_SBO_NORMAL)
            DataAttribute_create("SBO", (ModelNode*) parent, IEC61850_VISIBLE_STRING_129, IEC61850_FC_CO, 0, 0, 0);

        bool isTimeActivated = false;

        if (controlOptions & CDC_CTL_MODEL_IS_TIME_ACTIVATED)
            isTimeActivated = true;

        if (controlModel == CDC_CTL_MODEL_SBO_ENHANCED) {
            DataAttribute* sBOw =  DataAttribute_create("SBOw", (ModelNode*) parent, IEC61850_CONSTRUCTED, IEC61850_FC_CO, 0, 0, 0);

            CAC_AnalogueValue_create("ctlVal", (ModelNode*) sBOw, IEC61850_FC_CO, 0, isIntegerNotFloat);

            addCommonOperateElements(sBOw, isTimeActivated, true);
        }

        DataAttribute* oper =  DataAttribute_create("Oper", (ModelNode*) parent, IEC61850_CONSTRUCTED, IEC61850_FC_CO, 0, 0, 0);

        CAC_AnalogueValue_create("ctlVal", (ModelNode*) oper, IEC61850_FC_CO, 0, isIntegerNotFloat);

        addCommonOperateElements(oper, isTimeActivated, true);

        if (controlOptions & CDC_CTL_MODEL_HAS_CANCEL) {
            DataAttribute* cancel =  DataAttribute_create("Cancel", (ModelNode*) parent, IEC61850_CONSTRUCTED, IEC61850_FC_CO, 0, 0, 0);

            CAC_AnalogueValue_create("ctlVal", (ModelNode*) cancel, IEC61850_FC_CO, 0, isIntegerNotFloat);

            addCommonOperateElements(cancel, isTimeActivated, false);
        }

    }
}

static void
addControlStatusAttributesForAnalogControl(DataObject* dobj, uint32_t controlOptions)
{
    if (controlOptions & CDC_CTL_OPTION_ORIGIN)
        addOriginator("origin", (ModelNode*) dobj, IEC61850_FC_MX);

    if (controlOptions & CDC_CTL_OPTION_CTL_NUM)
        DataAttribute_create("ctlNum", (ModelNode*) dobj, IEC61850_INT8U, IEC61850_FC_MX, 0, 0, 0);
}

DataObject*
CDC_APC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool isIntegerNotFloat)
{
    DataObject* newAPC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    addControlStatusAttributesForAnalogControl(newAPC, controlOptions);

    CAC_AnalogueValue_create("mxVal", (ModelNode*) newAPC, IEC61850_FC_MX, TRG_OPT_DATA_CHANGED, isIntegerNotFloat);

    CDC_addTimeQuality(newAPC, IEC61850_FC_MX);

    if (controlOptions & CDC_CTL_OPTION_ST_SELD)
        DataAttribute_create("stSeld", (ModelNode*) newAPC, IEC61850_BOOLEAN, IEC61850_FC_MX, TRG_OPT_DATA_CHANGED, 0, 0);

    addCommonControlAttributes(newAPC, controlOptions);

    if (options & CDC_OPTION_PICS_SUBST) {
        DataAttribute_create("subEna", (ModelNode*) newAPC, IEC61850_BOOLEAN, IEC61850_FC_SV, 0, 0, 0);
        CAC_AnalogueValue_create("subVal", (ModelNode*) newAPC, IEC61850_FC_SV, 0, isIntegerNotFloat);
        DataAttribute_create("subQ", (ModelNode*) newAPC, IEC61850_QUALITY, IEC61850_FC_SV, 0, 0, 0);
        DataAttribute_create("subID", (ModelNode*) newAPC, IEC61850_VISIBLE_STRING_64, IEC61850_FC_SV, 0, 0, 0);
    }

    if (options & CDC_OPTION_BLK_ENA)
        DataAttribute_create("blkEna", (ModelNode*) newAPC, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0);

    addAnalogControls(newAPC, controlOptions, isIntegerNotFloat);

    CDC_addStandardOptions(newAPC, options);

    return newAPC;
}

DataObject*
CDC_INC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions)
{
    DataObject* newINC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    addOriginatorAndCtlNumOptions((ModelNode*) newINC, controlOptions);

    CDC_addStatusToDataObject(newINC, IEC61850_INT32);

    addControls(newINC, IEC61850_INT32, controlOptions);

    if (controlOptions & CDC_CTL_OPTION_ST_SELD)
        DataAttribute_create("stSeld", (ModelNode*) newINC, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);

    addCommonControlAttributes(newINC, controlOptions);

    if (options & CDC_OPTION_PICS_SUBST)
        CDC_addOptionPicsSubst(newINC, IEC61850_INT32);

    if (options & CDC_OPTION_BLK_ENA)
        DataAttribute_create("blkEna", (ModelNode*) newINC, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0);

    if (options & CDC_OPTION_MIN)
        DataAttribute_create("minVal", (ModelNode*) newINC, IEC61850_INT32, IEC61850_FC_CF, 0, 0, 0);

    if (options & CDC_OPTION_MAX)
        DataAttribute_create("maxVal", (ModelNode*) newINC, IEC61850_INT32, IEC61850_FC_CF, 0, 0, 0);

    if (options & CDC_OPTION_STEP_SIZE)
        DataAttribute_create("stepSize", (ModelNode*) newINC, IEC61850_INT32U, IEC61850_FC_CF, 0, 0, 0);

    CDC_addStandardOptions(newINC, options);

    return newINC;
}

DataObject*
CDC_ENC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions)
{
    DataObject* newENC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    addOriginatorAndCtlNumOptions((ModelNode*) newENC, controlOptions);

    CDC_addStatusToDataObject(newENC, IEC61850_ENUMERATED);

    addControls(newENC, IEC61850_ENUMERATED, controlOptions);

    if (controlOptions & CDC_CTL_OPTION_ST_SELD)
        DataAttribute_create("stSeld", (ModelNode*) newENC, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);

    addCommonControlAttributes(newENC, controlOptions);

    if (options & CDC_OPTION_PICS_SUBST)
        CDC_addOptionPicsSubst(newENC, IEC61850_ENUMERATED);

    if (options & CDC_OPTION_BLK_ENA)
        DataAttribute_create("blkEna", (ModelNode*) newENC, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0);

    CDC_addStandardOptions(newENC, options);

    return newENC;
}

DataObject*
CDC_BSC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool hasTransientIndicator)
{
    DataObject* newBSC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    addOriginatorAndCtlNumOptions((ModelNode*) newBSC, controlOptions);

    CAC_ValWithTrans_create("valWTr", (ModelNode*) newBSC, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, hasTransientIndicator);
    CDC_addTimeQuality(newBSC, IEC61850_FC_ST);

    DataAttribute_create("persistent", (ModelNode*) newBSC, IEC61850_BOOLEAN, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);

    addControls(newBSC, IEC61850_CODEDENUM, controlOptions);

    if (controlOptions & CDC_CTL_OPTION_ST_SELD)
        DataAttribute_create("stSeld", (ModelNode*) newBSC, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);

    addCommonControlAttributes(newBSC, controlOptions);

    if (options & CDC_OPTION_PICS_SUBST)
        CDC_addOptionPicsSubstValWithTrans(newBSC, hasTransientIndicator);

    if (options & CDC_OPTION_BLK_ENA)
        DataAttribute_create("blkEna", (ModelNode*) newBSC, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0);

    CDC_addStandardOptions(newBSC, options);

    return newBSC;
}

DataObject*
CDC_ISC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool hasTransientIndicator)
{
    DataObject* newISC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    addOriginatorAndCtlNumOptions((ModelNode*) newISC, controlOptions);

    CAC_ValWithTrans_create("valWTr", (ModelNode*) newISC, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, hasTransientIndicator);
    CDC_addTimeQuality(newISC, IEC61850_FC_ST);

    addControls(newISC, IEC61850_INT8, controlOptions);

    if (controlOptions & CDC_CTL_OPTION_ST_SELD)
        DataAttribute_create("stSeld", (ModelNode*) newISC, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);

    addCommonControlAttributes(newISC, controlOptions);

    if (options & CDC_OPTION_PICS_SUBST)
        CDC_addOptionPicsSubstValWithTrans(newISC, hasTransientIndicator);

    if (options & CDC_OPTION_BLK_ENA)
        DataAttribute_create("blkEna", (ModelNode*) newISC, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0);

    if (options & CDC_OPTION_MIN)
        DataAttribute_create("minVal", (ModelNode*) newISC, IEC61850_INT32, IEC61850_FC_CF, 0, 0, 0);

    if (options & CDC_OPTION_MAX)
        DataAttribute_create("maxVal", (ModelNode*) newISC, IEC61850_INT32, IEC61850_FC_CF, 0, 0, 0);

    CDC_addStandardOptions(newISC, options);

    return newISC;
}

DataObject*
CDC_BAC_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, bool isIntegerNotFloat)
{
    DataObject* newBAC = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    addControlStatusAttributesForAnalogControl(newBAC, controlOptions);

    CAC_AnalogueValue_create("mxVal", (ModelNode*) newBAC, IEC61850_FC_MX, TRG_OPT_DATA_CHANGED, isIntegerNotFloat);

    CDC_addTimeQuality(newBAC, IEC61850_FC_MX);

    if (controlOptions & CDC_CTL_OPTION_ST_SELD)
        DataAttribute_create("stSeld", (ModelNode*) newBAC, IEC61850_BOOLEAN, IEC61850_FC_MX, TRG_OPT_DATA_CHANGED, 0, 0);

    addControls(newBAC, IEC61850_INT8, controlOptions);

    if (options & CDC_OPTION_PICS_SUBST) {
        DataAttribute_create("subEna", (ModelNode*) newBAC, IEC61850_BOOLEAN, IEC61850_FC_SV, 0, 0, 0);
        CAC_AnalogueValue_create("subVal", (ModelNode*) newBAC, IEC61850_FC_SV, 0, isIntegerNotFloat);
        DataAttribute_create("subQ", (ModelNode*) newBAC, IEC61850_QUALITY, IEC61850_FC_SV, 0, 0, 0);
        DataAttribute_create("subID", (ModelNode*) newBAC, IEC61850_VISIBLE_STRING_64, IEC61850_FC_SV, 0, 0, 0);
    }

    if (options & CDC_OPTION_BLK_ENA)
        DataAttribute_create("blkEna", (ModelNode*) newBAC, IEC61850_BOOLEAN, IEC61850_FC_BL, 0, 0, 0);

    DataAttribute_create("persistent", (ModelNode*) newBAC, IEC61850_BOOLEAN, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);

    addAnalogControls(newBAC, controlOptions, isIntegerNotFloat);

    if (options & CDC_OPTION_MIN)
        CAC_AnalogueValue_create("minVal", (ModelNode*) newBAC, IEC61850_FC_CF, 0, isIntegerNotFloat);

    if (options & CDC_OPTION_MAX)
        CAC_AnalogueValue_create("maxVal", (ModelNode*) newBAC, IEC61850_FC_CF, 0, isIntegerNotFloat);

    if (options & CDC_OPTION_STEP_SIZE)
        CAC_AnalogueValue_create("stepSize", (ModelNode*) newBAC, IEC61850_FC_CF, 0, isIntegerNotFloat);

    CDC_addStandardOptions(newBAC, options);

    return newBAC;
}

DataObject*
CDC_LPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newLPL = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    DataAttribute_create("vendor", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);
    DataAttribute_create("swRev", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);

    if (options & CDC_OPTION_AC_LN0_M)
        DataAttribute_create("configRev", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);

    if (options & CDC_OPTION_AC_LN0_EX)
        DataAttribute_create("ldNs", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_EX, 0, 0, 0);

    if (options & CDC_OPTION_AC_DLD_M)
        DataAttribute_create("lnNs", (ModelNode*) newLPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_EX, 0, 0, 0);

    CDC_addStandardOptions(newLPL, options);

    return newLPL;
}

DataObject*
CDC_DPL_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newDPL = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    DataAttribute_create("vendor", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);

    if (options & CDC_OPTION_DPL_HWREV)
        DataAttribute_create("hwRev", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);

    if (options & CDC_OPTION_DPL_SWREV)
        DataAttribute_create("swRev", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);

    if (options & CDC_OPTION_DPL_SERNUM)
        DataAttribute_create("serNum", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);

    if (options & CDC_OPTION_DPL_MODEL)
        DataAttribute_create("model", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);

    if (options & CDC_OPTION_DPL_LOCATION)
        DataAttribute_create("location", (ModelNode*) newDPL, IEC61850_VISIBLE_STRING_255, IEC61850_FC_DC, 0, 0, 0);

    CDC_addStandardOptions(newDPL, options);

    return newDPL;
}

/* Directional protection activation information (ACD) */
DataObject*
CDC_ACD_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newACD = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    DataAttribute_create("general", (ModelNode*) newACD, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
    DataAttribute_create("dirGeneral", (ModelNode*) newACD, IEC61850_ENUMERATED, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);

    if (options & CDC_OPTION_PHASE_A) {
        DataAttribute_create("phsA", (ModelNode*) newACD, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
        DataAttribute_create("dirPhsA", (ModelNode*) newACD, IEC61850_ENUMERATED, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
    }

    if (options & CDC_OPTION_PHASE_B) {
        DataAttribute_create("phsB", (ModelNode*) newACD, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
        DataAttribute_create("dirPhsB", (ModelNode*) newACD, IEC61850_ENUMERATED, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
    }

    if (options & CDC_OPTION_PHASE_C) {
        DataAttribute_create("phsC", (ModelNode*) newACD, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
        DataAttribute_create("dirPhsC", (ModelNode*) newACD, IEC61850_ENUMERATED, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
    }

    if (options & CDC_OPTION_PHASE_NEUT) {
        DataAttribute_create("neut", (ModelNode*) newACD, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
        DataAttribute_create("dirNeut", (ModelNode*) newACD, IEC61850_ENUMERATED, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);
    }

    CDC_addTimeQuality(newACD, IEC61850_FC_ST);

    CDC_addStandardOptions(newACD, options);

    return newACD;
}

DataObject*
CDC_ACT_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newACT = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    DataAttribute_create("general", (ModelNode*) newACT, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);

    if (options & CDC_OPTION_PHASE_A)
        DataAttribute_create("phsA", (ModelNode*) newACT, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);

    if (options & CDC_OPTION_PHASE_B)
        DataAttribute_create("phsB", (ModelNode*) newACT, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);

    if (options & CDC_OPTION_PHASE_C)
        DataAttribute_create("phsC", (ModelNode*) newACT, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);

    if (options & CDC_OPTION_PHASE_NEUT)
        DataAttribute_create("neut", (ModelNode*) newACT, IEC61850_BOOLEAN, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);

    CDC_addTimeQuality(newACT, IEC61850_FC_ST);

    CDC_addStandardOptions(newACT, options);

    return newACT;
}

DataObject*
CDC_WYE_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newWYE = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    /* TODO check if some options should be masked */
    /* TODO take care for GC_1 */
    CDC_CMV_create("phsA", (ModelNode*) newWYE, options);
    CDC_CMV_create("phsB", (ModelNode*) newWYE, options);
    CDC_CMV_create("phsC", (ModelNode*) newWYE, options);
    CDC_CMV_create("neut", (ModelNode*) newWYE, options);
    CDC_CMV_create("net", (ModelNode*) newWYE, options);
    CDC_CMV_create("res", (ModelNode*) newWYE, options);

    if (options & CDC_OPTION_ANGLE_REF)
        DataAttribute_create("angRef", (ModelNode*) newWYE, IEC61850_ENUMERATED, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);

    CDC_addStandardOptions(newWYE, options);

    return newWYE;
}


DataObject*
CDC_DEL_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newDEL = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    /* TODO check if some options should be masked */
    CDC_CMV_create("phsAB", (ModelNode*) newDEL, options);
    CDC_CMV_create("phsBC", (ModelNode*) newDEL, options);
    CDC_CMV_create("phsCA", (ModelNode*) newDEL, options);

    if (options & CDC_OPTION_ANGLE_REF)
        DataAttribute_create("angRef", (ModelNode*) newDEL, IEC61850_ENUMERATED, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, 0, 0);

    CDC_addStandardOptions(newDEL, options);

    return newDEL;
}


DataObject*
CDC_SPG_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newSPG = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    DataAttribute_create("setVal", (ModelNode*) newSPG, IEC61850_BOOLEAN, IEC61850_FC_SP, TRG_OPT_DATA_CHANGED, 0, 0);

    CDC_addStandardOptions(newSPG, options);

    return newSPG;
}

DataObject*
CDC_VSG_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newSPG = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    DataAttribute_create("setVal", (ModelNode*) newSPG, IEC61850_VISIBLE_STRING_255, IEC61850_FC_SP, TRG_OPT_DATA_CHANGED, 0, 0);

    CDC_addStandardOptions(newSPG, options);

    return newSPG;
}


DataObject*
CDC_ENG_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newENG = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    DataAttribute_create("setVal", (ModelNode*) newENG, IEC61850_ENUMERATED, IEC61850_FC_SP, TRG_OPT_DATA_CHANGED, 0, 0);

    CDC_addStandardOptions(newENG, options);

    return newENG;
}

DataObject*
CDC_ING_create(const char* dataObjectName, ModelNode* parent, uint32_t options)
{
    DataObject* newING = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    DataAttribute_create("setVal", (ModelNode*) newING, IEC61850_INT32, IEC61850_FC_SP, TRG_OPT_DATA_CHANGED, 0, 0);

    if (options & CDC_OPTION_UNIT)
        CAC_Unit_create("units", (ModelNode*) newING, options & CDC_OPTION_UNIT_MULTIPLIER);

    if (options & CDC_OPTION_MIN)
        DataAttribute_create("minVal", (ModelNode*) newING, IEC61850_INT32, IEC61850_FC_SP, TRG_OPT_DATA_CHANGED, 0, 0);

    if (options & CDC_OPTION_MAX)
        DataAttribute_create("maxVal", (ModelNode*) newING, IEC61850_INT32, IEC61850_FC_SP, TRG_OPT_DATA_CHANGED, 0, 0);

    if (options & CDC_OPTION_STEP_SIZE)
        DataAttribute_create("stepSize", (ModelNode*) newING, IEC61850_INT32U, IEC61850_FC_SP, TRG_OPT_DATA_CHANGED, 0, 0);

    CDC_addStandardOptions(newING, options);


    return newING;
}

DataObject*
CDC_ASG_create(const char* dataObjectName, ModelNode* parent, uint32_t options, bool isIntegerNotFloat)
{
    DataObject* newASG = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    CAC_AnalogueValue_create("setMag", (ModelNode*) newASG, IEC61850_FC_SP, TRG_OPT_DATA_CHANGED, isIntegerNotFloat);

    if (options & CDC_OPTION_UNIT)
        CAC_Unit_create("units", (ModelNode*) newASG, options & CDC_OPTION_UNIT_MULTIPLIER);

    if (options & CDC_OPTION_AC_SCAV)
        CAC_ScaledValueConfig_create("sVC", (ModelNode*) newASG);

    if (options & CDC_OPTION_MIN)
        CAC_AnalogueValue_create("minVal", (ModelNode*) newASG, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, isIntegerNotFloat);

    if (options & CDC_OPTION_MAX)
        CAC_AnalogueValue_create("maxVal", (ModelNode*) newASG, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, isIntegerNotFloat);

    if (options & CDC_OPTION_STEP_SIZE)
        CAC_AnalogueValue_create("stepSize", (ModelNode*) newASG, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, isIntegerNotFloat);

    CDC_addStandardOptions(newASG, options);

    return newASG;
}

/**********************************************************************************
 * Wind power specific CDCs - according to 61400-25-2:2006
 *********************************************************************************/

DataObject*
CDC_SPV_create(const char* dataObjectName, ModelNode* parent, uint32_t options, uint32_t controlOptions, uint32_t wpOptions, bool hasChaManRs)
{
    DataObject* newSPV = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    if (hasChaManRs)
        CDC_SPC_create("chaManRs", (ModelNode*) newSPV, 0, CDC_CTL_MODEL_DIRECT_NORMAL);

    CDC_APC_create("actVal", (ModelNode*) newSPV, 0, controlOptions, false);

    /* TOOO add optional "oldVal" APC */

    if (wpOptions & CDC_OPTION_61400_MIN_MX_VAL)
        CAC_AnalogueValue_create("minMxVal", (ModelNode*) newSPV, IEC61850_FC_MX, 0, false);

    if (wpOptions & CDC_OPTION_61400_MAX_MX_VAL)
        CAC_AnalogueValue_create("maxMxVal", (ModelNode*) newSPV, IEC61850_FC_MX, 0, false);

    if (wpOptions & CDC_OPTION_61400_TOT_AV_VAL)
        CAC_AnalogueValue_create("totAvVal", (ModelNode*) newSPV, IEC61850_FC_MX, 0, false);

    if (wpOptions & CDC_OPTION_61400_SDV_VAL)
        CAC_AnalogueValue_create("sdvVal", (ModelNode*) newSPV, IEC61850_FC_MX, 0, false);

    if (options & CDC_OPTION_UNIT)
        CAC_Unit_create("units", (ModelNode*) newSPV, options & CDC_OPTION_UNIT_MULTIPLIER);

    if (options & CDC_OPTION_MIN)
        CAC_AnalogueValue_create("minVal", (ModelNode*) newSPV, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, false);

    if (options & CDC_OPTION_MAX)
        CAC_AnalogueValue_create("maxVal", (ModelNode*) newSPV, IEC61850_FC_CF, TRG_OPT_DATA_CHANGED, false);

    if (wpOptions & CDC_OPTION_61400_SP_ACS)
        DataAttribute_create("spAcs", (ModelNode*) newSPV, IEC61850_CODEDENUM, IEC61850_FC_CF, 0, 0, 0);

    if (wpOptions & CDC_OPTION_61400_CHA_PER_RS)
        DataAttribute_create("chaPerRs", (ModelNode*) newSPV, IEC61850_CODEDENUM, IEC61850_FC_CF, 0, 0, 0);

    CDC_addStandardOptions(newSPV, options);

    return newSPV;
}

DataObject*
CDC_STV_create(const char* dataObjectName, ModelNode* parent,
        uint32_t options,
        uint32_t controlOptions,
        uint32_t wpOptions,
        bool hasOldStatus)
{
    (void)controlOptions; /* TODO implement */
    (void)wpOptions; /* TODO implement */

    DataObject* newSTV = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    CDC_INS_create("actSt", (ModelNode*) newSTV, 0);

    if (hasOldStatus)
        CDC_INS_create("oldSt", (ModelNode*) newSTV, 0);

    CDC_addStandardOptions(newSTV, options);

    return newSTV;
}

DataObject*
CDC_ALM_create(const char* dataObjectName, ModelNode* parent,
        uint32_t options,
        uint32_t controlOptions,
        uint32_t wpOptions,
        bool hasOldStatus)
{
    (void)controlOptions; /* TODO implement */
    (void)wpOptions; /* TODO implement */

    DataObject* newALM = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    CDC_SPC_create("almAck", (ModelNode*) newALM, 0, CDC_CTL_MODEL_DIRECT_NORMAL | CDC_CTL_OPTION_ORIGIN);

    CDC_INS_create("actSt", (ModelNode*) newALM, 0);

    if (hasOldStatus)
        CDC_INS_create("oldSt", (ModelNode*) newALM, 0);

    CDC_addStandardOptions(newALM, options);

    return newALM;
}

DataObject*
CDC_CMD_create(const char* dataObjectName, ModelNode* parent,
        uint32_t options,
        uint32_t controlOptions,
        uint32_t wpOptions,
        bool hasOldStatus,
        bool hasCmTm,
        bool hasCmCt)
{
    (void)hasCmTm; /* TODO implement */
    (void)hasCmCt; /* TODO implement */

    DataObject* newCMD = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    CDC_INC_create("actSt", (ModelNode*) newCMD, 0, controlOptions);

    if (hasOldStatus)
        CDC_INS_create("oldSt", (ModelNode*) newCMD, 0);

    if (wpOptions & CDC_OPTION_61400_CM_ACS)
        DataAttribute_create("cmAcs", (ModelNode*) newCMD, IEC61850_INT8U, IEC61850_FC_CF, 0, 0, 0);

    CDC_addStandardOptions(newCMD, options);

    return newCMD;
}


/**
 * \brief create a new CDC instance of type CTE (Event counting)
 */
DataObject*
CDC_CTE_create(const char* dataObjectName, ModelNode* parent,
        uint32_t options,
        uint32_t controlOptions,
        uint32_t wpOptions,
        bool hasHisRs)
{
    (void)controlOptions; /* TODO implement */

    DataObject* newCTE = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    CDC_SPC_create("manRs", (ModelNode*) newCTE, 0, CDC_CTL_MODEL_DIRECT_NORMAL | CDC_CTL_OPTION_ORIGIN);

    if (hasHisRs)
        CDC_INC_create("hisRs", (ModelNode*) newCTE, 0, CDC_CTL_MODEL_DIRECT_NORMAL | CDC_CTL_OPTION_ORIGIN);

    CDC_INS_create("actCtVal", (ModelNode*) newCTE, 0);

    CDC_INS_create("oldCtVal", (ModelNode*) newCTE, 0);

    if (wpOptions & CDC_OPTION_61400_TM_TOT)
        DataAttribute_create("ctTot", (ModelNode*) newCTE, IEC61850_INT32U, IEC61850_FC_ST, 0, 0, 0);

    if (wpOptions & CDC_OPTION_61400_COUNTING_DAILY)
        DataAttribute_create("dly", (ModelNode*) newCTE, IEC61850_INT32U, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 32, 0);

    if (wpOptions & CDC_OPTION_61400_COUNTING_MONTHLY)
        DataAttribute_create("mly", (ModelNode*) newCTE, IEC61850_INT32U, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 13, 0);

    if (wpOptions & CDC_OPTION_61400_COUNTING_YEARLY)
        DataAttribute_create("mly", (ModelNode*) newCTE, IEC61850_INT32U, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 21, 0);

    if (wpOptions & CDC_OPTION_61400_COUNTING_TOTAL)
        DataAttribute_create("tot", (ModelNode*) newCTE, IEC61850_INT32U, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);

    CDC_addStandardOptions(newCTE, options);

    return newCTE;
}


DataObject*
CDC_TMS_create(const char* dataObjectName, ModelNode* parent,
        uint32_t options,
        uint32_t controlOptions,
        uint32_t wpOptions,
        bool hasHisRs)
{
    (void)controlOptions; /* TODO implement */

    DataObject* newTMS = (dataObjectName == NULL) ? (DataObject*)parent : DataObject_create(dataObjectName, parent, 0);

    CDC_SPC_create("manRs", (ModelNode*) newTMS, 0, CDC_CTL_MODEL_DIRECT_NORMAL | CDC_CTL_OPTION_ORIGIN);

    if (hasHisRs)
        CDC_INC_create("hisRs", (ModelNode*) newTMS, 0, CDC_CTL_MODEL_DIRECT_NORMAL | CDC_CTL_OPTION_ORIGIN);

    CDC_INS_create("actTmVal", (ModelNode*) newTMS, 0);

    CDC_INS_create("oldTmVal", (ModelNode*) newTMS, 0);

    if (wpOptions & CDC_OPTION_61400_TM_TOT)
        DataAttribute_create("tmTot", (ModelNode*) newTMS, IEC61850_INT32U, IEC61850_FC_ST, 0, 0, 0);

    if (wpOptions & CDC_OPTION_61400_COUNTING_DAILY)
        DataAttribute_create("dly", (ModelNode*) newTMS, IEC61850_INT32U, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 32, 0);

    if (wpOptions & CDC_OPTION_61400_COUNTING_MONTHLY)
        DataAttribute_create("mly", (ModelNode*) newTMS, IEC61850_INT32U, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 13, 0);

    if (wpOptions & CDC_OPTION_61400_COUNTING_YEARLY)
        DataAttribute_create("mly", (ModelNode*) newTMS, IEC61850_INT32U, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 21, 0);

    if (wpOptions & CDC_OPTION_61400_COUNTING_TOTAL)
        DataAttribute_create("tot", (ModelNode*) newTMS, IEC61850_INT32U, IEC61850_FC_ST, TRG_OPT_DATA_CHANGED, 0, 0);


    CDC_addStandardOptions(newTMS, options);

    return newTMS;
}
