using System;
using System.Diagnostics;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;

/* Overview: This example demonstrates: 
 * 1. Connect to VNA through TCP/IP socket. (The socket has to be turned on in VNA software by going to "system" -> "misc setup" -> "network setup" -> "Socket (on) 5025")
 * 2. Set VNA stimulus
 * 3. Read the frequency data and measured S parameter data
 * 4. Return the measured data to command prompt window.
 * 5. the functions under "All TCP communicatiuon support functions" section are very important for correct TCP communication with the VNA software.
 *    It is recommended for the users to keep or duplicate these support functions in their own code development. If users write the TCP communciation functions themselves,
 *    be sure to look for the "\n" termination character from VNA's responses or there is potential data loss when using more measurement points.
 * 
 * Before running the code:
 * 1. Make sure socket server is on: system -> misc setup -> remote control settings -> socket server on
 * 2. Make sure socket server is 5025: system -> misc setup -> remote control settings -> socket port 5025
 * 3. Make sure VNA software is always running when the code is running. The VNA software is the driver for the VNA.
 * 
 * Common troubleshooting tip:
 * Must have an *OPC? command after VNA setup commands like setting stimulus or traces
 * If there is a group of setup commands, it only needs one *OPC? command at the end of the group (This is demonstrated in this example)
 * Must have an *OPC? command after triggering a sweep (This is also demonstrated in this example)
 * 
 *  -- 4/8/2021
 */



namespace CMT_example
{
    internal class Program
    {
        private static TcpClient tcpClient;
        private static NetworkStream netStream;
        private static void Main(string[] args)

        {
            // --------------------------------------------------------------------------------------------------------
            // create client and open the socket
            try
            {
                tcpClient = new TcpClient("127.0.0.1", 5025); //simulated
            }
            catch
            {
                Console.WriteLine("Could not connect to the VNA software through TCP.\n" +
                    "Make sure the VNA software is running and socket server 5025 is turned on.\n" +
                    "For S2VNA/S4VNA/TRVNA, go to \"system\" -> \"misc setup\" -> \"network remote control settings\" -> \"socket server on\"\n" + 
                    "For RVNA, go to \"system\" -> \"network setup\" -> \"socket server on\"");
                Console.WriteLine("Press any key to close...");
                Console.ReadKey();
                Environment.Exit(0);
            }

            // create the stream
            try
            {
                netStream = tcpClient.GetStream();
            }
            catch
            {
                Console.WriteLine("Could not open a Network Stream.");
                Console.WriteLine("Press any key to close...");
                Console.ReadKey();
                Environment.Exit(0);
            }

            // ============================================================================================================
            // Start sending SCPI commands here
            // ============================================================================================================

            string id = sendReceive(netStream, tcpClient, "*IDN?");
            Console.WriteLine(id);

            //Set Up Instrument

            //Restore Factory Presets
            send(netStream, tcpClient, "SYST:PRES");

            //Set Start and Stop Frequencies
            send(netStream, tcpClient, String.Format("SENS:FREQ:STAR {0}", "1GHz"));
            send(netStream, tcpClient, String.Format("SENS:FREQ:STOP {0}", "9GHz"));

            //Set # of Sweep Points
            send(netStream, tcpClient, String.Format("SENS:SWE:POIN {0}", 1601.ToString()));

            //Define channel 1 parameter as S21
            send(netStream, tcpClient, String.Format("CALC:PAR1:DEF {0}", "S11"));

            //Select Trace1
            send(netStream, tcpClient, "CALC:PAR1:SEL");

            //Set Trace Format to Log Magnitude
            send(netStream, tcpClient, String.Format("CALC:FORM {0}", "MLOG"));

            //Set to Bus Trigger
            send(netStream, tcpClient, String.Format("TRIG:SOUR {0}", "BUS"));

            // VERY IMPORTANT! NEEDS ONE AFTER THE CONFIGURATIONS TO WAIT FOR ALL THE COMMANDS TO EXECUTE
            sendReceive(netStream, tcpClient, "*OPC?");

            // for loop for multiple sweeps
            //for (int i = 0; i < 20; i++)
            //{

            //Send Single Trigger and Wait for Sweep to Complete
            send(netStream, tcpClient, "TRIG:SING");
            sendReceive(netStream, tcpClient, "*OPC?");

            //Get Frequency List
            string frequencies = sendReceive(netStream, tcpClient, "SENS:FREQ:DATA?");
            double[] freq_array = frequencies.Split(',').Select(double.Parse).ToArray();

            //Get S-Param Data
            string measurements = sendReceive(netStream, tcpClient, "CALC:DATA:FDAT?");
            double[] meas_array = measurements.Split(',').Select(double.Parse).ToArray();
            //}

            // read number of points
            string num_points_str = sendReceive(netStream, tcpClient, "SENS:SWE:POIN?");
            int num_points = Convert.ToInt32(num_points_str);

            //Display Measurements
            Console.WriteLine("Frequencies       S11 logmag");
            for (int i = 0; i < num_points; i++)
            {
                Console.WriteLine("{0:E}   {1:N}", freq_array[i], meas_array[i * 2]);
            }

            // change measurement format to smith chart(R+jx), enable one marker and display its values
            send(netStream, tcpClient, "CALC:FORM SMIT");
            send(netStream, tcpClient, "CALC:MARK1:ACT");
            string marker_x_value = sendReceive(netStream, tcpClient, "CALC:MARK1:X?");
            string marker_y_value = sendReceive(netStream, tcpClient, "CALC:MARK1:Y?");
            double marker_x = Convert.ToDouble(marker_x_value);
            double[] marker_y = marker_y_value.Split(',').Select(double.Parse).ToArray();

            Console.WriteLine("marker frequency: {0:E}  complex impedance real: {1:N}, imag: {2:N}", marker_x, marker_y[0], marker_y[1]);

            //Return to internal Trigger
            send(netStream, tcpClient, String.Format("TRIG:SOUR {0}", "INT"));

            // --------------------------------------------------------------------------------------------------------
            // close the stream
            netStream.Close();

            // close the client
            tcpClient.Close();

            // --------------------------------------------------------------------------------------------------------
            Console.WriteLine("Press any key to close...");
            Console.ReadKey();
            // --------------------------------------------------------------------------------------------------------
        }

        // ============================================================================================================
        // All TCP communicatiuon support functions
        // ============================================================================================================

        private static void send(NetworkStream stream, TcpClient client, string command)

        {
            writeStream(stream, client, command);
        }

        private static string sendReceive(NetworkStream stream, TcpClient client, string command)

        {
            string response = "";
            writeStream(stream, client, command);
            response = readStream(stream, client);
            return response;
        }

        // ============================================================================================================
        private static void writeStream(NetworkStream stream, TcpClient client, string command)

        {
            try
            {
                if (stream.CanWrite)
                {
                    // convert command to bytes
                    byte[] sendBytes = Encoding.UTF8.GetBytes(command + "\n");

                    // write command to stream
                    stream.Write(sendBytes, 0, sendBytes.Length);
                }
                else
                {
                    // close the stream
                    stream.Close();
                    // close the client
                    client.Close();
                }
            }
            catch
            {
            }
        }

        // ============================================================================================================
        private static string readStream(NetworkStream stream, TcpClient client)
        {
            string returnData = "";
            string last_byte_in_string = "";
            if (stream.CanRead)
            {
                // read stream into a byte buffer
                byte[] bytes = new byte[client.ReceiveBufferSize];

                // loop until all data is received from the server
                StringBuilder completeDataStringBuilder = new StringBuilder();

                do
                {
                    // note: this method blocks until at least one byte is read
                    int byteCount = stream.Read(bytes, 0, bytes.Length);

                    // convert data received to string and append it
                    //completeDataStringBuilder.AppendFormat("{0}", Encoding.ASCII.GetString(bytes, 0, byteCount));

                    if (byteCount > 0)  // because of -1, we don't want this to be negative
                    {
                        completeDataStringBuilder.AppendFormat("{0}", Encoding.ASCII.GetString(bytes, 0, byteCount - 1));
                        last_byte_in_string = Encoding.ASCII.GetString(bytes, byteCount - 1, 1);
                        completeDataStringBuilder.AppendFormat("{0}", last_byte_in_string);
                    }
                    else
                    {
                        Thread.Sleep(100);
                    }
                } while (stream.DataAvailable || last_byte_in_string != "\n");
                
                returnData = completeDataStringBuilder.ToString();
                last_byte_in_string = ""; // reset last_byte_in_string
            }
            else
            {
                // close the stream
                stream.Close();
                // close the client
                client.Close();
            }
            return returnData;
        }
    }
}