/*
 * Smithsonian Astrophysical Observatory, Cambridge, MA, USA
 * This code has been modified under the terms listed below and is made
 * available under the same terms.
 */

/*
 *	Copyright 1995-2004 George A Howlett.
 *
 *	Permission is hereby granted, free of charge, to any person obtaining
 *	a copy of this software and associated documentation files (the
 *	"Software"), to deal in the Software without restriction, including
 *	without limitation the rights to use, copy, modify, merge, publish,
 *	distribute, sublicense, and/or sell copies of the Software, and to
 *	permit persons to whom the Software is furnished to do so, subject to
 *	the following conditions:
 *
 *	The above copyright notice and this permission notice shall be
 *	included in all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *	LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *	OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *	WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Code for binary data read operation was donated by Harold Kirsch.
 *
 */

/*
 * TODO:
 *	o Add H. Kirsch's vector binary read operation
 *		x binread file0
 *		x binread -file file0
 *
 *	o Add ASCII/binary file reader
 *		x read fileName
 *
 *	o Allow Tcl-based client notifications.
 *		vector x
 *		x notify call Display
 *		x notify delete Display
 *		x notify reorder #1 #2
 */

#include <float.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include <cmath>

#include "tkbltVecInt.h"
#include "tkbltOp.h"
#include "tkbltNsUtil.h"
#include "tkbltSwitch.h"

using namespace Blt;

extern int Blt_SimplifyLine (Point2d *origPts, int low, int high, 
			     double tolerance, int *indices);

typedef int (VectorCmdProc)(Vector *vPtr, Tcl_Interp* interp, int objc, 
			    Tcl_Obj* const objv[]);
typedef int (QSortCompareProc) (const void *, const void *);

static Blt_SwitchParseProc ObjToFFTVector;
static Blt_SwitchCustom fftVectorSwitch = {
  ObjToFFTVector, NULL, (ClientData)0,
};

static Blt_SwitchParseProc ObjToIndex;
static Blt_SwitchCustom indexSwitch = {
  ObjToIndex, NULL, (ClientData)0,
};

typedef struct {
  Tcl_Obj *formatObjPtr;
  int from, to;
} PrintSwitches;

static Blt_SwitchSpec printSwitches[] = 
  {
    {BLT_SWITCH_OBJ,    "-format", "string",
     Tk_Offset(PrintSwitches, formatObjPtr), 0},
    {BLT_SWITCH_CUSTOM, "-from",   "index",
     Tk_Offset(PrintSwitches, from),         0, 0, &indexSwitch},
    {BLT_SWITCH_CUSTOM, "-to",     "index",
     Tk_Offset(PrintSwitches, to),           0, 0, &indexSwitch},
    {BLT_SWITCH_END}
  };


typedef struct {
  int flags;
} SortSwitches;

#define SORT_DECREASING (1<<0)
#define SORT_UNIQUE	(1<<1)

static Blt_SwitchSpec sortSwitches[] = 
  {
    {BLT_SWITCH_BITMASK, "-decreasing", "",
	Tk_Offset(SortSwitches, flags),   0, SORT_DECREASING},
    {BLT_SWITCH_BITMASK, "-reverse",   "",
	Tk_Offset(SortSwitches, flags),   0, SORT_DECREASING},
    {BLT_SWITCH_BITMASK, "-uniq",     "", 
	Tk_Offset(SortSwitches, flags),   0, SORT_UNIQUE},
    {BLT_SWITCH_END}
  };

typedef struct {
  double delta;
  Vector *imagPtr;	/* Vector containing imaginary part. */
  Vector *freqPtr;	/* Vector containing frequencies. */
  VectorInterpData *dataPtr;
  int mask;			/* Flags controlling FFT. */
} FFTData;


static Blt_SwitchSpec fftSwitches[] = {
  {BLT_SWITCH_CUSTOM, "-imagpart",    "vector",
   Tk_Offset(FFTData, imagPtr), 0, 0, &fftVectorSwitch},
  {BLT_SWITCH_BITMASK, "-noconstant", "",
   Tk_Offset(FFTData, mask), 0, FFT_NO_CONSTANT},
  {BLT_SWITCH_BITMASK, "-spectrum", "",
   Tk_Offset(FFTData, mask), 0, FFT_SPECTRUM},
  {BLT_SWITCH_BITMASK, "-bartlett",  "",
   Tk_Offset(FFTData, mask), 0, FFT_BARTLETT},
  {BLT_SWITCH_DOUBLE, "-delta",   "float",
   Tk_Offset(FFTData, mask), 0, 0, },
  {BLT_SWITCH_CUSTOM, "-frequencies", "vector",
   Tk_Offset(FFTData, freqPtr), 0, 0, &fftVectorSwitch},
  {BLT_SWITCH_END}
};

static int Blt_ExprIntFromObj(Tcl_Interp* interp, Tcl_Obj *objPtr, 
			      int *valuePtr)
{
  // First try to extract the value as a simple integer.
  if (Tcl_GetIntFromObj((Tcl_Interp *)NULL, objPtr, valuePtr) == TCL_OK)
    return TCL_OK;
 
  // Otherwise try to parse it as an expression.
  long lvalue;
  if (Tcl_ExprLong(interp, Tcl_GetString(objPtr), &lvalue) == TCL_OK) {
    *valuePtr = lvalue;
    return TCL_OK;
  }

  return TCL_ERROR;
}

static int Blt_ExprDoubleFromObj(Tcl_Interp* interp, Tcl_Obj *objPtr, 
				 double *valuePtr)
{
  // First try to extract the value as a double precision number.
  if (Tcl_GetDoubleFromObj((Tcl_Interp *)NULL, objPtr, valuePtr) == TCL_OK)
    return TCL_OK;

  // Interpret the empty string as NaN
  int length;
  char *string;
  string = Tcl_GetStringFromObj(objPtr, &length);
  if (length == 0) {
    *valuePtr = NAN;
    return TCL_OK;
  }

  // Then try to parse it as an expression.
  if (Tcl_ExprDouble(interp, string, valuePtr) == TCL_OK)
    return TCL_OK;

  return TCL_ERROR;
}

static int ObjToFFTVector(ClientData clientData, Tcl_Interp* interp,
			  const char *switchName, Tcl_Obj *objPtr,
			  char *record, int offset, int flags)
{
  FFTData *dataPtr = (FFTData *)record;
  Vector *vPtr;
  Vector **vPtrPtr = (Vector **)(record + offset);
  int isNew;			/* Not used. */
  char *string;

  string = Tcl_GetString(objPtr);
  vPtr = Vec_Create(dataPtr->dataPtr, string, string, string, &isNew);
  if (vPtr == NULL) {
    return TCL_ERROR;
  }
  *vPtrPtr = vPtr;

  return TCL_OK;
}

static int ObjToIndex(ClientData clientData, Tcl_Interp* interp,
		      const char *switchName, Tcl_Obj *objPtr, char *record,
		      int offset, int flags)
{
  Vector *vPtr = (Vector*)clientData;
  int *indexPtr = (int *)(record + offset);
  int index;

  if (Vec_GetIndex(interp, vPtr, Tcl_GetString(objPtr), &index, 
		       INDEX_CHECK, (Blt_VectorIndexProc **)NULL) != TCL_OK) {
    return TCL_ERROR;
  }
  *indexPtr = index;

  return TCL_OK;
}

static Tcl_Obj* GetValues(Vector *vPtr, int first, int last)
{ 
  Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
  for (double *vp=vPtr->valueArr+first, *vend=vPtr->valueArr+last; 
       vp <= vend; vp++)
    Tcl_ListObjAppendElement(vPtr->interp, listObjPtr, Tcl_NewDoubleObj(*vp));

  return listObjPtr;
}

static void ReplicateValue(Vector *vPtr, int first, int last, double value)
{ 
  for (double *vp=vPtr->valueArr+first, *vend=vPtr->valueArr+last; 
       vp <= vend; vp++)
    *vp = value; 

  vPtr->notifyFlags |= UPDATE_RANGE; 
}

static int CopyList(Vector *vPtr, Tcl_Interp* interp, 
		    int objc, Tcl_Obj* const objv[])
{
  if (Vec_SetLength(interp, vPtr, objc) != TCL_OK)
    return TCL_ERROR;

  for (int ii = 0; ii < objc; ii++) {
    double value;
    if (Blt_ExprDoubleFromObj(interp, objv[ii], &value) != TCL_OK) {
      Vec_SetLength(interp, vPtr, ii);
      return TCL_ERROR;
    }
    vPtr->valueArr[ii] = value;
  }

  return TCL_OK;
}

static int AppendVector(Vector *destPtr, Vector *srcPtr)
{
  size_t oldSize = destPtr->length;
  size_t newSize = oldSize + srcPtr->last - srcPtr->first + 1;
  if (Vec_ChangeLength(destPtr->interp, destPtr, newSize) != TCL_OK) {
    return TCL_ERROR;
  }
  size_t nBytes = (newSize - oldSize) * sizeof(double);
  memcpy((char *)(destPtr->valueArr + oldSize),
	 (srcPtr->valueArr + srcPtr->first), nBytes);
  destPtr->notifyFlags |= UPDATE_RANGE;
  return TCL_OK;
}

static int AppendList(Vector *vPtr, int objc, Tcl_Obj* const objv[])
{
  Tcl_Interp* interp = vPtr->interp;

  int oldSize = vPtr->length;
  if (Vec_ChangeLength(interp, vPtr, vPtr->length + objc) != TCL_OK)
    return TCL_ERROR;

  int count = oldSize;
  for (int i = 0; i < objc; i++) {
    double value;
    if (Blt_ExprDoubleFromObj(interp, objv[i], &value) != TCL_OK) {
      Vec_ChangeLength(interp, vPtr, count);
      return TCL_ERROR;
    }
    vPtr->valueArr[count++] = value;
  }
  vPtr->notifyFlags |= UPDATE_RANGE;

  return TCL_OK;
}

// Vector instance option commands

static int AppendOp(Vector *vPtr, Tcl_Interp* interp, 
		    int objc, Tcl_Obj* const objv[])
{
  for (int i = 2; i < objc; i++) {
    Vector* v2Ptr = Vec_ParseElement((Tcl_Interp *)NULL, vPtr->dataPtr, 
				     Tcl_GetString(objv[i]), 
				     (const char **)NULL, NS_SEARCH_BOTH);
    int result;
    if (v2Ptr != NULL)
      result = AppendVector(vPtr, v2Ptr);
    else {
      int nElem;
      Tcl_Obj **elemObjArr;

      if (Tcl_ListObjGetElements(interp, objv[i], &nElem, &elemObjArr) 
	  != TCL_OK) {
	return TCL_ERROR;
      }
      result = AppendList(vPtr, nElem, elemObjArr);
    }

    if (result != TCL_OK)
      return TCL_ERROR;
  }

  if (objc > 2) {
    if (vPtr->flush)
      Vec_FlushCache(vPtr);
    Vec_UpdateClients(vPtr);
  }

  return TCL_OK;
}

static int ClearOp(Vector *vPtr, Tcl_Interp* interp, 
		   int objc, Tcl_Obj* const objv[])
{
  Vec_FlushCache(vPtr);
  return TCL_OK;
}

static int DeleteOp(Vector *vPtr, Tcl_Interp* interp, 
		    int objc, Tcl_Obj* const objv[])
{
  // FIXME: Don't delete vector with no indices
  if (objc == 2) {
    Vec_Free(vPtr);
    return TCL_OK;
  }

  // Allocate an "unset" bitmap the size of the vector
  unsigned char* unsetArr = 
    (unsigned char*)calloc(sizeof(unsigned char), (vPtr->length + 7) / 8);
#define SetBit(i) (unsetArr[(i) >> 3] |= (1 << ((i) & 0x07)))
#define GetBit(i) (unsetArr[(i) >> 3] &  (1 << ((i) & 0x07)))

  for (int i = 2; i < objc; i++) {
    char* string = Tcl_GetString(objv[i]);
    if (Vec_GetIndexRange(interp, vPtr, string, (INDEX_COLON | INDEX_CHECK),
			  (Blt_VectorIndexProc **) NULL) != TCL_OK) {
      free(unsetArr);
      return TCL_ERROR;
    }

    // Mark the range of elements for deletion
    for (int j = vPtr->first; j <= vPtr->last; j++)
      SetBit(j);		
  }

  int count = 0;
  for (int i = 0; i < vPtr->length; i++) {
    // Skip elements marked for deletion
    if (GetBit(i))
      continue;

    if (count < i) {
      vPtr->valueArr[count] = vPtr->valueArr[i];
    }
    count++;
  }
  free(unsetArr);
  vPtr->length = count;

  if (vPtr->flush)
    Vec_FlushCache(vPtr);
  Vec_UpdateClients(vPtr);

  return TCL_OK;
}

static int DupOp(Vector *vPtr, Tcl_Interp* interp, 
		 int objc, Tcl_Obj* const objv[])
{
  for (int i = 2; i < objc; i++) {
    char* name = Tcl_GetString(objv[i]);
    int isNew;
    Vector* v2Ptr = Vec_Create(vPtr->dataPtr, name, name, name, &isNew);
    if (v2Ptr == NULL)
      return TCL_ERROR;

    if (v2Ptr == vPtr)
      continue;

    if (Vec_Duplicate(v2Ptr, vPtr) != TCL_OK)
      return TCL_ERROR;

    if (!isNew) {
      if (v2Ptr->flush)
	Vec_FlushCache(v2Ptr);
      Vec_UpdateClients(v2Ptr);
    }
  }

  return TCL_OK;
}

static int FFTOp(Vector *vPtr, Tcl_Interp* interp, 
		 int objc, Tcl_Obj* const objv[])
{
  FFTData data;
  memset(&data, 0, sizeof(data));
  data.delta = 1.0;

  char* realVecName = Tcl_GetString(objv[2]);
  int isNew;
  Vector* v2Ptr = Vec_Create(vPtr->dataPtr, realVecName, realVecName, 
			     realVecName, &isNew);
  if (v2Ptr == NULL)
    return TCL_ERROR;

  if (v2Ptr == vPtr) {
    Tcl_AppendResult(interp, "real vector \"", realVecName, "\"", 
		     " can't be the same as the source", (char *)NULL);
    return TCL_ERROR;
  }

  if (ParseSwitches(interp, fftSwitches, objc - 3, objv + 3, &data, 
		    BLT_SWITCH_DEFAULTS) < 0)
    return TCL_ERROR;

  if (Vec_FFT(interp, v2Ptr, data.imagPtr, data.freqPtr, data.delta,
	      data.mask, vPtr) != TCL_OK)
    return TCL_ERROR;

  // Update bookkeeping
  if (!isNew) {
    if (v2Ptr->flush)
      Vec_FlushCache(v2Ptr);
    Vec_UpdateClients(v2Ptr);
  }

  if (data.imagPtr != NULL) {
    if (data.imagPtr->flush)
      Vec_FlushCache(data.imagPtr);
    Vec_UpdateClients(data.imagPtr);
  }

  if (data.freqPtr != NULL) {
    if (data.freqPtr->flush)
      Vec_FlushCache(data.freqPtr);
    Vec_UpdateClients(data.freqPtr);
  }

  return TCL_OK;
}	

static int InverseFFTOp(Vector *vPtr, Tcl_Interp* interp, 
			int objc, Tcl_Obj* const objv[])
{
  char* name = Tcl_GetString(objv[2]);
  Vector *srcImagPtr;
  if (Vec_LookupName(vPtr->dataPtr, name, &srcImagPtr) != TCL_OK )
    return TCL_ERROR;

  name = Tcl_GetString(objv[3]);
  int isNew;
  Vector* destRealPtr = Vec_Create(vPtr->dataPtr, name, name, name, &isNew);
  name = Tcl_GetString(objv[4]);
  Vector* destImagPtr = Vec_Create(vPtr->dataPtr, name, name, name, &isNew);

  if (Vec_InverseFFT(interp, srcImagPtr, destRealPtr, destImagPtr, vPtr) 
      != TCL_OK )
    return TCL_ERROR;

  if (destRealPtr->flush)
    Vec_FlushCache(destRealPtr);
  Vec_UpdateClients(destRealPtr);

  if (destImagPtr->flush)
    Vec_FlushCache(destImagPtr);
  Vec_UpdateClients(destImagPtr);

  return TCL_OK;
}

static int IndexOp(Vector *vPtr, Tcl_Interp* interp, 
		   int objc, Tcl_Obj* const objv[])
{
  char* string = Tcl_GetString(objv[2]);
  if (Vec_GetIndexRange(interp, vPtr, string, INDEX_ALL_FLAGS, 
			(Blt_VectorIndexProc **) NULL) != TCL_OK)
    return TCL_ERROR;

  int first = vPtr->first;
  int last = vPtr->last;
  if (objc == 3) {
    Tcl_Obj *listObjPtr;

    if (first == vPtr->length) {
      Tcl_AppendResult(interp, "can't get index \"", string, "\"",
		       (char *)NULL);
      return TCL_ERROR;	/* Can't read from index "++end" */
    }
    listObjPtr = GetValues(vPtr, first, last);
    Tcl_SetObjResult(interp, listObjPtr);
  }
  else {
    // FIXME: huh? Why set values here?
    if (first == SPECIAL_INDEX) {
      Tcl_AppendResult(interp, "can't set index \"", string, "\"",
		       (char *)NULL);
      // Tried to set "min" or "max"
      return TCL_ERROR;
    }

    double value;
    if (Blt_ExprDoubleFromObj(interp, objv[3], &value) != TCL_OK)
      return TCL_ERROR;

    if (first == vPtr->length) {
      if (Vec_ChangeLength(interp, vPtr, vPtr->length + 1) != TCL_OK) 
	return TCL_ERROR;
    }

    ReplicateValue(vPtr, first, last, value);
    Tcl_SetObjResult(interp, objv[3]);
    if (vPtr->flush)
      Vec_FlushCache(vPtr);
    Vec_UpdateClients(vPtr);
  }

  return TCL_OK;
}

static int LengthOp(Vector *vPtr, Tcl_Interp* interp, 
		    int objc, Tcl_Obj* const objv[])
{
  if (objc == 3) {
    int nElem;
    if (Tcl_GetIntFromObj(interp, objv[2], &nElem) != TCL_OK)
      return TCL_ERROR;

    if (nElem < 0) {
      Tcl_AppendResult(interp, "bad vector size \"", 
		       Tcl_GetString(objv[2]), "\"", (char *)NULL);
      return TCL_ERROR;
    }

    if ((Vec_SetSize(interp, vPtr, nElem) != TCL_OK) ||
	(Vec_SetLength(interp, vPtr, nElem) != TCL_OK))
      return TCL_ERROR;

    if (vPtr->flush)
      Vec_FlushCache(vPtr);
    Vec_UpdateClients(vPtr);
  }
  Tcl_SetIntObj(Tcl_GetObjResult(interp), vPtr->length);

  return TCL_OK;
}

static int MapOp(Vector *vPtr, Tcl_Interp* interp, 
		 int objc, Tcl_Obj* const objv[])
{
  if (objc > 2) {
    if (Vec_MapVariable(interp, vPtr, Tcl_GetString(objv[2])) 
	!= TCL_OK)
      return TCL_ERROR;
  }

  if (vPtr->arrayName != NULL)
    Tcl_SetStringObj(Tcl_GetObjResult(interp), vPtr->arrayName, -1);

  return TCL_OK;
}

static int MaxOp(Vector *vPtr, Tcl_Interp* interp, 
		 int objc, Tcl_Obj* const objv[])
{
  Tcl_SetDoubleObj(Tcl_GetObjResult(interp), Vec_Max(vPtr));
  return TCL_OK;
}

static int MergeOp(Vector *vPtr, Tcl_Interp* interp, 
		   int objc, Tcl_Obj* const objv[])
{
  // Allocate an array of vector pointers of each vector to be
  // merged in the current vector.
  Vector** vecArr = (Vector**)malloc(sizeof(Vector *) * objc);
  Vector** vPtrPtr = vecArr;

  int refSize = -1;
  int nElem = 0;
  for (int i = 2; i < objc; i++) {
    Vector *v2Ptr;
    if (Vec_LookupName(vPtr->dataPtr, Tcl_GetString(objv[i]), &v2Ptr)
	!= TCL_OK) {
      free(vecArr);
      return TCL_ERROR;
    }

    // Check that all the vectors are the same length
    int length = v2Ptr->last - v2Ptr->first + 1;
    if (refSize < 0)
      refSize = length;
    else if (length != refSize) {
      Tcl_AppendResult(vPtr->interp, "vectors \"", vPtr->name,
		       "\" and \"", v2Ptr->name, "\" differ in length",
		       (char *)NULL);
      free(vecArr);
      return TCL_ERROR;
    }
    *vPtrPtr++ = v2Ptr;
    nElem += refSize;
  }
  *vPtrPtr = NULL;

  double* valueArr = (double*)malloc(sizeof(double) * nElem);
  if (valueArr == NULL) {
    Tcl_AppendResult(vPtr->interp, "not enough memory to allocate ", 
		     Itoa(nElem), " vector elements", (char *)NULL);
    return TCL_ERROR;
  }

  // Merge the values from each of the vectors into the current vector
  double* valuePtr = valueArr;
  for (int i = 0; i < refSize; i++) {
    for (Vector** vpp = vecArr; *vpp != NULL; vpp++) {
      *valuePtr++ = (*vpp)->valueArr[i + (*vpp)->first];
    }
  }
  free(vecArr);
  Vec_Reset(vPtr, valueArr, nElem, nElem, TCL_DYNAMIC);

  return TCL_OK;
}

static int MinOp(Vector *vPtr, Tcl_Interp* interp, 
		 int objc, Tcl_Obj* const objv[])
{
  Tcl_SetDoubleObj(Tcl_GetObjResult(interp), Vec_Min(vPtr));
  return TCL_OK;
}

static int NormalizeOp(Vector *vPtr, Tcl_Interp* interp, 
		       int objc, Tcl_Obj* const objv[])
{
  Vec_UpdateRange(vPtr);
  double range = vPtr->max - vPtr->min;
  if (objc > 2) {
    char* string = Tcl_GetString(objv[2]);
    int isNew;
    Vector* v2Ptr = Vec_Create(vPtr->dataPtr, string, string, string, &isNew);
    if (v2Ptr == NULL)
      return TCL_ERROR;

    if (Vec_SetLength(interp, v2Ptr, vPtr->length) != TCL_OK)
      return TCL_ERROR;

    for (int i = 0; i < vPtr->length; i++)
      v2Ptr->valueArr[i] = (vPtr->valueArr[i] - vPtr->min) / range;

    Vec_UpdateRange(v2Ptr);
    if (!isNew) {
      if (v2Ptr->flush) {
	Vec_FlushCache(v2Ptr);
      }
      Vec_UpdateClients(v2Ptr);
    }
  }
  else {
    Tcl_Obj* listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
    for (int i = 0; i < vPtr->length; i++) {
      double norm = (vPtr->valueArr[i] - vPtr->min) / range;
      Tcl_ListObjAppendElement(interp, listObjPtr, 
			       Tcl_NewDoubleObj(norm));
    }
    Tcl_SetObjResult(interp, listObjPtr);
  }

  return TCL_OK;
}

static int NotifyOp(Vector *vPtr, Tcl_Interp* interp, 
		    int objc, Tcl_Obj* const objv[])
{
  enum optionIndices {
    OPTION_ALWAYS, OPTION_NEVER, OPTION_WHENIDLE, 
    OPTION_NOW, OPTION_CANCEL, OPTION_PENDING
  };
  static const char *optionArr[] = {
    "always", "never", "whenidle", "now", "cancel", "pending", NULL
  };

  int option;
  if (Tcl_GetIndexFromObj(interp, objv[2], optionArr, "qualifier", TCL_EXACT,
			  &option) != TCL_OK)
    return TCL_OK;

  switch (option) {
  case OPTION_ALWAYS:
    vPtr->notifyFlags &= ~NOTIFY_WHEN_MASK;
    vPtr->notifyFlags |= NOTIFY_ALWAYS;
    break;
  case OPTION_NEVER:
    vPtr->notifyFlags &= ~NOTIFY_WHEN_MASK;
    vPtr->notifyFlags |= NOTIFY_NEVER;
    break;
  case OPTION_WHENIDLE:
    vPtr->notifyFlags &= ~NOTIFY_WHEN_MASK;
    vPtr->notifyFlags |= NOTIFY_WHENIDLE;
    break;
  case OPTION_NOW:
    // FIXME: How does this play when an update is pending?
    Blt_Vec_NotifyClients(vPtr);
    break;
  case OPTION_CANCEL:
    if (vPtr->notifyFlags & NOTIFY_PENDING) {
      vPtr->notifyFlags &= ~NOTIFY_PENDING;
      Tcl_CancelIdleCall(Blt_Vec_NotifyClients, (ClientData)vPtr);
    }
    break;
  case OPTION_PENDING:
    int boll = (vPtr->notifyFlags & NOTIFY_PENDING);
    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), boll);
    break;
  }	

  return TCL_OK;
}

static int PopulateOp(Vector *vPtr, Tcl_Interp* interp, 
		      int objc, Tcl_Obj* const objv[])
{
  char* string = Tcl_GetString(objv[2]);
  int isNew;
  Vector* v2Ptr = Vec_Create(vPtr->dataPtr, string, string, string, &isNew);
  if (v2Ptr == NULL)
    return TCL_ERROR;

  // Source vector is empty
  if (vPtr->length == 0)
    return TCL_OK;

  int density;
  if (Tcl_GetIntFromObj(interp, objv[3], &density) != TCL_OK)
    return TCL_ERROR;

  if (density < 1) {
    Tcl_AppendResult(interp, "bad density \"", Tcl_GetString(objv[3]), 
		     "\"", (char *)NULL);
    return TCL_ERROR;
  }
  int size = (vPtr->length - 1) * (density + 1) + 1;
  if (Vec_SetLength(interp, v2Ptr, size) != TCL_OK)
    return TCL_ERROR;

  int count = 0;
  double* valuePtr = v2Ptr->valueArr;
  int i;
  for (i = 0; i < (vPtr->length - 1); i++) {
    double range = vPtr->valueArr[i + 1] - vPtr->valueArr[i];
    double slice = range / (double)(density + 1);
    for (int j = 0; j <= density; j++) {
      *valuePtr = vPtr->valueArr[i] + (slice * (double)j);
      valuePtr++;
      count++;
    }
  }
  count++;
  *valuePtr = vPtr->valueArr[i];
  if (!isNew) {
    if (v2Ptr->flush)
      Vec_FlushCache(v2Ptr);
    Vec_UpdateClients(v2Ptr);
  }

  return TCL_OK;
}

static int ValuesOp(Vector *vPtr, Tcl_Interp* interp, 
		    int objc, Tcl_Obj* const objv[])
{
  PrintSwitches switches;
  switches.formatObjPtr = NULL;
  switches.from = 0;
  switches.to = vPtr->length - 1;
  indexSwitch.clientData = vPtr;
  if (ParseSwitches(interp, printSwitches, objc - 2, objv + 2, &switches, 
			BLT_SWITCH_DEFAULTS) < 0)
    return TCL_ERROR;

  if (switches.from > switches.to) {
    // swap positions
    int tmp = switches.to;
    switches.to = switches.from;
    switches.from = tmp;
  }

  if (switches.formatObjPtr == NULL) {
    Tcl_Obj* listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
    for (int i = switches.from; i <= switches.to; i++)
      Tcl_ListObjAppendElement(interp, listObjPtr, 
			       Tcl_NewDoubleObj(vPtr->valueArr[i]));

    Tcl_SetObjResult(interp, listObjPtr);
  }
  else {
    Tcl_DString ds;
    Tcl_DStringInit(&ds);
    const char* fmt = Tcl_GetString(switches.formatObjPtr);
    for (int i = switches.from; i <= switches.to; i++) {
      char buffer[200];
      sprintf(buffer, fmt, vPtr->valueArr[i]);
      Tcl_DStringAppend(&ds, buffer, -1);
    }
    Tcl_DStringResult(interp, &ds);
    Tcl_DStringFree(&ds);
  }

  return TCL_OK;
}

static int RangeOp(Vector *vPtr, Tcl_Interp* interp, 
		   int objc, Tcl_Obj* const objv[])
{
  int first;
  int last;

  if (objc == 2) {
    first = 0;
    last = vPtr->length - 1;
  }
  else if (objc == 4) {
    if ((Vec_GetIndex(interp, vPtr, Tcl_GetString(objv[2]), &first, 
		      INDEX_CHECK, (Blt_VectorIndexProc **) NULL) != TCL_OK) ||
	(Vec_GetIndex(interp, vPtr, Tcl_GetString(objv[3]), &last, 
		      INDEX_CHECK, (Blt_VectorIndexProc **) NULL) != TCL_OK))
      return TCL_ERROR;

  }
  else {
    Tcl_AppendResult(interp, "wrong # args: should be \"",
		     Tcl_GetString(objv[0]), " range ?first last?",
		     (char *)NULL);
    return TCL_ERROR;
  }

  Tcl_Obj* listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
  if (first > last) {
    // Return the list reversed
    for (int i=last; i<=first; i++)
      Tcl_ListObjAppendElement(interp, listObjPtr, 
			       Tcl_NewDoubleObj(vPtr->valueArr[i]));
  }
  else {
    for (int i=first; i<=last; i++)
      Tcl_ListObjAppendElement(interp, listObjPtr, 
			       Tcl_NewDoubleObj(vPtr->valueArr[i]));
  }

  Tcl_SetObjResult(interp, listObjPtr);

  return TCL_OK;
}

static int InRange(double value, double min, double max)
{
  double range = max - min;
  if (range < DBL_EPSILON)
    return (fabs(max - value) < DBL_EPSILON);

  double norm = (value - min) / range;
  return ((norm >= -DBL_EPSILON) && ((norm - 1.0) < DBL_EPSILON));
}

enum NativeFormats {
  FMT_UNKNOWN = -1,
  FMT_UCHAR, FMT_CHAR,
  FMT_USHORT, FMT_SHORT,
  FMT_UINT, FMT_INT,
  FMT_ULONG, FMT_LONG,
  FMT_FLOAT, FMT_DOUBLE
};

/*
 *---------------------------------------------------------------------------
 *
 * GetBinaryFormat
 *
 *      Translates a format string into a native type.  Valid formats are
 *
 *		signed		i1, i2, i4, i8
 *		unsigned 	u1, u2, u4, u8
 *		real		r4, r8, r16
 *
 *	There must be a corresponding native type.  For example, this for
 *	reading 2-byte binary integers from an instrument and converting them
 *	to unsigned shorts or ints.
 *
 *---------------------------------------------------------------------------
 */
static enum NativeFormats GetBinaryFormat(Tcl_Interp* interp, char *string,
					  int *sizePtr)
{
  char c = tolower(string[0]);
  if (Tcl_GetInt(interp, string + 1, sizePtr) != TCL_OK) {
    Tcl_AppendResult(interp, "unknown binary format \"", string,
		     "\": incorrect byte size", (char *)NULL);
    return FMT_UNKNOWN;
  }

  switch (c) {
  case 'r':
    if (*sizePtr == sizeof(double))
      return FMT_DOUBLE;
    else if (*sizePtr == sizeof(float))
      return FMT_FLOAT;

    break;

  case 'i':
    if (*sizePtr == sizeof(char))
      return FMT_CHAR;
    else if (*sizePtr == sizeof(int))
      return FMT_INT;
    else if (*sizePtr == sizeof(long))
      return FMT_LONG;
    else if (*sizePtr == sizeof(short))
      return FMT_SHORT;

    break;

  case 'u':
    if (*sizePtr == sizeof(unsigned char))
      return FMT_UCHAR;
    else if (*sizePtr == sizeof(unsigned int))
      return FMT_UINT;
    else if (*sizePtr == sizeof(unsigned long))
      return FMT_ULONG;
    else if (*sizePtr == sizeof(unsigned short))
      return FMT_USHORT;

    break;

  default:
    Tcl_AppendResult(interp, "unknown binary format \"", string,
		     "\": should be either i#, r#, u# (where # is size in bytes)",
		     (char *)NULL);
    return FMT_UNKNOWN;
  }
  Tcl_AppendResult(interp, "can't handle format \"", string, "\"", 
		   (char *)NULL);

  return FMT_UNKNOWN;
}

static int CopyValues(Vector *vPtr, char *byteArr, enum NativeFormats fmt,
		      int size, int length, int swap, int *indexPtr)
{
  if ((swap) && (size > 1)) {
    int nBytes = size * length;
    for (int i = 0; i < nBytes; i += size) {
      unsigned char* p = (unsigned char *)(byteArr + i);
      int left, right;
      for (left = 0, right = size - 1; left < right; left++, right--) {
	p[left] ^= p[right];
	p[right] ^= p[left];
	p[left] ^= p[right];
      }
    }
  }

  int newSize = *indexPtr + length;
  if (newSize > vPtr->length) {
    if (Vec_ChangeLength(vPtr->interp, vPtr, newSize) != TCL_OK)
      return TCL_ERROR;
  }

#define CopyArrayToVector(vPtr, arr)			\
  for (int i = 0, n = *indexPtr; i < length; i++, n++) {	\
    (vPtr)->valueArr[n] = (double)(arr)[i];		\
  }

  switch (fmt) {
  case FMT_CHAR:
    CopyArrayToVector(vPtr, (char *)byteArr);
    break;

  case FMT_UCHAR:
    CopyArrayToVector(vPtr, (unsigned char *)byteArr);
    break;

  case FMT_INT:
    CopyArrayToVector(vPtr, (int *)byteArr);
    break;

  case FMT_UINT:
    CopyArrayToVector(vPtr, (unsigned int *)byteArr);
    break;

  case FMT_LONG:
    CopyArrayToVector(vPtr, (long *)byteArr);
    break;

  case FMT_ULONG:
    CopyArrayToVector(vPtr, (unsigned long *)byteArr);
    break;

  case FMT_SHORT:
    CopyArrayToVector(vPtr, (short int *)byteArr);
    break;

  case FMT_USHORT:
    CopyArrayToVector(vPtr, (unsigned short int *)byteArr);
    break;

  case FMT_FLOAT:
    CopyArrayToVector(vPtr, (float *)byteArr);
    break;

  case FMT_DOUBLE:
    CopyArrayToVector(vPtr, (double *)byteArr);
    break;

  case FMT_UNKNOWN:
    break;
  }
  *indexPtr += length;
  return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * BinreadOp --
 *
 *	Reads binary values from a TCL channel. Values are either appended to
 *	the end of the vector or placed at a given index (using the "-at"
 *	option), overwriting existing values.  Data is read until EOF is found
 *	on the channel or a specified number of values are read.  (note that
 *	this is not necessarily the same as the number of bytes).
 *
 *	The following flags are supported:
 *		-swap		Swap bytes
 *		-at index	Start writing data at the index.
 *		-format fmt	Specifies the format of the data.
 *
 *	This binary reader was created and graciously donated by Harald Kirsch
 *	(kir@iitb.fhg.de).  Anything that's wrong is due to my (gah) munging
 *	of the code.
 *
 * Results:
 *	Returns a standard TCL result. The interpreter result will contain the
 *	number of values (not the number of bytes) read.
 *
 * Caveats:
 *	Channel reads must end on an element boundary.
 *
 *---------------------------------------------------------------------------
 */

static int BinreadOp(Vector *vPtr, Tcl_Interp* interp, 
		     int objc, Tcl_Obj* const objv[])
{
  enum NativeFormats fmt;

  char* string = Tcl_GetString(objv[2]);
  int mode;
  Tcl_Channel channel = Tcl_GetChannel(interp, string, &mode);
  if (channel == NULL)
    return TCL_ERROR;

  if ((mode & TCL_READABLE) == 0) {
    Tcl_AppendResult(interp, "channel \"", string,
		     "\" wasn't opened for reading", (char *)NULL);
    return TCL_ERROR;
  }
  int first = vPtr->length;
  fmt = FMT_DOUBLE;
  int size = sizeof(double);
  int swap = 0;
  int count = 0;

  if (objc > 3) {
    string = Tcl_GetString(objv[3]);
    if (string[0] != '-') {
      long int value;
      // Get the number of values to read.
      if (Tcl_GetLongFromObj(interp, objv[3], &value) != TCL_OK)
	return TCL_ERROR;

      if (value < 0) {
	Tcl_AppendResult(interp, "count can't be negative", (char *)NULL);
	return TCL_ERROR;
      }
      count = (size_t)value;
      objc--, objv++;
    }
  }

  // Process any option-value pairs that remain.
  for (int i = 3; i < objc; i++) {
    string = Tcl_GetString(objv[i]);
    if (strcmp(string, "-swap") == 0)
      swap = 1;
    else if (strcmp(string, "-format") == 0) {
      i++;
      if (i >= objc) {
	Tcl_AppendResult(interp, "missing arg after \"", string,
			 "\"", (char *)NULL);
	return TCL_ERROR;
      }

      string = Tcl_GetString(objv[i]);
      fmt = GetBinaryFormat(interp, string, &size);
      if (fmt == FMT_UNKNOWN)
	return TCL_ERROR;
    }
    else if (strcmp(string, "-at") == 0) {
      i++;
      if (i >= objc) {
	Tcl_AppendResult(interp, "missing arg after \"", string,
			 "\"", (char *)NULL);
	return TCL_ERROR;
      }

      string = Tcl_GetString(objv[i]);
      if (Vec_GetIndex(interp, vPtr, string, &first, 0, 
			   (Blt_VectorIndexProc **)NULL) != TCL_OK)
	return TCL_ERROR;

      if (first > vPtr->length) {
	Tcl_AppendResult(interp, "index \"", string,
			 "\" is out of range", (char *)NULL);
	return TCL_ERROR;
      }
    }
  }

#define BUFFER_SIZE 1024
  int arraySize = (count == 0) ? BUFFER_SIZE*size : count*size;

  char* byteArr = (char*)malloc(arraySize);
  // FIXME: restore old channel translation later?
  if (Tcl_SetChannelOption(interp, channel, "-translation","binary") != TCL_OK)
    return TCL_ERROR;

  int total = 0;
  while (!Tcl_Eof(channel)) {
    int bytesRead = Tcl_Read(channel, byteArr, arraySize);
    if (bytesRead < 0) {
      Tcl_AppendResult(interp, "error reading channel: ",
		       Tcl_PosixError(interp), (char *)NULL);
      return TCL_ERROR;
    }

    if ((bytesRead % size) != 0) {
      Tcl_AppendResult(interp, "error reading channel: short read",
		       (char *)NULL);
      return TCL_ERROR;
    }

    int length = bytesRead / size;
    if (CopyValues(vPtr, byteArr, fmt, size, length, swap, &first) != TCL_OK)
      return TCL_ERROR;

    total += length;
    if (count > 0)
      break;
  }
  free(byteArr);

  if (vPtr->flush)
    Vec_FlushCache(vPtr);
  Vec_UpdateClients(vPtr);

  // Set the result as the number of values read
  Tcl_SetIntObj(Tcl_GetObjResult(interp), total);

  return TCL_OK;
}

static int SearchOp(Vector *vPtr, Tcl_Interp* interp, 
		    int objc, Tcl_Obj* const objv[])
{
  int wantValue = 0;
  char* string = Tcl_GetString(objv[2]);
  if ((string[0] == '-') && (strcmp(string, "-value") == 0)) {
    wantValue = 1;
    objv++, objc--;
  }
  double min;
  if (Blt_ExprDoubleFromObj(interp, objv[2], &min) != TCL_OK)
    return TCL_ERROR;

  double max = min;
  if (objc > 4) {
    Tcl_AppendResult(interp, "wrong # arguments: should be \"",
		     Tcl_GetString(objv[0]), " search ?-value? min ?max?", 
		     (char *)NULL);
    return TCL_ERROR;
  }

  if ((objc > 3) && (Blt_ExprDoubleFromObj(interp, objv[3], &max) != TCL_OK))
    return TCL_ERROR;

  // Bogus range. Don't bother looking
  if ((min - max) >= DBL_EPSILON)
    return TCL_OK;

  Tcl_Obj* listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
  if (wantValue) {
    for (int i = 0; i < vPtr->length; i++) {
      if (InRange(vPtr->valueArr[i], min, max))
	Tcl_ListObjAppendElement(interp, listObjPtr, 
				 Tcl_NewDoubleObj(vPtr->valueArr[i]));
    }
  }
  else {
    for (int i = 0; i < vPtr->length; i++) {
      if (InRange(vPtr->valueArr[i], min, max))
	Tcl_ListObjAppendElement(interp, listObjPtr,
				 Tcl_NewIntObj(i + vPtr->offset));
    }
  }
  Tcl_SetObjResult(interp, listObjPtr);

  return TCL_OK;
}

static int OffsetOp(Vector *vPtr, Tcl_Interp* interp, 
		    int objc, Tcl_Obj* const objv[])
{
  if (objc == 3) {
    int newOffset;
    if (Tcl_GetIntFromObj(interp, objv[2], &newOffset) != TCL_OK)
      return TCL_ERROR;

    vPtr->offset = newOffset;
  }
  Tcl_SetIntObj(Tcl_GetObjResult(interp), vPtr->offset);

  return TCL_OK;
}

static int RandomOp(Vector *vPtr, Tcl_Interp* interp, 
		    int objc, Tcl_Obj* const objv[])
{
  for (int i = 0; i < vPtr->length; i++)
    vPtr->valueArr[i] = drand48();

  if (vPtr->flush)
    Vec_FlushCache(vPtr);
  Vec_UpdateClients(vPtr);

  return TCL_OK;
}

static int SeqOp(Vector *vPtr, Tcl_Interp* interp, 
		 int objc, Tcl_Obj* const objv[])
{
  double start;
  if (Blt_ExprDoubleFromObj(interp, objv[2], &start) != TCL_OK)
    return TCL_ERROR;

  double stop;
  if (Blt_ExprDoubleFromObj(interp, objv[3], &stop) != TCL_OK)
    return TCL_ERROR;

  int n = vPtr->length;
  if ((objc > 4) && (Blt_ExprIntFromObj(interp, objv[4], &n) != TCL_OK))
    return TCL_ERROR;

  if (n > 1) {
    if (Vec_SetLength(interp, vPtr, n) != TCL_OK)
      return TCL_ERROR;

    double step = (stop - start) / (double)(n - 1);
    for (int i = 0; i < n; i++)
      vPtr->valueArr[i] = start + (step * i);

    if (vPtr->flush)
      Vec_FlushCache(vPtr);

    Vec_UpdateClients(vPtr);
  }
  return TCL_OK;
}

static int SetOp(Vector *vPtr, Tcl_Interp* interp, 
		 int objc, Tcl_Obj* const objv[])
{
  int nElem;
  Tcl_Obj **elemObjArr;

  // The source can be either a list of numbers or another vector.

  Vector* v2Ptr = Vec_ParseElement((Tcl_Interp *)NULL, vPtr->dataPtr, 
				   Tcl_GetString(objv[2]), NULL, 
				   NS_SEARCH_BOTH);
  int result;
  if (v2Ptr != NULL) {
    if (vPtr == v2Ptr) {
      // Source and destination vectors are the same.  Copy the source
      // first into a temporary vector to avoid memory overlaps.
      Vector* tmpPtr = Vec_New(vPtr->dataPtr);
      result = Vec_Duplicate(tmpPtr, v2Ptr);
      if (result == TCL_OK) {
	result = Vec_Duplicate(vPtr, tmpPtr);
      }
      Vec_Free(tmpPtr);
    }
    else
      result = Vec_Duplicate(vPtr, v2Ptr);
  }
  else if (Tcl_ListObjGetElements(interp, objv[2], &nElem, &elemObjArr) 
	   == TCL_OK)
    result = CopyList(vPtr, interp, nElem, elemObjArr);
  else
    return TCL_ERROR;

  if (result == TCL_OK) {
    // The vector has changed; so flush the array indices (they're wrong
    // now), find the new range of the data, and notify the vector's
    //clients that it's been modified.
    if (vPtr->flush)
      Vec_FlushCache(vPtr);
    Vec_UpdateClients(vPtr);
  }

  return result;
}

static int SimplifyOp(Vector *vPtr, Tcl_Interp* interp, 
		      int objc, Tcl_Obj* const objv[])
{
  double tolerance = 10.0;

  int nPoints = vPtr->length / 2;
  int* simple  = (int*)malloc(nPoints * sizeof(int));
  Point2d* reduced = (Point2d*)malloc(nPoints * sizeof(Point2d));
  Point2d* orig = (Point2d *)vPtr->valueArr;
  int n = Blt_SimplifyLine(orig, 0, nPoints - 1, tolerance, simple);
  for (int i = 0; i < n; i++)
    reduced[i] = orig[simple[i]];

  free(simple);
  Vec_Reset(vPtr, (double *)reduced, n * 2, vPtr->length, TCL_DYNAMIC);
  // The vector has changed; so flush the array indices (they're wrong
  // now), find the new range of the data, and notify the vector's
  // clients that it's been modified.
  if (vPtr->flush)
    Vec_FlushCache(vPtr);
  Vec_UpdateClients(vPtr);

  return TCL_OK;
}

static int SplitOp(Vector *vPtr, Tcl_Interp* interp, 
		   int objc, Tcl_Obj* const objv[])
{
  int nVectors = objc - 2;
  if ((vPtr->length % nVectors) != 0) {
    Tcl_AppendResult(interp, "can't split vector \"", vPtr->name, 
		     "\" into ", Itoa(nVectors), " even parts.", (char *)NULL);
    return TCL_ERROR;
  }

  if (nVectors > 0) {
    int extra = vPtr->length / nVectors;
    for (int i = 0; i < nVectors; i++) {
      char* string = Tcl_GetString(objv[i+2]);
      int isNew;
      Vector* v2Ptr = Vec_Create(vPtr->dataPtr, string, string, string, &isNew);
      int oldSize = v2Ptr->length;
      int newSize = oldSize + extra;
      if (Vec_SetLength(interp, v2Ptr, newSize) != TCL_OK)
	return TCL_ERROR;

      int j,k;
      for (j = i, k = oldSize; j < vPtr->length; j += nVectors, k++)
	v2Ptr->valueArr[k] = vPtr->valueArr[j];

      Vec_UpdateClients(v2Ptr);
      if (v2Ptr->flush) {
	Vec_FlushCache(v2Ptr);
      }
    }
  }
  return TCL_OK;
}


// Pointer to the array of values currently being sorted.
static Vector **sortVectors;
// Indicates the ordering of the sort. If non-zero, the vectors are sorted in
// decreasing order
static int sortDecreasing;
static int nSortVectors;

static int CompareVectors(void *a, void *b)
{
  int sign = (sortDecreasing) ? -1 : 1;
  for (int i = 0; i < nSortVectors; i++) {
    Vector* vPtr = sortVectors[i];
    double delta = vPtr->valueArr[*(int *)a] - vPtr->valueArr[*(int *)b];
    if (delta < 0.0)
      return (-1 * sign);
    else if (delta > 0.0)
      return (1 * sign);
  }

  return 0;
}

size_t* Blt::Vec_SortMap(Vector **vectors, int nVectors)
{
  Vector *vPtr = *vectors;
  int length = vPtr->last - vPtr->first + 1;
  size_t* map = (size_t*)malloc(sizeof(size_t) * length);
  for (int i = vPtr->first; i <= vPtr->last; i++)
    map[i] = i;

  // Set global variables for sorting routine
  sortVectors = vectors;
  nSortVectors = nVectors;
  qsort((char *)map, length, sizeof(size_t),(QSortCompareProc *)CompareVectors);

  return map;
}

static size_t* SortVectors(Vector *vPtr, Tcl_Interp* interp, 
			   int objc, Tcl_Obj* const objv[])
{

  Vector** vectors = (Vector**)malloc(sizeof(Vector *) * (objc + 1));
  vectors[0] = vPtr;
  size_t* map = NULL;
  for (int i = 0; i < objc; i++) {
    Vector* v2Ptr;
    if (Vec_LookupName(vPtr->dataPtr, Tcl_GetString(objv[i]), 
			   &v2Ptr) != TCL_OK)
      goto error;

    if (v2Ptr->length != vPtr->length) {
      Tcl_AppendResult(interp, "vector \"", v2Ptr->name,
		       "\" is not the same size as \"", vPtr->name, "\"",
		       (char *)NULL);
      goto error;
    }
    vectors[i + 1] = v2Ptr;
  }
  map = Vec_SortMap(vectors, objc + 1);

 error:
  free(vectors);

  return map;
}

static int SortOp(Vector *vPtr, Tcl_Interp* interp, 
		  int objc, Tcl_Obj* const objv[])
{
  sortDecreasing = 0;
  SortSwitches switches;
  switches.flags = 0;
  int i = ParseSwitches(interp, sortSwitches, objc - 2, objv + 2, &switches, 
			BLT_SWITCH_OBJV_PARTIAL);
  if (i < 0)
    return TCL_ERROR;

  objc -= i, objv += i;
  sortDecreasing = (switches.flags & SORT_DECREASING);

  size_t *map = (objc > 2) ? SortVectors(vPtr, interp, objc - 2, objv + 2) :
    Vec_SortMap(&vPtr, 1);

  if (map == NULL)
    return TCL_ERROR;

  int sortLength = vPtr->length;

  // Create an array to store a copy of the current values of the
  // vector. We'll merge the values back into the vector based upon the
  // indices found in the index array.
  size_t nBytes = sizeof(double) * sortLength;
  double* copy = (double*)malloc(nBytes);
  memcpy((char *)copy, (char *)vPtr->valueArr, nBytes);
  if (switches.flags & SORT_UNIQUE) {
    int count =1;
    for (int n = 1; n < sortLength; n++) {
      size_t next = map[n];
      size_t prev = map[n - 1];
      if (copy[next] != copy[prev]) {
	map[count] = next;
	count++;
      }
    }
    sortLength = count;
    nBytes = sortLength * sizeof(double);
  }

  if (sortLength != vPtr->length)
    Vec_SetLength(interp, vPtr, sortLength);

  for (int n = 0; n < sortLength; n++)
    vPtr->valueArr[n] = copy[map[n]];

  if (vPtr->flush)
    Vec_FlushCache(vPtr);
  Vec_UpdateClients(vPtr);

  // Now sort any other vectors in the same fashion.  The vectors must be
  // the same size as the map though
  int result = TCL_ERROR;
  for (int i = 2; i < objc; i++) {
    Vector *v2Ptr;
    if (Vec_LookupName(vPtr->dataPtr, Tcl_GetString(objv[i]), &v2Ptr) != TCL_OK)
      goto error;

    if (sortLength != v2Ptr->length)
      Vec_SetLength(interp, v2Ptr, sortLength);

    memcpy((char *)copy, (char *)v2Ptr->valueArr, nBytes);
    for (int n = 0; n < sortLength; n++)
      v2Ptr->valueArr[n] = copy[map[n]];

    Vec_UpdateClients(v2Ptr);
    if (v2Ptr->flush)
      Vec_FlushCache(v2Ptr);
  }
  result = TCL_OK;

 error:
  free(copy);
  free(map);

  return result;
}

static int InstExprOp(Vector *vPtr, Tcl_Interp* interp, 
		      int objc, Tcl_Obj* const objv[])
{
  if (Blt_ExprVector(interp, Tcl_GetString(objv[2]), (Blt_Vector *)vPtr) != TCL_OK)
    return TCL_ERROR;

  if (vPtr->flush)
    Vec_FlushCache(vPtr);
  Vec_UpdateClients(vPtr);

  return TCL_OK;
}

static int ArithOp(Vector *vPtr, Tcl_Interp* interp, 
		   int objc, Tcl_Obj* const objv[])
{
  double value;
  double scalar;

  Vector* v2Ptr = Vec_ParseElement((Tcl_Interp *)NULL, vPtr->dataPtr, 
				   Tcl_GetString(objv[2]), NULL, 
				   NS_SEARCH_BOTH);
  if (v2Ptr != NULL) {
    int length = v2Ptr->last - v2Ptr->first + 1;
    if (length != vPtr->length) {
      Tcl_AppendResult(interp, "vectors \"", Tcl_GetString(objv[0]), 
		       "\" and \"", Tcl_GetString(objv[2]), 
		       "\" are not the same length", (char *)NULL);
      return TCL_ERROR;
    }

    char* string = Tcl_GetString(objv[1]);
    Tcl_Obj* listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
    switch (string[0]) {
    case '*':
      for (int i = 0, j = v2Ptr->first; i < vPtr->length; i++, j++) {
	value = vPtr->valueArr[i] * v2Ptr->valueArr[j];
	Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(value));
      }
      break;

    case '/':
      for (int i = 0, j = v2Ptr->first; i < vPtr->length; i++, j++) {
	value = vPtr->valueArr[i] / v2Ptr->valueArr[j];
	Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(value));
      }
      break;

    case '-':
      for (int i = 0, j = v2Ptr->first; i < vPtr->length; i++, j++) {
	value = vPtr->valueArr[i] - v2Ptr->valueArr[j];
	Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(value));
      }
      break;

    case '+':
      for (int i = 0, j = v2Ptr->first; i < vPtr->length; i++, j++) {
	value = vPtr->valueArr[i] + v2Ptr->valueArr[j];
	Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(value));
      }
      break;
    }
    Tcl_SetObjResult(interp, listObjPtr);

  }
  else if (Blt_ExprDoubleFromObj(interp, objv[2], &scalar) == TCL_OK) {
    Tcl_Obj* listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
    char* string = Tcl_GetString(objv[1]);
    switch (string[0]) {
    case '*':
      for (int i = 0; i < vPtr->length; i++) {
	value = vPtr->valueArr[i] * scalar;
	Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(value));
      }
      break;

    case '/':
      for (int i = 0; i < vPtr->length; i++) {
	value = vPtr->valueArr[i] / scalar;
	Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(value));
      }
      break;

    case '-':
      for (int i = 0; i < vPtr->length; i++) {
	value = vPtr->valueArr[i] - scalar;
	Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(value));
      }
      break;

    case '+':
      for (int i = 0; i < vPtr->length; i++) {
	value = vPtr->valueArr[i] + scalar;
	Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(value));
      }
      break;
    }
    Tcl_SetObjResult(interp, listObjPtr);
  }
  else
    return TCL_ERROR;

  return TCL_OK;
}

static Blt_OpSpec vectorInstOps[] =
  {
    {"*",         1, (void*)ArithOp,     3, 3, "item",},	/*Deprecated*/
    {"+",         1, (void*)ArithOp,     3, 3, "item",},	/*Deprecated*/
    {"-",         1, (void*)ArithOp,     3, 3, "item",},	/*Deprecated*/
    {"/",         1, (void*)ArithOp,     3, 3, "item",},	/*Deprecated*/
    {"append",    1, (void*)AppendOp,    3, 0, "item ?item...?",},
    {"binread",   1, (void*)BinreadOp,   3, 0, "channel ?numValues? ?flags?",},
    {"clear",     1, (void*)ClearOp,     2, 2, "",},
    {"delete",    2, (void*)DeleteOp,    2, 0, "index ?index...?",},
    {"dup",       2, (void*)DupOp,       3, 0, "vecName",},
    {"expr",      1, (void*)InstExprOp,  3, 3, "expression",},
    {"fft",	  1, (void*)FFTOp,	  3, 0, "vecName ?switches?",},
    {"index",     3, (void*)IndexOp,     3, 4, "index ?value?",},
    {"inversefft",3, (void*)InverseFFTOp,4, 4, "vecName vecName",},
    {"length",    1, (void*)LengthOp,    2, 3, "?newSize?",},
    {"max",       2, (void*)MaxOp,       2, 2, "",},
    {"merge",     2, (void*)MergeOp,     3, 0, "vecName ?vecName...?",},
    {"min",       2, (void*)MinOp,       2, 2, "",},
    {"normalize", 3, (void*)NormalizeOp, 2, 3, "?vecName?",},	/*Deprecated*/
    {"notify",    3, (void*)NotifyOp,    3, 3, "keyword",},
    {"offset",    1, (void*)OffsetOp,    2, 3, "?offset?",},
    {"populate",  1, (void*)PopulateOp,  4, 4, "vecName density",},
    {"random",    4, (void*)RandomOp,    2, 2, "",},	/*Deprecated*/
    {"range",     4, (void*)RangeOp,     2, 4, "first last",},
    {"search",    3, (void*)SearchOp,    3, 5, "?-value? value ?value?",},
    {"seq",       3, (void*)SeqOp,       4, 5, "begin end ?num?",},
    {"set",       3, (void*)SetOp,       3, 3, "list",},
    {"simplify",  2, (void*)SimplifyOp,  2, 2, },
    {"sort",      2, (void*)SortOp,      2, 0, "?switches? ?vecName...?",},
    {"split",     2, (void*)SplitOp,     2, 0, "?vecName...?",},
    {"values",    3, (void*)ValuesOp,    2, 0, "?switches?",},
    {"variable",  3, (void*)MapOp,       2, 3, "?varName?",},
  };

static int nInstOps = sizeof(vectorInstOps) / sizeof(Blt_OpSpec);

int Blt::Vec_InstCmd(ClientData clientData, Tcl_Interp* interp, 
		    int objc, Tcl_Obj* const objv[])
{
  Vector* vPtr = (Vector*)clientData;
  vPtr->first = 0;
  vPtr->last = vPtr->length - 1;
  VectorCmdProc *proc =
    (VectorCmdProc*)GetOpFromObj(interp, nInstOps, vectorInstOps, 
				 BLT_OP_ARG1, objc, objv, 0);
  if (proc == NULL)
    return TCL_ERROR;

  return (*proc) (vPtr, interp, objc, objv);
}

#define MAX_ERR_MSG 1023
static char message[MAX_ERR_MSG + 1];
char* Blt::Vec_VarTrace(ClientData clientData, Tcl_Interp* interp, 
		       const char *part1, const char *part2, int flags)
{
  Blt_VectorIndexProc *indexProc;
  Vector* vPtr = (Vector*)clientData;

  if (part2 == NULL) {
    if (flags & TCL_TRACE_UNSETS) {
      free((void*)(vPtr->arrayName));
      vPtr->arrayName = NULL;
      if (vPtr->freeOnUnset)
	Vec_Free(vPtr);
    }

    return NULL;
  }

  int first;
  int last;
  int varFlags;

  if (Vec_GetIndexRange(interp, vPtr, part2, INDEX_ALL_FLAGS, &indexProc)
      != TCL_OK)
    goto error;

  first = vPtr->first;
  last = vPtr->last;
  varFlags = TCL_LEAVE_ERR_MSG | (TCL_GLOBAL_ONLY & flags);
  if (flags & TCL_TRACE_WRITES) {
    // Tried to set "min" or "max"
    if (first == SPECIAL_INDEX)
      return (char *)"read-only index";

    Tcl_Obj* objPtr = Tcl_GetVar2Ex(interp, part1, part2, varFlags);
    if (objPtr == NULL)
      goto error;

    double value;
    if (Blt_ExprDoubleFromObj(interp, objPtr, &value) != TCL_OK) {
      // Single numeric index. Reset the array element to
      // its old value on errors
      if ((last == first) && (first >= 0))
	Tcl_SetVar2Ex(interp, part1, part2, objPtr, varFlags);
      goto error;
    }

    if (first == vPtr->length) {
      if (Vec_ChangeLength((Tcl_Interp *)NULL, vPtr, vPtr->length + 1)
	  != TCL_OK)
	return (char *)"error resizing vector";
    }

    // Set possibly an entire range of values
    ReplicateValue(vPtr, first, last, value);
  }
  else if (flags & TCL_TRACE_READS) {
    Tcl_Obj *objPtr;

    if (vPtr->length == 0) {
      if (Tcl_SetVar2(interp, part1, part2, "", varFlags) == NULL)
	goto error;

      return NULL;
    }

    if  (first == vPtr->length)
      return (char *)"write-only index";

    if (first == last) {
      double value;
      if (first >= 0)
	value = vPtr->valueArr[first];
      else {
	vPtr->first = 0, vPtr->last = vPtr->length - 1;
	value = (*indexProc) ((Blt_Vector *) vPtr);
      }

      objPtr = Tcl_NewDoubleObj(value);
      if (Tcl_SetVar2Ex(interp, part1, part2, objPtr, varFlags) == NULL) {
	Tcl_DecrRefCount(objPtr);
	goto error;
      }
    }
    else {
      objPtr = GetValues(vPtr, first, last);
      if (Tcl_SetVar2Ex(interp, part1, part2, objPtr, varFlags) == NULL)
	Tcl_DecrRefCount(objPtr);
	goto error;
    }
  }
  else if (flags & TCL_TRACE_UNSETS) {
    if ((first == vPtr->length) || (first == SPECIAL_INDEX))
      return (char *)"special vector index";

    // Collapse the vector from the point of the first unset element.
    // Also flush any array variable entries so that the shift is
    // reflected when the array variable is read.
    for (int i = first, j = last + 1; j < vPtr->length; i++, j++)
      vPtr->valueArr[i] = vPtr->valueArr[j];

    vPtr->length -= ((last - first) + 1);
    if (vPtr->flush)
      Vec_FlushCache(vPtr);

  }
  else
    return (char *)"unknown variable trace flag";

  if (flags & (TCL_TRACE_UNSETS | TCL_TRACE_WRITES))
    Vec_UpdateClients(vPtr);

  Tcl_ResetResult(interp);
  return NULL;

 error: 
  strncpy(message, Tcl_GetStringResult(interp), MAX_ERR_MSG);
  message[MAX_ERR_MSG] = '\0';
  return message;
}
