
/*
  Copyright (c) 2009, Pavel Demin

  All rights reserved.

  Redistribution and use in source and binary forms,
  with or without modification, are permitted
  provided that the following conditions are met:

      * Redistributions of source code must retain
        the above copyright notice, this list of conditions
        and the following disclaimer.
      * Redistributions in binary form must reproduce
        the above copyright notice, this list of conditions
        and the following disclaimer in the documentation
        and/or other materials provided with the distribution.
      * Neither the name of the SRMlite nor the names of its
        contributors may be used to endorse or promote products
        derived from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <tcl.h>

#include <stdint.h>

#include <usb.h>
#include <blt.h>

/* ----------------------------------------------------------------- */

typedef struct UsbDevice {
  Tcl_Command token;
  usb_dev_handle *device;
} UsbDevice;

/* ----------------------------------------------------------------- */

static void
UsbDeviceDestroy(ClientData clientData)
{
  UsbDevice *statePtr = (UsbDevice *) clientData;

  if(statePtr->device != NULL)
  {
    usb_close(statePtr->device);
  }

  ckfree((char *)statePtr);
}

/* ----------------------------------------------------------------- */

static int
UsbReadRawObjCmd(UsbDevice *statePtr, Tcl_Interp *interp, Tcl_Obj *CONST obj)
{
  unsigned char *data;
  int read_size, size;

  Tcl_Obj *result;

  if(TCL_OK != Tcl_GetIntFromObj(interp, obj, &size))
  {
    Tcl_AppendResult(interp, "Parameter size is not an integer", NULL);
    return TCL_ERROR;
  }

  data = ckalloc((unsigned int)(size + 2));
  read_size = usb_bulk_read(statePtr->device, 0x86, data, size + 2, 1000);

  if(read_size < 0)
  {
    ckfree(data);

    Tcl_AppendResult(interp, usb_strerror(), NULL);

    return TCL_ERROR;
  }

  if(data[0] > 0)
  {
    ckfree(data);

    Tcl_AppendResult(interp, "Busy", NULL);

    return 5;
  }

  if(read_size != size + 2)
  {
    ckfree(data);

    Tcl_AppendResult(interp, "Read less than requested", NULL);

    return TCL_ERROR;
  }

  result = Tcl_NewByteArrayObj(data + 2, size);

  ckfree(data);

  Tcl_SetObjResult(interp, result);

  return TCL_OK;
}

/* ----------------------------------------------------------------- */

static int
UsbReadHexObjCmd(UsbDevice *statePtr, Tcl_Interp *interp, Tcl_Obj *CONST objv[])
{
  unsigned char *buffer;
  unsigned char *data;
  int word_size, char_size, data_size, read_size, i, j, pos;

  unsigned char hexval[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

  Tcl_Obj *result;

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[0], &word_size))
  {
    Tcl_AppendResult(interp, "Parameter width is not an integer", NULL);
    return TCL_ERROR;
  }

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[1], &data_size))
  {
    Tcl_AppendResult(interp, "Parameter size is not an integer", NULL);
    return TCL_ERROR;
  }

  char_size = 3 + 2*word_size;

  data = ckalloc((unsigned int)(data_size*word_size + 2));
  buffer = ckalloc((unsigned int)(data_size*char_size));

  read_size = usb_bulk_read(statePtr->device, 0x86, data, data_size*word_size + 2, 1000);

  if(read_size < 0)
  {
    ckfree(buffer);
    ckfree(data);

    Tcl_AppendResult(interp, usb_strerror(), NULL);

    return TCL_ERROR;
  }

  if(data[0] > 0)
  {
    ckfree(buffer);
    ckfree(data);

    Tcl_AppendResult(interp, "Busy", NULL);

    return 5;
  }

  if(read_size != data_size*word_size + 2)
  {
    ckfree(buffer);
    ckfree(data);

    Tcl_AppendResult(interp, "Read less than requested", NULL);

    return TCL_ERROR;
  }

  for(i = 0; i < data_size; ++i)
  {
    buffer[i*char_size + 0] = '0';
    buffer[i*char_size + 1] = 'x';
    for(j = 1; j <= word_size; ++j)
    {
      pos = 2 + i*word_size + word_size - j;
      buffer[i*char_size + 0 + 2*j] = hexval[(data[pos] >> 4) & 0x0F];
      buffer[i*char_size + 1 + 2*j] = hexval[(data[pos]) & 0x0F];
    }
    buffer[i*char_size + 2*word_size + 2] = ' ';
  }

  result = Tcl_NewStringObj(buffer, data_size*char_size);

  ckfree(buffer);
  ckfree(data);

  Tcl_SetObjResult(interp, result);

  return TCL_OK;
}

/* ----------------------------------------------------------------- */

static int
UsbReadBltObjCmd(UsbDevice *statePtr, Tcl_Interp *interp, Tcl_Obj *CONST objv[])
{
  unsigned char *data;
  int word_size, data_size, read_size, i, j, pos;
  unsigned long long int value;

  char *name;
  Blt_Vector *vec;

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[0], &word_size))
  {
    Tcl_AppendResult(interp, "Parameter width is not an integer", NULL);
    return TCL_ERROR;
  }

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[1], &data_size))
  {
    Tcl_AppendResult(interp, "Parameter size is not an integer", NULL);
    return TCL_ERROR;
  }

  name =  Tcl_GetString(objv[2]);
  if(TCL_OK != Blt_GetVector(interp, name, &vec))
  {
    Tcl_AppendResult(interp, "Cannot find BLT vector", name, NULL);
    return TCL_ERROR;
  }

  if(Blt_VecSize(vec) < data_size)
  {
    Tcl_AppendResult(interp, "BLT vector size is less than the data size", NULL);
    return TCL_ERROR;
  }

  data = ckalloc((unsigned int)(data_size*word_size + 2));

  read_size = usb_bulk_read(statePtr->device, 0x86, data, data_size*word_size + 2, 1000);

  if(read_size < 0)
  {
    ckfree(data);

    Tcl_AppendResult(interp, usb_strerror(), NULL);

    return TCL_ERROR;
  }

  if(data[0] > 0)
  {
    ckfree(data);

    Tcl_AppendResult(interp, "Busy", NULL);

    return 5;
  }

  if(read_size != data_size*word_size + 2)
  {
    ckfree(data);

    Tcl_AppendResult(interp, "Read less than requested", NULL);

    return TCL_ERROR;
  }

  for(i = 0; i < data_size; ++i)
  {
    value = 0;
    for(j = 1; j <= word_size; ++j)
    {
      pos = 2 + i*word_size + word_size - j;
      value = (value << 8) | data[pos];
    }
    Blt_VecData(vec)[i] = (double)value;
  }

  ckfree(data);

  Blt_ResetVector(vec, Blt_VecData(vec), data_size, Blt_VecSize(vec), TCL_STATIC);

  return TCL_OK;
}

/* ----------------------------------------------------------------- */

static int
UsbReadOscObjCmd(UsbDevice *statePtr, Tcl_Interp *interp, Tcl_Obj *CONST objv[])
{
  unsigned char *data;
  int data_size, read_size, i, j, pos, value;

  Tcl_DictSearch search;
  Tcl_Obj *key, *object;
  int done;

  char *name;
  Blt_Vector *vec[15];

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[0], &data_size))
  {
    Tcl_AppendResult(interp, "Parameter size is not an integer", NULL);
    return TCL_ERROR;
  }

  if(TCL_OK != Tcl_DictObjFirst(interp, objv[1], &search, &key, &object, &done))
  {
    Tcl_AppendResult(interp, "Cannot read dict variable", name, NULL);
    return TCL_ERROR;
  }

  for(i = 0; (i < 15) && (!done) ; ++i, Tcl_DictObjNext(&search, &key, &object, &done))
  {
    name =  Tcl_GetString(object);
    if(TCL_OK != Blt_GetVector(interp, name, &vec[i]))
    {
      Tcl_DictObjDone(&search);
      Tcl_AppendResult(interp, "Cannot find BLT vector", name, NULL);
      return TCL_ERROR;
    }
    if(Blt_VecSize(vec[i]) < data_size)
    {
      Tcl_DictObjDone(&search);
      Tcl_AppendResult(interp, "BLT vector size is less than the data size", NULL);
      return TCL_ERROR;
    }
  }
  Tcl_DictObjDone(&search);

  data = ckalloc((unsigned int)(data_size * 8 + 2));

  read_size = usb_bulk_read(statePtr->device, 0x86, data, data_size * 8 + 2, 1000);

  if(read_size < 0)
  {
    ckfree(data);

    Tcl_AppendResult(interp, usb_strerror(), NULL);

    return TCL_ERROR;
  }

  if(data[0] > 0)
  {
    ckfree(data);

    Tcl_AppendResult(interp, "Busy", NULL);

    return 5;
  }

  if(read_size != data_size * 8 + 2)
  {
    ckfree(data);

    Tcl_AppendResult(interp, "Read less than requested", NULL);

    return TCL_ERROR;
  }

  for(i = 0; i < data_size; ++i)
  {
    pos = 2 + i * 8;

    value = data[pos + 1] & 0x0F;
    value = (value << 8) | data[pos];
    Blt_VecData(vec[0])[i] = (double)value;

    value = data[pos + 2];
    value = (value << 4) | (data[pos + 1] >> 4);
    Blt_VecData(vec[1])[i] = (double)value;

    value = data[pos + 4] & 0x0F;
    value = (value << 8) | data[pos + 3];
    Blt_VecData(vec[2])[i] = (double)value;

    value = data[pos + 5];
    value = (value << 4) | (data[pos + 4] >> 4);

    for(j = 0; j < 12; ++j)
    {
      Blt_VecData(vec[j+3])[i] = (double)((value & 1) + (j * 2));
      value >>= 1;
    }
  }

  ckfree(data);

  for(i = 0; i < 15; ++i)
  {
    Blt_ResetVector(vec[i], Blt_VecData(vec[i]), data_size, Blt_VecSize(vec[i]), TCL_STATIC);
  }

  return TCL_OK;
}

/* ----------------------------------------------------------------- */

static int
UsbWriteRawObjCmd(UsbDevice *statePtr, Tcl_Interp *interp, Tcl_Obj *CONST obj)
{
  unsigned char *data;
  int size, i;

  Tcl_Obj *result;

  data = Tcl_GetByteArrayFromObj(obj, &size);
/*
  for(i = 0; i < size; ++i) printf(" %02x", data[i]);
  printf("\n");
  printf("size = %d\n", size);
  fflush(stdout);
*/
//  size = usb_bulk_write(statePtr->device , 0x06, data, size, 1000);
  size = usb_bulk_write(statePtr->device , 0x08, data, size, 1000);

  if(size >= 0)
  {
    result = Tcl_NewIntObj(size);

    Tcl_SetObjResult(interp, result);

    return TCL_OK;
  }
  else
  {
    Tcl_AppendResult(interp, usb_strerror(), NULL);

    return TCL_ERROR;
  }
}

/* ----------------------------------------------------------------- */

static int
UsbControlObjCmd(UsbDevice *statePtr, Tcl_Interp *interp, Tcl_Obj *CONST objv[])
{
  unsigned char *data;
  int addr, size, i;

  Tcl_Obj *result;

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[0], &addr))
  {
    Tcl_AppendResult(interp, "Parameter addr is not an integer", NULL);
    return TCL_ERROR;
  }

  data = Tcl_GetByteArrayFromObj(objv[1], &size);

  size = usb_control_msg(statePtr->device, 0x40, 0xa0, addr, 0, data, size, 1000);

  if(size >= 0)
  {
    result = Tcl_NewIntObj(size);

    Tcl_SetObjResult(interp, result);

    return TCL_OK;
  }
  else
  {
    Tcl_AppendResult(interp, usb_strerror(), NULL);

    return TCL_ERROR;
  }
}


/* ----------------------------------------------------------------- */

static int
UsbDeviceObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
  char *option;

  UsbDevice *statePtr = (UsbDevice *) clientData;

  if(objc < 2)
  {
    Tcl_WrongNumArgs(interp, 1, objv, "command ?arg?");
    return TCL_ERROR;
  }

  option = Tcl_GetStringFromObj(objv[1], NULL);

  if(strcmp(option, "readRaw") == 0)
  {
    if(objc != 3)
    {
      Tcl_WrongNumArgs(interp, 1, objv, "readRaw size");
      return TCL_ERROR;
    }
    return UsbReadRawObjCmd(statePtr, interp, objv[2]);
  }
  else if(strcmp(option, "readHex") == 0)
  {
    if(objc != 4)
    {
      Tcl_WrongNumArgs(interp, 1, objv, "readHex width size");
      return TCL_ERROR;
    }
    return UsbReadHexObjCmd(statePtr, interp, objv + 2);
  }
  else if(strcmp(option, "readBlt") == 0)
  {
    if(objc != 5)
    {
      Tcl_WrongNumArgs(interp, 1, objv, "readBlt width size vector");
      return TCL_ERROR;
    }
    return UsbReadBltObjCmd(statePtr, interp, objv + 2);
  }
  else if(strcmp(option, "readOsc") == 0)
  {
    if(objc != 4)
    {
      Tcl_WrongNumArgs(interp, 1, objv, "readOsc size dict");
      return TCL_ERROR;
    }
    return UsbReadOscObjCmd(statePtr, interp, objv + 2);
  }
  else if(strcmp(option, "writeRaw") == 0)
  {
    if(objc != 3)
    {
      Tcl_WrongNumArgs(interp, 1, objv, "writeRaw data");
      return TCL_ERROR;
    }
    return UsbWriteRawObjCmd(statePtr, interp, objv[2]);
  }
  else if(strcmp(option, "control") == 0)
  {
    if(objc != 4)
    {
      Tcl_WrongNumArgs(interp, 1, objv, "control addr data");
      return TCL_ERROR;
    }
    return UsbControlObjCmd(statePtr, interp, objv + 2);
  }
  else if(strcmp(option, "disconnect") == 0)
  {
    if(objc != 2)
    {
      Tcl_WrongNumArgs(interp, 1, objv, "disconnect");
      return TCL_ERROR;
    }
    Tcl_DeleteCommandFromToken(interp, statePtr->token);
    return TCL_OK;
  }

  Tcl_AppendResult(interp, "bad option \"", option,
    "\": must be read, read8, read16, read32, write, control, convert or disconnect", NULL);
  return TCL_ERROR;
}

/* ----------------------------------------------------------------- */

static int
UsbConnectObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
  char cmdName[256];
  Tcl_CmdInfo cmdInfo;
  int cmdCounter;

  int idVendor, idProduct;
  int bConfigurationValue, bInterfaceNumber, bAlternateSetting;

  UsbDevice *statePtr = (UsbDevice *) clientData;

  struct usb_bus *bus;
  struct usb_device *dev;
  struct usb_device *current_device;
  struct usb_dev_handle *device_handle;

  int rc;

  if(objc != 6)
  {
    Tcl_WrongNumArgs(interp, 1, objv, "idVendor idProduct bConfigurationValue bInterfaceNumber bAlternateSetting");
    return TCL_ERROR;
  }

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[1], &idVendor))
  {
    Tcl_AppendResult(interp, "Parameter idVendor is not an integer", NULL);
    return TCL_ERROR;
  }

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[2], &idProduct))
  {
    Tcl_AppendResult(interp, "Parameter idProduct is not an integer", NULL);
    return TCL_ERROR;
  }

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[3], &bConfigurationValue))
  {
    Tcl_AppendResult(interp, "Parameter bConfigurationValue is not an integer", NULL);
    return TCL_ERROR;
  }

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[4], &bInterfaceNumber))
  {
    Tcl_AppendResult(interp, "Parameter bInterfaceNumber is not an integer", NULL);
    return TCL_ERROR;
  }

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[5], &bAlternateSetting))
  {
    Tcl_AppendResult(interp, "Parameter bAlternateSetting is not an integer", NULL);
    return TCL_ERROR;
  }

  usb_find_busses();
  usb_find_devices();

  current_device = NULL;
  for(bus = usb_get_busses(); bus && current_device == NULL; bus = bus->next)
  {
    for(dev = bus->devices; dev && current_device == NULL; dev = dev->next)
    {
   		if((dev->descriptor.idVendor == idVendor) &&
         (dev->descriptor.idProduct == idProduct)) {
          current_device = dev;
      }
    }
  }
  
  if(current_device == NULL)
  {
    Tcl_AppendResult(interp, "No CY7C68013 device present", NULL);
    return TCL_ERROR;
  }

  device_handle = usb_open(current_device);
  if(device_handle == NULL)
  {
    Tcl_AppendResult(interp, usb_strerror(), NULL);
    return TCL_ERROR;
  }

  rc = usb_set_configuration(device_handle, bConfigurationValue);
  if(rc != 0)
  {
    Tcl_AppendResult(interp, usb_strerror(), NULL);
    return TCL_ERROR;
  }

  rc = usb_claim_interface(device_handle, bInterfaceNumber);
  if(rc != 0)
  {
    Tcl_AppendResult(interp, usb_strerror(), NULL);
    return TCL_ERROR;
  }

  rc = usb_set_altinterface(device_handle, bAlternateSetting);
  if(rc != 0)
  {
    Tcl_AppendResult(interp, usb_strerror(), NULL);
    return TCL_ERROR;
  }

  statePtr = (UsbDevice *) ckalloc((unsigned int) sizeof(UsbDevice));
  memset(statePtr, 0, sizeof(UsbDevice));

  statePtr->device = device_handle;

  cmdCounter = 0;
  do {
    sprintf(cmdName, "::usb::device_%d_%d_%d", idVendor, idProduct, cmdCounter);
    cmdCounter++;
  } while(Tcl_GetCommandInfo(interp, cmdName, &cmdInfo));

  statePtr->token = Tcl_CreateObjCommand(interp, cmdName, UsbDeviceObjCmd,
    (ClientData) statePtr, UsbDeviceDestroy);

  Tcl_SetResult(interp, cmdName, TCL_VOLATILE);
  return TCL_OK;
}

/* ----------------------------------------------------------------- */

static int
UsbConvertObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
  unsigned char *buffer, tmp[3];
  unsigned char *data;
  int length, size, i;

  Tcl_Obj *result;

  if(objc != 2)
  {
    Tcl_WrongNumArgs(interp, 1, objv, "data");
		return TCL_ERROR;
  }

  data = Tcl_GetStringFromObj(objv[1], &size);

  buffer = ckalloc((unsigned int) (size/2));

  tmp[2] = 0;
  length = 0;

  for(i = 1; i < size; i += 2)
  {
    tmp[0] = data[i - 1];
    tmp[1] = data[i];
  	buffer[length++] = strtoul(tmp, 0, 16);
  }

  result = Tcl_NewByteArrayObj(buffer, length);

  ckfree(buffer);

  Tcl_SetObjResult(interp, result);

  return TCL_OK;
}

/* ----------------------------------------------------------------- */

static int
UsbConvertBltObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
  unsigned char *data;
  int size, width, length, i, j, pos, value;

  char *name;
  Blt_Vector *vec;

  if(objc != 4)
  {
    Tcl_WrongNumArgs(interp, 1, objv, "data width vector");
		return TCL_ERROR;
  }

  data = Tcl_GetByteArrayFromObj(objv[1], &size);

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[2], &width))
  {
    Tcl_AppendResult(interp, "Parameter width is not an integer", NULL);
    return TCL_ERROR;
  }

  length = size / width;

  name =  Tcl_GetString(objv[3]);
  if(TCL_OK != Blt_GetVector(interp, name, &vec))
  {
    Tcl_AppendResult(interp, "Cannot find BLT vector", name, NULL);
    return TCL_ERROR;
  }

  if(Blt_VecSize(vec) < length)
  {
    Tcl_AppendResult(interp, "BLT vector size is less than the data length", NULL);
    return TCL_ERROR;
  }

  for(i = 0; i < length; ++i)
  {
    value = 0;
    for(j = 1; j <= width; ++j)
    {
      pos = i*width + width - j;
      value = (value << 8) | data[pos];
    }
    Blt_VecData(vec)[i] = (double)value;
  }

  Blt_ResetVector(vec, Blt_VecData(vec), length, Blt_VecSize(vec), TCL_STATIC);

  return TCL_OK;
}

/* ----------------------------------------------------------------- */

static int
UsbConvertEptObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
  unsigned char *data;
  int size, length, i, j, pos, value;

  Tcl_DictSearch search;
  Tcl_Obj *key, *object;
  int done;

  char *name;
  Blt_Vector *vec[16];

  if(objc != 3)
  {
    Tcl_WrongNumArgs(interp, 1, objv, "data dict");
		return TCL_ERROR;
  }

  data = Tcl_GetByteArrayFromObj(objv[1], &size);
  length = size / 8;

  if(TCL_OK != Tcl_DictObjFirst(interp, objv[2], &search, &key, &object, &done))
  {
    Tcl_AppendResult(interp, "Cannot read dict variable", name, NULL);
    return TCL_ERROR;
  }

  for(i = 0; (i < 16) && (!done) ; ++i, Tcl_DictObjNext(&search, &key, &object, &done))
  {
    name =  Tcl_GetString(object);
    if(TCL_OK != Blt_GetVector(interp, name, &vec[i]))
    {
      Tcl_DictObjDone(&search);
      Tcl_AppendResult(interp, "Cannot find BLT vector", name, NULL);
      return TCL_ERROR;
    }
    if(Blt_VecSize(vec[i]) < length)
    {
      Tcl_DictObjDone(&search);
      Tcl_AppendResult(interp, "BLT vector size is less than the data size", NULL);
      return TCL_ERROR;
    }
  }
  Tcl_DictObjDone(&search);

  for(i = 0; i < length; ++i)
  {
    pos = i * 8;

    value = data[pos + 1] & 0x0F;
    value = (value << 8) | data[pos];
    Blt_VecData(vec[0])[i] = (double)value;

    value = data[pos + 2];
    value = (value << 4) | (data[pos + 1] >> 4);
    Blt_VecData(vec[1])[i] = (double)value;

    value = data[pos + 4] & 0x0F;
    value = (value << 8) | data[pos + 3];
    Blt_VecData(vec[2])[i] = (double)value;

    value = data[pos + 5];
    value = (value << 4) | (data[pos + 4] >> 4);
    Blt_VecData(vec[3])[i] = (double)value;

    value = data[pos + 7] & 0x0F;
    value = (value << 8) | data[pos + 6];

    for(j = 0; j < 12; ++j)
    {
      Blt_VecData(vec[j+4])[i] = (double)((value & 1) + (j * 2));
      value >>= 1;
    }
  }

  for(i = 0; i < 16; ++i)
  {
    Blt_ResetVector(vec[i], Blt_VecData(vec[i]), length, Blt_VecSize(vec[i]), TCL_STATIC);
  }

  return TCL_OK;
}

/* ----------------------------------------------------------------- */

static int
UsbConvertOscObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
  unsigned char *data;
  int size, length, i, j, pos, value;

  Tcl_DictSearch search;
  Tcl_Obj *key, *object;
  int done;

  char *name;
  Blt_Vector *vec[16];

  if(objc != 3)
  {
    Tcl_WrongNumArgs(interp, 1, objv, "data dict");
		return TCL_ERROR;
  }

  data = Tcl_GetByteArrayFromObj(objv[1], &size);
  length = size / 8;

  if(TCL_OK != Tcl_DictObjFirst(interp, objv[2], &search, &key, &object, &done))
  {
    Tcl_AppendResult(interp, "Cannot read dict variable", name, NULL);
    return TCL_ERROR;
  }

  for(i = 0; (i < 16) && (!done) ; ++i, Tcl_DictObjNext(&search, &key, &object, &done))
  {
    name =  Tcl_GetString(object);
    if(TCL_OK != Blt_GetVector(interp, name, &vec[i]))
    {
      Tcl_DictObjDone(&search);
      Tcl_AppendResult(interp, "Cannot find BLT vector", name, NULL);
      return TCL_ERROR;
    }
    if(Blt_VecSize(vec[i]) < length)
    {
      Tcl_DictObjDone(&search);
      Tcl_AppendResult(interp, "BLT vector size is less than the data size", NULL);
      return TCL_ERROR;
    }
  }
  Tcl_DictObjDone(&search);

  for(i = 0; i < length; ++i)
  {
    pos = i * 8;

    value = data[pos + 1] & 0x0F;
    value = (value << 8) | data[pos];
    Blt_VecData(vec[0])[i] = (double)value;

    value = data[pos + 2];
    value = (value << 4) | (data[pos + 1] >> 4);
    Blt_VecData(vec[1])[i] = (double)value;

    value = data[pos + 4] & 0x0F;
    value = (value << 8) | data[pos + 3];
    Blt_VecData(vec[2])[i] = (double)value;

    value = data[pos + 5];
    value = (value << 4) | (data[pos + 4] >> 4);
    Blt_VecData(vec[3])[i] = (double)value;

    value = data[pos + 7] & 0x0F;
    value = (value << 8) | data[pos + 6];
    Blt_VecData(vec[4])[i] = (double)value;

    value = data[pos + 7] >> 4;

    for(j = 0; j < 4; ++j)
    {
      Blt_VecData(vec[j+5])[i] = (double)((value & 1) << 8);
      value >>= 1;
    }
  }

  for(i = 0; i < 9; ++i)
  {
    Blt_ResetVector(vec[i], Blt_VecData(vec[i]), length, Blt_VecSize(vec[i]), TCL_STATIC);
  }

  return TCL_OK;
}

/* ----------------------------------------------------------------- */

/* ----------------------------------------------------------------- */

static int
UsbConvertOsc16ObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
  unsigned char *data;
  int size, length, i, j, pos, value;

  Tcl_DictSearch search;
  Tcl_Obj *key, *object;
  int done;

  char *name;
  Blt_Vector *vec[18];

  if(objc != 3)
  {
    Tcl_WrongNumArgs(interp, 1, objv, "data dict");
		return TCL_ERROR;
  }

  data = Tcl_GetByteArrayFromObj(objv[1], &size);
  length = size / 16;

  if(TCL_OK != Tcl_DictObjFirst(interp, objv[2], &search, &key, &object, &done))
  {
    Tcl_AppendResult(interp, "Cannot read dict variable", name, NULL);
    return TCL_ERROR;
  }

  for(i = 0; (i < 18) && (!done) ; ++i, Tcl_DictObjNext(&search, &key, &object, &done))
  {
    name =  Tcl_GetString(object);
    if(TCL_OK != Blt_GetVector(interp, name, &vec[i]))
    {
      Tcl_DictObjDone(&search);
      Tcl_AppendResult(interp, "Cannot find BLT vector", name, NULL);
      return TCL_ERROR;
    }
    if(Blt_VecSize(vec[i]) < length)
    {
      Tcl_DictObjDone(&search);
      Tcl_AppendResult(interp, "BLT vector size is less than the data size", NULL);
      return TCL_ERROR;
    }
  }
  Tcl_DictObjDone(&search);

  for(i = 0; i < length; ++i)
  {
    pos = i * 16;

    value = data[pos + 1] & 0x0F;
    value = (value << 8) | data[pos];
    Blt_VecData(vec[0])[i] = (double)value;

    value = data[pos + 2];
    value = (value << 4) | (data[pos + 1] >> 4);
    Blt_VecData(vec[1])[i] = (double)value;

    value = data[pos + 4] & 0x0F;
    value = (value << 8) | data[pos + 3];
    Blt_VecData(vec[2])[i] = (double)value;

    value = data[pos + 5];
    value = (value << 4) | (data[pos + 4] >> 4);
    Blt_VecData(vec[3])[i] = (double)value;

    value = data[pos + 7] & 0x0F;
    value = (value << 8) | data[pos + 6];
    Blt_VecData(vec[4])[i] = (double)value;

    value = data[pos + 8];
    value = (value << 4) | (data[pos + 7] >> 4);
    Blt_VecData(vec[5])[i] = (double)value;

    value = data[pos + 10] & 0x0F;
    value = (value << 8) | data[pos + 9];
    Blt_VecData(vec[6])[i] = (double)value;

    value = data[pos + 11];
    value = (value << 4) | (data[pos + 10] >> 4);
    Blt_VecData(vec[7])[i] = (double)value;

    value = data[pos + 13] & 0x0F;
    value = (value << 8) | data[pos + 12];
    Blt_VecData(vec[8])[i] = (double)value;

    value = data[pos + 14];
    value = (value << 4) | (data[pos + 13] >> 4);
    Blt_VecData(vec[9])[i] = (double)value;

    value = data[pos + 15];

    for(j = 0; j < 8; ++j)
    {
      Blt_VecData(vec[j+10])[i] = (double)((value & 1) << 8);
      value >>= 1;
    }
  }

  for(i = 0; i < 18; ++i)
  {
    Blt_ResetVector(vec[i], Blt_VecData(vec[i]), length, Blt_VecSize(vec[i]), TCL_STATIC);
  }

  return TCL_OK;
}

/* ----------------------------------------------------------------- */

static int
UsbFormatOscCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
  char *name;
  int first, last, done, max, i, j;

  Tcl_DictSearch search;
  Tcl_Obj *key, *object, *result, *eol;

  Blt_Vector *vec[32];

  if(objc != 4)
  {
    Tcl_WrongNumArgs(interp, 1, objv, "dict first last");
		return TCL_ERROR;
  }

  if(TCL_OK != Tcl_DictObjFirst(interp, objv[1], &search, &key, &object, &done))
  {
    Tcl_AppendResult(interp, "Cannot read dict variable", name, NULL);
    return TCL_ERROR;
  }

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[2], &first))
  {
    Tcl_AppendResult(interp, "Parameter first is not an integer", NULL);
    return TCL_ERROR;
  }

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[3], &last))
  {
    Tcl_AppendResult(interp, "Parameter last is not an integer", NULL);
    return TCL_ERROR;
  }
  
  if(last < first)
  {
    return TCL_OK;
  }

  for(i = 0; (i < 32) && (!done); ++i, Tcl_DictObjNext(&search, &key, &object, &done))
  {
    name =  Tcl_GetString(object);
    if(TCL_OK != Blt_GetVector(interp, name, &vec[i]))
    {
      Tcl_DictObjDone(&search);
      Tcl_AppendResult(interp, "Cannot find BLT vector", name, NULL);
      return TCL_ERROR;
    }
    if(Blt_VecLength(vec[i]) <= last)
    {
      last = Blt_VecLength(vec[i]) - 1;
    }
    max = i;
  }
  Tcl_DictObjDone(&search);

  if(first < 0)
  {
    first = 0;
  }

  result = Tcl_NewObj();
  eol = Tcl_NewStringObj("\n", -1);

  Tcl_IncrRefCount(result);
  Tcl_IncrRefCount(eol);

  for(i = first; i <= last; ++i)
  {
    for(j = 0; j <= max; ++j)
    {
      if(j > 0)
      {
        Tcl_AppendPrintfToObj(result, "\t%g", Blt_VecData(vec[j])[i]);
      }
      else
      {
        Tcl_AppendPrintfToObj(result, "%g", Blt_VecData(vec[j])[i]);
      }
    }
    Tcl_AppendObjToObj(result, eol);
  }

  Tcl_SetObjResult(interp, result);

  Tcl_DecrRefCount(eol);
  Tcl_DecrRefCount(result);

  return TCL_OK;
}

/* ----------------------------------------------------------------- */

static int
UsbIntegrateBltObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
  int length, i, xmin, xmax, flag;
  double entr, mean;

  char *name;
  Blt_Vector *vec;

  Tcl_Obj *result;

  if(objc != 5)
  {
    Tcl_WrongNumArgs(interp, 1, objv, "vector xmin xmax flag");
		return TCL_ERROR;
  }

  name =  Tcl_GetString(objv[1]);
  if(TCL_OK != Blt_GetVector(interp, name, &vec))
  {
    Tcl_AppendResult(interp, "Cannot find BLT vector", name, NULL);
    return TCL_ERROR;
  }

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[2], &xmin))
  {
    Tcl_AppendResult(interp, "Parameter xmin is not an integer", NULL);
    return TCL_ERROR;
  }

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[3], &xmax))
  {
    Tcl_AppendResult(interp, "Parameter xmax is not an integer", NULL);
    return TCL_ERROR;
  }

  if(TCL_OK != Tcl_GetIntFromObj(interp, objv[4], &flag))
  {
    Tcl_AppendResult(interp, "Parameter flag is not an integer", NULL);
    return TCL_ERROR;
  }

  length = Blt_VecLength(vec);
  entr = 0.0;
  mean = 0.0;
  
  if(xmin < 0) xmin = 0;
  if(xmax >= length) xmax = length - 1;
  if(xmax < xmin) xmax = xmin;

  for(i = xmin; i <= xmax; ++i)
  {
    entr += Blt_VecData(vec)[i];
    mean += i * Blt_VecData(vec)[i];
  }

  if(flag)
  {
    result = Tcl_NewDoubleObj(entr > 0.0 ? mean / entr : 0.0);
  }
  else
  {
    result = Tcl_NewDoubleObj(entr);
  }

  Tcl_SetObjResult(interp, result);

  return TCL_OK;
}

/* ----------------------------------------------------------------- */

int
Usb_Init(Tcl_Interp *interp)
{
  usb_init();

  Tcl_CreateObjCommand(interp, "usb::connect", UsbConnectObjCmd,
    (ClientData) 0, (Tcl_CmdDeleteProc *) NULL);

  Tcl_CreateObjCommand(interp, "usb::convert", UsbConvertObjCmd,
    (ClientData) 0, (Tcl_CmdDeleteProc *) NULL);

  Tcl_CreateObjCommand(interp, "usb::convertBlt", UsbConvertBltObjCmd,
    (ClientData) 0, (Tcl_CmdDeleteProc *) NULL);

  Tcl_CreateObjCommand(interp, "usb::convertEpt", UsbConvertOscObjCmd,
    (ClientData) 0, (Tcl_CmdDeleteProc *) NULL);

  Tcl_CreateObjCommand(interp, "usb::convertOsc", UsbConvertOscObjCmd,
    (ClientData) 0, (Tcl_CmdDeleteProc *) NULL);

  Tcl_CreateObjCommand(interp, "usb::convertOsc16", UsbConvertOsc16ObjCmd,
    (ClientData) 0, (Tcl_CmdDeleteProc *) NULL);

	Tcl_CreateObjCommand(interp, "usb::formatOsc", UsbFormatOscCmd,
    (ClientData) 0, (Tcl_CmdDeleteProc *) NULL);

  Tcl_CreateObjCommand(interp, "usb::integrateBlt", UsbIntegrateBltObjCmd,
    (ClientData) 0, (Tcl_CmdDeleteProc *) NULL);

  return Tcl_PkgProvide(interp, "usb", "0.1");
}
