/*--------------------------------------------------------------------------
  This is one implementation file for the TASMAD simulation module for
  SICS. The requirement is to make SICS look as much as TASMAD as 
  possible. This includes:
  - TASMAD is variable driven
  - Sometimes variables are accessed in storage order.
  - A complicated calculation has to be done for getting the instruments
    settings right. The appropriate F77 routine from TASMAD will be 
    reused.
  - The scan logic is different.
  - Output of ILL-formatted data files is required. 

  This file implements the initialization  part of the whole thing.

  Mark Koennecke, November 2000
---------------------------------------------------------------------------*/
#include <stdlib.h>
#include <assert.h>
#include <fortify.h>
#include <sics.h>
#include <sicsvar.h>
#include <counter.h>
#include <motor.h>
#include <scan.h>
#include <scan.i>
#include "tas.h"
#include "tasu.h"

extern int getSRO(SConnection * pCon, float *fVal);     /* tasutil.c */

/*
  As variables may be accessed in storage order, it is necessary to 
  know the order of the motors
*/
char *tasMotorOrder[MAXMOT + 1] = { "a1",
  "a2",
  "a3",
  "a4",
  "a5",
  "a6",
  "mcv",
  "sro",
  "ach",
  "mtl",
  "mtu",
  "stl",
  "stu",
  "stl",
  "atu",
  "mgl",
  "sgl",
  "sgu",
  "agl",
  "atl",
  "tt",
  "i1",
  "i2",
  "i3",
  "i4",
  "hxx",
  "hyy",
  "hzz",
  "i5",
  "i6",
  "i7",
  "i8",
  "mf",
  "tem",
  NULL
};

/*
  In order to initialise the variable array in the TAS data structure we
  need to know the names of the variables in the right order. This order
  has to match the order defined in tas.h through the defines. Otherwise 
  quite weird things may happen at runtime.
*/
char *tasVariableOrder[MAXPAR + 1] = {
  "wav",
  "dm",
  "da",
  "etam",
  "etaa",
  "ti",
  "mn",
  "if1v",
  "if1h",
  "if2v",
  "if2h",
  "helm",
  "as",
  "bs",
  "cs",
  "aa",
  "bb",
  "cc",
  "etas",
  "ax",
  "ay",
  "az",
  "bx",
  "by",
  "bz",
  "ei",
  "ki",
  "ef",
  "kf",
  "qh",
  "qk",
  "ql",
  "en",
  "qm",
  "hx",
  "hy",
  "hz",
  "da1",
  "da2",
  "da3",
  "da4",
  "da5",
  "da6",
  "dmcv",
  "dsro",
  "dach",
  "dmtl",
  "dmtu",
  "dstl",
  "dstu",
  "datl",
  "datu",
  "dmgl",
  "dsgl",
  "dsgu",
  "dagl",
  "dei",
  "dki",
  "def",
  "dkf",
  "dqh",
  "dqk",
  "dql",
  "den",
  "dqm",
  "dtt",
  "sm",
  "ss",
  "sa",
  "fx",
  "np",
  "f1",
  "f2",
  "lpa",
  "mrx1",
  "mrx2",
  "arx1",
  "arx2",
  "instrument",
  "title",
  "user",
  "lastcommand",
  "alf1",
  "alf2",
  "alf3",
  "alf4",
  "bet1",
  "bet2",
  "bet3",
  "bet4",
  "output",
  "local",
  "swunit",
  "scaninfo",
  "tei",
  "tki",
  "tef",
  "tkf",
  "tqh",
  "tqk",
  "tql",
  "ten",
  "tqm",
  "ti1",
  "ti2",
  "ti3",
  "ti4",
  "ti5",
  "ti6",
  "ti7",
  "ti8",
  "di1",
  "di2",
  "di3",
  "di4",
  "di5",
  "di6",
  "di7",
  "di8",
  "dhx",
  "dhy",
  "dhz",
  "thx",
  "thy",
  "thz",
  "hconv1",
  "hconv2",
  "hconv3",
  "hconv4",
  "polfile",
  "pix",
  "piy",
  "piz",
  "pfx",
  "pfy",
  "pfz",
  "dmf",
  "dtem",
  NULL
};

/*-------------------------------------------------------------------
  Normally SICS does not store motor hardware limits into status files as 
  they are read from the motor controller. However, in order to better 
  synchronize simulation and instrument this has to be done for TASP.
  TasSave does just that: saving hard limits in order for the simulation to
  be able to read them.
  ------------------------------------------------------------------------*/
static int TasSaveStatus(void *self, char *name, FILE * fd)
{
  int i = 0, iscat;
  pMotor pMot;
  float value;
  SConnection *pCon = NULL;
  pSicsVariable pScat = NULL; 

  pCon = SCCreateDummyConnection(pServ->pSics);
  if (!pCon) {
    return 0;
  }
  if (strcmp(name, "dr") != 0) {        /* save only once (for dr, but not for sc,fs,updateqe) */
    return 1;
  }
  while (tasMotorOrder[i] != NULL) {
    pMot = FindMotor(pServ->pSics, tasMotorOrder[i]);
    if (pMot) {
      MotorGetPar(pMot, "hardupperlim", &value);
      fprintf(fd, "catch {%s hardupperlim %f}\n", tasMotorOrder[i], value);
      MotorGetPar(pMot, "hardlowerlim", &value);
      fprintf(fd, "catch {%s hardlowerlim %f}\n", tasMotorOrder[i], value);
      /*
         DISABLED: reading all the motors made to much of a delay during
         normal operation of the instrument. This is mainly due  to the 
         sloooooooooowwwwwwwww SINQ hardware
         MotorGetSoftPosition(pMot,pCon,&value);
         fprintf(fd,"run %s %f\n",tasMotorOrder[i], value);
       */

    }
    i++;
  }
  pScat = (pSicsVariable)FindCommandData(pServ->pSics,"SS","SicsVariable");
  if(pScat != NULL){
    VarGetInt(pScat,&iscat);
    fprintf(fd,"catch {scatfix ss %d}\n", iscat);
  }
  pScat = (pSicsVariable)FindCommandData(pServ->pSics,"SA","SicsVariable");
  if(pScat != NULL){
    VarGetInt(pScat,&iscat);
    fprintf(fd,"catch {scatfix sa %d}\n", iscat);
  }

  SCDeleteConnection(pCon);
  /*
     fprintf(fd,"success\n");
   */

  fprintf(fd, "updateqe\n");
  return 1;
}

/*---------------------------------------------------------------------
  There is a special feauture in MAD where the count mode is determined
  which variable, MN for monitor od TI for time has been set as the last
  one. In order to implement this in SICS callback functions will be 
  used on the variables which switch the counter box into the appropriate 
  mode.
-----------------------------------------------------------------------*/
static int MonitorCallback(int iEvent, void *pEvent, void *pUser)
{
  pTASdata self = (pTASdata) pUser;
  assert(self);

  if (iEvent != VALUECHANGE)
    return 0;

  SetCounterMode(self->pScan->pCounterData, ePreset);
  SetCounterPreset(self->pScan->pCounterData,
                   (float) self->tasPar[MN]->iVal);
  return 1;
}

/*---------------------------------------------------------------------*/
static int TimerCallback(int iEvent, void *pEvent, void *pUser)
{
  pTASdata self = (pTASdata) pUser;
  assert(self);

  if (iEvent != VALUECHANGE)
    return 0;

  SetCounterMode(self->pScan->pCounterData, eTimer);
  SetCounterPreset(self->pScan->pCounterData, self->tasPar[TI]->fVal);
  return 1;
}

/*-----------------------------------------------------------------------
  This is an interpreter wrapper function which allows to call for the
  recalculation of the energy variables from scripts.
--------------------------------------------------------------------------*/
extern int TASUpdate(pTASdata self, SConnection * pCon);        /* tasutil.c */

static int RecalcAction(SConnection * pCon, SicsInterp * pSics,
                        void *pData, int argc, char *argv[])
{
  pTASdata self = NULL;

  assert(pCon);
  assert(pSics);
  self = (pTASdata) pData;
  assert(self);

  return TASUpdate(self, pCon);
}

/*--------------------------------------------------------------------------
 when A6 has been drive Bertrand wants to wait for some seconds in order
 to allow for the analyzer shielding to settle down. This is done
 through this callback function
 ---------------------------------------------------------------------------*/
static int A6WaitCallback(int iEvent, void *pEventData, void *pUserData)
{
  if (iEvent == MOTEND) {
    SicsWait(5);
  }
  return 1;
}

/*-----------------------------------------------------------------------
  A function for killing the TAS data structure is needed
  -------------------------------------------------------------------------*/
static void TASKill(void *pData)
{
  pTASdata self = (pTASdata) pData;
  if (!self)
    return;

  if (self->pDes)
    DeleteDescriptor(self->pDes);
  free(self);
}

/*-----------------------------------------------------------------------*/
int TASFactory(SConnection * pCon, SicsInterp * pSics, void *pData,
               int argc, char *argv[])
{
  pTASdata pNew = NULL;
  int iPtr, iError;
  char pBueffel[132];
  pSicsVariable pVar = NULL;
  CommandList *pCom = NULL;
  pMotor pMot = NULL;
  commandContext comCon;

  /* check arguments */
  if (argc < 2) {
    SCWrite(pCon, "ERROR: insufficient paarameters to MakeTAS", eError);
    return 0;
  }

  /* create a new data structure */
  pNew = (pTASdata) malloc(sizeof(TASdata));
  if (!pNew) {
    SCWrite(pCon,
            "ERROR: insufficient memory to allocate TAS data structure",
            eError);
    return 0;
  }
  memset(pNew, 0, sizeof(TASdata));
  pNew->pDes = CreateDescriptor("TAS");
  if (!pNew->pDes) {
    SCWrite(pCon,
            "ERROR: insufficient memory to allocate TAS data structure",
            eError);
    free(pNew);
    return 0;
  }
  pNew->iPOL = -1;
  pNew->pDes->SaveStatus = TasSaveStatus;

  /* connect to all the variables  */
  iPtr = 0;
  iError = 0;
  while (tasVariableOrder[iPtr] != NULL) {
    pNew->tasPar[iPtr] = FindVariable(pSics, tasVariableOrder[iPtr]);
    if (!pNew->tasPar[iPtr]) {
      sprintf(pBueffel, "ERROR: TAS variable %s not found",
              tasVariableOrder[iPtr]);
      SCWrite(pCon, pBueffel, eError);
      iError++;
    }
    iPtr++;
  }
  if (iError != 0) {
    SCWrite(pCon, "ERROR: TAS bad initialisation, TAS not created",
            eError);
    TASKill(pNew);
    return 0;
  }

  /* connect to the scan object */
  pCom = FindCommand(pSics, argv[1]);
  if (!pCom) {
    SCWrite(pCon, "ERROR: no scan routine for TAS found", eError);
    TASKill(pNew);
    return 0;
  }
  pNew->pScan = (pScanData) pCom->pData;

  /* 
     Install the callbacks for TI and MN. Sloppy error checking because
     the variables should have been accessed earlier on.
   */
  pVar = FindVariable(pSics, "MN");
  if (pVar) {
    RegisterCallback(pVar->pCall, VALUECHANGE, MonitorCallback, pNew,
                     NULL);
  }
  pVar = FindVariable(pSics, "TI");
  if (pVar) {
    RegisterCallback(pVar->pCall, VALUECHANGE, TimerCallback, pNew, NULL);
  }

  /*
     Install the wait callback for A6
   */
  pMot = FindMotor(pSics, "a6");
  if (pMot != NULL) {
    RegisterCallback(pMot->pCall, MOTEND, A6WaitCallback, NULL, NULL);
  }


  /*
     initialize SRO value
   */
  getSRO(pCon, &pNew->oldSRO);

  /* install TAS commands */
  iError = AddCommand(pSics, "dr", TASDrive, TASKill, pNew);
  if (!iError) {
    SCWrite(pCon, "ERROR: duplicate dr command not created", eError);
    TASKill(pNew);
    return 0;
  }
  iError = AddCommand(pSics, "sc", TASScan, NULL, pNew);
  if (!iError) {
    SCWrite(pCon, "ERROR: duplicate sc command not created", eError);
    TASKill(pNew);
    return 0;
  }
  iError = AddCommand(pSics, "fs", TASScan, NULL, pNew);
  if (!iError) {
    SCWrite(pCon, "ERROR: duplicate sf command not created", eError);
    TASKill(pNew);
    return 0;
  }
  iError = AddCommand(pSics, "updateqe", RecalcAction, NULL, pNew);
  if (!iError) {
    SCWrite(pCon, "ERROR: duplicate updateqe command not created", eError);
    TASKill(pNew);
    return 0;
  }
  return 1;
}
