/*
 *  model.h
 *
 *  Copyright 2013-2024 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.
 */

#ifndef MODEL_H_
#define MODEL_H_

#include "iec61850_common.h"

#ifdef __cplusplus
extern "C" {
#endif

/** \addtogroup server_api_group
 *  @{
 */

/**
 * @defgroup DATA_MODEL General data model definitions, access and iteration functions
 *
 * \brief Functions and structures to access and iterate the IEC 61850 data model
 *
 * @{
 */

/**
 * \brief abstract base type for IEC 61850 data model nodes
 */
typedef struct sModelNode ModelNode;

/**
 * \brief IEC 61850 data model element of type data attribute
 */
typedef struct sDataAttribute DataAttribute;

/**
 * \brief IEC 61850 data model element of type data object
 */
typedef struct sDataObject DataObject;

/**
 * \brief IEC 61850 data model element of type logical node
 */
typedef struct sLogicalNode LogicalNode;

/**
 * \brief IEC 61850 data model element of type logical device
 */
typedef struct sLogicalDevice LogicalDevice;

/**
 * \brief Root node of the IEC 61850 data model. This is usually created by the model generator tool (genmodel.jar)
 */
typedef struct sIedModel IedModel;

typedef struct sDataSet DataSet;
typedef struct sReportControlBlock ReportControlBlock;

/**
 * \brief IEC 61850 data model of setting group control block (SGCB)
 */
typedef struct sSettingGroupControlBlock SettingGroupControlBlock;

typedef struct sGSEControlBlock GSEControlBlock;

typedef struct sSVControlBlock SVControlBlock;

typedef struct sLogControlBlock LogControlBlock;

typedef struct sLog Log;

typedef enum {
	IEC61850_UNKNOWN_TYPE = -1,
	IEC61850_BOOLEAN = 0,/* int */
	IEC61850_INT8 = 1,   /* int8_t */
	IEC61850_INT16 = 2,  /* int16_t */
	IEC61850_INT32 = 3,  /* int32_t */
	IEC61850_INT64 = 4,  /* int64_t */
	IEC61850_INT128 = 5, /* no native mapping! */
	IEC61850_INT8U = 6,  /* uint8_t */
	IEC61850_INT16U = 7, /* uint16_t */
	IEC61850_INT24U = 8, /* uint32_t */
	IEC61850_INT32U = 9, /* uint32_t */
	IEC61850_FLOAT32 = 10, /* float */
	IEC61850_FLOAT64 = 11, /* double */
	IEC61850_ENUMERATED = 12,
	IEC61850_OCTET_STRING_64 = 13,
	IEC61850_OCTET_STRING_6 = 14,
	IEC61850_OCTET_STRING_8 = 15,
	IEC61850_VISIBLE_STRING_32 = 16,
	IEC61850_VISIBLE_STRING_64 = 17,
	IEC61850_VISIBLE_STRING_65 = 18,
	IEC61850_VISIBLE_STRING_129 = 19,
	IEC61850_VISIBLE_STRING_255 = 20,
	IEC61850_UNICODE_STRING_255 = 21,
	IEC61850_TIMESTAMP = 22,
	IEC61850_QUALITY = 23,
	IEC61850_CHECK = 24,
	IEC61850_CODEDENUM = 25,
	IEC61850_GENERIC_BITSTRING = 26,
	IEC61850_CONSTRUCTED = 27,
	IEC61850_ENTRY_TIME = 28,
	IEC61850_PHYCOMADDR = 29,
	IEC61850_CURRENCY = 30,
	IEC61850_OPTFLDS = 31, /* bit-string(10) */
	IEC61850_TRGOPS = 32 /* bit-string(6) */
} DataAttributeType;

typedef enum {
    LogicalDeviceModelType,
    LogicalNodeModelType,
    DataObjectModelType,
    DataAttributeModelType
} ModelNodeType;

struct sIedModel {
    char* name;
    LogicalDevice* firstChild;
    DataSet* dataSets;
    ReportControlBlock* rcbs;
    GSEControlBlock* gseCBs;
    SVControlBlock* svCBs;
    SettingGroupControlBlock* sgcbs;
    LogControlBlock* lcbs;
    Log* logs;
    void (*initializer) (void);
};

struct sLogicalDevice {
    ModelNodeType modelType;
    char* name; /* LD instance */
    ModelNode* parent;
    ModelNode* sibling;
    ModelNode* firstChild;
    char* ldName; /* ldName (when using functional naming) */
};

struct sModelNode {
    ModelNodeType modelType;
    char* name;
    ModelNode* parent;
    ModelNode* sibling;
    ModelNode* firstChild;
};

struct sLogicalNode {
    ModelNodeType modelType;
    char* name;
    ModelNode* parent;
    ModelNode* sibling;
    ModelNode* firstChild;
};

struct sDataObject {
    ModelNodeType modelType;
    char* name;
    ModelNode* parent;
    ModelNode* sibling;
    ModelNode* firstChild;

    int elementCount; /* value > 0 if this is an array */
    int arrayIndex; /* value > -1 when this is an array element */
};

struct sDataAttribute {
    ModelNodeType modelType;
    char* name;
    ModelNode* parent;
    ModelNode* sibling;
    ModelNode* firstChild;

    int elementCount; /* value > 0 if this is an array */
    int arrayIndex; /* value > -1 when this is an array element */

    FunctionalConstraint fc;
    DataAttributeType type;

    uint8_t triggerOptions; /* TRG_OPT_DATA_CHANGED | TRG_OPT_QUALITY_CHANGED | TRG_OPT_DATA_UPDATE */

    MmsValue* mmsValue;

    uint32_t sAddr; /* TODO remove in version 2.0 */
};

typedef struct sDataSetEntry {
    char* logicalDeviceName; /* logical device instance name */
    bool isLDNameDynamicallyAllocated;
    char* variableName;
    int index;
    char* componentName;
    MmsValue* value;
    struct sDataSetEntry* sibling;
} DataSetEntry;

struct sDataSet {
    char* logicalDeviceName; /* logical device instance name */
    char* name; /* eg. MMXU1$dataset1 */
    int elementCount;
    DataSetEntry* fcdas;
    DataSet* sibling;
};

struct sReportControlBlock {
    LogicalNode* parent;
    char* name;
    char* rptId;
    bool buffered;
    char* dataSetName; /* pre loaded with relative name in logical node */

    uint32_t confRef; /* ConfRef - configuration revision */
    uint8_t trgOps; /* TrgOps - trigger conditions */
    uint8_t options; /* OptFlds */
    uint32_t bufferTime; /* BufTm - time to buffer events until a report is generated */
    uint32_t intPeriod; /* IntgPd - integrity period */

    /* type (first byte) and address of the pre-configured client
       type can be one of (0 - no reservation, 4 - IPv4 client, 6 - IPv6 client) */
    uint8_t clientReservation[17];

    ReportControlBlock* sibling; /* next control block in list or NULL if this is the last entry
                                  * at runtime reuse as pointer to ReportControl instance!
                                  **/
};

struct sLogControlBlock {
    LogicalNode* parent;

    char* name;

    char* dataSetName;
    char* logRef;        /* object reference to the journal */

    uint8_t trgOps;      /* TrgOps - trigger conditions */
    uint32_t intPeriod;  /* IntgPd - integrity period */

    bool logEna;         /* enable log by default */
    bool reasonCode;     /* include reason code in log */

    LogControlBlock* sibling; /* next control block in list or NULL if this is the last entry */
};

struct sLog {
    LogicalNode* parent;

    char* name;

    Log* sibling; /* next log instance in list or NULL if this is the last entry */
};

struct sSettingGroupControlBlock {
    LogicalNode* parent;

    uint8_t actSG;       /* value from SCL file */
    uint8_t numOfSGs;    /* value from SCL file */

    uint8_t editSG;      /* 0 at power-up */
    bool cnfEdit;        /* false at power-up */
    uint64_t timestamp;
    uint16_t resvTms;

    SettingGroupControlBlock* sibling; /* next control block in list or NULL if this is the last entry */
};

struct sGSEControlBlock {
    LogicalNode* parent;
    char* name;
    char* appId;
    char* dataSetName; /* pre loaded with relative name in logical node */
    uint32_t confRev;  /* ConfRev - configuration revision */
    bool fixedOffs;    /* fixed offsets */
    PhyComAddress* address; /* GSE communication parameters */
    int minTime; /* optional minTime parameter --> -1 if not present */
    int maxTime; /* optional maxTime parameter --> -1 if not present */
    GSEControlBlock* sibling; /* next control block in list or NULL if this is the last entry */
};

struct sSVControlBlock {
    LogicalNode* parent;
    char* name;

    char* svId; /* MsvUD/UsvID */
    char* dataSetName; /* pre loaded with relative name in logical node */

    uint8_t optFlds;

    uint8_t smpMod;
    uint16_t smpRate;

    uint32_t confRev;  /* ConfRev - configuration revision */

    PhyComAddress* dstAddress; /* SV communication parameters */

    bool isUnicast;

    int noASDU;

    SVControlBlock* sibling; /* next control block in list or NULL if this is the last entry */
};

/**
 * \brief get the number of direct children of a model node
 *
 * \param self the model node instance
 *
 * \return the number of children of the model node
 * ¸
 */
LIB61850_API int
ModelNode_getChildCount(ModelNode* self);

/**
 * \brief return a child model node
 *
 * \param self the model node instance
 * \param name the name of the child model node
 *
 * \return the model node instance or NULL if model node does not exist.
 */
LIB61850_API ModelNode*
ModelNode_getChild(ModelNode* self, const char* name);

/**
 * \brief return the child node of an array or other structure
 *
 * \param self the model node instance
 * \param idx the index (e.g. array index) starting with 0
 *
 * \return the model node instance or NULL if model node with given index does not exist.
 */
LIB61850_API ModelNode*
ModelNode_getChildWithIdx(ModelNode* self, int idx);

/**
 * \brief return a child model node with a given functional constraint
 *
 * Sometimes the name is not enough to identify a model node. This is the case when
 * editable setting groups are used. In this case the setting group members have two different
 * model nodes associated that differ in their FC (SG and SE).
 *
 * \param self the model node instance
 * \param name the name of the child model node
 * \param fc the functional constraint of the model node
 *
 * \return  the model node instance or NULL if model node does not exist.
 */
LIB61850_API ModelNode*
ModelNode_getChildWithFc(ModelNode* self, const char* name, FunctionalConstraint fc);

/**
 * \brief Return the IEC 61850 object reference of a model node
 *
 * \param self the model node instance
 * \param objectReference pointer to a buffer where to write the object reference string. If NULL
 *        is given the buffer is allocated by the function.
 *
 * \return the object reference string
 */
LIB61850_API char*
ModelNode_getObjectReference(ModelNode* self, char* objectReference);

/**
 * \brief Return the IEC 61850 object reference of a model node
 *
 * \param self the model node instance
 * \param objectReference pointer to a buffer where to write the object reference string. If NULL
 *        is given the buffer is allocated by the function.
 * \param withoutIedName create object reference without IED name part
 *
 * \return the object reference string
 */
LIB61850_API char*
ModelNode_getObjectReferenceEx(ModelNode* node, char* objectReference, bool withoutIedName);

/**
 * \brief Get the type of the ModelNode
 *
 * \param self the ModelNode instance
 *
 * \return the type of the ModelNode (one of LD, LN, DO, DA)
 */
LIB61850_API ModelNodeType
ModelNode_getType(ModelNode* self);

/**
 * \brief Get the name of the ModelNode
 *
 * \param self the ModelNode instance
 *
 * \return the name of the ModelNode
 */
LIB61850_API const char*
ModelNode_getName(ModelNode* self);

/**
 * \brief Get the parent ModelNode of this ModelNode instance
 *
 * \param self the ModelNode instance
 *
 * \return the parent instance, or NULL when the ModelNode has no parent
 */
LIB61850_API ModelNode*
ModelNode_getParent(ModelNode* self);

/**
 * \brief Get the list of direct child nodes
 *
 * \param self the ModelNode instance
 *
 * \return the list of private child nodes, or NULL when the node has no children
 */
LIB61850_API LinkedList
ModelNode_getChildren(ModelNode* self);

/**
 * \brief Set the name of the IED
 *
 * This will change the default name (usualy "TEMPLATE") to a user configured values.
 * NOTE: This function has to be called before IedServer_create !
 *
 * \param model the IedModel instance
 * \param the name of the configured IED
 */
LIB61850_API void
IedModel_setIedName(IedModel* self, const char* iedName);

/**
 * \brief Lookup a model node by its object reference
 *
 * This function uses the full logical device name as part of the object reference
 * as it happens to appear on the wire. E.g. if IED name in SCL file would be "IED1"
 * and the logical device "WD1" the resulting LD name would be "IED1WD".
 * When using functional naming in the LD (with ldName attribute) then the logical
 * device name is identical with the ldName attribute.
 *
 * \param self the IedModel instance that holds the model node
 * \param objectReference the IEC 61850 object reference
 *
 * \return the model node instance or NULL if model node does not exist.
 */
LIB61850_API ModelNode*
IedModel_getModelNodeByObjectReference(IedModel* self, const char* objectReference);

LIB61850_API SVControlBlock*
IedModel_getSVControlBlock(IedModel* self, LogicalNode* parentLN, const char* svcbName);

/**
 * \brief Lookup a model node by its short (normalized) reference
 *
 * This version uses the object reference that does not contain the
 * IED name or functional name as part of the logical device name.
 * Instead the LD part consists of the LD instance name ("inst" attribute).
 * This function is useful for
 * devices where the IED name can be configured.
 *
 * \param self the IedModel instance that holds the model node
 * \param objectReference the IEC 61850 object reference
 *
 * \return the model node instance or NULL if model node does not exist.
 */
LIB61850_API ModelNode*
IedModel_getModelNodeByShortObjectReference(IedModel* self, const char* objectReference);

/**
 * \brief Lookup a model node by its short address
 *
 * Short address is a 32 bit unsigned integer as specified in the "sAddr" attribute of
 * the ICD file or in the configuration file.
 *
 * \param self the IedModel instance that holds the model node
 * \param shortAddress
 *
 * \return the model node instance or NULL if model node does not exist.
 */
LIB61850_API ModelNode*
IedModel_getModelNodeByShortAddress(IedModel* self, uint32_t shortAddress);

/**
 * \brief Lookup logical device (LD) by device instance name (SCL attribute "inst")
 *
 * \param self IedModel instance
 * \param ldInst the logical device instance name (SCL attribute "inst")
 *
 * \return The matching LogicalDevice instance
 */
LIB61850_API LogicalDevice*
IedModel_getDeviceByInst(IedModel* self, const char* ldInst);

/**
 * \brief Lookup logical device (LD) instance by index
 *
 * \param self IedModel instance
 * \param index the index of the LD in the range (0 .. number of LDs - 1)
 *
 * \return the corresponding LogicalDevice* object or NULL if the index is out of range
 */
LIB61850_API LogicalDevice*
IedModel_getDeviceByIndex(IedModel* self, int index);


/**
 * \brief Lookup a logical node by name that is part of the given logical device
 *
 * \param device the logical device instance
 * \param lnName the logical node name
 *
 * \return the logical device instance or NULL if it does not exist
 */
LIB61850_API LogicalNode*
LogicalDevice_getLogicalNode(LogicalDevice* self, const char* lnName);

/**
 * \brief Get the setting group control block (SGCB) of the logical device
 *
 * \param device the logical device instance
 *
 * \return the SGCB instance or NULL if no SGCB is available
 */
LIB61850_API SettingGroupControlBlock*
LogicalDevice_getSettingGroupControlBlock(LogicalDevice* self);

/**@}*/

/**@}*/


/**
 * \brief unset all MmsValue references in the data model
 *
 * \param self the IedModel instance that holds the model node
 */
LIB61850_API void
IedModel_setAttributeValuesToNull(IedModel* self);

/**
 * \brief Lookup logical device (LD) by device name
 *
 * \param self IedModel instance
 * \param ldInst the logical device name (as it is seen from the protocol side - MMS domain name)
 *
 * \return The matching LogicalDevice instance
 */
LIB61850_API LogicalDevice*
IedModel_getDevice(IedModel* self, const char* ldName);

/**
 * \brief Lookup a data set in the IED model
 *
 * \param self IedModel instance
 * \param dataSetReference MMS mapping object reference! e.g. ied1Inverter/LLN0$dataset1
 *
 * \return The matching DataSet instance
 */
LIB61850_API DataSet*
IedModel_lookupDataSet(IedModel* self, const char* dataSetReference);

/**
 * \brief Lookup a DataAttribute instance with the corresponding MmsValue instance
 *
 * \param self IedModel instance
 * \param value the MmsValue instance (from the MMS value cache)
 *
 * \return the matching DataAttribute instance
 */
LIB61850_API DataAttribute*
IedModel_lookupDataAttributeByMmsValue(IedModel* self, MmsValue* value);


/**
 * \brief Get the number of logical devices
 *
 * \param self IedModel instance
 *
 * \return the number of logical devices
 */
LIB61850_API int
IedModel_getLogicalDeviceCount(IedModel* self);

LIB61850_API int
LogicalDevice_getLogicalNodeCount(LogicalDevice* self);

LIB61850_API ModelNode*
LogicalDevice_getChildByMmsVariableName(LogicalDevice* self, const char* mmsVariableName);

LIB61850_API bool
LogicalNode_hasFCData(LogicalNode* self, FunctionalConstraint fc);

LIB61850_API bool
LogicalNode_hasBufferedReports(LogicalNode* self);

LIB61850_API bool
LogicalNode_hasUnbufferedReports(LogicalNode* self);

/**
 * \brief get a data set instance
 *
 * \param self the logical node instance of the data set
 * \param dataSetName the name of the data set
 *
 * \return the data set instance or NULL if the data set does not exist
 */
LIB61850_API DataSet*
LogicalNode_getDataSet(LogicalNode* self, const char* dataSetName);

LIB61850_API bool
DataObject_hasFCData(DataObject* self, FunctionalConstraint fc);


#ifdef __cplusplus
}
#endif


#endif /* MODEL_H_ */
