/****************************************************************************
 * This file is part of the project AqHome.
 * AqHome (c) by 2023 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 "./unit_p.h"

#include <gwenhywfar/debug.h>


GWEN_LIST_FUNCTIONS(AQHREACT_UNIT, AQHREACT_Unit)
GWEN_INHERIT_FUNCTIONS(AQHREACT_UNIT)



AQHREACT_UNIT *AQHREACT_Unit_new(AQH_OBJECT *aqh)
{
  AQHREACT_UNIT *unit;

  GWEN_NEW_OBJECT(AQHREACT_UNIT, unit);
  GWEN_INHERIT_INIT(AQHREACT_UNIT, unit);
  GWEN_LIST_INIT(AQHREACT_UNIT, unit);

  unit->aqHomeReact=aqh;
  unit->paramList=AQHREACT_Param_List_new();
  unit->inputPortList=AQHREACT_Port_List_new();
  unit->outputPortList=AQHREACT_Port_List_new();

  return unit;
}



void AQHREACT_Unit_free(AQHREACT_UNIT *unit)
{
  if (unit) {
    GWEN_LIST_FINI(AQHREACT_UNIT, unit);
    GWEN_INHERIT_FINI(AQHREACT_UNIT, unit);
    free(unit->typeName);
    free(unit->description);
    free(unit->id);
    AQHREACT_Port_List_free(unit->outputPortList);
    AQHREACT_Port_List_free(unit->inputPortList);
    AQHREACT_Param_List_free(unit->paramList);
    GWEN_FREE_OBJECT(unit);
  }
}



AQH_OBJECT *AQHREACT_Unit_GetAqHomeReact(const AQHREACT_UNIT *unit)
{
  return unit?unit->aqHomeReact:NULL;
}



const char *AQHREACT_Unit_GetTypeName(const AQHREACT_UNIT *unit)
{
  return unit?unit->typeName:NULL;
}



void AQHREACT_Unit_SetTypeName(AQHREACT_UNIT *unit, const char *s)
{
  if (unit) {
    free(unit->typeName);
    unit->typeName=s?strdup(s):NULL;
  }
}



const char *AQHREACT_Unit_GetDescription(const AQHREACT_UNIT *unit)
{
  return unit?unit->description:NULL;
}



void AQHREACT_Unit_SetDescription(AQHREACT_UNIT *unit, const char *s)
{
  if (unit) {
    free(unit->description);
    unit->description=s?strdup(s):NULL;
  }
}



const char *AQHREACT_Unit_GetId(const AQHREACT_UNIT *unit)
{
  return unit?unit->id:NULL;
}



void AQHREACT_Unit_SetId(AQHREACT_UNIT *unit, const char *s)
{
  if (unit) {
    free(unit->id);
    unit->id=s?strdup(s):NULL;
  }
}



void AQHREACT_Unit_SetNextInputPortId(AQHREACT_UNIT *unit, int i)
{
  if (unit)
    unit->nextInputPortId=i;
}



uint32_t AQHREACT_Unit_GetFlags(const AQHREACT_UNIT *unit)
{
  return unit?unit->flags:0;
}



void AQHREACT_Unit_SetFlags(AQHREACT_UNIT *unit, uint32_t i)
{
  if (unit)
    unit->flags=i;
}



void AQHREACT_Unit_AddFlags(AQHREACT_UNIT *unit, uint32_t i)
{
  if (unit)
    unit->flags|=i;
}



void AQHREACT_Unit_SubFlags(AQHREACT_UNIT *unit, uint32_t i)
{
  if (unit)
    unit->flags&=~i;
}



uint64_t AQHREACT_Unit_GetGpTimestamp(const AQHREACT_UNIT *unit)
{
  return unit?unit->gpTimestamp:0;
}



void AQHREACT_Unit_SetGpTimestamp(AQHREACT_UNIT *unit, uint64_t t)
{
  if (unit)
    unit->gpTimestamp=t;
}



int AQHREACT_Unit_GetGpInt(const AQHREACT_UNIT *unit)
{
  return unit?unit->gpInt:0;
}



void AQHREACT_Unit_SetGpInt(AQHREACT_UNIT *unit, int i)
{
  if (unit)
    unit->gpInt=i;
}






AQHREACT_PORT_LIST *AQHREACT_Unit_GetInputPortList(const AQHREACT_UNIT *unit)
{
  return unit?unit->inputPortList:NULL;
}



void AQHREACT_Unit_AddInputPort(AQHREACT_UNIT *unit, AQHREACT_PORT *port)
{
  if (unit && port)
    AQHREACT_Port_List_Add(port, unit->inputPortList);
}



AQHREACT_PORT *AQHREACT_Unit_GetInputPortByName(const AQHREACT_UNIT *unit, const char *name)
{
  return (unit && name)?AQHREACT_Port_List_GetByName(unit->inputPortList, name):NULL;
}



AQHREACT_PORT *AQHREACT_Unit_GetInputPortByIdForUnit(const AQHREACT_UNIT *unit, int id)
{
  return unit?AQHREACT_Port_List_GetByIdForUnit(unit->inputPortList, id):NULL;
}



AQHREACT_PORT *AQHREACT_Unit_GetOrCreateUnusedInputPortByName(AQHREACT_UNIT *unit, const char *s)
{
  if (unit && unit->inputPortList && s && *s) {
    AQHREACT_PORT *port;

    port=AQHREACT_Unit_GetInputPortByName(unit, s);
    if (port) {
      uint32_t flags;
      const char *portName;

      portName=AQHREACT_Port_GetName(port);
      flags=AQHREACT_Port_GetFlags(port);
      if (!(flags & AQHREACT_UNIT_FLAGS_INUSE))
        return port;
      if (flags & AQHREACT_UNIT_FLAGS_MULTI) { /* can we just duplicate existing? */
        AQHREACT_PORT *newPort;

        newPort=AQHREACT_Port_dup(port);
        AQHREACT_Port_SetIdForUnit(port, (unit->nextInputPortId)++);
        AQHREACT_Unit_AddInputPort(unit, newPort);
        return newPort;
      }
      else {
        DBG_INFO(NULL, "Port \"%s\" found but in use, no multi flag", portName);
      }
    }
  }

  return NULL;
}



AQHREACT_PORT_LIST *AQHREACT_Unit_GetOutputPortList(const AQHREACT_UNIT *unit)
{
  return unit?unit->outputPortList:NULL;
}



void AQHREACT_Unit_AddOutputPort(AQHREACT_UNIT *unit, AQHREACT_PORT *port)
{
  if (unit && port)
    AQHREACT_Port_List_Add(port, unit->outputPortList);
}



AQHREACT_PORT *AQHREACT_Unit_GetOutputPortByName(const AQHREACT_UNIT *unit, const char *name)
{
  return (unit && name)?AQHREACT_Port_List_GetByName(unit->outputPortList, name):NULL;
}



AQHREACT_PORT *AQHREACT_Unit_GetOutputPortByIdForUnit(const AQHREACT_UNIT *unit, int id)
{
  return unit?AQHREACT_Port_List_GetByIdForUnit(unit->outputPortList, id):NULL;
}









AQHREACT_PARAM_LIST *AQHREACT_Unit_GetParamList(const AQHREACT_UNIT *unit)
{
  return unit?unit->paramList:NULL;
}



void AQHREACT_Unit_AddParam(AQHREACT_UNIT *unit, AQHREACT_PARAM *param)
{
  if (unit)
    AQHREACT_Param_List_Add(param, unit->paramList);
}



AQHREACT_PARAM *AQHREACT_Unit_GetParamByName(const AQHREACT_UNIT *unit, const char *paramName)
{
  if (unit) {
    if (unit->getParamByNameFn)
      return unit->getParamByNameFn(unit, paramName);
    else {
      if (unit->paramList && paramName && *paramName) {
	AQHREACT_PARAM *param;

	param=AQHREACT_Param_List_First(unit->paramList);
	while(param) {
	  const char *s;

	  s=AQHREACT_Param_GetName(param);
	  if (s && *s && strcasecmp(paramName, s)==0)
	    return param;
	  param=AQHREACT_Param_List_Next(param);
	}
      }
    } /* else */
  } /* if (unit) */
      return NULL;
}



double AQHREACT_Unit_GetParamValueDouble(const AQHREACT_UNIT *unit, const char *paramName, double defVal)
{
  AQHREACT_PARAM *param;

  param=AQHREACT_Unit_GetParamByName(unit, paramName);
  if (param) {
    if (AQHREACT_Param_GetDataType(param)==AQHREACT_DATAOBJECTTYPE_DOUBLE) {
      return AQHREACT_Param_GetDoubleValue(param);
    }
    else {
      DBG_INFO(NULL, "Datatype for param \"%s/%s\" is not DOUBLE", (unit->typeName)?unit->typeName:"<unnamed>", paramName);
    }
  }

  return defVal;
}



void AQHREACT_Unit_SetParamValueDouble(AQHREACT_UNIT *unit, const char *paramName, double val)
{
  AQHREACT_PARAM *param;

  param=AQHREACT_Unit_GetParamByName(unit, paramName);
  if (param) {
    if (AQHREACT_Param_GetDataType(param)==AQHREACT_DATAOBJECTTYPE_DOUBLE) {
      AQHREACT_Param_SetDoubleValue(param, val);
    }
    else {
      DBG_INFO(NULL, "Datatype for param \"%s/%s\" is not DOUBLE", (unit->typeName)?unit->typeName:"<unnamed>", paramName);
    }
  }
  else {
    DBG_INFO(NULL, "Param \"%s\" not found in unit %s", paramName, (unit->typeName)?unit->typeName:"<unnamed>");
  }
}



const char *AQHREACT_Unit_GetParamValueString(const AQHREACT_UNIT *unit, const char *paramName, const char *defVal)
{
  AQHREACT_PARAM *param;

  param=AQHREACT_Unit_GetParamByName(unit, paramName);
  if (param) {
    if (AQHREACT_Param_GetDataType(param)==AQHREACT_DATAOBJECTTYPE_STRING) {
      return AQHREACT_Param_GetStringValue(param);
    }
    else {
      DBG_INFO(NULL, "Datatype for param \"%s/%s\" is not STRING", (unit->typeName)?unit->typeName:"<unnamed>", paramName);
    }
  }
  else {
    DBG_INFO(NULL, "Param \"%s\" not found in unit %s", paramName, (unit->typeName)?unit->typeName:"<unnamed>");
  }

  return defVal;
}



void AQHREACT_Unit_SetParamValueString(AQHREACT_UNIT *unit, const char *paramName, const char *val)
{
  AQHREACT_PARAM *param;

  param=AQHREACT_Unit_GetParamByName(unit, paramName);
  if (param) {
    if (AQHREACT_Param_GetDataType(param)==AQHREACT_DATAOBJECTTYPE_STRING) {
      AQHREACT_Param_SetStringValue(param, val);
    }
    else {
      DBG_INFO(NULL, "Datatype for param \"%s\" is not STRING", paramName);
    }
  }
  else {
    DBG_INFO(NULL, "Param \"%s\" not found in unit %s", paramName, (unit->typeName)?unit->typeName:"<unnamed>");
  }
}




AQHREACT_UNIT_INPUTDATA_FN AQHREACT_Unit_SetInputDataFn(AQHREACT_UNIT *unit, AQHREACT_UNIT_INPUTDATA_FN f)
{
  if (unit) {
    AQHREACT_UNIT_INPUTDATA_FN oldFn;

    oldFn=unit->inputDataFn;
    unit->inputDataFn=f;
    return oldFn;
  }
  return NULL;
}



AQHREACT_UNIT_OUTPUTDATA_FN AQHREACT_Unit_SetOutputDataFn(AQHREACT_UNIT *unit, AQHREACT_UNIT_OUTPUTDATA_FN f)
{
  if (unit) {
    AQHREACT_UNIT_OUTPUTDATA_FN oldFn;

    oldFn=unit->outputDataFn;
    unit->outputDataFn=f;
    return oldFn;
  }
  return NULL;
}



AQHREACT_UNIT_GETPARAMBYNAME_FN AQHREACT_Unit_SetGetParamByNameFn(AQHREACT_UNIT *unit, AQHREACT_UNIT_GETPARAMBYNAME_FN f)
{
  if (unit) {
    AQHREACT_UNIT_GETPARAMBYNAME_FN oldFn;

    oldFn=unit->getParamByNameFn;
    unit->getParamByNameFn=f;
    return oldFn;
  }
  return NULL;
}



AQHREACT_UNIT_PROCESS_FN AQHREACT_Unit_SetProcessFn(AQHREACT_UNIT *unit, AQHREACT_UNIT_PROCESS_FN f)
{
  if (unit) {
    AQHREACT_UNIT_PROCESS_FN oldFn;

    oldFn=unit->processFn;
    unit->processFn=f;
    return oldFn;
  }
  return NULL;
}



AQHREACT_UNIT_DUMP_FN AQHREACT_Unit_SetDumpFn(AQHREACT_UNIT *unit, AQHREACT_UNIT_DUMP_FN f)
{
  if (unit) {
    AQHREACT_UNIT_DUMP_FN oldFn;

    oldFn=unit->dumpFn;
    unit->dumpFn=f;
    return oldFn;
  }
  return NULL;
}



void AQHREACT_Unit_OutputData(AQHREACT_UNIT *unit, AQHREACT_PORT *port, const AQHREACT_DATAOBJECT *dataObject)
{
  if (unit && port) {
    if (unit->outputDataFn)
      (unit->outputDataFn)(unit, port, dataObject);
    else {
      AQHREACT_Port_SendData(port, dataObject);
    }
  }
}



void AQHREACT_Unit_OutputDoubleData(AQHREACT_UNIT *unit, AQHREACT_PORT *port, double data)
{
  if (unit && port && unit->outputPortList) {
    AQHREACT_DATAOBJECT *dataObject;

    dataObject=AQHREACT_DataObject_new();
    AQHREACT_DataObject_SetDataType(dataObject, AQHREACT_DATAOBJECTTYPE_DOUBLE);
    AQHREACT_DataObject_SetDoubleData(dataObject, data);
    AQHREACT_Unit_OutputData(unit, port, dataObject);
    AQHREACT_DataObject_free(dataObject);
  }
}



void AQHREACT_Unit_OutputStringData(AQHREACT_UNIT *unit, AQHREACT_PORT *port, const char *data)
{
  if (unit && port && unit->outputPortList) {
    AQHREACT_DATAOBJECT *dataObject;

    dataObject=AQHREACT_DataObject_new();
    AQHREACT_DataObject_SetDataType(dataObject, AQHREACT_DATAOBJECTTYPE_STRING);
    AQHREACT_DataObject_SetStringData(dataObject, data);
    AQHREACT_Unit_OutputData(unit, port, dataObject);
    AQHREACT_DataObject_free(dataObject);
  }
}



void AQHREACT_Unit_InputData(AQHREACT_UNIT *unit, AQHREACT_PORT *port, const AQHREACT_DATAOBJECT *dataObject)
{
  if (unit && port) {
    if (unit->inputDataFn)
      (unit->inputDataFn)(unit, port, dataObject);
    else {
      if (dataObject) {
	if (AQHREACT_DataObject_GetDataType(dataObject)==AQHREACT_Port_GetDataType(port)) {
	  AQHREACT_Port_SetCurrentDataObject(port, AQHREACT_DataObject_dup(dataObject));
	  AQHREACT_Port_SendData(port, dataObject);
	  AQHREACT_Port_AddFlags(port, AQHREACT_UNIT_FLAGS_CHANGED);
	  AQHREACT_Unit_AddFlags(unit, AQHREACT_UNIT_FLAGS_CHANGED);
	}
      }
    }
  }
  else {
    DBG_ERROR(NULL, "Nullpointer");
  }
}




int AQHREACT_Unit_Process(AQHREACT_UNIT *unit)
{
  if (unit) {
    if (unit->processFn)
      return (unit->processFn)(unit);
    else
      AQHREACT_Unit_ClearChangeFlagsInUnitAndInputPorts(unit);
  }

  return 0;
}



void AQHREACT_Unit_ClearChangeFlagsInUnitAndInputPorts(AQHREACT_UNIT *unit)
{
  if (unit) {
    AQHREACT_PORT *port;

    unit->flags&=~AQHREACT_UNIT_FLAGS_CHANGED;
    port=AQHREACT_Port_List_First(unit->inputPortList);
    while(port) {
      AQHREACT_Port_SubFlags(port, AQHREACT_UNIT_FLAGS_CHANGED);
      port=AQHREACT_Port_List_Next(port);
    }
  }
}



int AQHREACT_Unit_InputHasChanged(const AQHREACT_UNIT *unit)
{
  if (unit) {
    AQHREACT_PORT *port;

    if (unit->flags & AQHREACT_UNIT_FLAGS_CHANGED)
      return 1;

    port=AQHREACT_Port_List_First(unit->inputPortList);
    while(port) {
      if (AQHREACT_Port_GetFlags(port) & AQHREACT_UNIT_FLAGS_CHANGED)
        return 1;
      port=AQHREACT_Port_List_Next(port);
    }
  }
  return 0;
}



AQHREACT_UNIT *AQHREACT_Unit_List_GetById(const AQHREACT_UNIT_LIST *unitList, const char *id)
{
  if (unitList && id && *id) {
    AQHREACT_UNIT *unit;

    unit=AQHREACT_Unit_List_First(unitList);
    while(unit) {
      const char *s;

      s=AQHREACT_Unit_GetId(unit);
      if (s && *s && strcasecmp(s, id)==0)
        return unit;
      unit=AQHREACT_Unit_List_Next(unit);
    }
  }

  return NULL;
}



void AQHREACT_Unit_Dump(const AQHREACT_UNIT *unit, GWEN_BUFFER *buf, int indent)
{
  if (unit && buf) {
    if (indent)
      GWEN_Buffer_FillWithBytes(buf, ' ', indent);
    GWEN_Buffer_AppendArgs(buf, "id.........: %s\n", (unit->id)?(unit->id):"<empty>");

    if (indent)
      GWEN_Buffer_FillWithBytes(buf, ' ', indent);
    GWEN_Buffer_AppendArgs(buf, "type.......: %s\n", (unit->typeName)?(unit->typeName):"<empty>");

    if (indent)
      GWEN_Buffer_FillWithBytes(buf, ' ', indent);
    GWEN_Buffer_AppendArgs(buf, "description: %s\n", (unit->description)?(unit->description):"<empty>");

    if (indent)
      GWEN_Buffer_FillWithBytes(buf, ' ', indent);
    GWEN_Buffer_AppendArgs(buf, "flags......: %08x\n", unit->flags);

    if (unit->paramList && AQHREACT_Param_List_GetCount(unit->paramList))
      AQHREACT_Param_List_Dump(unit->paramList, buf, indent, "Unit Parameter List:");
    if (unit->inputPortList && AQHREACT_Port_List_GetCount(unit->inputPortList))
      AQHREACT_Port_List_Dump(unit->inputPortList, buf, indent, "Input Port List:");
    if (unit->outputPortList && AQHREACT_Port_List_GetCount(unit->outputPortList))
      AQHREACT_Port_List_Dump(unit->outputPortList, buf, indent, "Output Port List:");

    if (unit->dumpFn)
      unit->dumpFn(unit, buf, indent+2);
  }
}



void AQHREACT_Unit_List_Dump(const AQHREACT_UNIT_LIST *unitList, GWEN_BUFFER *buf, int indent, const char *title)
{
  if (unitList && buf) {
    const AQHREACT_UNIT *unit;

    if (indent)
      GWEN_Buffer_FillWithBytes(buf, ' ', indent);
    GWEN_Buffer_AppendArgs(buf, "%s\n", (title && *title)?title:"Unit List:\n");

    unit=AQHREACT_Unit_List_First(unitList);
    while(unit) {
      GWEN_Buffer_FillWithBytes(buf, ' ', indent+2);
      GWEN_Buffer_AppendString(buf, "Unit:\n");
      AQHREACT_Unit_Dump(unit, buf, indent+4);

      unit=AQHREACT_Unit_List_Next(unit);
    }

  }
}







