/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* This file is distributed subject to a Software License Agreement found
* in the file LICENSE that is included with this distribution. 
\*************************************************************************/

/*
 * $Log: not supported by cvs2svn $
 * Revision 1.6  2000/02/02 21:14:11  soliday
 * Added support for IsReadOnly column.
 *
 * Revision 1.5  1999/07/28 15:05:57  soliday
 * Added some code to free up StringDataArray elements when StringDataCopy is used.
 *
 * Revision 1.4  1999/07/28 14:28:59  soliday
 * Fixed bug by commenting out a free(stringDataCopy) that was causing the
 * program to crash
 *
 * Revision 1.3  1999/07/27 19:14:48  soliday
 * Modified borland's fix to use Tcl_Alloc instead of SDDS_Malloc to be consistant.
 *
 * Revision 1.2  1999/07/27 14:31:55  borland
 * Temporary fix for problem of value strings that contain spaces.  They
 * need to be enclosed in double-quotes for writign to the file.
 *
 * Revision 1.1  1999/06/10 19:47:04  soliday
 * Moved to oag tree.
 *
 * Revision 1.5  1999/06/09 21:42:50  soliday
 * It now creates a CAError column in the snapshot files.
 *
 * Revision 1.4  1999/06/08 22:14:10  soliday
 * Removed compiler warnings under Linux.
 *
 * Revision 1.3  1999/05/17 18:57:40  soliday
 * Improved some error messages
 *
 * Revision 1.2  1999/05/17 18:28:52  soliday
 * lost connections no longer force an error message
 *
 * Revision 1.1  1999/04/07 16:10:28  soliday
 * Initial version of the snapshot shared library
 *
 * */

#include <stdlib.h>
#include <time.h>
#include <ellLib.h>
#include "mdb.h"
#include "scan.h"
#include <tclSnapshot.h>

static SDDS_FD sddsFdTable[MAX_OPEN_SDDS_FILES];

static short FDINIT;
static ELLLIST itemList;


/*****************************/
/* Local Function Prototypes */
/*****************************/
static int tclSnapshotGetFreeFd();
void exceptionHandler(struct exception_handler_args arg);
void registerFD(void *dummy, int fd, int condition);
void epicsFdMonitor(void *dummy, int mask);
static void pendRecycleProc(ClientData clientData);
void connectionHandler(struct connection_handler_args args);
void updateChCtrl(struct event_handler_args args);
static long convertTypeStringToNumeric(char *type);


/******************************************
 *
 *****************************************/
int tclSnapshotInit(Tcl_Interp *interp) {
  int i;

  for (i=0 ; i < MAX_OPEN_SDDS_FILES ; i++) {
    sddsFdTable[i].free = 1;
    sddsFdTable[i].currentPage = 0;
    sddsFdTable[i].fileName[0] = '\0';
    sddsFdTable[i].mode = 'r';
    sddsFdTable[i].pageStarted = 0;
  }
  return TCL_OK;
}

/******************************************
 *
 *****************************************/
static int tclSnapshotGetFreeFd() {
  int i = 0;
  while (i < MAX_OPEN_SDDS_FILES) {
    if (sddsFdTable[i].free == 1) 
      break;
    i++;
  }
  if (i < MAX_OPEN_SDDS_FILES) 
    return i;
  else 
    return -1;
}


/******************************************
 *
 *****************************************/
int tclSnapshotOpen(Tcl_Interp *interp, char *fileName, char mode) {
  char **sddsErrs;
  long sddsNumErrs;
  int fd;
  long dataMode = SDDS_BINARY;
  
  if ((fd = tclSnapshotGetFreeFd()) == -1) {
    Tcl_AppendResult(interp, "snapshot write:tclSnapshotOpen: too many SDDS files open", NULL);
    return -1;
  }

  sddsFdTable[fd].mode = mode;
  if (mode == 'r') {
    if (!SDDS_InitializeInput(&(sddsFdTable[fd].table), fileName)) {
      sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
      Tcl_AppendResult(interp, "snapshot write:tclSnapshotOpen: ", sddsErrs[0], NULL);
      SDDS_ClearErrors();
      free(sddsErrs[0]);
      return -1;
    }
  } else {  /* mode == 'w' */
    if (!SDDS_InitializeOutput(&(sddsFdTable[fd].table), dataMode, 1, NULL, NULL, fileName)) {
      sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
      Tcl_AppendResult(interp, "sdds open:tclSnapshotOpen: ", sddsErrs[0], NULL);
      SDDS_ClearErrors();
      free(sddsErrs[0]);
      return -1;
    }
  }
  strcpy(sddsFdTable[fd].fileName,fileName);
  sddsFdTable[fd].free = 0;

  return fd;
}

/*****************************************
 *
 *****************************************/
int tclSnapshotClose(Tcl_Interp *interp, int fd) {
  char **sddsErrs;
  long sddsNumErrs;

  if ((fd > MAX_OPEN_SDDS_FILES) || (sddsFdTable[fd].free == 1)) { 
    Tcl_AppendResult(interp, "snapshot write:tclSnapshotClose: unknown file descriptor", NULL);
    return -1;
  }
  if (!SDDS_Terminate(&(sddsFdTable[fd].table))) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot write:tclSnapshotClose: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    return -1;
  }
  sddsFdTable[fd].free = 1;
  sddsFdTable[fd].currentPage = 0;
  return 1;
}

/******************************************
 *
 *****************************************/
int tclSnapshotReadRequest(Tcl_Interp *interp, int argc, char *argv[]) {
  char *input, *columnName;
  int fdInput;
  char **sddsErrs;
  long sddsNumErrs;
  long columnType, nrows, i, tableRead;
  char **stringData;
  Tcl_DString result;

  if (argc!=3) {
    Tcl_SetResult(interp, "usage: snapshot readRequest <request file>", NULL);
    return TCL_ERROR;
  }

  input = argv[2];
  columnName = "ControlName";
  /* open request file */
  if ((fdInput = tclSnapshotOpen(interp, input, 'r')) == -1) {
    return TCL_ERROR;
  }

  /* read first page */
  if ((tableRead = SDDS_ReadTable(&(sddsFdTable[fdInput].table))) <= 0) {
    if (tableRead == -1) {
      Tcl_SetResult(interp, "EOF", NULL);
      tclSnapshotClose(interp, fdInput);
      return TCL_ERROR;
    } else {
      sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
      Tcl_AppendResult(interp, "snapshot readRequest: ", sddsErrs[0], NULL);
      SDDS_ClearErrors();
      free(sddsErrs[0]);
      tclSnapshotClose(interp, fdInput);
      return TCL_ERROR;	
    }
  }

  /* get column type */
  if (SDDS_GetColumnInformation(&(sddsFdTable[fdInput].table), "type", 
				&columnType,SDDS_GET_BY_NAME, columnName) 
      == 0) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot readRequest: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    tclSnapshotClose(interp, fdInput);
    return TCL_ERROR;
  }

  /* count rows */
  if ((nrows = SDDS_RowCount(&(sddsFdTable[fdInput].table))) == -1) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot readRequest: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    tclSnapshotClose(interp, fdInput);
    return TCL_ERROR;
  }

  Tcl_DStringInit(&result);

  switch (columnType) {
  case SDDS_STRING: {
    if ((stringData = SDDS_GetColumn(&(sddsFdTable[fdInput].table), columnName))
	== NULL) {
      sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
      Tcl_AppendResult(interp, "snapshot readRequest: ", sddsErrs[0], NULL);
      SDDS_ClearErrors();
      free(sddsErrs[0]);
      Tcl_DStringFree(&result);
      tclSnapshotClose(interp, fdInput);
      return TCL_ERROR;
    }
    for (i=0 ; i < nrows ; i++)
      Tcl_DStringAppendElement(&result,stringData[i]);
    
    for (i=0 ; i < nrows ; i++)
      free(stringData[i]);
    free(stringData);
    break;
  }
  default: {
    Tcl_AppendResult(interp, "snapshot readRequest: ", columnName, " must be string type", NULL);
    Tcl_DStringFree(&result);
    tclSnapshotClose(interp, fdInput);
    return TCL_ERROR;    
  }}

  tclSnapshotClose(interp, fdInput);
  Tcl_DStringResult(interp, &result);
  return TCL_OK;
}

/******************************************
 *
 *****************************************/
int tclSnapshotReadSnapshot(Tcl_Interp *interp, int argc, char *argv[]) {
  char *input, *columnName, *columnName2, *controlNameVar, *valueStringVar, *namesNotStored;
  int fdInput;
  char **sddsErrs, *CAErrorName, *IsReadOnlyName, *charData, *charData2;
  long sddsNumErrs;
  long columnType, columnType2, nrows, i, tableRead;
  char **stringData, **stringData2;
  Tcl_Obj *listObj, *listObj2, *listObj3, *obj, *listName, *listName2, *listName3;


  if (argc!=6) {
    Tcl_SetResult(interp, "usage: snapshot readSnapshot <snapshot file> <ControlName var> <ValueString var> <Names not stored var>", NULL);
    return TCL_ERROR;    
  }

  input = argv[2];
  controlNameVar = argv[3];
  valueStringVar = argv[4];
  namesNotStored = argv[5];
  columnName = "ControlName";
  columnName2 = "ValueString";
  /* open request file */
  if ((fdInput = tclSnapshotOpen(interp, input, 'r')) == -1) {
    return TCL_ERROR;
  }

  /* read first page */
  if ((tableRead = SDDS_ReadTable(&(sddsFdTable[fdInput].table))) <= 0) {
    if (tableRead == -1) {
      Tcl_SetResult(interp, "EOF", NULL);
      tclSnapshotClose(interp, fdInput);
      return TCL_ERROR;
    } else {
      sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
      Tcl_AppendResult(interp, "snapshot readSnapshot: ", sddsErrs[0], NULL);
      SDDS_ClearErrors();
      free(sddsErrs[0]);
      tclSnapshotClose(interp, fdInput);
      return TCL_ERROR;	
    }
  }

  /* get column types */
  if (!(CAErrorName = 
	SDDS_FindColumn(&(sddsFdTable[fdInput].table), FIND_SPECIFIED_TYPE,
			SDDS_CHARACTER, "CAError", NULL))) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot readSnapshot: CAError column is missing or not character type", NULL);
    SDDS_ClearErrors();
    tclSnapshotClose(interp, fdInput);
    return TCL_ERROR;
  }
  if (!(IsReadOnlyName = 
	SDDS_FindColumn(&(sddsFdTable[fdInput].table), FIND_SPECIFIED_TYPE,
			SDDS_CHARACTER, "IsReadOnly", NULL))) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot readSnapshot: IsReadOnly column is missing or not character type", NULL);
    SDDS_ClearErrors();
    tclSnapshotClose(interp, fdInput);
    return TCL_ERROR;
  }

  if (SDDS_GetColumnInformation(&(sddsFdTable[fdInput].table), "type", 
				&columnType,SDDS_GET_BY_NAME, columnName) 
      == 0) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot readSnapshot: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    tclSnapshotClose(interp, fdInput);
    return TCL_ERROR;
  }
  if (SDDS_GetColumnInformation(&(sddsFdTable[fdInput].table), "type", 
				&columnType2,SDDS_GET_BY_NAME, columnName2) 
      == 0) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot readSnapshot: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    tclSnapshotClose(interp, fdInput);
    return TCL_ERROR;
  }

  /* count rows */
  if ((nrows = SDDS_RowCount(&(sddsFdTable[fdInput].table))) == -1) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot readSnapshot: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    tclSnapshotClose(interp, fdInput);
    return TCL_ERROR;
  }

  if ((columnType != SDDS_STRING) || (columnType2 != SDDS_STRING)) {
    Tcl_AppendResult(interp, "snapshot readSnapshot: ", columnName, " and ", columnName2, " must be string type", NULL);
    tclSnapshotClose(interp, fdInput);
    return TCL_ERROR;    
  }    
  if ((stringData = SDDS_GetColumn(&(sddsFdTable[fdInput].table), columnName))
      == NULL) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot readSnapshot: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    tclSnapshotClose(interp, fdInput);
    return TCL_ERROR;
  }
  if ((stringData2 = SDDS_GetColumn(&(sddsFdTable[fdInput].table), columnName2))
      == NULL) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot readSnapshot: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    tclSnapshotClose(interp, fdInput);
    for (i=0 ; i < nrows ; i++)
      free(stringData[i]);
    free(stringData);
    return TCL_ERROR;
  }

  if ((charData = SDDS_GetColumn(&(sddsFdTable[fdInput].table), CAErrorName))
      == NULL) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot readSnapshot: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    tclSnapshotClose(interp, fdInput);
    for (i=0 ; i < nrows ; i++) {
      free(stringData[i]);
      free(stringData2[i]);
    }
    free(stringData);
    free(stringData2);
    return TCL_ERROR;
  }
  if ((charData2 = SDDS_GetColumn(&(sddsFdTable[fdInput].table), IsReadOnlyName))
      == NULL) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot readSnapshot: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    tclSnapshotClose(interp, fdInput);
    for (i=0 ; i < nrows ; i++) {
      free(stringData[i]);
      free(stringData2[i]);
    }
    free(charData);
    free(stringData);
    free(stringData2);
    return TCL_ERROR;
  }
  tclSnapshotClose(interp, fdInput);

  listObj = Tcl_NewListObj(0, NULL);
  listName = Tcl_NewObj();
  listObj2 = Tcl_NewListObj(0, NULL);
  listName2 = Tcl_NewObj();
  listObj3 = Tcl_NewListObj(0, NULL);
  listName3 = Tcl_NewObj();
  Tcl_SetStringObj(listName, controlNameVar, -1);
  Tcl_SetStringObj(listName2, valueStringVar, -1);
  Tcl_SetStringObj(listName3, namesNotStored, -1);
  for (i=0; i < nrows; i++) {
    if (charData2[i] == 'n') {
      if (charData[i] == 'n') {
	obj = Tcl_NewObj();
	Tcl_SetStringObj(obj, stringData[i], strlen(stringData[i]));
	Tcl_ListObjAppendElement(interp, listObj, obj);
	obj = Tcl_NewObj();
	Tcl_SetStringObj(obj, stringData2[i], strlen(stringData2[i]));
	Tcl_ListObjAppendElement(interp, listObj2, obj);
      } else {
	obj = Tcl_NewObj();
	Tcl_SetStringObj(obj, stringData[i], strlen(stringData[i]));
	Tcl_ListObjAppendElement(interp, listObj3, obj);
      }
    }
  }
  
  free(charData);
  free(charData2);

  Tcl_ObjSetVar2(interp, listName, NULL, listObj, TCL_LEAVE_ERR_MSG);
  Tcl_ObjSetVar2(interp, listName2, NULL, listObj2, TCL_LEAVE_ERR_MSG);
  Tcl_ObjSetVar2(interp, listName3, NULL, listObj3, TCL_LEAVE_ERR_MSG);
  for (i=0 ; i < nrows ; i++) {
    free(stringData[i]);
    free(stringData2[i]);
  }
  free(stringData);
  free(stringData2);
  
  return TCL_OK;
}

/******************************************
 *
 *****************************************/
int tclSnapshotLinkPVs(Tcl_Interp *interp, int argc, char *argv[]) {
  char *errNameVar;
  float pendioTime;
  int status, i, listArgcpv, index, newPv, nBytes;
  char **listArgvpv;
  ITEM *pitem;
  CHANDATA *pchandata;
  ITEMNODE *itemNode;
  Tcl_Obj *listObj, *obj, *listName;


  if (argc!=5) {
    Tcl_SetResult(interp, "usage: snapshot linkPVs <PV list> <timeout> <error name var>", NULL);
    return TCL_ERROR;    
  }
  if ((pendioTime = atof(argv[3])) == 0) {
    Tcl_SetResult(interp, "snapshot linkPVs: timeout must be floating point number", NULL);
    return TCL_ERROR;    
  }
  errNameVar = argv[4];

  if (Tcl_SplitList(interp,argv[2], &listArgcpv, &listArgvpv ) != TCL_OK) {
    Tcl_SetResult(interp,"snapshot linkPVs: Unable to split the PV list: ",NULL);
    return TCL_ERROR;
  }

  if (FDINIT != 1) {
    FDINIT = 1;
    status = ca_task_initialize();
    if (status != ECA_NORMAL) {
      Tcl_SetResult(interp,"snapshot linkPVs:ca initialize: ",NULL);
      Tcl_Free((char *) listArgvpv);
      return TCL_ERROR;
    }

    status = ca_add_exception_event(exceptionHandler,NULL);
    if (status != ECA_NORMAL) {
      Tcl_SetResult(interp,"snapshot linkPVs:ca add_exception_event: ",NULL);
      Tcl_Free((char *) listArgvpv);
      return TCL_ERROR;
    }

    status = ca_add_fd_registration(registerFD, NULL); 
    if (status != ECA_NORMAL) {
      Tcl_SetResult(interp,"snapshot linkPVs:FD Registration: ",NULL);
      Tcl_Free((char *) listArgvpv);
      return TCL_ERROR;
    }
    status = ca_pend_io(pendioTime);
    if (status != ECA_NORMAL) {
      Tcl_SetResult(interp,"snapshot linkPVs:FD Registration: ",NULL);
      Tcl_Free((char *) listArgvpv);
      return TCL_ERROR;
    }
#if (TCL_MAJOR_VERSION >= 7) && (TCL_MINOR_VERSION >= 5)
    Tcl_CreateTimerHandler(100,pendRecycleProc,NULL);
#else
    Tk_CreateTimerHandler(100,pendRecycleProc,NULL);
#endif
    for (i=0 ; i<4096 ; i++) 
      vdisdat[i].x = i;
  }
  
  ellInit(&itemList);
  
  index = 0;
  pitem = NULL;
  while (index < listArgcpv) {
    pitem = (ITEM *)pv_search(listArgvpv[index]);
    if (pitem == NULL) {
      Tcl_AppendResult(interp,"snapshot linkPVs:HASH:error: ", listArgvpv[index],NULL);
      ellFree(&itemList);
      Tcl_Free((char *) listArgvpv);
      return TCL_ERROR;
    } 
    newPv = 0;
    pchandata = pitem->pchandata;
    
    if (pchandata->castate == NewConnection ||
	pchandata->castate == NeverConnected ||
	(newPv=strcmp(listArgvpv[index], ca_name(pchandata->chid)))) {
      itemNode = (ITEMNODE *)malloc(sizeof(ITEMNODE));
      itemNode->pitem = pitem;
      itemNode->index = index;
      ellAdd(&itemList, (ELLNODE *)itemNode);
      
      /* If we're linking same tcl var to a new pv, free up ca resources. */
      if (newPv) {
	if (pchandata->evid != NULL) {
	  status = ca_clear_event(pchandata->evid);
	  pchandata->evid = NULL;
	  if (pchandata->pscript != NULL) {
	    if (pchandata->pscript->number == 1) {
	      Tcl_DStringFree(&pchandata->pscript->script);
	      free(pchandata->pscript);
	    } else {
	      --pchandata->pscript->number;
	    }
	    pchandata->pscript = NULL; 	
	  }
	}
	free(pchandata->pdata);
	Tcl_UnlinkVar(interp,listArgvpv[index]);
	Tcl_UnsetVar(interp,listArgvpv[index],0);
	if (pchandata->iType == DBF_STRING ||
	    pchandata->iType == DBF_ENUM) {
	  free(pchandata->tclValue.tclStr);
	}
	ca_clear_channel(pchandata->chid);
      }
      
      /* NewConnection or retry on NeverConnected */
      pchandata->castate = NeverConnected;
      pchandata->pitem = pitem;
      pchandata->interp = interp;
      status = ca_search(listArgvpv[index],&(pchandata->chid));
      if (status != ECA_NORMAL) {
	Tcl_AppendResult(interp,"snapshot linkPVs:Error encountered while searching for PV: ",listArgvpv[index],NULL);
	free ((char *) listArgvpv);
	ellFree(&itemList);
	return TCL_ERROR;
      }
      ca_puser(pchandata->chid) = pchandata; 
    }
    index++;
  } /* end while */
  
  status = ca_pend_io(pendioTime);
  
  /* Channel access connections completed. Now scan over pv's again
     to get ca DBR_CTRL information. */
  itemNode = (ITEMNODE *)ellFirst(&itemList);
  while (itemNode != NULL) {
    pitem = itemNode->pitem;
    pchandata = pitem->pchandata;
    pchandata->iType = (int)ca_field_type(pchandata->chid);
    pchandata->tclType = caTypeToTclType(pchandata->iType);
    pchandata->skip = 0;
    if (!ca_read_access(pchandata->chid)) {
      pchandata->skip = 1;
      itemNode = (ITEMNODE *)ellNext(itemNode);
      continue;
    }
    if (ca_field_type(pchandata->chid) == DBF_STRING) {
      status = ca_array_get( 
			    dbf_type_to_DBR_STS(ca_field_type(pchandata->chid)),
			    1, pchandata->chid,&pchandata->info);
    } else { 
      status = ca_array_get( 
			    dbf_type_to_DBR_CTRL(ca_field_type(pchandata->chid)),
			    1, pchandata->chid,&pchandata->info);
    } 
    itemNode = (ITEMNODE *)ellNext(itemNode);
  } /* end while */
  
  status = ca_pend_io(pendioTime);    
  
  /* Channel access control information retrieved, now initialize
     connection handler, chandata struct and link to tcl var. */
  listObj = Tcl_NewListObj(0, NULL);
  listName = Tcl_NewObj();
  Tcl_SetStringObj(listName, errNameVar, -1);
  itemNode = (ITEMNODE *)ellFirst(&itemList);
  while (itemNode != NULL) {
    pitem = itemNode->pitem;
    pchandata = pitem->pchandata;
    if (pchandata->skip == 1) {
      obj = Tcl_NewObj();
      Tcl_SetStringObj(obj, pitem->tclname, strlen(pitem->tclname));
      Tcl_ListObjAppendElement(interp, listObj, obj);
      itemNode = (ITEMNODE *)ellNext(itemNode);
      continue;
    }
    pchandata->castate = ConnectionOK;
    
    status = ca_change_connection_event(pchandata->chid,connectionHandler);
    if (status != ECA_NORMAL) {
      ca_clear_channel(pchandata->chid);
      pchandata->castate = NeverConnected;
      Tcl_AppendResult(interp,"snapshot linkPVs:No connection handler: ",
		       listArgvpv[itemNode->index], NULL);
      free ((char *) listArgvpv);
      ellFree(&itemList);
      return TCL_ERROR;
    }
    nBytes = howManyBytes(pchandata);
    if (nBytes == -1) {
      ca_clear_channel(pchandata->chid);
      pchandata->castate = NeverConnected;
      Tcl_AppendResult(interp,"snapshot linkPVs:unknown type ",listArgvpv[itemNode->index], NULL);
      free ((char *) listArgvpv);
      ellFree(&itemList);
      return TCL_ERROR;
    }
    pchandata->initSize = nBytes;
    updateLink(pchandata);
    
    itemNode = (ITEMNODE *)ellNext(itemNode);
  } /* end while */

  Tcl_ObjSetVar2(interp, listName, NULL, listObj, TCL_LEAVE_ERR_MSG);
  free ((char *) listArgvpv);
  ellFree(&itemList);

  return TCL_OK;
}

/******************************************
 *
 *****************************************/
int tclSnapshotGetPVs(Tcl_Interp *interp, int argc, char *argv[]) {
  int status, ignoreNeverConnected;
  int index, listArgc;
  char **listArgv, *errNameVar;
  ITEM *pitem;
  CHANDATA *pchandata;
  Tcl_Obj *listObj, *obj, *listName;
  float pendioTime;

  if (argc!=6) {
    Tcl_SetResult(interp, "usage: snapshot getPVs <PV list> <timeout> <ignore PVs never connected [1|0]> <error name var>", NULL);
    return TCL_ERROR;    
  }
  if ((pendioTime = atof(argv[3])) == 0) {
    Tcl_SetResult(interp, "snapshot getPVs: timeout must be floating point number", NULL);
    return TCL_ERROR;    
  }
  if ((ignoreNeverConnected = atoi(argv[4])) == 0) {
    Tcl_SetResult(interp, "snapshot getPVs: ignore option must be an integer", NULL);
    return TCL_ERROR;    
  }
  errNameVar = argv[5];

  if (Tcl_SplitList(interp,argv[2], &listArgc, &listArgv ) != TCL_OK) {
    Tcl_SetResult(interp,"snapshot getPVs:tcl:splitList: unable to split PV list",NULL);
    return TCL_ERROR;
  }

  for (index = 0; index < listArgc; index++) {
    pitem = (ITEM *)pv_exists(listArgv[index]);
    if (pitem == NULL) {
      Tcl_AppendResult(interp, "snapshot getPVs: ", listArgv[index], " not linked", NULL);
      Tcl_Free((char *) listArgv);
      return TCL_ERROR;
    }
  }

  listName = Tcl_NewStringObj(errNameVar, -1);
  listObj = Tcl_ObjGetVar2(interp, listName, NULL, 0);
 
  for (index = 0; index < listArgc; index++) {
    pitem = (ITEM *)pv_exists(listArgv[index]);
    pchandata = pitem->pchandata;
    if (pchandata->castate == NeverConnected) {
      if (ignoreNeverConnected) {
	continue;
      } else {
	Tcl_AppendResult(interp, "snapshot getPVs: ", listArgv[index], " never connected", NULL);
	Tcl_Free((char *) listArgv);
	return TCL_ERROR;
      }
    } else if (pchandata->castate == LostConnection) {
      obj = Tcl_NewStringObj(listArgv[index], -1);
      Tcl_ListObjAppendElement(interp, listObj, obj); 
      continue;
    } else if (!ca_read_access(pchandata->chid)) {
      pchandata->castate = ReadAccess;
      Tcl_AppendResult(interp, "snapshot getPVs: ", listArgv[index], " don't have read access", NULL);
      Tcl_Free((char *) listArgv);
      return TCL_ERROR;
    } else if (pchandata->evid != NULL) {
      Tcl_AppendResult(interp, "snapshot getPVs: ", listArgv[index], " evid error", NULL);
      Tcl_Free((char *) listArgv);
      return TCL_ERROR;
    } else {
      status = ca_array_get(
			    dbf_type_to_DBR_TIME(ca_field_type(pchandata->chid)),
			    ca_element_count(pchandata->chid),
			    pchandata->chid,
			    (void *)pchandata->pdata
			    );
      if (status != ECA_NORMAL) {
	Tcl_AppendResult(interp,"snapshot getPVs:CA:get_callback (Error encountered while getting PV value): ",listArgv[index], NULL);
	Tcl_Free((char *) listArgv);
	return TCL_ERROR;
      }
    }    
  } /* end while */
  Tcl_ObjSetVar2(interp, listName, NULL, listObj, TCL_LEAVE_ERR_MSG);

  status = ca_pend_io(pendioTime);
  if (status != ECA_NORMAL) {
    /*
    Tcl_SetResult(interp,"snapshot getPVs:ca:pend_io (Timed out while communicating with PVs)", NULL);
    Tcl_Free((char *) listArgv);
    return TCL_ERROR;
    */
  }

  for (index = 0; index < listArgc; index++) {
    pitem = (ITEM *)pv_exists(listArgv[index]);
    pchandata = pitem->pchandata;
    if (pchandata->castate == NeverConnected)  {
      continue;
    }
    copyCaTypeToTclType(pchandata);
  } /* end while */
  
  Tcl_Free((char *) listArgv);
  return TCL_OK;
}

/******************************************
 *
 *****************************************/
int tclSnapshotPutPVs(Tcl_Interp *interp, int argc, char *argv[]) {
  char **listArgvpv, **listArgvValues, *errNameVar;
  int listArgcpv, listArgcValues, ignoreNeverConnected;
  float pendioTime;

  int status, index;
  ITEM *pitem;
  CHANDATA *pch;
  CA_SYNC_GID gid;
  void *dataPtr;
  Tcl_Obj *listObj, *obj, *listName;

  if (argc!=7) {
    Tcl_SetResult(interp, "usage: snapshot putPVs <PV list> <value list> <timeout> <ignore PVs never connected [1|0]> <error name var>", NULL);
    return TCL_ERROR;    
  }
  if (Tcl_SplitList(interp, argv[2], &listArgcpv, &listArgvpv ) != TCL_OK) {
    Tcl_SetResult(interp,"snapshot putPVs:tcl:splitList: unable to split PV list",NULL);
    return TCL_ERROR;
  }
  if (Tcl_SplitList(interp, argv[3], &listArgcValues, &listArgvValues ) != TCL_OK) {
    Tcl_SetResult(interp,"snapshot putPVs:tcl:splitList: unable to split list",NULL);
    Tcl_Free((char *) listArgvpv);
    return TCL_ERROR;
  }
  if ((pendioTime = atof(argv[4])) == 0) {
    Tcl_SetResult(interp, "snapshot putPVs: timeout must be floating point number", NULL);
    Tcl_Free((char *) listArgvpv);
    Tcl_Free((char *) listArgvValues);
    return TCL_ERROR;    
  }
  if ((ignoreNeverConnected = atoi(argv[5])) == 0) {
    Tcl_SetResult(interp, "snapshot putPVs: ignore option must be an integer", NULL);
    return TCL_ERROR;    
  }
  errNameVar = argv[6];

  if (listArgcpv != listArgcValues) {
    Tcl_SetResult(interp, "snapshot putPVs: PV list and value list do not have the same number of elements", NULL);
    Tcl_Free((char *) listArgvpv);
    Tcl_Free((char *) listArgvValues);
    return TCL_ERROR;    
  }

  for (index = 0; index < listArgcpv; index++) {
    pitem = (ITEM *)pv_exists(listArgvpv[index]);
    if (pitem == NULL) {
      Tcl_AppendResult(interp, "snapshot putPVs: ", listArgvpv[index], " not linked", NULL);
      Tcl_Free((char *) listArgvpv);
      Tcl_Free((char *) listArgvValues);
      return TCL_ERROR;
    }
    Tcl_SetVar(interp, listArgvpv[index], listArgvValues[index], TCL_LEAVE_ERR_MSG);
  }
  Tcl_Free((char *) listArgvValues);

  status =ca_sg_create(&gid);
  if (status != ECA_NORMAL) {
      Tcl_SetResult(interp, "snapshot putPVs: ca_sg_create error", NULL);
      Tcl_Free((char *) listArgvpv);
      return TCL_ERROR;
  }
  
  listName = Tcl_NewStringObj(errNameVar, -1);
  listObj = Tcl_ObjGetVar2(interp, listName, NULL, 0);

  for (index = 0; index < listArgcpv; index++) {
    pitem = (ITEM *)pv_exists(listArgvpv[index]);
    pch = pitem->pchandata;
    if (pch->castate == NeverConnected) {
      if (ignoreNeverConnected) {
	continue;
      } else {
	Tcl_AppendResult(interp, "snapshot putPVs: ", listArgvpv[index], " never connected", NULL);
	Tcl_Free((char *) listArgvpv);
	return TCL_ERROR;
      }
    } else if (pch->castate == LostConnection) {
      obj = Tcl_NewStringObj(listArgvpv[index], -1);
      Tcl_ListObjAppendElement(interp, listObj, obj); 
      continue;
    } else if (!ca_write_access(pch->chid)) {
      pch->castate = WriteAccess;
      continue;
    } else {
      dataPtr = (void *)copyTclTypeToCaType(pch);
      if (dataPtr) {
	status = ca_sg_array_put(gid,
				 pch->iType,
				 ca_element_count(pch->chid),
				 pch->chid,
				 dataPtr);
      } else {
	Tcl_AppendResult(interp, "snapshot putPVs: ", pch->tclValue.tclStr, "NOT A NUMBER: ", listArgvpv[index], NULL);
	Tcl_Free((char *) listArgvpv);
	ca_sg_delete(gid);
	return TCL_ERROR;
      }

      if (status != ECA_NORMAL) {
	Tcl_AppendResult(interp, "snapshot putPVs: ca_put (Error encountered while putting PV value):", listArgvpv[index], NULL);
	Tcl_Free((char *) listArgvpv);
	ca_sg_delete(gid);
	return TCL_ERROR;
      }
    }
  }
  
  Tcl_ObjSetVar2(interp, listName, NULL, listObj, TCL_LEAVE_ERR_MSG);

  status = ca_sg_block(gid,pendioTime);
  ca_sg_delete(gid);
  if (status == ECA_PUTFAIL) {
    Tcl_SetResult(interp, "snapshot putPVs: putfail (Error encountered while putting PV values)", NULL);
    Tcl_Free((char *) listArgvpv);
    return TCL_ERROR;
  } else if (status != ECA_NORMAL) {
    /*
    Tcl_SetResult(interp, "snapshot putPVs: ca_sg_block (Timed out while putting PV values)", NULL);
    Tcl_Free((char *) listArgvpv);
    return TCL_ERROR;
    */
  }
  
  free ((char *) listArgvpv);
  return TCL_OK;
}

/******************************************
 *
 *****************************************/
int tclSnapshotWrite(Tcl_Interp *interp, int argc, char *argv[]) {
  char **orig_column_name;
  long orig_column_names;
  
  char **orig_parameter_name;
  long orig_parameter_names, orig_parameter_flag;

  long new_parameter_names, ntype;
  
  char **stringDataArray, *stringDataCopy;
  int *memChanged;

  char *input, *output;
  char **sddsErrs;
  long i, j;
  int fdInput, fdOutput;
  long sddsNumErrs;
  long rows;
  
  double doubleData;
  float floatData;
  long longData;
  int intData;
  short shortData;

  int elements;

  char **listArgvPara[20];
  int listArgcPara[20];

  char *errorValue;

  new_parameter_names = elements = 0;
  input = output = NULL;
  stringDataArray = NULL;
  memChanged = NULL;
  orig_parameter_name = orig_column_name = NULL;
  for (j=0; j<20; j++)
    listArgvPara[j] = NULL;

  if (argc<5) {
    Tcl_SetResult(interp, "usage: snapshot write <request file> <snapshot file> <pv list> [new parameter list {<name> <type> <value>}]...", NULL);
    return TCL_ERROR;    
  }
  input = argv[2];
  output = argv[3];
  if (Tcl_SplitList(interp, argv[4], &elements, &stringDataArray) != TCL_OK) {
    Tcl_SetResult(interp,"snapshot write:tcl:splitList: unable to split PV list",NULL);
    return TCL_ERROR;
  }
  
  for (j=0; j < elements; j++) {
    stringDataArray[j] = Tcl_GetVar(interp,stringDataArray[j],TCL_LEAVE_ERR_MSG);
  }

  memChanged = (int *)malloc(sizeof(int)*elements);

  new_parameter_names = argc - 5;
  if (new_parameter_names > 20) {
      Tcl_SetResult(interp,"snapshot write: unable to add more than 20 new parameters",NULL);
      if (stringDataArray) Tcl_Free((char *) stringDataArray);
      return TCL_ERROR;
  }
  for (i=0; i < new_parameter_names; i++) {
    if (Tcl_SplitList(interp, argv[i+5], &(listArgcPara[i]), &(listArgvPara[i])) != TCL_OK) {
      Tcl_SetResult(interp,"snapshot write:tcl:splitList: unable to split new parameter list",NULL);
      for (j=0; j< new_parameter_names; j++)
	if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
      if (stringDataArray) Tcl_Free((char *) stringDataArray);
      return TCL_ERROR;
    }
    if (listArgcPara[i] != 3) {
      Tcl_SetResult(interp,"snapshot write: new parameter list does not have three elements",NULL);
      for (j=0; j< new_parameter_names; j++)
	if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
      if (stringDataArray) Tcl_Free((char *) stringDataArray);
      return TCL_ERROR;      
    }
  }


  if (elements == 0) {
    Tcl_SetResult(interp, "snapshot write: no elements found in the pv list", NULL);
    for (j=0; j< new_parameter_names; j++)
      if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
    if (stringDataArray) Tcl_Free((char *) stringDataArray);
    return TCL_ERROR;
  }

  /* open request file */
  if ((fdInput = tclSnapshotOpen(interp, input, 'r')) == -1) {
    for (j=0; j< new_parameter_names; j++)
      if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
    if (stringDataArray) Tcl_Free((char *) stringDataArray);
    return TCL_ERROR;
  }

  /* open snapshot file */
  if ((fdOutput = tclSnapshotOpen(interp, output, 'w')) == -1) {
    tclSnapshotClose(interp, fdInput);
    for (j=0; j< new_parameter_names; j++)
      if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
    if (stringDataArray) Tcl_Free((char *) stringDataArray);
    return TCL_ERROR;
  }
   
  /* Getting column names from input file */
  if (!(orig_column_name = SDDS_GetColumnNames(&(sddsFdTable[fdInput].table), &orig_column_names))) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    for (j=0; j< new_parameter_names; j++)
      if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
    if (stringDataArray) Tcl_Free((char *) stringDataArray);
    tclSnapshotClose(interp, fdInput);
    tclSnapshotClose(interp, fdOutput);
    return TCL_ERROR;
  }
  
  /* Getting parameter names from the input file */
  if (!(orig_parameter_name = SDDS_GetParameterNames(&(sddsFdTable[fdInput].table), &orig_parameter_names))) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    for (j=0; j< new_parameter_names; j++)
      if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
    if (stringDataArray) Tcl_Free((char *) stringDataArray);
    tclSnapshotClose(interp, fdInput);
    tclSnapshotClose(interp, fdOutput);
    return TCL_ERROR;
  }

  /*Transferring parameter definitions to new file*/
  for (i=0; i<orig_parameter_names; i++) {
    orig_parameter_flag = 1;
    for (j=0; j<new_parameter_names; j++) {
      if (!strcmp(orig_parameter_name[i],listArgvPara[j][0])) {
	orig_parameter_flag = 0;
      }
    }
    if (orig_parameter_flag) {
      if (!SDDS_TransferParameterDefinition(&(sddsFdTable[fdOutput].table), &(sddsFdTable[fdInput].table),
					    orig_parameter_name[i], orig_parameter_name[i])) {
	Tcl_AppendResult(interp, "snapshot write: unable to transfer parameter ", orig_parameter_name[i], NULL);
	for (j=0; j< new_parameter_names; j++)
	  if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
	if (stringDataArray) Tcl_Free((char *) stringDataArray);
	tclSnapshotClose(interp, fdInput);
	tclSnapshotClose(interp, fdOutput);
	return TCL_ERROR;
      }
    }
  }

  /* Defining new parameters */
  for (i=0; i<new_parameter_names; i++) {
    if ((ntype = convertTypeStringToNumeric(listArgvPara[i][1])) == -1) {
      Tcl_SetResult(interp, "snapshot write: invalid type given for -newParameter",NULL);
      for (j=0; j< new_parameter_names; j++)
	if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
      if (stringDataArray) Tcl_Free((char *) stringDataArray);
      tclSnapshotClose(interp, fdInput);
      tclSnapshotClose(interp, fdOutput);
      return TCL_ERROR;
    }
    if (SDDS_DefineParameter(&(sddsFdTable[fdOutput].table), listArgvPara[i][0], NULL, 
			     NULL, NULL, NULL, ntype, NULL) == -1) {
      sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
      Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
      SDDS_ClearErrors();
      free(sddsErrs[0]);
      for (j=0; j< new_parameter_names; j++)
	if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
      if (stringDataArray) Tcl_Free((char *) stringDataArray);
      tclSnapshotClose(interp, fdInput);
      tclSnapshotClose(interp, fdOutput);
      return TCL_ERROR;
    }
  }
  
  /*Transferring column definitions to new file*/
  for (i=0; i<orig_column_names; i++) {
    if ((strcmp(orig_column_name[i],"ValueString")) && \
	(strcmp(orig_column_name[i],"CAError"))) {
      if (!SDDS_TransferColumnDefinition(&(sddsFdTable[fdOutput].table), &(sddsFdTable[fdInput].table),
					 orig_column_name[i], orig_column_name[i])) {
	Tcl_AppendResult(interp, "snapshot write: unable to transfer column ", orig_column_name[i], NULL);
	for (j=0; j< new_parameter_names; j++)
	  if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
	if (stringDataArray) Tcl_Free((char *) stringDataArray);
	tclSnapshotClose(interp, fdInput);
	tclSnapshotClose(interp, fdOutput);
	return TCL_ERROR;
      }
    }
  }
  
  /* Defining new column */
  if ((SDDS_DefineColumn(&(sddsFdTable[fdOutput].table), "ValueString", NULL, NULL,
			NULL, NULL, convertTypeStringToNumeric("SDDS_STRING"), 0) == -1) ||
      (SDDS_DefineColumn(&(sddsFdTable[fdOutput].table), "CAError", NULL, NULL,
			 NULL, NULL, convertTypeStringToNumeric("SDDS_CHARACTER"), 0) == -1)) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    for (j=0; j< new_parameter_names; j++)
      if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
    if (stringDataArray) Tcl_Free((char *) stringDataArray);
    tclSnapshotClose(interp, fdInput);
    tclSnapshotClose(interp, fdOutput);
    return TCL_ERROR;
  }
    
  /* Saving layout */
  if (!SDDS_SaveLayout(&(sddsFdTable[fdOutput].table))) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    for (j=0; j< new_parameter_names; j++)
      if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
    if (stringDataArray) Tcl_Free((char *) stringDataArray);
    tclSnapshotClose(interp, fdInput);
    tclSnapshotClose(interp, fdOutput);
    return TCL_ERROR;
  }

  /* Writing layout */
  if (!SDDS_WriteLayout(&(sddsFdTable[fdOutput].table))) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    for (j=0; j< new_parameter_names; j++)
      if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
    if (stringDataArray) Tcl_Free((char *) stringDataArray);
    tclSnapshotClose(interp, fdInput);
    tclSnapshotClose(interp, fdOutput);
    return TCL_ERROR;
  }  
  
  /* Reading file */
  if (SDDS_ReadPage(&(sddsFdTable[fdInput].table))<=0) {
    Tcl_AppendResult(interp, "snapshot write: problem reading input page", NULL);
    for (j=0; j< new_parameter_names; j++)
      if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
    if (stringDataArray) Tcl_Free((char *) stringDataArray);
    tclSnapshotClose(interp, fdInput);
    tclSnapshotClose(interp, fdOutput);
    return TCL_ERROR;
  }
  
  /* Counting rows */
  if ((rows = SDDS_RowCount(&(sddsFdTable[fdInput].table)))<0) {
    Tcl_AppendResult(interp, "snapshot write: problem counting rows in input page", NULL);
    for (j=0; j< new_parameter_names; j++)
      if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
    if (stringDataArray) Tcl_Free((char *) stringDataArray);
    tclSnapshotClose(interp, fdInput);
    tclSnapshotClose(interp, fdOutput);
    return TCL_ERROR;
  }

  /* Comparing rows to number of elements in ValueString */
  if (elements != rows) {
    Tcl_AppendResult(interp, "snapshot write: rows in inputfile different then rows in ValueString column", NULL);
    for (j=0; j< new_parameter_names; j++)
      if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
    if (stringDataArray) Tcl_Free((char *) stringDataArray);
    tclSnapshotClose(interp, fdInput);
    tclSnapshotClose(interp, fdOutput);
    return TCL_ERROR;
  }

  /* Copy request file page to snapshot file */
  if (!SDDS_CopyPage(&(sddsFdTable[fdOutput].table), &(sddsFdTable[fdInput].table))) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    for (j=0; j< new_parameter_names; j++)
      if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
    if (stringDataArray) Tcl_Free((char *) stringDataArray);
    tclSnapshotClose(interp, fdInput);
    tclSnapshotClose(interp, fdOutput);
    return TCL_ERROR;
  }

  /* Set new parameter values */
  for (i=0; i<new_parameter_names; i++) {
    ntype = convertTypeStringToNumeric(listArgvPara[i][1]);
    switch (ntype) {
    case SDDS_DOUBLE: {
      if (Tcl_GetDouble(interp,listArgvPara[i][2],&doubleData) != TCL_OK) {
	Tcl_SetResult(interp, "snapshot write:  value cannot be converted to native data type (SDDS_DOUBLE)",NULL);
	for (j=0; j< new_parameter_names; j++)
	  if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
	if (stringDataArray) Tcl_Free((char *) stringDataArray);
	tclSnapshotClose(interp, fdInput);
	tclSnapshotClose(interp, fdOutput);
	return TCL_ERROR;
      }
      if (!SDDS_SetParameters(&(sddsFdTable[fdOutput].table),SDDS_SET_BY_NAME|SDDS_PASS_BY_VALUE,
			      listArgvPara[i][0],doubleData, NULL)) {
	sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
	Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
	SDDS_ClearErrors();
	free(sddsErrs[0]);
	for (j=0; j< new_parameter_names; j++)
	  if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
	if (stringDataArray) Tcl_Free((char *) stringDataArray);
	tclSnapshotClose(interp, fdInput);
	tclSnapshotClose(interp, fdOutput);
	return TCL_ERROR;
      }
      break;
    }
    case SDDS_FLOAT: {
      if (Tcl_GetDouble(interp,listArgvPara[i][2],&doubleData) != TCL_OK) {
	Tcl_SetResult(interp, "snapshot write:  value cannot be converted to native data type (SDDS_FLOAT)",NULL);
	for (j=0; j< new_parameter_names; j++)
	  if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
	if (stringDataArray) Tcl_Free((char *) stringDataArray);
	tclSnapshotClose(interp, fdInput);
	tclSnapshotClose(interp, fdOutput);
	return TCL_ERROR;
      }
      floatData = doubleData;
      if (!SDDS_SetParameters(&(sddsFdTable[fdOutput].table),SDDS_SET_BY_NAME|SDDS_PASS_BY_VALUE,
			      listArgvPara[i][0],floatData, NULL)) {
	sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
	Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
	SDDS_ClearErrors();
	free(sddsErrs[0]);
	for (j=0; j< new_parameter_names; j++)
	  if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
	if (stringDataArray) Tcl_Free((char *) stringDataArray);
	tclSnapshotClose(interp, fdInput);
	tclSnapshotClose(interp, fdOutput);
	return TCL_ERROR;
      }
      break;
    }
    case SDDS_LONG: {
      if (Tcl_GetInt(interp,listArgvPara[i][2],&intData) != TCL_OK) {
	Tcl_SetResult(interp, "snapshot write:  value cannot be converted to native data type (SDDS_LONG)",NULL);
	for (j=0; j< new_parameter_names; j++)
	  if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
	if (stringDataArray) Tcl_Free((char *) stringDataArray);
	tclSnapshotClose(interp, fdInput);
	tclSnapshotClose(interp, fdOutput);
	return TCL_ERROR;
      }
      longData = intData;
      if (!SDDS_SetParameters(&(sddsFdTable[fdOutput].table),SDDS_SET_BY_NAME|SDDS_PASS_BY_VALUE,
			      listArgvPara[i][0],longData, NULL)) {
	sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
	Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
	SDDS_ClearErrors();
	free(sddsErrs[0]);
	for (j=0; j< new_parameter_names; j++)
	  if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
	if (stringDataArray) Tcl_Free((char *) stringDataArray);
	tclSnapshotClose(interp, fdInput);
	tclSnapshotClose(interp, fdOutput);
	return TCL_ERROR;
      }
      break;
    }
    case SDDS_SHORT: {
      if (Tcl_GetInt(interp,listArgvPara[i][2],&intData) != TCL_OK) {
	Tcl_SetResult(interp, "snapshot write:  value cannot be converted to native data type (SDDS_SHORT)",NULL);
	for (j=0; j< new_parameter_names; j++)
	  if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
	if (stringDataArray) Tcl_Free((char *) stringDataArray);
	tclSnapshotClose(interp, fdInput);
	tclSnapshotClose(interp, fdOutput);
	return TCL_ERROR;
      }
      shortData = intData;
      if (!SDDS_SetParameters(&(sddsFdTable[fdOutput].table),SDDS_SET_BY_NAME|SDDS_PASS_BY_VALUE,
			      listArgvPara[i][0],shortData, NULL)) {
	sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
	Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
	SDDS_ClearErrors();
	free(sddsErrs[0]);
	for (j=0; j< new_parameter_names; j++)
	  if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
	if (stringDataArray) Tcl_Free((char *) stringDataArray);
	tclSnapshotClose(interp, fdInput);
	tclSnapshotClose(interp, fdOutput);
	return TCL_ERROR;
      }
      break;
    }
    case SDDS_STRING: {
      if (!SDDS_SetParameters(&(sddsFdTable[fdOutput].table),SDDS_SET_BY_NAME|SDDS_PASS_BY_VALUE,
			      listArgvPara[i][0],listArgvPara[i][2], NULL)) {
	sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
	Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
	SDDS_ClearErrors();
	free(sddsErrs[0]);
	for (j=0; j< new_parameter_names; j++)
	  if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
	if (stringDataArray) Tcl_Free((char *) stringDataArray);
	tclSnapshotClose(interp, fdInput);
	tclSnapshotClose(interp, fdOutput);
	return TCL_ERROR;
      }
      break;
    }
    case SDDS_CHARACTER: {
      if (!SDDS_SetParameters(&(sddsFdTable[fdOutput].table),SDDS_SET_BY_NAME|SDDS_PASS_BY_VALUE,
			      listArgvPara[i][0],(listArgvPara[i][2])[0], NULL)) {
	sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
	Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
	SDDS_ClearErrors();
	free(sddsErrs[0]);
	for (j=0; j< new_parameter_names; j++)
	  if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
	if (stringDataArray) Tcl_Free((char *) stringDataArray);
	tclSnapshotClose(interp, fdInput);
	tclSnapshotClose(interp, fdOutput);
	return TCL_ERROR;
      }
      break;
    }
    }
  }

  for (j=0; j< new_parameter_names; j++)
    if (listArgvPara[j]) Tcl_Free((char *) listArgvPara[j]);
  
  errorValue = (char *)malloc(sizeof(char)*elements);
  for (j=0; j < elements; j++) {
    if (strcmp(stringDataArray[j],"--Snapshot Error--")) {
      errorValue[j] = 'n';
      if (strchr(stringDataArray[j], ' ') && 
	  (stringDataArray[j][0]!='"' || stringDataArray[j][strlen(stringDataArray[j])-1]!='"')) {
	memChanged[j] = 1;
	stringDataCopy = stringDataArray[j];
	stringDataArray[j] = Tcl_Alloc(sizeof(char)*(strlen(stringDataArray[j])+2));
	sprintf(stringDataArray[j], "\"%s\"", stringDataCopy);
	/*	free(stringDataCopy);*/
      } else {
	memChanged[j] = 0;
      }
    } else {
      errorValue[j] = 'y';
      stringDataArray[j] = "";
      memChanged[j] = 0;
    }
  }

  if (!SDDS_SetColumn(&(sddsFdTable[fdOutput].table), SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE, 
		      stringDataArray, elements, "ValueString")) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    free(errorValue);
    for (j=0; j < elements; j++) {
      if (memChanged[j]) {
	Tcl_Free(stringDataArray[j]);
      }
    }
    free(memChanged);
    if (stringDataArray) Tcl_Free((char *) stringDataArray);
    tclSnapshotClose(interp, fdInput);
    tclSnapshotClose(interp, fdOutput);
    return TCL_ERROR;
  }

  if (!SDDS_SetColumn(&(sddsFdTable[fdOutput].table), SDDS_SET_BY_NAME | SDDS_PASS_BY_VALUE, 
		      errorValue, elements, "CAError")) {
    sddsErrs = SDDS_GetErrorMessages(&sddsNumErrs, SDDS_ERROR_MODE);
    Tcl_AppendResult(interp, "snapshot write: ", sddsErrs[0], NULL);
    SDDS_ClearErrors();
    free(sddsErrs[0]);
    free(errorValue);
    for (j=0; j < elements; j++) {
      if (memChanged[j]) {
	Tcl_Free(stringDataArray[j]);
      }
    }
    free(memChanged);
    if (stringDataArray) Tcl_Free((char *) stringDataArray);
    tclSnapshotClose(interp, fdInput);
    tclSnapshotClose(interp, fdOutput);
    return TCL_ERROR;
  }

  free(errorValue);
  for (j=0; j < elements; j++) {
    if (memChanged[j]) {
      Tcl_Free(stringDataArray[j]);
    }
  }
  free(memChanged);
  if (stringDataArray) Tcl_Free((char *) stringDataArray);
    
  if (!SDDS_WritePage(&(sddsFdTable[fdOutput].table))) {
    Tcl_AppendResult(interp, "snapshot write:  problem writing page to file ", 
		     sddsFdTable[fdOutput].fileName, NULL);
    tclSnapshotClose(interp, fdInput);
    tclSnapshotClose(interp, fdOutput);
    return TCL_ERROR;
  }

  tclSnapshotClose(interp, fdInput);
  tclSnapshotClose(interp, fdOutput);
  
  return TCL_OK;
}


/******************************************
 * exception event handler
 *****************************************/
void exceptionHandler(struct exception_handler_args arg) {

  chid pCh;
  int stat;

  pCh = arg.chid;
  stat = arg.stat;
  (void)printf("CA exception handler entered\n");
  if (pCh) {
    (void)printf("CA channel name=%s\n", ca_name(pCh));
  }
  (void)printf("CA status=%s\n", ca_message(stat));
  (void)printf("CA context=%s\n", arg.ctx);
  (void)printf("CA op=%ld data type=%s count=%ld\n",
	       arg.op,
	       dbr_type_to_text(arg.type),
	       arg.count);
}

/*******************************************************
 * .register a file descriptor with tcl
 * .becomes part of event loop after registration
 *   so that any input on fd will activate epicsFdMonitor
 *   which then processes the channel  IO
 *******************************************************/
     
void registerFD(void *dummy, int fd, int condition) {

  if (condition) {
#if (TCL_MAJOR_VERSION >= 7) 
    Tcl_CreateFileHandler( fd, TK_READABLE, epicsFdMonitor, NULL);
#else
    Tk_CreateFileHandler( fd, TK_READABLE, epicsFdMonitor, NULL);
#endif
  } else {
#if (TCL_MAJOR_VERSION >= 7) 
    Tcl_DeleteFileHandler( fd);
#else
    Tk_DeleteFileHandler( fd);
#endif
  }
}

/*******************************************************
* TK will call whenever fd gets input...see registerFD above
 *******************************************************/

void epicsFdMonitor(void *dummy, int mask) {
  ca_pend_event(MAGIC_POLL);
}

/*----------------------------------------------------------------------
 *
 * pendRecycleProc --
 *
 *	This procedure is invoked once every .1sec 
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A new timer callback is created to call us again in another
 *	5 seconds
 *----------------------------------------------------------------------
 */

static void pendRecycleProc(ClientData clientData) {
  /* NOTE: changed original .001 to 1e-9. A "magic number" timeout which
           is less than 1e-6 results in a poll. Anything greater waits.
  */
  ca_pend_event(MAGIC_POLL);
  Tk_CreateTimerHandler(5000, pendRecycleProc, (ClientData) NULL);
}

/******************************************
 * connection event handler
 *****************************************/
void connectionHandler(struct connection_handler_args args) {
  
  CHANDATA *pch;
  int status, nBytes;
  
  pch = (CHANDATA *)ca_puser(args.chid);
  
  switch (args.op) {
  case CA_OP_CONN_UP:
    if (pch->castate == NeverConnected) {
      if (!ca_read_access(pch->chid)) {
	pch->castate = ReadAccess;
	break;
      }
      pch->iType = (int)ca_field_type(pch->chid);
      pch->tclType = caTypeToTclType(pch->iType);
      nBytes = howManyBytes(pch);
      if(pch->iType == DBF_STRING ) {
	status = ca_array_get_callback(
				       dbf_type_to_DBR_STS(pch->iType),
				       1,
				       pch->chid,
				       updateChCtrl,
				       (void *)pch);
	if (status != ECA_NORMAL) {
	  ca_clear_channel(pch->chid);
	} 
      } else { 
	status = ca_array_get_callback(
				       dbf_type_to_DBR_CTRL(pch->iType),
				       1,
				       pch->chid,
				       updateChCtrl,
				       (void *)pch);
	if (status != ECA_NORMAL) {
	  ca_clear_channel(pch->chid);
	}
      } 
    } else { 
      pch->castate = ConnectionOK;
      pch->countPendIO=0;
    }
    break;
  case CA_OP_CONN_DOWN:
    pch->castate = LostConnection;
    break;
  }

  return;
} 

/*******************************************************
* whenever async cntl data is received...retrieve info buffer
 *******************************************************/
void updateChCtrl(struct event_handler_args args) {

  CHANDATA *pch;
  INFOBUF *pinfo;
  int nBytes;

  pch = (CHANDATA *)args.usr;
  nBytes = dbr_size_n(args.type,args.count);
  pinfo = &pch->info;

  memcpy((char *)pinfo, args.dbr, (size_t)nBytes);

  pch->initSize = nBytes;
  updateLink(pch);
}

/******************************************
 *
 *****************************************/
static long convertTypeStringToNumeric(char *type) {
  if (!strcmp(type,"SDDS_DOUBLE")) {
    return 1;
  } else if (!strcmp(type,"SDDS_FLOAT")) {
    return 2;
  } else if (!strcmp(type,"SDDS_LONG")) {
    return 3;
  } else if (!strcmp(type,"SDDS_SHORT")) {
    return 4;
  } else if (!strcmp(type,"SDDS_STRING")) {
    return 5;
  } else if (!strcmp(type,"SDDS_CHARACTER")) {
    return 6;
  } else {
    return -1;
  }
}
