/****************************************************************************
 * This file is part of the project AqHome.
 * AqHome (c) by 2024 Martin Preuss, all rights reserved.
 *
 * The license for this file can be found in the file COPYING which you
 * should have received along with this file.
 ****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "./u_module_p.h"

#include <gwenhywfar/debug.h>
#include <gwenhywfar/xml.h>
#include <gwenhywfar/text.h>



/* ------------------------------------------------------------------------------------------------
 * defines
 * ------------------------------------------------------------------------------------------------
 */

GWEN_INHERIT(AQHREACT_UNIT, AQHREACT_UNIT_MODULE);

GWEN_LIST_FUNCTIONS(MODULE_PROXY_DESCR, ModuleProxyDescr);



/* ------------------------------------------------------------------------------------------------
 * forward declarations
 * ------------------------------------------------------------------------------------------------
 */

static void GWENHYWFAR_CB _freeData(void *bp, void *p);
static void _cbInputData(AQHREACT_UNIT *unit, AQHREACT_PORT *port, const AQHREACT_DATAOBJECT *dataObject);
static AQHREACT_PARAM *_cbGetParamByName(const AQHREACT_UNIT *unit, const char *paramName);
static int _cbProcess(AQHREACT_UNIT *unit);
static void _cbDump(const AQHREACT_UNIT *unit, GWEN_BUFFER *buf, int indent);

static void _readProxyFromXml(GWEN_XMLNODE *xmlNode,
                              MODULE_PROXY_DESCR_LIST *proxyDescrList,
                              const char *mainGroupName,
                              const char *groupName,
                              const char *nameProperty,
                              const char *targetObjectProperty,
                              const char *targetNameProperty);
static int _readInputPortsFromXml(AQHREACT_UNIT *unit, GWEN_XMLNODE *xmlNode);
static int _readOutputPortsFromXml(AQHREACT_UNIT *unit, GWEN_XMLNODE *xmlNode);
static int _finishParams(AQHREACT_UNIT *unit);
static void _readUnitsFromXml(AQH_OBJECT *aqh, AQHREACT_UNIT *unit, GWEN_XMLNODE *xmlNode);
static AQHREACT_UNIT *_readOneUnitFromXml(AQH_OBJECT *aqh, GWEN_XMLNODE *xmlNode);
static int _readParamsFromXml(AQHREACT_UNIT *unit, GWEN_XMLNODE *xmlNodeUnit, const char *mainGroupName);
static int _readParamFromXml(AQHREACT_UNIT *unit, GWEN_XMLNODE *paramNode);
static int _setParamDataFromString(AQHREACT_PARAM *param, const char *value);
static int _readLinksFromXml(AQH_OBJECT *aqh, AQHREACT_UNIT *unit, GWEN_XMLNODE *xmlNode);
static int _readLinkFromXml(AQH_OBJECT *aqh, AQHREACT_UNIT *unit, GWEN_XMLNODE *linkNode);
static int _linkFromThisModulesInput(AQH_OBJECT *aqh,
                                     AQHREACT_UNIT *unit,
                                     const char *emittingPortName,
                                     const char *receivingUnitName,
                                     const char *receivingPortName);
static int _linkToThisModulesOutput(AQH_OBJECT *aqh,
                                    AQHREACT_UNIT *unit,
                                    const char *emittingUnitName,
                                    const char *emittingPortName,
                                    const char *receivingPortName);
static int _linkBetweenUnits(AQH_OBJECT *aqh,
                             AQHREACT_UNIT *unit,
                             const char *emittingUnitName,
                             const char *emittingPortName,
                             const char *receivingUnitName,
                             const char *receivingPortName);



/* ------------------------------------------------------------------------------------------------
 * implementations
 * ------------------------------------------------------------------------------------------------
 */


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * ModuleProxyDescr
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */

MODULE_PROXY_DESCR *ModuleProxyDescr_new(const char *name, const char *targetObject, const char *targetName)
{
  MODULE_PROXY_DESCR *pd;

  GWEN_NEW_OBJECT(MODULE_PROXY_DESCR, pd);
  GWEN_LIST_INIT(MODULE_PROXY_DESCR, pd);
  pd->name=name?strdup(name):NULL;
  pd->targetObject=targetObject?strdup(targetObject):NULL;
  pd->targetName=targetName?strdup(targetName):NULL;

  return pd;
}



void ModuleProxyDescr_free(MODULE_PROXY_DESCR *pd)
{
  if (pd) {
    GWEN_LIST_FINI(MODULE_PROXY_DESCR, pd);
    free(pd->targetName);
    free(pd->targetObject);
    free(pd->name);
    GWEN_FREE_OBJECT(pd);
  }
}



MODULE_PROXY_DESCR *ModuleProxyDescr_List_FindByName(const MODULE_PROXY_DESCR_LIST *pdList, const char *s)
{
  if (pdList && s && *s) {
    MODULE_PROXY_DESCR *pd;

    pd=ModuleProxyDescr_List_First(pdList);
    while(pd) {
      if (pd->name && strcasecmp(pd->name, s)==0)
        return pd;
      pd=ModuleProxyDescr_List_Next(pd);
    }
  }

  return NULL;
}





/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * AqHomeReact_UnitModule
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */

AQHREACT_UNIT *AqHomeReact_UnitModule_new(AQH_OBJECT *aqh)
{
  AQHREACT_UNIT *unit;
  AQHREACT_UNIT_MODULE *xunit;

  unit=AQHREACT_Unit_new(aqh);
  GWEN_NEW_OBJECT(AQHREACT_UNIT_MODULE, xunit);
  GWEN_INHERIT_SETDATA(AQHREACT_UNIT, AQHREACT_UNIT_MODULE, unit, xunit, _freeData);

  xunit->paramProxyList=ModuleProxyDescr_List_new();
  xunit->unitList=AQHREACT_Unit_List_new();

  AQHREACT_Unit_SetInputDataFn(unit, _cbInputData);
  AQHREACT_Unit_SetGetParamByNameFn(unit, _cbGetParamByName);
  AQHREACT_Unit_SetProcessFn(unit, _cbProcess);
  AQHREACT_Unit_SetDumpFn(unit, _cbDump);

  return unit;
}



void _freeData(GWEN_UNUSED void *bp, void *p)
{
  AQHREACT_UNIT_MODULE *xunit;

  xunit=(AQHREACT_UNIT_MODULE*) p;

  AQHREACT_Unit_List_free(xunit->unitList);
  ModuleProxyDescr_List_free(xunit->paramProxyList);

  GWEN_FREE_OBJECT(xunit);
}



AQHREACT_UNIT *AqHomeReact_UnitModule_fromXml(AQH_OBJECT *aqh, GWEN_XMLNODE *xmlNode)
{
  AQHREACT_UNIT *unit;
  AQHREACT_UNIT_MODULE *xunit;
  int rv;

  unit=AqHomeReact_UnitModule_new(aqh);
  xunit=(AQHREACT_UNIT_MODULE*)GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_MODULE, unit);

  AQHREACT_Unit_SetTypeName(unit, GWEN_XMLNode_GetProperty(xmlNode, "type", NULL));
  AQHREACT_Unit_SetId(unit, GWEN_XMLNode_GetProperty(xmlNode, "id", NULL));

  _readProxyFromXml(xmlNode, xunit->paramProxyList, "paramdefs", "param", "name", "targetModule", "targetParam");

  rv=_readInputPortsFromXml(unit, xmlNode);
  if (rv<0) {
    DBG_INFO(NULL, "here (%d)", rv);
    AQHREACT_Unit_free(unit);
    return NULL;
  }

  rv=_readOutputPortsFromXml(unit, xmlNode);
  if (rv<0) {
    DBG_INFO(NULL, "here (%d)", rv);
    AQHREACT_Unit_free(unit);
    return NULL;
  }

  _readUnitsFromXml(aqh, unit, xmlNode);

  rv=_finishParams(unit);
  if (rv<0) {
    DBG_INFO(NULL, "here (%d)", rv);
    AQHREACT_Unit_free(unit);
    return NULL;
  }

  rv=_readParamsFromXml(unit, xmlNode, "paramDefs");
  if (rv<0) {
    DBG_INFO(NULL, "here (%d)", rv);
    AQHREACT_Unit_free(unit);
    return NULL;
  }

  rv=_readLinksFromXml(aqh, unit, xmlNode);
  if (rv<0) {
    DBG_INFO(NULL, "here (%d)", rv);
    AQHREACT_Unit_free(unit);
    return NULL;
  }

  return unit;
}



void _cbInputData(GWEN_UNUSED AQHREACT_UNIT *unit, AQHREACT_PORT *port, const AQHREACT_DATAOBJECT *dataObject)
{
  if (port)
    AQHREACT_Port_SendData(port, dataObject);
}



AQHREACT_PARAM *_cbGetParamByName(const AQHREACT_UNIT *unit, const char *paramName)
{
  AQHREACT_UNIT_MODULE *xunit;

  DBG_INFO(NULL, "Looking for param \"%s\" in proxy descr of module \"%s\"", paramName, AQHREACT_Unit_GetTypeName(unit));
  xunit=(AQHREACT_UNIT_MODULE*)GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_MODULE, unit);
  if (xunit) {
    const MODULE_PROXY_DESCR *pd;

    pd=ModuleProxyDescr_List_FindByName(xunit->paramProxyList, paramName);
    if (pd) {
      DBG_INFO(NULL, "Found proxydescr for param (-> %s:%s)",
               (pd->targetObjectPtr)?AQHREACT_Unit_GetTypeName(pd->targetObjectPtr):"<no unit>",
               (pd->targetName)?(pd->targetName):"<no param>");
      return AQHREACT_Unit_GetParamByName(pd->targetObjectPtr, pd->targetName);
    }
  }
  return NULL;
}



int _cbProcess(AQHREACT_UNIT *unit)
{
  AQHREACT_UNIT_MODULE *xunit;
  int result=0;

  xunit=(AQHREACT_UNIT_MODULE*)GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_MODULE, unit);
  if (xunit) {
    AQHREACT_UNIT *subUnit;

    subUnit=AQHREACT_Unit_List_First(xunit->unitList);
    while(subUnit) {
      int rv;

      rv=AQHREACT_Unit_Process(subUnit);
      if (rv<0)
        result=rv;
      else if (result>=0)
        result=rv;
      subUnit=AQHREACT_Unit_List_Next(subUnit);
    }
  }
  return result;
}



void _cbDump(const AQHREACT_UNIT *unit, GWEN_BUFFER *buf, int indent)
{
  AQHREACT_UNIT_MODULE *xunit;

  xunit=(AQHREACT_UNIT_MODULE*)GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_MODULE, unit);
  if (xunit)
    AQHREACT_Unit_List_Dump(xunit->unitList, buf, indent, "Sub Units:");
}



void _readProxyFromXml(GWEN_XMLNODE *xmlNode,
                       MODULE_PROXY_DESCR_LIST *proxyDescrList,
                       const char *mainGroupName,
                       const char *groupName,
                       const char *nameProperty,
                       const char *targetObjectProperty,
                       const char *targetNameProperty)
{
  GWEN_XMLNODE *nGroup;

  nGroup=GWEN_XMLNode_FindFirstTag(xmlNode, mainGroupName, NULL, NULL);
  if (nGroup) {
    GWEN_XMLNODE *n;

    n=GWEN_XMLNode_FindFirstTag(nGroup, groupName, NULL, NULL);
    while(n) {
      const char *name;
      const char *targetObject;
      const char *targetName;

      name=GWEN_XMLNode_GetProperty(n, nameProperty, NULL);
      targetObject=GWEN_XMLNode_GetProperty(n, targetObjectProperty, NULL);
      targetName=GWEN_XMLNode_GetProperty(n, targetNameProperty, NULL);

      if (name && targetObject && targetName) {
        MODULE_PROXY_DESCR *pd;

        pd=ModuleProxyDescr_new(name, targetObject, targetName);
        ModuleProxyDescr_List_Add(pd, proxyDescrList);
      }
      else {
        DBG_ERROR(NULL, "Incomplete entry in list \"%s\", ignoring", mainGroupName);
      }
      n=GWEN_XMLNode_FindNextTag(n, groupName, NULL, NULL);
    }
  }
}



int _readInputPortsFromXml(AQHREACT_UNIT *unit, GWEN_XMLNODE *xmlNode)
{
  AQHREACT_UNIT_MODULE *xunit;
  GWEN_XMLNODE *nGroup;

  xunit=(AQHREACT_UNIT_MODULE*)GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_MODULE, unit);

  nGroup=GWEN_XMLNode_FindFirstTag(xmlNode, "inputPorts", NULL, NULL);
  if (nGroup) {
    GWEN_XMLNODE *n;

    n=GWEN_XMLNode_FindFirstTag(nGroup, "inputPort", NULL, NULL);
    while(n) {
      const char *name;
      int dataType;
      AQHREACT_PORT *port;

      name=GWEN_XMLNode_GetProperty(n, "name", NULL);
      dataType=AQHREACT_DataObjectType_fromString(GWEN_XMLNode_GetProperty(n, "dataType", "double"));
      if (dataType==AQHREACT_DATAOBJECTTYPE_UNKNOWN) {
        DBG_ERROR(NULL, "Unknown dataType: %s", GWEN_XMLNode_GetProperty(n, "dataType", "double"));
        return GWEN_ERROR_BAD_DATA;
      }
      if (name==NULL) {
        DBG_ERROR(NULL, "Missing name in input port");
        return GWEN_ERROR_BAD_DATA;
      }

      /* create input port */
      port=AQHREACT_Port_new();
      AQHREACT_Port_SetName(port, name);
      AQHREACT_Port_SetIdForUnit(port, ++(xunit->lastPortId));
      AQHREACT_Port_SetDataType(port, dataType);
      AQHREACT_Unit_AddInputPort(unit, port);
      n=GWEN_XMLNode_FindNextTag(n, "inputPort", NULL, NULL);
    } /* while */
  }

  return 0;
}



int _readOutputPortsFromXml(AQHREACT_UNIT *unit, GWEN_XMLNODE *xmlNode)
{
  AQHREACT_UNIT_MODULE *xunit;
  GWEN_XMLNODE *nGroup;

  xunit=(AQHREACT_UNIT_MODULE*)GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_MODULE, unit);

  nGroup=GWEN_XMLNode_FindFirstTag(xmlNode, "outputPorts", NULL, NULL);
  if (nGroup) {
    GWEN_XMLNODE *n;

    n=GWEN_XMLNode_FindFirstTag(nGroup, "outputPort", NULL, NULL);
    while(n) {
      const char *name;
      int dataType;
      AQHREACT_PORT *port;

      name=GWEN_XMLNode_GetProperty(n, "name", NULL);
      dataType=AQHREACT_DataObjectType_fromString(GWEN_XMLNode_GetProperty(n, "dataType", "double"));
      if (dataType==AQHREACT_DATAOBJECTTYPE_UNKNOWN) {
        DBG_ERROR(NULL, "Unknown dataType: %s", GWEN_XMLNode_GetProperty(n, "dataType", "double"));
        return GWEN_ERROR_BAD_DATA;
      }
      if (name==NULL) {
        DBG_ERROR(NULL, "Missing name in output port");
        return GWEN_ERROR_BAD_DATA;
      }

      /* create input port */
      port=AQHREACT_Port_new();
      AQHREACT_Port_SetName(port, name);
      AQHREACT_Port_SetIdForUnit(port, ++(xunit->lastPortId));
      AQHREACT_Port_SetDataType(port, dataType);
      AQHREACT_Unit_AddOutputPort(unit, port);
      n=GWEN_XMLNode_FindNextTag(n, "outputPort", NULL, NULL);
    }
  }

  return 0;
}



int _finishParams(AQHREACT_UNIT *unit)
{
  AQHREACT_UNIT_MODULE *xunit;
  MODULE_PROXY_DESCR *pd;
  const char *unitName;

  xunit=(AQHREACT_UNIT_MODULE*)GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_MODULE, unit);
  unitName=AQHREACT_Unit_GetTypeName(unit);
  pd=ModuleProxyDescr_List_First(xunit->paramProxyList);
  while(pd) {
    AQHREACT_UNIT *subUnit;

    subUnit=AQHREACT_Unit_List_GetById(xunit->unitList, pd->targetObject);
    if (subUnit==NULL) {
      DBG_ERROR(NULL, "Unit %s: Subunit \"%s\" not found for param \"%s\"", unitName, pd->targetObject, pd->name);
      return GWEN_ERROR_BAD_DATA;
    }
    pd->targetObjectPtr=subUnit;

    pd=ModuleProxyDescr_List_Next(pd);
  }

  return 0;
}



void _readUnitsFromXml(AQH_OBJECT *aqh, AQHREACT_UNIT *unit, GWEN_XMLNODE *xmlNode)
{
  GWEN_XMLNODE *nGroup;
  AQHREACT_UNIT_MODULE *xunit;

  xunit=(AQHREACT_UNIT_MODULE*)GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_MODULE, unit);

  nGroup=GWEN_XMLNode_FindFirstTag(xmlNode, "units", NULL, NULL);
  if (nGroup) {
    GWEN_XMLNODE *n;

    n=GWEN_XMLNode_FindFirstTag(nGroup, "unit", NULL, NULL);
    while(n) {
      AQHREACT_UNIT *subUnit;

      subUnit=_readOneUnitFromXml(aqh, n);
      if (subUnit)
        AQHREACT_Unit_List_Add(subUnit, xunit->unitList);
      else {
        DBG_INFO(NULL, "Error reading unit from XML node");
      }

      n=GWEN_XMLNode_FindNextTag(n, "unit", NULL, NULL);
    }
  }
}



AQHREACT_UNIT *_readOneUnitFromXml(AQH_OBJECT *aqh, GWEN_XMLNODE *xmlNode)
{
  const char *id;
  const char *t;
  const char *s;

  id=GWEN_XMLNode_GetProperty(xmlNode, "id", NULL);
  t=GWEN_XMLNode_GetProperty(xmlNode, "type", NULL);
  s=GWEN_XMLNode_GetProperty(xmlNode, "invert", NULL);

  if (id && t) {
    AQHREACT_UNIT *subUnit;
    int rv;

    subUnit=AQH_ReactServer_CreateUnitByName(aqh, t);
    if (subUnit==NULL) {
      DBG_INFO(NULL, "Could not create unit of type \"%s\"", t);
      return NULL;
    }
    AQHREACT_Unit_SetId(subUnit, id);
    if (s && strcasecmp(s, "TRUE")==0)
      AQHREACT_Unit_AddFlags(subUnit, AQHREACT_UNIT_FLAGS_INVERT);

    rv=_readParamsFromXml(subUnit, xmlNode, "params");
    if (rv<0) {
      DBG_INFO(NULL, "here (%d)", rv);
      AQHREACT_Unit_free(subUnit);
      return NULL;
    }
    return subUnit;
  }
  else {
    DBG_ERROR(NULL, "Incomplete unit, ignoring");
    return NULL;
  }
}



int _readParamsFromXml(AQHREACT_UNIT *unit, GWEN_XMLNODE *xmlNodeUnit, const char *mainGroupName)
{
  GWEN_XMLNODE *nGroup;

  nGroup=GWEN_XMLNode_FindFirstTag(xmlNodeUnit, mainGroupName, NULL, NULL);
  if (nGroup) {
    GWEN_XMLNODE *n;

    n=GWEN_XMLNode_FindFirstTag(nGroup, "param", NULL, NULL);
    while(n) {
      int rv;

      rv=_readParamFromXml(unit, n);
      if (rv<0) {
        DBG_INFO(NULL, "here (%d)", rv);
        return rv;
      }
      n=GWEN_XMLNode_FindNextTag(n, "param", NULL, NULL);
    }
  }
  return 0;
}



int _readParamFromXml(AQHREACT_UNIT *unit, GWEN_XMLNODE *paramNode)
{
  const char *paramName;
  
  paramName=GWEN_XMLNode_GetProperty(paramNode, "name", NULL);
  if (paramName && *paramName) {
    AQHREACT_PARAM *param;
  
    param=AQHREACT_Unit_GetParamByName(unit, paramName);
    if (param) {
      const char *value;
  
      value=GWEN_XMLNode_GetCharValue(paramNode, NULL, NULL);
      if (value && *value) {
        int rv;

        rv=_setParamDataFromString(param, value);
        if (rv<0) {
          DBG_ERROR(NULL, "Error setting param data for param \"%s\" (%d)", paramName, rv);
          return rv;
        }
      } /* if value */
    }
    else {
      DBG_ERROR(NULL, "No param name \"%s\" in unit \"%s\"", paramName, AQHREACT_Unit_GetTypeName(unit));
      return GWEN_ERROR_BAD_DATA;
    }
  }

  return 0;
}



int _setParamDataFromString(AQHREACT_PARAM *param, const char *value)
{
  if (value && *value) {
    int dataType;

    dataType=AQHREACT_Param_GetDataType(param);
    switch(dataType) {
    case AQHREACT_DATAOBJECTTYPE_DOUBLE:
      {
        int rv;
        double valueAsDouble;

        rv=GWEN_Text_StringToDouble(value, &valueAsDouble);
        if (rv<0) {
          DBG_ERROR(NULL, "Not a DOUBLE value for param \"%s\" [%s]", AQHREACT_Param_GetName(param), value);
          return rv;
        }
        AQHREACT_Param_SetDoubleValue(param, valueAsDouble);
        break;
      }
    case AQHREACT_DATAOBJECTTYPE_STRING:
    default:
      AQHREACT_Param_SetStringValue(param, value);
      break;
    }
  }
  return 0;
}



int _readLinksFromXml(AQH_OBJECT *aqh, AQHREACT_UNIT *unit, GWEN_XMLNODE *xmlNode)
{
  GWEN_XMLNODE *nGroup;

  nGroup=GWEN_XMLNode_FindFirstTag(xmlNode, "links", NULL, NULL);
  if (nGroup) {
    GWEN_XMLNODE *n;

    n=GWEN_XMLNode_FindFirstTag(nGroup, "link", NULL, NULL);
    while(n) {
      int rv;

      rv=_readLinkFromXml(aqh, unit, n);
      if (rv<0) {
        DBG_ERROR(NULL, "Error reading link in net \"%s\" (%d)", AQHREACT_Unit_GetTypeName(unit), rv);
        return GWEN_ERROR_BAD_DATA;
      }
      n=GWEN_XMLNode_FindNextTag(n, "link", NULL, NULL);
    }
  }
  return 0;
}



int _readLinkFromXml(AQH_OBJECT *aqh, AQHREACT_UNIT *unit, GWEN_XMLNODE *linkNode)
{
  const char *emittingUnitName;
  const char *emittingPortName;
  const char *receivingUnitName;
  const char *receivingPortName;
  int rv;

  emittingUnitName=GWEN_XMLNode_GetProperty(linkNode, "sourceUnit", ".");
  emittingPortName=GWEN_XMLNode_GetProperty(linkNode, "sourcePort", NULL);
  receivingUnitName=GWEN_XMLNode_GetProperty(linkNode, "targetUnit", ".");
  receivingPortName=GWEN_XMLNode_GetProperty(linkNode, "targetPort", NULL);

  if (!(emittingUnitName && *emittingUnitName && emittingPortName && *emittingPortName &&
        receivingUnitName && *receivingUnitName && receivingPortName && *receivingPortName)) {
    DBG_ERROR(NULL,
              "Link in net \"%s\" needs properties sourceUnit, sourcePort, targetUnit and targetPort",
              AQHREACT_Unit_GetTypeName(unit));
    return GWEN_ERROR_BAD_DATA;
  }

  if (strcasecmp(emittingUnitName, ".")==0)
    rv=_linkFromThisModulesInput(aqh, unit, emittingPortName, receivingUnitName, receivingPortName);
  else if (strcasecmp(receivingUnitName, ".")==0)
    rv=_linkToThisModulesOutput(aqh, unit, emittingUnitName, emittingPortName, receivingPortName);
  else
    rv=_linkBetweenUnits(aqh, unit, emittingUnitName, emittingPortName, receivingUnitName, receivingPortName);
  if (rv<0) {
    DBG_INFO(NULL, "Error creating link: %s:%s -> %s:%s (%d)",
             emittingUnitName, emittingPortName, receivingUnitName, receivingPortName, rv);
    return rv;
  }

  return 0;
}



int _linkFromThisModulesInput(AQH_OBJECT *aqh,
                              AQHREACT_UNIT *unit,
                              const char *emittingPortName,
                              const char *receivingUnitName,
                              const char *receivingPortName)
{
  AQHREACT_UNIT_MODULE *xunit;
  AQHREACT_UNIT *receivingUnit;
  AQHREACT_PORT *receivingPort;
  AQHREACT_PORT *emittingPort;
  AQHREACT_LINK *link;

  xunit=(AQHREACT_UNIT_MODULE*)GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_MODULE, unit);

  receivingUnit=AQHREACT_Unit_List_GetById(xunit->unitList, receivingUnitName);
  if (receivingUnit==NULL)
    receivingUnit=AQH_ReactServer_FindUnitByUnitId(aqh, receivingUnitName);
  if (receivingUnit==NULL) {
    DBG_ERROR(NULL, "Target unit \"%s\" not found", receivingUnitName);
    return GWEN_ERROR_NOT_FOUND;
  }

  emittingPort=AQHREACT_Unit_GetInputPortByName(unit, emittingPortName); /* find in modules input port list! */
  if (emittingPort==NULL) {
    DBG_ERROR(NULL, "Target port \"%s\" not found for source unit \"%s\"", emittingPortName, AQHREACT_Unit_GetTypeName(unit));
    return GWEN_ERROR_NOT_FOUND;
  }

  receivingPort=AQHREACT_Unit_GetOrCreateUnusedInputPortByName(receivingUnit, receivingPortName);
  /*receivingPort=AQHREACT_Unit_GetInputPortByName(receivingUnit, receivingPortName);*/
  if (receivingPort==NULL) {
    DBG_ERROR(NULL, "Input port \"%s\" not found for target unit \"%s\"", receivingPortName, receivingUnitName);
    return GWEN_ERROR_NOT_FOUND;
  }
  AQHREACT_Port_AddFlags(receivingPort, AQHREACT_UNIT_FLAGS_INUSE);

  link=AQHREACT_Link_new();
  AQHREACT_Link_SetTargetUnit(link, receivingUnit);
  AQHREACT_Link_SetTargetPort(link, receivingPort);
  AQHREACT_Port_AddLink(emittingPort, link);
  return 0;
}



int _linkToThisModulesOutput(AQH_OBJECT *aqh,
                             AQHREACT_UNIT *unit,
                             const char *emittingUnitName,
                             const char *emittingPortName,
                             const char *receivingPortName)
{
  AQHREACT_UNIT_MODULE *xunit;
  AQHREACT_UNIT *receivingUnit;
  AQHREACT_PORT *receivingPort;
  AQHREACT_UNIT *emittingUnit;
  AQHREACT_PORT *emittingPort;
  AQHREACT_LINK *link;

  xunit=(AQHREACT_UNIT_MODULE*)GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_MODULE, unit);

  emittingUnit=AQHREACT_Unit_List_GetById(xunit->unitList, emittingUnitName);
  if (emittingUnit==NULL)
    emittingUnit=AQH_ReactServer_FindUnitByUnitId(aqh, emittingUnitName);
  if (emittingUnit==NULL) {
    DBG_ERROR(NULL, "Source unit \"%s\" not found", emittingUnitName);
    return GWEN_ERROR_NOT_FOUND;
  }

  receivingUnit=unit;

  emittingPort=AQHREACT_Unit_GetOutputPortByName(emittingUnit, emittingPortName);
  if (emittingPort==NULL) {
    DBG_ERROR(NULL, "Output port \"%s\" not found", emittingPortName);
    return GWEN_ERROR_NOT_FOUND;
  }

  receivingPort=AQHREACT_Unit_GetOutputPortByName(unit, receivingPortName); /* find in modules output port list! */
  if (receivingPort==NULL) {
    DBG_ERROR(NULL, "Target port \"%s\" not found", receivingPortName);
    return GWEN_ERROR_NOT_FOUND;
  }

  link=AQHREACT_Link_new();
  AQHREACT_Link_SetTargetUnit(link, receivingUnit);
  AQHREACT_Link_SetTargetPort(link, receivingPort);
  AQHREACT_Port_AddLink(emittingPort, link);
  return 0;
}



int _linkBetweenUnits(AQH_OBJECT *aqh,
                      AQHREACT_UNIT *unit,
                      const char *emittingUnitName,
                      const char *emittingPortName,
                      const char *receivingUnitName,
                      const char *receivingPortName)
{
  AQHREACT_UNIT_MODULE *xunit;
  AQHREACT_UNIT *receivingUnit;
  AQHREACT_PORT *receivingPort;
  AQHREACT_UNIT *emittingUnit;
  AQHREACT_PORT *emittingPort;
  AQHREACT_LINK *link;

  xunit=(AQHREACT_UNIT_MODULE*)GWEN_INHERIT_GETDATA(AQHREACT_UNIT, AQHREACT_UNIT_MODULE, unit);

  emittingUnit=AQHREACT_Unit_List_GetById(xunit->unitList, emittingUnitName);
  if (emittingUnit==NULL)
    emittingUnit=AQH_ReactServer_FindUnitByUnitId(aqh, emittingUnitName);
  if (emittingUnit==NULL) {
    DBG_ERROR(NULL, "Source unit \"%s\" not found", emittingUnitName);
    return GWEN_ERROR_NOT_FOUND;
  }

  receivingUnit=AQHREACT_Unit_List_GetById(xunit->unitList, receivingUnitName);
  if (receivingUnit==NULL)
    receivingUnit=AQH_ReactServer_FindUnitByUnitId(aqh, receivingUnitName);
  if (receivingUnit==NULL) {
    DBG_ERROR(NULL, "Target unit \"%s\" not found", receivingUnitName);
    return GWEN_ERROR_NOT_FOUND;
  }

  emittingPort=AQHREACT_Unit_GetOutputPortByName(emittingUnit, emittingPortName);
  if (emittingPort==NULL) {
    DBG_ERROR(NULL, "Output port \"%s\" not found for source unit \"%s\"", emittingPortName, emittingUnitName);
    return GWEN_ERROR_NOT_FOUND;
  }

  receivingPort=AQHREACT_Unit_GetOrCreateUnusedInputPortByName(receivingUnit, receivingPortName);
  if (receivingPort==NULL) {
    DBG_ERROR(NULL, "Input port \"%s\" not found for target unit \"%s\"", receivingPortName, receivingUnitName);
    return GWEN_ERROR_NOT_FOUND;
  }
  AQHREACT_Port_AddFlags(receivingPort, AQHREACT_UNIT_FLAGS_INUSE);

  link=AQHREACT_Link_new();
  AQHREACT_Link_SetTargetUnit(link, receivingUnit);
  AQHREACT_Link_SetTargetPort(link, receivingPort);
  AQHREACT_Port_AddLink(emittingPort, link);
  return 0;
}





