// Test program for the Vaunix LDA.
//
// HME  2018-08-24     Version for 0.99 using libusb 1.0
// HME  2019-01-04     Added local definition of Pi since math.h deleted it?
//                     Corrected username access to restore 'su' warning
// RD	2019-5-18	       Added more functions for test coverage. All parameters are read out and displayed, then set.
// RD 2019-12-12       Added coverage for devices with long profiles


#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>     /* HME Added during the 0.1->1.0 switch */

#define FALSE 0
#define TRUE !FALSE

#define THIS_FILE_DATE "2020-9-27"

#ifndef M_PI            // Apparently math.h isn't including it???
#define M_PI 3.14159
#endif

#define DEBUG_LEVEL 0
#define QUICK_TEST TRUE

/* 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, 1000 is the maximum size
                            // Only devices with long profiles support 1000 elements in RAM only
                            // Legacy devices may support 50 or 100 element profiles


/* 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 DeviceFeatures;
  int MaxProfileLength;
  int ProfileLength;

  /* 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


  if (DEBUG_LEVEL > 0) printf("Calling fnLDA_Init()\r\n");
  fnLDA_Init();
  if (DEBUG_LEVEL > 0) printf("Returned from fnLDA_Init()\r\n");

// -- we can ask the LDA library for trace messages to assist with debugging --
// fnLDA_SetTraceLevel(DEBUG_LEVEL, 4, TRUE);
  fnLDA_SetTraceLevel(0, 0, FALSE);

  /* If you have actual hardware attached, set this to TRUE. Setting to FALSE will run in test mode */
  realhardware = TRUE;
  if (DEBUG_LEVEL > 0) printf("Calling fnLDA_SetTestMode()\r\n");
  fnLDA_SetTestMode(!realhardware);
  if (DEBUG_LEVEL > 0) printf("Returned from fnLDA_SetTestMode()\r\n");



 start_over:
  if (DEBUG_LEVEL > 0) printf("Calling fnLDA_GetNumDevices()\r\n");
  nDevices = fnLDA_GetNumDevices();
  if (DEBUG_LEVEL > 0) printf("Returned from fnLDA_GetNumDevices()\r\n");
  printf("LDA test/demonstration program %s using library version %s\r\n\r\n", THIS_FILE_DATE, fnLDA_LibVersion());
  printf("I think we have %d\r\n", nDevices);
  if (0 == nDevices) {
    printf("No Vaunix LDA devices located. Would you like to run in test mode? "); fflush(0);
    c = getchar();
    if ('Y' == (c & 0xdf)) {
      printf("\r\nSwitching to test mode.\r\n");
      realhardware = FALSE;
      fnLDA_Init();
      fnLDA_SetTestMode(!realhardware);
      nDevices = fnLDA_GetNumDevices();
    }
  }
  printf("Found %d devices\r\n", nDevices);

  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));
  }

  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 base device has %d channels\r\n", result);
  }
  printf("\r\n");

// -- lets test all the functions which get data. Doing that before we set any values will ensure that the library 	queries the device for the data --

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

    // find out the size of profiles supported by this device
    ProfileLength = fnLDA_GetProfileMaxLength(activeDevices[i]);
    if (status < 0) goto device_pulled;
    if (ProfileLength > 1000) ProfileLength = 1000; // defend our array
    MaxProfileLength = ProfileLength;

    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);
      /* only do this if not in test mode */
      printf("  Device %d is active\r\n", activeDevices[i]);
      /* dump what we know - that we read from the hardware */
      if (realhardware) {
        result = fnLDA_GetFeatures(activeDevices[i]);
        DeviceFeatures = result;
		    if (DeviceFeatures < 0) goto device_pulled;
		    printf("   GetFeatures returned %x\r\n", DeviceFeatures);
		    printf("  GetAttenuation returned %d\r\n", result = fnLDA_GetAttenuation(activeDevices[i]));
		    if (result < 0) goto device_pulled;
		    printf("  GetWorkingFrequency returned %d\r\n", result = fnLDA_GetWorkingFrequency(activeDevices[i]));
		    if (result < 0) goto device_pulled;
		    printf("  GetRampStart returned %d\r\n", result = fnLDA_GetRampStart(activeDevices[i]));
		    if (result < 0) goto device_pulled;
		    printf("  GetRampEnd returned %d\r\n", result = fnLDA_GetRampEnd(activeDevices[i]));
		    if (result < 0) goto device_pulled;
		    printf("  GetDwellTime returned %d\r\n", result = fnLDA_GetDwellTime(activeDevices[i]));
		    if (result < 0) goto device_pulled;
		    printf("  GetIdleTime returned %d\r\n", result = fnLDA_GetIdleTime(activeDevices[i]));
		    if (result < 0) goto device_pulled;

		    printf("  GetAttenuationStep returned %d\r\n", result = fnLDA_GetAttenuationStep(activeDevices[i]));
		    if (result < 0) goto device_pulled;

		    // -- Ramp parameters for bidirectional ramps --
		    if (DeviceFeatures & HAS_BIDIR_RAMPS) {
			     printf("  GetAttenuationStepTwo returned %d\r\n", result = fnLDA_GetAttenuationStepTwo(activeDevices[i]));
			     if (result < 0) goto device_pulled;
			     printf("  GetDwellTimeTwo returned %d\r\n", result = fnLDA_GetDwellTimeTwo(activeDevices[i]));
			     if (result < 0) goto device_pulled;
			     printf("  GetHoldTime returned %d\r\n", result = fnLDA_GetHoldTime(activeDevices[i]));
			     if (result < 0) goto device_pulled;
		    }

		    printf("  GetRF_On returned %d\r\n", result = fnLDA_GetRF_On(activeDevices[i]));
		    if (result < 0) goto device_pulled;

		    printf("  GetMaxAttenuation returned %d\r\n", result = fnLDA_GetMaxAttenuation(activeDevices[i]));
		    if (result < 0) goto device_pulled;
		    printf("  GetMinAttenuation returned %d\r\n", result = fnLDA_GetMinAttenuation(activeDevices[i]));
		    if (result < 0) goto device_pulled;

		    if (DeviceFeatures & HAS_HIRES) {
			    printf("  GetMinWorkingFrequency returned %d\r\n", result = fnLDA_GetMinWorkingFrequency(activeDevices[i]));
			    if (result < 0) goto device_pulled;
			    printf("  GetMaxWorkingFrequency returned %d\r\n", result = fnLDA_GetMaxWorkingFrequency(activeDevices[i]));
			    if (result < 0) goto device_pulled;
		    }

		    if (DeviceFeatures & HAS_PROFILES) {
          printf("   Maximimum profile length is %d elements\r\n", ProfileLength);
          printf("  GetProfileCount returned %d\r\n", result = fnLDA_GetProfileCount(activeDevices[i]));
          if (result < 0) goto device_pulled;
          printf("  GetProfileDwellTime 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;

          result = fnLDA_GetProfileIndex(activeDevices[i]);
          if (result < 0 && result == DATA_UNAVAILABLE) {
            printf("  GetProfileIndex is unavailable\r\n");
          }
          else {
            printf("  GetProfileIndex returned %d\r\n", result);
          }
          if (result < 0 && result != DATA_UNAVAILABLE) goto device_pulled;

          // low resolution LDA devices can have up to 100 elements in a profile, Hi-Res devices support up to 50 elements
          // some newer devices support profiles with 1000 elements
 #if (!QUICK_TEST)
           for (j=0; j<MaxProfileLength; 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;
              }
            }
#endif

        }
      }  // if real hardware

      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);

 #if (!QUICK_TEST)

      printf("Minimum attenuation for 2 seconds...\r\n");
      status = fnLDA_SetAttenuation(activeDevices[i], 0);
      if (status < 0) goto device_pulled;
      sleep(2);
      printf("Maximum attenuation for 2 seconds...\r\n");
      status = fnLDA_SetAttenuation(activeDevices[i], fnLDA_GetMaxAttenuation(activeDevices[i]));
      if (status < 0) goto device_pulled;
      sleep(2);
      printf("30 dB attenuation for 2 seconds...\r\n");
      status = fnLDA_SetAttenuation(activeDevices[i], 30 * 20);
      if (status < 0) goto device_pulled;
      sleep(2);
      printf("45 dB attenuation for 2 seconds...\r\n");
      status = fnLDA_SetAttenuation(activeDevices[i], 45 * 20);
      if (status < 0) goto device_pulled;
      sleep(2);
      printf("Minimum attenuation for 2 seconds...\r\n");
      status = fnLDA_SetAttenuation(activeDevices[i], 0);
      if (status < 0) goto device_pulled;
      sleep(2);
#endif
	    // -- exercise the Set API functions
	    if (DeviceFeatures & HAS_HIRES) {
        status = fnLDA_SetWorkingFrequency(activeDevices[i], 9000); // try setting the working frequency to 900 MHz
		    printf(" SetWorkingFrequency status = %s\r\n", fnLDA_perror(status));
      }

	    // we will set the attenuation based on the channel for ease of testing (11, 12, 13, 14 ... 10+NChannels)
	    status = fnLDA_SetAttenuationQ(activeDevices[i], ((10 + k) * 20), k);
	    printf(" SetAttenuationQ status for channel %d = %s\r\n", k, fnLDA_perror(status));

	    // we will set the ramp parameters based on the channel for ease of testing
	    status = fnLDA_SetRampStart(activeDevices[i], ((5+k)*20));
	    printf(" SetRampStart status for channel %d = %s\r\n", k, fnLDA_perror(status));

	    status = fnLDA_SetRampEnd(activeDevices[i], ((10+k)*20));
	    printf(" SetRampEnd status for channel %d = %s\r\n", k, fnLDA_perror(status));

	    status = fnLDA_SetAttenuationStep(activeDevices[i], (k*20));
	    printf(" SetAttenuationStep status for channel %d = %s\r\n", k, fnLDA_perror(status));

	    status = fnLDA_SetDwellTime(activeDevices[i], (100 + k));	// 100ms, 200ms, 300ms, etc.
	    printf(" SetDwellTime status for channel %d = %s\r\n", k, fnLDA_perror(status));

	    status = fnLDA_SetIdleTime(activeDevices[i], (50 + k));
	    printf(" SetIdleTime status for channel %d = %s\r\n", k, fnLDA_perror(status));

	    // -- parameters for devices which support bi-directional ramps
	    if (DeviceFeatures & HAS_BIDIR_RAMPS) {
        status = fnLDA_SetAttenuationStepTwo(activeDevices[i], (2*k*20));
        printf(" SetAttenuationStepTwo status for channel %d = %s\r\n", k, fnLDA_perror(status));

		    status = fnLDA_SetDwellTimeTwo(activeDevices[i], (200 + k));
		    printf(" SetDwellTimeTwo status for channel %d = %s\r\n", k, fnLDA_perror(status));

		    status = fnLDA_SetHoldTime(activeDevices[i], (60 + k));
		    printf(" SetHoldTime status for channel %d = %s\r\n", k, fnLDA_perror(status));
	    }

	    if (DeviceFeatures & HAS_PROFILES) {
#if (!QUICK_TEST)
      // download a test profile to the device
      // for every other device, alternate between a sine wave and a triangle wave
      if (0 == i%2)
        profileSine(fnLDA_GetMaxAttenuation(activeDevices[i]), ProfileLength);
      else
     		profileTriangle(fnLDA_GetMaxAttenuation(activeDevices[i]), ProfileLength);

		  for (j=0; j<ProfileLength; j++) {
        printf(" Setting Profile Element %d to %d\r\n", j, profile[j]);
        status = fnLDA_SetProfileElement(activeDevices[i], j, profile[j]);
        printf(" SetProfileElement status for element %d = %s\r\n", j, fnLDA_perror(status));
      }
#endif

		  status = fnLDA_SetProfileCount(activeDevices[i], MaxProfileLength);
		  printf(" SetProfileCount status for channel %d = %s\r\n", k, fnLDA_perror(status));

		  status = fnLDA_SetProfileIdleTime(activeDevices[i], (100 + k));		// Profile idle times of 101 ms, 102 ms, 103 ms, etc.
		  printf(" SetProfileIdleTime status for channel %d = %s\r\n", k, fnLDA_perror(status));

		  status = fnLDA_SetProfileDwellTime(activeDevices[i], (60 + k));
		  printf(" SetProfileDwellTime status for channel %d = %s\r\n", k, fnLDA_perror(status));

		  status = fnLDA_StartProfile(activeDevices[i], PROFILE_ONCE);
	    }

// -- this is an example of how to generate a profile from the host side --
#if 0
      printf("stepping through a host generated profile...\r\n");
      /* now step through the profile - the size can be whatever you desire */
      for (j=0; j<1000; j++) {
        if (j>0) sleep(1); /* wait one second except before the first one
                              nanosleep can be used for shorter delays */
	      status = fnLDA_SetAttenuation(activeDevices[i], profile[j]);
	      if (status < 0) goto device_pulled;
	      printf("Set attenuation for device %d to %d (%.1f dB). %d seconds remain... Return status=0x%08x (%s)\r\n", activeDevices[i], profile[j], (float)profile[j]/20, 1000-j, status, fnLDA_perror(status));
      }
#endif
    } // 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++) {
    printf("calling fnLDA_CloseDevice with deviceID = %d\r\n", activeDevices[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:
  // -- we either got an error, or the device was removed --
  printf(" Error Status = 0x%08x (%s)\r\n", result, fnLDA_perror(result));
  // Assume we got an error, lets not leave the device open...
  status = fnLDA_CloseDevice(activeDevices[i]);
  printf("Closed device %d after an error. Return status=0x%08x (%s)\r\n", activeDevices[i], status, fnLDA_perror(status));

#if (QUICK_TEST)
  // just exit since we have an error or the device was unplugged
  return 0;
#endif
  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' array 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' array 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' array 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;
  }
}
