#include <stdio.h>
#include <string.h>
#include <vector>
#include <iostream>
#include "htra_api.h" 
#include "portaudio.h"
#include "example.h"

using namespace std;

#define IS_USB 1 // By default, a USB-type device is used. If using an Ethernet-type device, define IS_USB as 0.

int DSP_AMDemod()
{
	int Status = 0;      // Return value of the function.
	void* Device = NULL; // Memory address of the current device.
	int DevNum = 0;      // Specifies the device number.

	BootProfile_TypeDef BootProfile; // Boot profile structure, includes physical interface, power supply type, etc.
	BootInfo_TypeDef BootInfo;       // Boot information structure, includes device info, USB speed, etc.

	BootProfile.DevicePowerSupply = USBPortAndPowerPort; // Use both USB data port and external power port for power supply.

#if IS_USB==1
	// Configure USB interface.
	BootProfile.PhysicalInterface = USB;
#else 
	// Configure Ethernet interface.
	BootProfile.PhysicalInterface = ETH;
	BootProfile.ETH_IPVersion = IPv4;
	BootProfile.ETH_RemotePort = 5000;
	BootProfile.ETH_ReadTimeOut = 5000;
	BootProfile.ETH_IPAddress[0] = 192;
	BootProfile.ETH_IPAddress[1] = 168;
	BootProfile.ETH_IPAddress[2] = 1;
	BootProfile.ETH_IPAddress[3] = 100;
#endif

	Status = Device_Open(&Device, DevNum, &BootProfile, &BootInfo);              // Open the device.

	Device_Open_ErrorHandling(Status, &Device, DevNum, &BootProfile, &BootInfo); // If Status is not 0, perform corresponding error handling based on the return value.

	IQS_Profile_TypeDef IQS_ProfileIn;                     //Configure parameters for IQS mode including start/stop frequency, decimate factor, R.L. etc.
	IQS_Profile_TypeDef IQS_ProfileOut;                    //Feedback information for IQS mode including start/stop frequency, decimate factor, R.L. etc.
	IQS_StreamInfo_TypeDef StreamInfo;                     //feedback information for the configuration including IQ data points, time domain data etc.

	Status = IQS_ProfileDeInit(&Device, &IQS_ProfileIn); // Initialize related parameters for IQS mode configuration.

	IQS_ProfileIn.CenterFreq_Hz = 97.6e6;    // Set the center frequency.
	IQS_ProfileIn.RefLevel_dBm = -35;        // Set the reference level.
	IQS_ProfileIn.DataFormat = Complex16bit; // Set the IQ data format.
	IQS_ProfileIn.TriggerMode = Adaptive;    // Set the trigger mode. In FixedPoints mode, sampling starts at the rising edge of the trigger signal and stops after collecting TriggerLength points. In Adaptive mode, sampling starts at the rising edge and ends at the falling edge of the trigger signal.
	IQS_ProfileIn.TriggerSource = Bus;       // Set the trigger source as internal bus trigger. If the device uses an external trigger source, set it to External.
	IQS_ProfileIn.DecimateFactor = 512;      // Set the decimation factor.
	IQS_ProfileIn.BusTimeout_ms = 50000;     // Set the bus timeout (in milliseconds).


	Status = IQS_Configuration(&Device, &IQS_ProfileIn, &IQS_ProfileOut, &StreamInfo); // Configure IQS mode settings by calling this function.

	IQS_Configuration_ErrorHandling(Status, &Device, DevNum, &BootProfile, &BootInfo, &IQS_ProfileIn, &IQS_ProfileOut, &StreamInfo); // Perform corresponding error handling based on the Status return value when it is not 0.

	IQStream_TypeDef IQStream;                        // Stores IQ data packets (IQ data and related configuration information).
	void* ADM = nullptr;
	ADM_Open(&ADM);

	/* Audio playback configuration */
	PaStreamParameters outputParameters; // Structure to store audio configuration parameters such as device ID, channel count, and data format.
	PaStream* stream;                    // Pointer used by PortAudio to store the audio input/output stream information.
	PaError err;                         // Error code returned by PortAudio functions.

	err = Pa_Initialize();                             // Initialize PortAudio.
	vector<float> audio(StreamInfo.PacketSamples);     // Allocate memory to store demodulated audio data.
	vector<float> LPF_audio(StreamInfo.PacketSamples); // Allocate memory to store demodulated and low-pass filtered audio data.

	outputParameters.device = Pa_GetDefaultOutputDevice();									                // Configure the default output audio device.
	outputParameters.channelCount = 1;									                                    // Configure the number of audio channels.
	outputParameters.sampleFormat = paFloat32;									                            // Configure the audio data type. Each audio sample is stored as a 32-bit signed floating-point value, with a range from -1.0 to 1.0.
	outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency; // Configure the desired latency.
	outputParameters.hostApiSpecificStreamInfo = NULL;                                                      // Optional pointer to a host API-specific data structure for device or stream-specific settings. Set to NULL if not used.

	err = Pa_OpenStream(&stream, NULL, &outputParameters, StreamInfo.IQSampleRate, StreamInfo.PacketSamples, paClipOff, NULL, NULL); // Open an audio stream.
	err = Pa_StartStream(stream);																									 // Start audio processing.

	/* Start DSP functionality */
	void* DSP = NULL;        // Allocate memory for digital signal processing.
	Status = DSP_Open(&DSP); // Enable signal processing functionality.

	/* Configure low-pass filter to process audio data */
	Filter_TypeDef LPF_ProfileIn;
	Filter_TypeDef LPF_ProfileOut;

	DSP_LPF_DeInit(&LPF_ProfileIn); // Initialize LPF configuration parameters.

	LPF_ProfileIn.As = 90;   // Configure stopband attenuation.
	LPF_ProfileIn.fc = 0.25; // Configure cutoff frequency.
	LPF_ProfileIn.mu = 0;    // Configure fractional sampling offset.
	LPF_ProfileIn.n = 90;    // Configure filter order.

	DSP_LPF_Configuration(&DSP, &LPF_ProfileIn, &LPF_ProfileOut); // Apply LPF configuration.

	Status = IQS_BusTriggerStart(&Device); // Trigger the device using IQS_BusTriggerStart. If the trigger source is external, this function is not required.

	// Continuously acquire and play AM broadcast
	while (1)
	{
		Status = IQS_GetIQStream_PM1(&Device, &IQStream); // Acquire IQ data.
		if (Status == APIRETVAL_NoError)
		{
			ADM_AMDemod(&ADM, IQStream.AlternIQStream, IQStream.IQS_Profile.DataFormat, StreamInfo.PacketSamples, IQStream.IQS_StreamInfo.IQSampleRate, audio.data());

			for (int i = 0; i < StreamInfo.PacketSamples; i++) // Scale demodulated audio data to the -1~1 range. The divisor can be adjusted as needed.
			{
				audio[i] /= 32;
			}

			DSP_LPF_Execute_Real(&DSP, audio.data(), LPF_audio.data());               // Apply low-pass filtering to the audio data.
			err = Pa_WriteStream(stream, LPF_audio.data(), StreamInfo.PacketSamples); // Play the filtered audio data.
		}
		else // Perform corresponding error handling when Status is not 0.
		{
			IQS_ErrorHandlingExceptOpenAndConfiguration(Status, &Device, DevNum, &BootProfile, &BootInfo, &IQS_ProfileIn, &IQS_ProfileOut, &StreamInfo);
		}

	}

	Status = IQS_BusTriggerStop(&Device); // Stop triggering the device using IQS_BusTriggerStop. If the trigger source is external, this function is not required.

	// Close all functional modules.
	err = Pa_StopStream(stream);  // Stop audio processing.
	err = Pa_CloseStream(stream); // Close the audio stream.
	Pa_Terminate();               // Release all resources allocated by PortAudio.

	DSP_Close(&DSP);                // Release all resources allocated for digital signal processing.
	ADM_Close(&ADM);          // Release all resources allocated for analog demodulation.
	Status = Device_Close(&Device); // Close the device.
	return 0;

}