// Test program for the Vaunix LDA profiles.
//
// RD 5/8/2019    This program exercises the API functions that manage profiles
// RD 12/12/2019  Adapted to use the maximum profile length that the device supports
//

#include <linux/hid.h>	/* AK: Changed include for modern linux */
#include <stdbool.h>    /* AK: Added include for 'bool' type */
#include <stdio.h>
#include <unistd.h>   	/* AK: Added include for error-free getlogin(). */
#include "LDAhid.h"
#include <math.h>
#include <string.h>

#define FALSE 0
#define TRUE 1            // for consistency with standards

#define THIS_FILE_DATE "2019-12-12"

#define PROFILELENGTH 50	// length of the profile used for testing, HiRes LDA devices support 50 element profiles, other devices support 100 elements
                          // some newer devices support 1000 element profiles, but only save the first 50 elements in response to a save settings command
#ifndef M_PI              // Apparently math.h isn't including it???
#define M_PI 3.14159
#endif

#define DEBUG_LEVEL 1
#define LDA_LIB_DEBUG 0		// Defines the debug levels passed to the LDA library 0 = none, 4 = everything
#define LDA_LIB_IO_DEBUG 0	// Defines the debug levels for IO operations in the LDA library, 0 = none, 4 = everything
#define LDA_LIB_V_DEBUG FALSE	// Set to TRUE for libusb debug output, the LIB_IO_DEBUG value will be passed to libusb as its debug level

/* function prototypes */
void profileSine(int attenmax, int profile_length);
void profileTriangle(int attenmax, int profile_length);
void profileSquare(int attenmax, int profile_length);
void profileShow(int height, int profile_length);

/* globals */
unsigned int profile[1000];	// storage for an attenuation profile in .05 db units


// code begins here
int main (int argc, char *argv[]) {
  int nDevices, nActive, nChannels;
  int i, j, k, result, status;
  int powerlevel;
  char cModelName[32];
  char c;
  char *username;
  char user[32];
  DEVID activeDevices[MAXDEVICES];
  bool realhardware;
  int profile_length;
  int profile_element;

  /* AK: Added <unistd.h> to includes to avoid seg fault on getlogin(). */
  username = getlogin();
  if (username)
    strcpy(user, username);
  else
    strcpy(user, "Vaunix customer");

#if 1
  if (0 != strcmp(user, "root")) {
    printf("Hi %s,\r\n", user);
    printf("Accessing USB ports on a Linux machine may require root level\r\n");
    printf("access. You are not logged in as root. You may be able to\r\n");
    printf("proceed if you have used 'chmod' to change the access mode\r\n");
    printf("of the appropriate devices in /dev/bus/usb. That requires\r\n");
    printf("root access also. We'll continue, but if you don't see your\r\n");
    printf("LDA devices or no data can be read from or written to them,\r\n");
    printf("that's probably the problem. su to root and try again.\r\n\r\n");
    printf("Try running with 'sudo', or become root by running 'su' before.\r\n\r\n");
	printf("You can use udev rules to allow access to USB devices by user processes.\r\n\r\n");

  }
#endif



  // -- our first step is to initialize the library --
  fnLDA_Init();
  if (DEBUG_LEVEL > 0) printf("Returned from fnLDA_Init()\r\n");

  realhardware = TRUE;
  fnLDA_SetTestMode(!realhardware);

  fnLDA_SetTraceLevel(LDA_LIB_DEBUG, LDA_LIB_IO_DEBUG, LDA_LIB_V_DEBUG);

  printf("LDA profile test program %s using library version %s\r\n\r\n", THIS_FILE_DATE, fnLDA_LibVersion());


 start_over:
  nDevices = fnLDA_GetNumDevices();
  if (DEBUG_LEVEL > 0) printf("Returned from fnLDA_GetNumDevices()\r\n");
  printf("Found %d devices\r\n", nDevices);

  for (i=1; i<=nDevices; i++){
    result = fnLDA_GetModelName(i, cModelName);
    printf("  Model %d is %s (%d chars)\r\n", i, cModelName, result);
    result = fnLDA_GetNumChannels(i);
    printf("  This device has %d channels\r\n", result);
  }
  printf("\r\n");

  nActive = fnLDA_GetDevInfo(activeDevices);
  printf("We have %d active devices\r\n", nActive);

  for (i=0; i<nActive; i++){
    /* let's open and init each device to get the threads running */
    printf("  Opening device %d of %d.\r\n", activeDevices[i], nActive);
    status = fnLDA_InitDevice(activeDevices[i]);
    printf("  Opened device %d of %d. Return status=0x%08x (%s)\r\n", activeDevices[i], nActive, status, fnLDA_perror(status));
  }

// -- we will loop over all the devices and all the channels

  for (i=0; i<nActive; i++){
    if (i > 0) printf("\r\n");

    // get the maximum profile size for this device
    profile_length = fnLDA_GetProfileMaxLength(activeDevices[i]);
    if (status < 0) goto device_pulled;
    printf("   Maximimum profile length is %d elements\r\n", profile_length);

    nChannels = fnLDA_GetNumChannels(activeDevices[i]);
    /* Run the test in each of the device's channels while we're at it */
    for (k=1; k<=nChannels; k++){
      printf("  Starting a round of tests on channel %d of %d\r\n", k, nChannels);
      result = fnLDA_SetChannel(activeDevices[i], k);
      printf("  Device %d is active\r\n", activeDevices[i]);
      status = fnLDA_GetModelName(activeDevices[i], cModelName);
      if (status < 0) goto device_pulled;
      printf("  Device %d (%s) has ", activeDevices[i], cModelName);
      status = fnLDA_GetSerialNumber(activeDevices[i]);
      if (status < 0) goto device_pulled;
      printf("  Serial number=%d\r\n", status);
      /* dump what we know - that we read from the hardware */
      printf("  GetAttenuation returned %d\r\n", result=fnLDA_GetAttenuation(activeDevices[i]));
      if (result < 0) goto device_pulled;
      printf("  GetWorkingFrequency returned %d\r\n", fnLDA_GetWorkingFrequency(activeDevices[i]));
      if (result < 0) goto device_pulled;
      printf("  GetProfileCount returned %d\r\n", result=fnLDA_GetProfileCount(activeDevices[i]));
      if (result < 0) goto device_pulled;
      printf("  GetDwellTime returned %d\r\n", result=fnLDA_GetProfileDwellTime(activeDevices[i]));
      if (result < 0) goto device_pulled;
      printf("  GetProfileIdleTime returned %d\r\n", result=fnLDA_GetProfileIdleTime(activeDevices[i]));
      if (result < 0) goto device_pulled;

      // show the contents of the profile for this channel
      for (j=0; j<profile_length; j++){
        printf("  Profile element %d = %d\r\n", j, result=fnLDA_GetProfileElement(activeDevices[i], j));
        if (result < 0){
			  printf("   GetProfileElement returned error=0x%08x (%s)\r\n", result, fnLDA_perror(result));
        goto device_pulled;
        }
	     }
#if 0
       // RD testing 12-3-19
       // close the devices
       printf("test sez: closing the devices\r\n");
       for (i=0; i<nActive; i++) {
         status = fnLDA_CloseDevice(activeDevices[i]);
         printf("Closed device %d. Return status=0x%08x (%s)\r\n", activeDevices[i], status, fnLDA_perror(status));
       }
       printf("End of RD test\r\n");
       return 0;
#endif
	      // set the profile to a sine wave or a triangle wave
        // for every other device, alternate between a sine wave and a triangle wave
       if (0 == i%2)
		     profileSine(fnLDA_GetMaxAttenuation(activeDevices[i]), profile_length);
       else
		     profileTriangle(fnLDA_GetMaxAttenuation(activeDevices[i]), profile_length);

       // load the profile into the LDA device

	     for (j=0; j<profile_length; j++){
		       profile_element = profile[j];
		       printf("  Setting Profile element %d to %d\r\n", j, profile_element);
		       result=fnLDA_SetProfileElement(activeDevices[i], j, profile_element);
		       if (result < 0){
			       printf("   SetProfileElement returned error=0x%08x (%s)\r\n", result, fnLDA_perror(result));
			       goto device_pulled;
		       }
        }

	      // Set the dwell time on each element
	      fnLDA_SetProfileDwellTime(activeDevices[i], 1000+k);		// RD - this is just to make it easier to test, each channel has a different dwell time
	      if (result < 0){
			    printf("   SetProfileDwellTime returned error=0x%08x (%s)\r\n", result, fnLDA_perror(result));
			    goto device_pulled;
	      }

	      // Start the profile running
	      fnLDA_StartProfile(activeDevices[i], PROFILE_ONCE);
	      if (result < 0){
			    printf("   StartProfile returned error=0x%08x (%s)\r\n", result, fnLDA_perror(result));
			    goto device_pulled;
	      }

	      // show the first 10 seconds of the profile
	      for (j=0; j<10; j++){
		        sleep(1);
		        result = fnLDA_GetAttenuation(activeDevices[i]);
		        if (result < 0){
			        printf("   SetProfileDwellTime returned error=0x%08x (%s)\r\n", result, fnLDA_perror(result));
			        goto device_pulled;
		        }
		        else{
			        printf("    Channel %d attenuation is %d \r\n", k, result);
	  	      }
	      }

    } // for loop for channels (k)
  } // for loop for devices (i)


  // close the devices
  printf("test sez: closing the devices\r\n");
  for (i=0; i<nActive; i++) {
    status = fnLDA_CloseDevice(activeDevices[i]);
    printf("Closed device %d. Return status=0x%08x (%s)\r\n", activeDevices[i], status, fnLDA_perror(status));
  }
  printf("End of test\r\n");
  return 0;

device_pulled:

  // close the devices
  printf("closing the devices due to an error\r\n");
  for (i=0; i<nActive; i++) {
    status = fnLDA_CloseDevice(activeDevices[i]);
    printf("Closed device %d. Return status=0x%08x (%s)\r\n", activeDevices[i], status, fnLDA_perror(status));
  }
  printf("Replace the LDA USB plug to try again. Press Ctrl-C to exit.\r\n");
  nDevices = fnLDA_GetNumDevices();
  while (0 == nDevices) {
    nDevices = fnLDA_GetNumDevices();
  }
  goto start_over;
}

/* support functions */
void profileSine(int attenmax, int profile_length) {
  /* calculate values for a sine wave attenuation profile. Use the size of
     the profile and divide a full wave into that many segments. */
  int i, nsegs;
  float fi, fstart, fend, fstep;
  float ftemp;
  //  #define M_PI 3.14159

 // catch a bad profile length argument to defend our profile array
 if (profile_length < 1 || profile_length > 1000) profile_length = 1000;

  nsegs = profile_length;
  printf("Making a sine wave in %d segments\r\n", nsegs);
  fstart = 0;
  fend = 2.0 * M_PI; /* 2 PI = 1 whole circle */
  fstep = (fend - fstart) / (float)nsegs;
  fi = fstart;
  for (i=0; i<nsegs; i++) {
    /* sin() results range from -1.0 to +1.0, and we want te rescale this
       to 0.0 to 1.0 */
    ftemp = (1.0 + sin(fi)) / 2.0;
    /* and now that we have a 0-1 value, multiply that by the maximum
       attenuation value */
    ftemp = ftemp * (float)attenmax;
    /* store that as the next step in the profile */
    profile[i] = (int)ftemp;
    /* we've set a value where the *attenuation* follows the curve. Now
       let's invert that so the *signal* follows. Comment this out if
       you want the attenuation to follow the instead. */
    profile[i] = attenmax - profile[i];
    /* get ready for the next one */
    fi = fi + fstep;
  }
}

void profileTriangle(int attenmax, int profile_length) {
  /* calculate values for a triangle attenuation profile. Use the size of
     the profile and divide a full wave into that many segments. */
  int i, nsegs;
  float fi, fstep;
  float ftemp;

  // catch a bad profile length argument to defend our profile array
  if (profile_length < 1 || profile_length > 1000) profile_length = 1000;
  nsegs = profile_length;

  printf("Making a triangle wave in %d segs\r\n", nsegs);
  /* the wave really has 4 parts - up to max, down to 0, down to min, up to 0
     so we'll divide into 4 pieces and then 'bounce' off of the extremes */
  fstep = 4.0 / (float)nsegs;
  fi = 0.0;
  for (i=0; i<nsegs; i++) {
    ftemp = (1.0 + fi) / 2.0;
    /* and now that we have a 0-1 value, multiply that by the maximum
       attenuation value */
    ftemp = ftemp * (float)attenmax;
    /* store that as the next step in the profile */
    profile[i] = (int)ftemp;
    /* we've set a value where the *attenuation* ramps. Now let's invert that
       so the *signal* ramps. Comment ths out if you want the attenuation
       to follow the ramp instead. */
    profile[i] = attenmax - profile[i];
    /* get ready for the next one */
    fi = fi + fstep;
    if (fi >= 1.0) {
      fi = 1.0;
      fstep = -fstep;
    }
    if (fi <= -1.0) {
      fi = -1.0;
      fstep = -fstep;
    }
  }
}

/* a little bonus profile generator - not as exciting as the other two */
void profileSquare(int attenmax, int profile_length) {
  /* calculate values for a square wave attenuation profile. Use the size of
     the profile and divide a full wave into that many segments. */
  int i, nsegs;

  // catch a bad profile length argument to defend our profile array
  if (profile_length < 1 || profile_length > 1000) profile_length = 1000;

  nsegs = profile_length;

  printf("Making two square waves in %d segs\r\n", nsegs);
  /* the wave really has 4 parts - max, min, max, min so we'll divide into
     4 pieces */
  for (i=0; i<nsegs; i++) {
    if ((i < (nsegs/4)) || ((i > nsegs/2) && (i < (3*nsegs)/4)))
      profile[i] = attenmax;
    else
      profile[i] = 0;
    /* we've set a value where the *attenuation* ramps. Now let's invert that
       so the *signal* ramps. Comment ths out if you want the attenuation
       to follow the ramp instead. */
    profile[i] = attenmax - profile[i];
  }
}

/* displays the profile data as a cheesy graph on the terminal output */
void profileShow(int height, int profile_length) {
  int i, j;
  int rl, rh, rs;

  // catch a bad profile length argument to defend our profile array
  if (profile_length < 1 || profile_length > 1000) profile_length = 1000;

  rs = 252 / height;
  rh = 252;
  rl = rh - rs + 1;
  for (i=height; i>0; i--) {
    for (j=0; j<profile_length; j++) {
      if ((profile[j] >= rl) && (profile[j] <= rh))
	printf("*");
      else
	printf(" ");
    }
    printf("\r\n");
    rh = rh - rs;
    rl = rl - rs;
    if (rl < rs) rl = 0;
  }
}
