/*---------------------------------------------------------------------------
pardef.c

my way to define and handle object parameters

Markus Zolliker, March 2005
----------------------------------------------------------------------------
*/

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <fortify.h>
#include <limits.h>
#include <ctype.h>
#include "logger.h"
#include "pardef.h"
#include "sugar.h"
#include "commandlog.h"
#include "dynstring.h"

#define ILLNUM -2
#define ILLARGC -3
#define AMBIGUOS -4
#define ILLPRIV -5
#define BADLOG -6
#define UNKPAR -7
#define PARUNDEF -8
#define ERRCMD -9

typedef enum { NO_OP, FMT_OP, SET_OP, GET_OP, INIT_OP } ParOp;

/* Context holds all the information needed during the pardef function */
typedef struct Context {
  struct Context *next;
  ParData *obj;
  ParAct act;                   /* action called */
  ParAct action;                /* copy of act, but set to PAR_NOOP when name does not match */
  char *parName;
  int argc;
  char **argv;
  char *valueArg;
  char *thisPar;
  int returnValue;
  SConnection *con;
  char *fmt;
  int access;
  FILE *saveFile;
  int doit;
  char *listTail;
  char **enumList;
  char *logName;
  time_t now;
  ParInfo *par;
  float value;
  char combiName[80];
  char *grpFmt;
  int exact;
  char *callName;               /* the called name of the object (different from obj->name in case of an alias) */
  int enumText;                 /* show enum text */
} Context;

static char *loggerDir = NULL;
static int showTime = 0;
static void *initObj;           /* this object is in initialization mode */

static Context *ctx = NULL, *freeContexts = NULL;

void ParKill(void *object);

static ParClass parClass = { "Par", sizeof(ParData), 0, {&parClass}
};

/*----------------------------------------------------------------------------*/
void *ParCast(ParClass * class, void *object)
{
  ParClass *t;

  if (!object)
    return NULL;
  t = ((ParData *) object)->class;
  assert(t->base[0] == &parClass);
  if (t && t->base[class->level] == class) {
    return object;
  } else {
    return NULL;
  }
}

/*----------------------------------------------------------------------------*/
void *ParCheck(ParClass * class, void *object)
{
  ParClass *t;

  assert(object);
  t = ((ParData *) object)->class;
  assert(t->base[0] == &parClass);
  assert(t && t->base[class->level] == class);
  return object;
}

/*----------------------------------------------------------------------------*/
void *ParMakeClass(ParClass * class, ParClass * base)
{
  int i;

  if (class->base[0] == NULL) { /* initialize */
    if (base == NULL) {
      base = &parClass;
    }
    assert(class->size >= base->size);
    for (i = 0; i <= base->level; i++) {
      class->base[i] = base->base[i];
    }
    assert(i < PAR_LEVELS);
    class->base[i] = class;
    class->level = i;
  } else if (base) {            /* check */
    for (i = 0; i <= base->level; i++) {
      assert(class->base[i] == base->base[i]);
    }
    assert(class->base[i] == class);
    assert(class->level == i);
  }
  return class;
}

/*--------------------------------------------------------------------------*/
int ParPrintf(void *object, int iOut, const char *fmt, ...)
{
  va_list ap;
  char buf0[128];
  int l;
  char *buf, *dyn = NULL;
  SConnection *con;
  ParData *pobj = object;

  con = NULL;
  if (pobj) {
    if (ctx && pobj == ctx->obj && ctx->con) {
      con = ctx->con;
    } else {
      con = pobj->conn;
    }
  } else if (ctx) {
    con = ctx->con;
  }

  if (iOut < 0) {
    if (!con)
      return 0;                 /* no connection, no verbose output */
    if (-iOut > pobj->verbose)
      return 0;                 /* check verbosity level */
    iOut = eLog;
  }

  va_start(ap, fmt);
  l = vsnprintf(buf0, sizeof(buf0), fmt, ap);
  va_end(ap);
  if (l >= sizeof buf0) {
    /* we have probably a C99 conforming snprintf and need a larger buffer */
    dyn = malloc(l + 1);
    if (dyn != NULL) {
      va_start(ap, fmt);
      vsnprintf(dyn, l + 1, fmt, ap);
      va_end(ap);
    }
    buf = dyn;
  } else {
    buf = buf0;
  }

  if (con) {
    SCWrite(con, buf, iOut);    /* writes to command log when user or manager */
  } else if (iOut >= 0) {
    WriteToCommandLog(">", buf);        /* no connection, write to commandlog only */
  }

  if (dyn)
    free(dyn);
  return 1;
}

/*-------------------------------------------------------------------------*/
static void ParBegin(void)
{
  Context *p;

  if (freeContexts) {
    p = freeContexts;
    freeContexts = p->next;
  } else {
    p = calloc(1, sizeof(*ctx));
    assert(p);
  }
  p->next = ctx;
  ctx = p;
  ctx->callName = NULL;
}

/*-------------------------------------------------------------------------*/
static void ParEnd(void)
{
  Context *p;

  assert(ctx);
  p = ctx->next;
  ctx->next = freeContexts;
  freeContexts = ctx;
  ctx = p;
}

/*-------------------------------------------------------------------------*/
static void ParDo(SConnection * con, ParData * o, ParAct a, char *parName)
{
  ctx->act = a;
  ctx->action = a;
  ctx->con = con;
  if (parName && strcmp(parName, "*") == 0) {
    ctx->thisPar = "";
  } else {
    ctx->thisPar = parName;
  }
  ctx->returnValue = 0;
  ctx->par = NULL;
  ctx->grpFmt = NULL;
  ctx->obj = o;
  if (!ctx->callName) {
    ctx->callName = o->name;
  }
  o->pardef(o);
}

/*--------------------------------------------------------------------------*/
char *ParArg2Str(int argc, char *argv[], char *result, int maxsize)
{
  int i, argsize;
  char *p, *res;

  argsize = 0;
  if (result == NULL) {
    maxsize = INT_MAX;
    for (i = 0; i < argc; i++) {
      argsize += strlen(argv[i]) + 1;
    }
    if (argsize == 0) argsize = 1;
    result = malloc(argsize);
    if (!result)
      return NULL;
    res = result;
  } else {
    res = result;
    for (i = 0; i < argc; i++) {
      argsize += strlen(argv[i]) + 1;
      if (argsize > maxsize) {
        argc = i;
        res = NULL;
        break;
      }
    }
  }
  p = result;
  *p = '\0';
  for (i = 0; i < argc; i++) {
    if (i > 0) {
      *p = ' ';
      p++;
    }
    strcpy(p, argv[i]);
    p += strlen(argv[i]);
  }
  return res;
}

/*-------------------------------------------------------------------------*/
static int ParSaveAll(void *object, char *name, FILE * fil)
{
  ParData *o = ParCheck(&parClass, object);
  int ret;

  ParBegin();
  ctx->saveFile = fil;
  /*
   * Markus: why is this? This killed SICS unexpectedly once for me.
   * And it is not required: the code can work even if name and o->name
   * do not match.
   *
   * assert(0 == strcasecmp(o->name, name));
   */
  if (o->creationCmd) {
    fprintf(fil, "%s\n", o->creationCmd);
  }
  ParDo(0, o, PAR_SAVE, NULL);
  if (o->creationCmd) {
    fprintf(fil, "%s endinit\n\n", name);
  }
  ctx->saveFile = NULL;
  ret = ctx->returnValue;
  ParEnd();
  return ret;
}

/*--------------------------------------------------------------------------*/
FILE *ParSaveFile(void)
{
  if (ctx) {
    return ctx->saveFile;
  }
  return NULL;
}

/*--------------------------------------------------------------------------*/
int ParLog(void *object)
{
  ParData *o = ParCheck(&parClass, object);
  int next;

  if (o->desc == NULL) {
    free(o);
    return 0;
  }
  if (o->logPending == 2) {
    o->logPending = 0;
    return 0;
  }
  ParBegin();
  ctx->now = time(NULL);
  next = ctx->now - (o->logTime / o->period + 1) * o->period;
  if (next < 0) {
    ParEnd();
    return 1;
  }
  showTime = 1;
  ParDo(0, o, PAR_LOG, NULL);
  o->logTime = ctx->now;
  o->logPending = 0;
  ParEnd();
  return 0;
}

/*--------------------------------------------------------------------------*/
void ParLogForced(void *object)
{
  ParData *o = ParCheck(&parClass, object);
  int next;

  ParBegin();
  ctx->now = time(NULL);
  showTime = 1;
  ParDo(0, o, PAR_LOG, NULL);
  o->logTime = ctx->now;
  if (o->logPending) {
    o->logPending = 2;          /* tell ParLog that we are done already, but task is still pending */
  }
  ParEnd();
}

/*-------------------------------------------------------------------------*/
static int ParCallBack(int event, void *eventData, void *userData)
{
  char *pBuf = (char *) eventData;
  SConnection *con = (SConnection *) userData;

  /* check kill condition */
  if (con == NULL || !SCisConnected(con)) {
    return -1;
  }

  if (event == VALUECHANGE) {
    SCWrite(con, pBuf, eValue);
    return 1;
  }
  return 1;
}

/*----------------------------------------------------------------------------*/
static int ParOutError(SConnection * con, ParData * o)
{
  switch (ctx->returnValue) {
  case AMBIGUOS:
    SCPrintf(con, eError, "ERROR: doubly defined parameter %s.%s", o->name,
             ctx->thisPar);
    break;
  case ILLPRIV:
    SCPrintf(con, eError, "ERROR: insufficient privilege for %s.%s",
             o->name, ctx->thisPar);
    break;
  case ILLNUM:
    SCPrintf(con, eError, "ERROR: illegal value", o->name, ctx->valueArg);
    break;
  case ILLARGC:
    SCPrintf(con, eError, "ERROR: illegal number of arguments for %s %s",
             o->name, ctx->thisPar);
    break;
  case BADLOG:
    SCPrintf(con, eError, "ERROR: can not create log directory for %s %s",
             o->name, ctx->thisPar);
    break;
  case UNKPAR:
    SCPrintf(con, eError, "ERROR: %s %s is unknown", o->name,
             ctx->thisPar);
    break;
  case PARUNDEF:
    SCPrintf(con, eError, "ERROR: %s %s is undefined",
             o->name, ctx->thisPar);
    break;
  default:
    if (ctx->returnValue < 0) {
      return -1;
    } else {
      return 1;
    }
  }
  return -1;
}

/*----------------------------------------------------------------------------*/
static void ParListSugar(SConnection * con, ParData * o)
{
  ParInfo *p;
  char buf[512];
  int l, i;
  char *sugar;

  p = o->infoList;
  l = snprintf(buf, sizeof buf, "%s sugar =", o->name);
  while (p != NULL) {
    if (p->log) {
      sugar = LoggerName(p->log);
      if (strchr(sugar, '.') == NULL && strcmp(sugar, o->name) != 0) {
        i = snprintf(buf + l, sizeof buf - l, " %s", sugar);
        if (i <= 0)
          break;
        l += i;
        if (l >= sizeof buf - 1)
          break;
      }
    }
    p = p->next;
  }
  SCWrite(con, buf, eValue);
}

/*----------------------------------------------------------------------------*/
void ParSaveConn(void *object, SConnection * con)
{
  ParData *o = ParCheck(&parClass, object);
  int rights;

  rights = SCGetRights(con);
  if (rights >= usMugger && rights <= usUser && con->sockHandle >= 0) {
    if (o->conn != NULL) {
      if (o->conn->ident != con->ident) {
        SCDeleteConnection(o->conn);
        o->conn = SCCopyConnection(con);
      }
    } else {
      o->conn = SCCopyConnection(con);
    }
    SCsetMacro(o->conn, 0);
    if (o->conn->write != SCNormalWrite) {
      o->conn->write = SCNormalWrite;
    }
  }
}

/*----------------------------------------------------------------------------*/
static int ParExecute(SConnection * con, SicsInterp * sics, void *object,
                      int argc, char *argv[])
{
  static char *empty[1] = { "" };
  long id;
  int iret, logIt = 0;
  ParData *o = ParCheck(&parClass, object);
  char *setArgv[2];
  ParInfo *info;
  
  /* debugging
  int i;
  printf("ParExecute");
  for (i=0;i<argc;i++) {
    printf(" %s", argv[i]);
  }
  printf("\n");
  */
  if (argc >= 2 && 0 == strcasecmp(argv[1], "loggeditems")) {
    pDynString dyn = CreateDynString(124,128);
    for (info = o->infoList; info != NULL; info = info->next) {
      if (info->log && (argc==2 || strcasecmp(argv[2], info->name) == 0)) {
        DynStringConcat(dyn, " ");
        DynStringConcat(dyn, argv[0]);
        if (info->name[0] != '\0') {
          DynStringConcat(dyn, ".");
          DynStringConcat(dyn, info->name);
        }
      }
    }
    SCWrite(con, GetCharArray(dyn) + 1, eValue);
    DeleteDynString(dyn);
    return 1;
  }
  ParBegin();
  ctx->callName = argv[0];
  ParSaveConn(o, con);

  if (argc > 1)
    ctx->thisPar = argv[1];
  if (argc >= 2 && 0 == strcasecmp(argv[1], "list")) {
    ctx->argc = argc - 2;
    ctx->argv = argv + 2;
    ParDo(con, o, PAR_LIST, NULL);
    ParEnd();
    return 1;
  }
  if (argc == 1) {              /* no args */
    ctx->enumText = 0;
    ParDo(con, o, PAR_SHOW, "");
    if (ctx->returnValue == 0) {
      SCSendOK(con);
      ctx->returnValue = 1;
    }
  } else
      if ((0 == strcasecmp(argv[1], "log")
           || 0 == strcasecmp(argv[1], "unlog"))) {
    if (argc < 3) {
      ctx->returnValue = ILLARGC;
      ctx->thisPar = argv[1];
    } else {
      ctx->argc = argc - 3;
      ctx->argv = argv + 3;
      ctx->doit = toupper(argv[1][0]) != 'U';
      ctx->exact = 0;
      ParDo(con, o, PAR_LOGSWITCH, argv[2]);
    }
  } else
      if ((0 == strcasecmp(argv[1], "save")
           || 0 == strcasecmp(argv[1], "unsave"))) {
    if (argc != 3) {
      ctx->returnValue = ILLARGC;
    } else {
      ctx->doit = toupper(argv[1][0]) != 'U';
      ParDo(con, o, PAR_SAVESWITCH, argv[2]);
    }
    SCparChange(con);
  } else if (strcmp(argv[1], "interest") == 0) {
    if (!o->pCall) {
      o->pCall = CreateCallBackInterface();
    }
    assert(o->pCall);
    id = RegisterCallback(o->pCall, VALUECHANGE, ParCallBack,
                          SCCopyConnection(con), SCDeleteConnection);
    SCSendOK(con);
    ParEnd();
    return 1;
  } else if (strcmp(argv[1], "uninterest") == 0) {
    if (o->pCall) {
      RemoveCallbackCon(o->pCall, con);
    }
    SCSendOK(con);
    ParEnd();
    return 1;
  } else if (strcmp(argv[1], "sugar") == 0) {
    ParListSugar(con, o);
    ParEnd();
    return 1;
  } else if (strcmp(argv[1], "enumText") == 0) {
    if (argc == 3) {
      ctx->enumText = 1;
      ParDo(con, o, PAR_SHOW, argv[2]);
    }
  } else if (strcmp(argv[1], "endinit") == 0) {
    initObj = NULL;
    ParEnd();
    return 1;
  } else {
    if (strcmp(argv[1], "=") == 0) {
      ctx->argc = argc - 2;
      ctx->argv = argv + 2;
      ctx->valueArg = argv[2];
      ParDo(con, o, PAR_SET, "");
      logIt = 1;
    } else {
      if (argc == 2) {
        ctx->enumText = 0;
        ctx->argc = 0;
        ParDo(con, o, PAR_SHOW, argv[1]);
      } else {
        ctx->argc = argc - 2;
        ctx->argv = argv + 2;
        ctx->valueArg = argv[2];
        ParDo(con, o, PAR_SET, argv[1]);
        logIt = 1;
      }
      /* parameter not found, try to use args as set value for pure object
         not a very good idea: a typo in a parameter name will change pure value
         --> use always '='
         if (ctx->returnValue == 0) {
         ctx->argc = argc - 1;
         ctx->argv = argv + 1;
         errPar = ctx->thisPar;
         ParDo(con, o, PAR_SET, "");
         if (ctx->returnValue <= 0) {
         ctx->thisPar = errPar;
         ctx->returnValue = UNKPAR;
         }
         }
       */
    }
  }
  if (ctx->returnValue == 0) {
    ctx->returnValue = UNKPAR;
  }
  iret = ParOutError(con, o);
  if (logIt)
    ParLogForced(o);            /* log changes */
  ParEnd();
  return iret;
}

/*----------------------------------------------------------------------------*/
static void KillLogger(ParInfo * par)
{
  if (par->log != NULL) {
    LoggerKill(par->log);
    if (par->sugarStatus == 1) {
      RemoveCommand(pServ->pSics, LoggerName(par->log));
      par->sugarStatus = 0;
    }
    par->log = NULL;
  }
}

/*----------------------------------------------------------------------------*/
int ParSwitchLog(int on, char *name)
{
  char buf[80], alias[80];

  KillLogger(ctx->par);
  if (on) {
    if (ctx->par->name[0] == '\0') {
      snprintf(buf, sizeof buf, "%s", ctx->obj->name);
    } else {
      snprintf(buf, sizeof buf, "%s.%s", ctx->obj->name, ctx->par->name);
    }
    if (name == NULL) {
      name = buf;
    }
    if (loggerDir == NULL) {
      loggerDir = IFindOption(pSICSOptions, "LoggerDir");
      if (loggerDir == NULL)
        loggerDir = "./";
      LoggerSetDir(loggerDir);
    }
    ctx->par->log = LoggerMake(name, ctx->obj->period, ctx->exact);
    if (ctx->par->log == NULL) {
      return BADLOG;
    }
    if (ctx->par->sugarStatus == 0 && name != buf
        && strcmp(name, buf) != 0) {
      snprintf(alias, sizeof alias, "%s %s", ctx->obj->name,
               ctx->par->name);
      ctx->par->sugarStatus = SugarMake(name, alias);
    }
  }
  return 1;
}

/*--------------------------------------------------------------------------*/
void ParFind(void)
{
  ParInfo *p, **last, **endList;

  assert(ctx);
  if (ctx->par == NULL) {
    last = &ctx->obj->infoList;
    ctx->par = *last;
  } else {
    last = &ctx->par->next;
  }
  if (ctx->par) {
    /* start search after the actual parameter */
    p = ctx->par->next;
    while (p != NULL && 0 != strcmp(p->name, ctx->parName)) {
      p = p->next;
    }
  } else {
    p = NULL;
  }
  if (p == NULL) {              /* not found: search again from list head */
    p = ctx->obj->infoList;
    while (p != NULL && 0 != strcmp(p->name, ctx->parName)) {
      if (p == ctx->par) {
        p = NULL;
        break;
      }
      p = p->next;
    }
    if (p == NULL) {
      p = calloc(1, sizeof *p);
      if (p == NULL)
        return;
      p->name = strdup(ctx->parName);
      p->log = NULL;
      p->saveIt = 0;
      p->saveLog = 0;
      p->state = PAR_ALWAYS_READY;
      p->next = *last;
      *last = p;
    }
  }
  ctx->par = p;
}

/*--------------------------------------------------------------------------*/
long ParText2Int(char *text)
{
  long num = 0;

  if (strcasecmp(text, "undefined") == 0 || text[0] == '\0') {
    return PAR_LNAN;
  }
  if (ctx->enumList) {
    while (ctx->enumList[num] != NULL) {
      if (strcasecmp(ctx->enumList[num], text) == 0) {
        return num;
      }
      num++;
    }
  }
  return -1;
}

/*--------------------------------------------------------------------------*/
char *ParInt2Text(int num)
{
  int i;
  static char buf[12];

  if (num == PAR_LNAN) {
    return "";                  /* undefined */
  }
  if (!ctx->enumList)
    return NULL;
  for (i = 0; i <= num; i++) {
    if (ctx->enumList[i] == NULL) {
      return NULL;
    }
  }
  return ctx->enumList[num];
}

/*----------------------------------------------------------------------------*/
void ParGroup(char *groupFmt)
{
  ctx->grpFmt = groupFmt;
}

/*----------------------------------------------------------------------------*/
void ParName(char *name)
{

  if (ctx->grpFmt && *ctx->grpFmt) {
    snprintf(ctx->combiName, sizeof ctx->combiName, ctx->grpFmt, name);
    ctx->parName = ctx->combiName;
  } else {
    ctx->parName = name;
  }
  ParFind();
  ctx->fmt = NULL;
  ctx->action = ctx->act;

  assert(ctx);
  switch (ctx->act) {
  case PAR_SHOW:
  case PAR_SET:
    ctx->enumList = NULL;
    if (0 == strcasecmp(name, ctx->thisPar)) {
      ctx->access = -1;
    } else {
      ctx->action = PAR_NOOP;
    }
    return;
  case PAR_LIST:
    ctx->enumList = NULL;
    ctx->listTail = NULL;
    ctx->doit = -1;
    return;
  case PAR_LOG:
    return;
  case PAR_LOGSWITCH:
    if (0 == strcasecmp(name, ctx->thisPar)) {
      ctx->access = -1;
    } else {
      ctx->action = PAR_NOOP;
    }
    return;
  case PAR_SAVESWITCH:
    if (0 == strcasecmp(name, ctx->thisPar)) {
      ctx->access = -1;
    } else {
      ctx->action = PAR_NOOP;
    }
    return;
  case PAR_INIT:
    /* for save: use -1 as default value */
    ctx->access = -1;
    ctx->doit = -1;
    ctx->logName = "";          /* log by default */
    ctx->exact = 0;             /* not exact by default */
    return;
  case PAR_GET:
    if (0 != strcasecmp(name, ctx->thisPar)) {
      ctx->action = PAR_NOOP;
    }
    return;
  case PAR_SAVE:
  case PAR_KILL:
  case PAR_NOOP:
    return;
  }
}

/*----------------------------------------------------------------------------*/
static int RestoreMode(void)
{
  assert(ctx);
  if (initObj == ctx->obj) {
    return 1;
  }
  initObj = NULL;
  return 0;
}

/*----------------------------------------------------------------------------*/
ParOp ParWhat(int numeric)
{
  static char buf[80];
  char *sp, *lname;
  int i;
  ParOp op;
  int on;

  assert(ctx);
  switch (ctx->action) {
  case PAR_LIST:
    if (ctx->doit < 0) {
      if (ctx->listTail && ctx->argc == 0) {
        ctx->doit = 1;
      } else {
        ctx->doit = 0;
      }
    }
    if (ctx->doit
        || (ctx->argc > 0 && 0 == strcasecmp(ctx->argv[0], "all")))
      return FMT_OP;
    break;
  case PAR_LOG:
    if (ctx->par && ctx->par->log) {
      return FMT_OP;
    }
    break;
  case PAR_LOGSWITCH:
    if (ctx->argc > 1) {
      ctx->returnValue = ILLARGC;
      break;
    }
    if (ctx->returnValue) {
      ctx->returnValue = AMBIGUOS;
      break;
    }
    if (ctx->par) {
      ctx->returnValue = 1;
      if (ctx->argc > 0) {
        lname = ctx->argv[0];
      } else {
        lname = NULL;
      }
      ctx->par->saveLog = 1;
      ParSwitchLog(ctx->doit, lname);
      LoggerSetNumeric(ctx->par->log, numeric);
      SCparChange(ctx->con);
      return NO_OP;
    }
    break;
  case PAR_SAVESWITCH:
    if (ctx->argc > 1) {
      ctx->returnValue = ILLARGC;
      break;
    }
    if (ctx->returnValue) {
      ctx->returnValue = AMBIGUOS;
      break;
    }
    if (ctx->par) {
      ctx->returnValue = 1;
      ctx->par->saveIt = ctx->doit;
      return NO_OP;
    }
    break;
  case PAR_INIT:
    if (!RestoreMode()) {
      if (ctx->access < 0) {
        ctx->access = usInternal;
      }
      if (ctx->doit < 0) {
        if (ctx->access == usInternal) {
          ctx->doit = 0;
        } else {
          ctx->doit = 1;
        }
      }
      if (ctx->doit) {          /* set save flag */
        if (ctx->par) {
          ctx->par->saveIt = 1;
        }
      }
    }
    if (ctx->logName) {         /* set log */
      if (ctx->par) {
        if (ctx->logName[0] != '\0') {
          lname = ctx->logName;
        } else {
          lname = NULL;
        }
        ParSwitchLog(1, lname);
        LoggerSetNumeric(ctx->par->log, numeric);
      }
    }
    return INIT_OP;
  case PAR_SHOW:
    if (ctx->returnValue) {
      ctx->returnValue = AMBIGUOS;
      break;
    }
    ctx->returnValue = 1;
    return FMT_OP;
  case PAR_SET:
    if (ctx->returnValue) {
      ctx->returnValue = AMBIGUOS;
      break;
    }
    if (ctx->access < 0) {
      ctx->access = usInternal;
    }
    if (!RestoreMode() && ctx->access < SCGetRights(ctx->con)) {
      ctx->returnValue = ILLPRIV;
      break;
    }
    ctx->returnValue = 1;
    return SET_OP;
  case PAR_SAVE:
    if (ctx->parName[0] == '\0') {
      ctx->parName = "*";
    }
    if (ctx->par->saveLog) {
      if (ctx->par->log) {
        fprintf(ctx->saveFile, "%s log %s %s\n", ctx->obj->name,
                ctx->parName, LoggerName(ctx->par->log));
      } else {
        fprintf(ctx->saveFile, "%s unlog %s\n", ctx->obj->name,
                ctx->parName);
      }
    }
    if (ctx->par && ctx->par->saveIt == 1) {
      return FMT_OP;
    }
    break;
  case PAR_GET:
    if (ctx->returnValue) {
      ctx->returnValue = AMBIGUOS;
      break;
    }
    return GET_OP;
  default:
    return NO_OP;
  }
  ctx->action = PAR_NOOP;       /* for the result of ParActionIs */
  return NO_OP;
}

/*----------------------------------------------------------------------------*/
void ParOut(char *buf)
{
  int l, i, j, m, ln, lp, iret;
  char *p;
  char buffer[256];
  char *endp;
  char *pnam;
  char *sep;
  char *saved, *logged;
  time_t last;

  assert(ctx);
  assert(ctx->fmt == NULL);
  switch (ctx->action) {
  case PAR_LIST:
    /*
       if (ctx->par->log) {
       snprintf(buffer, sizeof buffer, "%s", LoggerName(ctx->par->log));
       } else {
     */
    if (ctx->parName[0] != '\0') {
      snprintf(buffer, sizeof buffer, "%s.%s", ctx->callName,
               ctx->parName);
    } else {
      snprintf(buffer, sizeof buffer, "%s", ctx->callName);
    }

    if (ctx->enumList) {
      i = strtol(buf, &endp, 0);
      if (endp != buf) {
        ctx->listTail = ParInt2Text(i); /* overwrite ctx->listTail */
      }
    }
    p = buf;
    /* find dot or end of number */
    while (*p == ' ')
      p++;                      /* skip blanks */
    i = 0;
    if (p[i] == '-')
      i++;
    j = i;
    while (p[i] >= '0' && p[i] <= '9')
      i++;
    l = strlen(p);
    ln = strlen(buffer) - strlen(ctx->callName);
    if (i != j && (buf[i] == '.' || buf[i] <= ' ')) {
      l += 16 - i - ln;         /* decimal point or end of number at least 16 chars after object name */
      lp = strlen(p);
      if (l < lp)
        l = lp;
      m = 23 - l - ln;          /* unit/comment at least 23 chars after object name */
    } else {                    /* non numeric value */
      l = 21 - ln;              /* text right justified if possible */
      /* m = 1; */
      m = 23 - l - ln;
    }
    if (m < 1)
      m = 1;
    if (ctx->listTail == NULL)
      ctx->listTail = "";
    m += strlen(ctx->listTail);
    if (l <= 0)
      l = 1;
    saved = "";
    logged = "";
    if (ctx->argc > 0 && 0 == strcasecmp(ctx->argv[0], "all")) {
      if (!ctx->par->log) {
        logged = " (not logged)";
      }
      if (ctx->par->saveIt == 1) {
        saved = " (saved)";
      } else if (ctx->par->saveIt == 2) {
        saved = " (saved by driver)";
      }
    }
    ParPrintf(NULL, eWarning, "%s %*s%*s%s%s", buffer, l, p, m,
              ctx->listTail, logged, saved);
    break;
  case PAR_SHOW:
    if (ctx->enumText) {
      i = strtol(buf, &endp, 0);
      if (endp != buf) {
        p = ParInt2Text(i);
      } else {
        p = NULL;
      }
      if (p == NULL)
        p = "";                 /* undefined */
      ParPrintf(NULL, eValue, "%s", p);
      break;
    }
    if (ctx->parName[0]) {
      p = " ";
    } else {
      p = "";
    }
    ParPrintf(NULL, eValue, "%s%s%s = %s", ctx->callName,
              p, ctx->parName, buf);
    break;
  case PAR_SET:
    if (ctx->parName[0]) {
      p = " ";
    } else {
      p = "";
    }
    ParPrintf(NULL, eValue, "%s%s%s = %s", ctx->callName, p, ctx->parName,
              buf);
    if (!ctx->obj->logPending) {
      ctx->obj->logPending = 1;
      TaskRegisterN(pServ->pTasker,"parlog", ParLog, NULL, NULL, ctx->obj, 0);    /* schedule ParLog */
    }
    break;
  case PAR_LOG:
    if (ctx->par->log) {
      if (ctx->par->state == PAR_NOW_READY) {
        ctx->par->state = PAR_NOT_READY;
        last = LoggerLastTime(ctx->par->log);
        if (last != 0 && ctx->now - ctx->obj->period > last) {
          LoggerWrite(ctx->par->log, ctx->now - ctx->obj->period,
                      ctx->obj->period, buf);
        }
      } else if (ctx->par->state != PAR_ALWAYS_READY) {
        break;
      }
      if (showTime && ctx->obj->pCall) {
        snprintf(buffer, sizeof buffer, "<time> = %ld", (long) ctx->now);
        InvokeCallBack(ctx->obj->pCall, VALUECHANGE, buffer);
        showTime = 0;
      }
      iret = LoggerWrite(ctx->par->log, ctx->now, ctx->obj->period, buf);
      if (iret && ctx->obj->pCall) {
        snprintf(buffer, sizeof buffer, "%s = %s",
                 LoggerName(ctx->par->log), buf);
        InvokeCallBack(ctx->obj->pCall, VALUECHANGE, buffer);
      }
    }
    break;
  case PAR_SAVE:
    if (strchr(buf, '\n') != NULL) {
      fprintf(ctx->saveFile, "%s %s {%s}\n", ctx->obj->name,
              ctx->parName, buf);
    } else {
      fprintf(ctx->saveFile, "%s %s %s\n", ctx->obj->name,
              ctx->parName, buf);
    }
    break;
  default:
    break;
  }
}

/*----------------------------------------------------------------------------*/
void ParLogReady(ReadyState state)
{
  if (ctx->action == PAR_LOG && ctx->par) {
    ctx->par->state = state;
  }
}

/*----------------------------------------------------------------------------*/
void ParFmt(char *format)
{
  ctx->fmt = format;            /* used for various actions */
}

/*----------------------------------------------------------------------------*/
void ParAccess(int accessRights)
{
  ctx->access = accessRights;   /* used for PAR_SET & PAR_INIT */
}

/*----------------------------------------------------------------------------*/
void ParEnum(char *list[])
{
  ctx->enumList = list;         /* used for various actions */
}

/*----------------------------------------------------------------------------*/
void ParSave(int saveIt)
{
  if (ctx->action == PAR_INIT) {
    ctx->doit = saveIt;
  } else if (ctx->action == PAR_SAVE && saveIt == 2 && ctx->par) {
    ctx->par->saveIt = 2;
  }
}

/*----------------------------------------------------------------------------*/
void ParList(char *group)
{
  if (ctx->action == PAR_LIST) {
    if (group == NULL && ctx->argc == 0) {
      ctx->doit = 1;
    } else if (ctx->argc > 0 && group != NULL
               && 0 == strcasecmp(ctx->argv[0], group)) {
      ctx->doit = 1;
    } else if (ctx->doit < 0) {
      ctx->doit = 0;
    }
  }
}

/*----------------------------------------------------------------------------*/
void ParTail(char *tail)
{
  if (ctx->action == PAR_LIST) {
    ctx->listTail = tail;
  }
}

/*----------------------------------------------------------------------------*/
void ParLogAs(char *name)
{
  if (ctx->action == PAR_INIT) {
    ctx->logName = name;
  }
}

/*----------------------------------------------------------------------------*/
void ParLogExact(int arg)
{
  ctx->exact = arg;
}

/*----------------------------------------------------------------------------*/
void ParHasChanged(void)
{
  if (ctx->par) {
    if (RestoreMode()) {
      ctx->par->saveIt = 1;
    };
    if (ctx->par->saveIt) {
      SCparChange(ctx->con);
    }
  }
}

/*----------------------------------------------------------------------------*/
void ParFloat(float *value, float defValue)
{
  char *endp;
  float f;
  char buf[132];

  switch (ParWhat(1)) {
  case SET_OP:
    if (ctx->argc > 1) {
      ctx->returnValue = ILLARGC;
      return;
    }
    if (ctx->valueArg) {
      f = ParText2Int(ctx->valueArg);
      if (f < 0) {
        f = strtod(ctx->valueArg, &endp);
        if (endp == ctx->valueArg) {
          ctx->returnValue = ILLNUM;
          break;
        }
      }
      *value = f;
    }
    ParHasChanged();
    /* fall through */
  case FMT_OP:
    if (*value == PAR_NAN) {
      ctx->fmt = NULL;
      /* ctx->returnValue = PARUNDEF; */
      ParOut("");
    } else {
      if (ctx->fmt == NULL) {
        ctx->fmt = "%.5g";
      }
      snprintf(buf, sizeof buf, ctx->fmt, *value);
      ctx->fmt = NULL;
      ParOut(buf);
    }
    break;
  case GET_OP:
    ctx->value = *value;
    ctx->returnValue = 1;
    break;
  case INIT_OP:
    *value = defValue;
    break;
  case NO_OP:
    break;
  }
}

/*----------------------------------------------------------------------------*/
void ParInt(int *value, int defValue)
{
  char *endp;
  int i;
  char buf[132];

  switch (ParWhat(1)) {
  case SET_OP:
    if (ctx->argc > 1) {
      ctx->returnValue = ILLARGC;
      return;
    }
    if (ctx->valueArg) {
      i = ParText2Int(ctx->valueArg);
      if (i < 0) {
        i = strtol(ctx->valueArg, &endp, 0);
        if (endp == ctx->valueArg) {
          ctx->returnValue = ILLNUM;
          break;
        }
      }
      *value = i;
    }
    ParHasChanged();
    /* fall through */
  case FMT_OP:
    if (*value == PAR_LNAN) {
      /* ctx->returnValue = PARUNDEF; */
      ParOut(""); /* undefined */
    } else {
      snprintf(buf, sizeof buf, "%d", *value);
      ParOut(buf);
    }
    break;
  case GET_OP:
    ctx->value = *value;
    ctx->returnValue = 1;
    break;
  case INIT_OP:
    *value = defValue;
    break;
  default:
    break;
  }
}

/*----------------------------------------------------------------------------*/
void ParStr(char **value, char *defValue)
{
  static char *empty = "";

  switch (ParWhat(0)) {
  case SET_OP:
    if (*value != NULL)
      free(*value);
    if (ctx->argc > 0 && strcmp(ctx->argv[0], "=") == 0) {
      *value = ParArg2Str(ctx->argc - 1, ctx->argv + 1, NULL, 0);
    } else {
      *value = ParArg2Str(ctx->argc, ctx->argv, NULL, 0);
    }
    ParHasChanged();
    /* fall through */
  case FMT_OP:
    if (*value == NULL)
      value = &empty;
    ParOut(*value);
    break;
  case INIT_OP:
    ctx->exact = 1;
    if (defValue != NULL) {
      *value = strdup(defValue);
    }
    break;
  default:
    if (ctx->act == PAR_KILL && *value != NULL) {
      free(*value);
      *value = NULL;
    }
    break;
  }
}

/*----------------------------------------------------------------------------*/
void ParFixedStr(char *value, int maxsize, char *defValue)
{

  switch (ParWhat(0)) {
  case SET_OP:
    ParArg2Str(ctx->argc, ctx->argv, value, maxsize);
    ParHasChanged();
    /* fall through */
  case FMT_OP:
    if (value == NULL)
      value = "";
    ParOut(value);
    break;
  case INIT_OP:
    ctx->exact = 1;
    if (defValue != NULL) {
      snprintf(value, maxsize, "%s", defValue);
    }
    break;
  default:
    break;
  }
}

/*----------------------------------------------------------------------------*/
int ParCmd(ParCommand cmd, void *userarg)
{
  int iret;

  if (ctx->action != PAR_SHOW && ctx->action != PAR_SET)
    return 0;
  if (ctx->access < 0)
    ctx->access = usUser;
  if (!RestoreMode() && ctx->access < SCGetRights(ctx->con)) {
    ctx->returnValue = ILLPRIV;
    ctx->action = PAR_NOOP;
    return 0;
  }
  iret = cmd(ctx->obj, userarg, ctx->argc, ctx->argv);
  if (iret == 0) {
    ctx->returnValue = ERRCMD;
    return -1;
  }
  ctx->returnValue = iret;
  return 1;
}

/*----------------------------------------------------------------------------*/
int ParActionIs(ParAct a)
{
  if (ctx->action == a)
    return 1;
  if (ctx->act == a)
    return -1;
  return 0;
}

/*----------------------------------------------------------------------------*/
char *ParGetValueArg()
{
  char *ret;
  if (ctx->action == PAR_SET) {
    ret = ctx->valueArg;
    ctx->valueArg = NULL;
    return ret;
  } else {
    return NULL;
  }
}

/*----------------------------------------------------------------------------*/
void *ParObject(void)
{
  return ctx->obj;
}

/*----------------------------------------------------------------------------*/
void ParStdDef(void)
{
  ParName("verbose");
  ParAccess(usUser);
  ParSave(0);
  ParLogAs(NULL);
  ParInt(&ctx->obj->verbose, 1);
  ParName("driver");
  ParLogAs(NULL);
  ParStr(&ctx->obj->desc->name, NULL);
  ParName("creationCmd");
  ParLogAs(NULL);
  ParStr(&ctx->obj->creationCmd, NULL);
}

/*----------------------------------------------------------------------------*/
void ParGetFloat(SConnection * con, void *object, char *name, float *value)
{
  ParData *o = ParCheck(&parClass, object);

  ParBegin();
  ParDo(con, o, PAR_GET, name);
  if (ctx->returnValue == 1) {
    *value = ctx->value;
  } else {
    if (ctx->returnValue == 0) {
      ctx->returnValue = UNKPAR;
    }
    if (con != NULL) {
      ParOutError(con, o);
    }
  }
  ParEnd();
}

/*----------------------------------------------------------------------------*/
void ParKill(void *object)
{
  ParData *o = ParCheck(&parClass, object);
  ParInfo *p, *q;

  ParBegin();
  ParDo(NULL, o, PAR_KILL, NULL);       /* free dynamic strings and custom content */
  if (o->name)
    free(o->name);
  if (o->creationCmd)
    free(o->creationCmd);
  if (o->pCall)
    DeleteCallBackInterface(o->pCall);
  if (o->desc)
    DeleteDescriptor(o->desc);
  if (o->conn)
    SCDeleteConnection(o->conn);
  p = o->infoList;
  while (p) {
    q = p->next;
    p->next = NULL;
    KillLogger(p);
    if (p->name) {
      free(p->name);
    }
    free(p);
    p = q;
  }
  if (o->logPending) {          /* will be free in scheduled ParLog function */
    o->desc = NULL;
  } else {
    free(o);
  }
  ParEnd();
  if (ctx == NULL && pServ->pSics->iDeleting) { /* finish, no more contexts nested -> free all of them */
    while (freeContexts) {
      ctx = freeContexts->next;
      free(freeContexts);
      freeContexts = ctx;
    }
    ctx = NULL;
  }
}

/*----------------------------------------------------------------------------*/
void ParInitPar(void *object, char *name)
{

  ParBegin();
  ctx->obj = object;
  ctx->parName = name;
  ctx->par = NULL;
  ParFind();
  ParSwitchLog(1, NULL);
  ParEnd();
}

/*----------------------------------------------------------------------------*/
void *ParMake(SConnection * con, char *name, ParClass * class,
              ParDef pardef, char *creationCmd)
{
  ParData *o;
  int i;

  assert(pardef);
  o = FindCommandData(pServ->pSics, name, class->name);
  if (o != NULL) {
    SCPrintf(con, eWarning, "overwrite '%s'", name);
    RemoveCommand(pServ->pSics, name);
  }
  o = calloc(1, class->size);
  assert(o);
  o->class = class;
  ParCheck(&parClass, o);
  if (!AddCommand(pServ->pSics, name, ParExecute, ParKill, o)) {
    SCPrintf(con, eError, "'%s' already exists as different object", name);
    free(o);
    return NULL;
  }
  o->name = strdup(name);
  assert(o->name);
  o->desc = CreateDescriptor(class->name);
  assert(o->desc);
  o->desc->SaveStatus = ParSaveAll;
  o->creationCmd = creationCmd;
  o->pCall = NULL;
  o->logTime = 0;
  o->period = 5;
  o->infoList = NULL;
  o->verbose = 0;
  o->logPending = 0;
  o->conn = NULL;
  ParSaveConn(o, con);
  o->pardef = pardef;
  ParBegin();
  ParDo(con, o, PAR_INIT, NULL);
  SCparChange(con);
  initObj = o;
  ParEnd();
  return o;
}

/*----------------------------------------------------------------------------*/
void ParStartup(void)
{
}
