/*
 * 9/2020 (c) DJ0ABR, Kurt Moraw
 * License: GPL 3.0
 * 
 * udp.cs
 * ------
 * 
 * Creates a new thread which handles all incoming and outgoing UDP traffic.
 * Communication to other threads is done via a thread-safe pipe.
 * The UDP transmitter handles the correct datarate according to the modem speed.
*/

using System;
using System.Collections;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace oscardata
{
    public static class Udp
    {
        // this thread handles udp RX
        static Thread udprx_thread;
        static Thread udptx_thread;

        // Pipes for data transferred via UDP ports
        static UdpQueue uq_rx = new UdpQueue();
        static UdpQueue uq_tx = new UdpQueue();
        static UdpQueue uq_ctrl = new UdpQueue();
        static UdpQueue uq_fft = new UdpQueue();
        static UdpQueue uq_iq = new UdpQueue();
        static UdpQueue uq_rtty_rx = new UdpQueue();

        public static int searchtimeout = 0;
        static String last_audiodevstring = "";

        // Constructor
        // called when Udp is created by the main program
        public static void InitUdp()
        {
            // create thread for UDP RX
            udprx_thread = new Thread(new ThreadStart(Udprxloop));
            udprx_thread.Name = "Thread: oscardata UDP-RX";
            udprx_thread.Start();

            // create thread for UDP TX
            udptx_thread = new Thread(new ThreadStart(Udptxloop));
            udptx_thread.Name = "Thread: oscardata UDP-TX";
            udptx_thread.Start();
        }

        public static void Close()
        {
            try
            {
                udprx_thread.Abort();
                udptx_thread.Abort();
            }
            catch { }
        }

        // Udp RX Loop runs in its own thread
        
        static void Udprxloop()
        {
            int extIPcnt = 0;

            // define UDP port
            UdpClient udpc = new UdpClient(statics.UdpRXport);
            udpc.Client.ReceiveTimeout = 100;

            while (statics.running)
            {
                try
                {
                    // receive data from UDP port
                    IPEndPoint RemoteEndpoint = new IPEndPoint(IPAddress.Any, 0);
                    Byte[] rxarr = udpc.Receive(ref RemoteEndpoint);
                    if (rxarr != null)
                    {
                        // Data received:
                        // RemoteEndpoint.Address ... IP address of the sender
                        // RemoteEndpoint.Port ... port
                        // b[0] ... Type of data
                        // b+1 ... Byte array containing the data
                        int rxtype = rxarr[0];
                        Byte[] b = new byte[rxarr.Length - 1];
                        Array.Copy(rxarr, 1, b, 0, b.Length);

                        // payload
                        if (rxtype == statics.udp_payload)
                        {
                            //Console.WriteLine("payload");
                            uq_rx.Add(b);
                        }

                        // Broadcast response
                        if (rxtype == statics.udp_bc)
                        {
                            String ModIP = RemoteEndpoint.Address.ToString();
                            if (ModIP != statics.MyIP)
                            {
                                // this is not the local IP
                                // wait for 3 Receptions before accepting it
                                if (extIPcnt < 3)
                                {
                                    //Console.WriteLine("myIP:"+statics.MyIP+" modem is ext IP:"+ModIP+", waiting:" + extIPcnt);
                                    if (extIPcnt < 4) extIPcnt++;
                                    if (extIPcnt < 3)
                                        continue;
                                }
                                //Console.WriteLine("modem is ext IP, accepted");
                            }
                            else
                            {
                                //Console.WriteLine("modem is local IP");
                                extIPcnt = 0;
                            }

                            statics.ModemIP = ModIP;
                            searchtimeout = 0;
                            // message b contains audio devices and init status

                            statics.initAudioStatus = (b[0] == '1') ? 2 : 0;
                            statics.initAudioStatus |= (b[1] == '1') ? 1 : 0;
                            statics.initVoiceStatus = (b[2] == '1') ? 2 : 0;
                            statics.initVoiceStatus |= (b[3] == '1') ? 1 : 0;

                            //String s = statics.ByteArrayToString(b,4);
                            String s = statics.ByteArrayToStringUtf8(b, 4);
                            String[] sa1 = s.Split(new char[] { '^' });
                            statics.AudioPBdevs = sa1[0].Split(new char[] { '~' });
                            statics.AudioCAPdevs = sa1[1].Split(new char[] { '~' }); 

                            // has the device list changed ?
                            if(s != last_audiodevstring)
                            {
                                statics.GotAudioDevices = 1;
                                last_audiodevstring = s;
                            }

                            if(statics.GotAudioDevices == 0)
                                statics.GotAudioDevices = 1;
                        }

                        // FFT data
                        if (rxtype == statics.udp_fft)
                        {
                            int idx = 0;
                            statics.PBfifousage = b[idx++];
                            statics.CAPfifousage = b[idx++];
                            statics.RXlevelDetected = b[idx++];
                            statics.RXinSync = b[idx++];
                            statics.maxRXlevel = b[idx++];
                            statics.maxTXlevel = b[idx++];
                            statics.tune_frequency = b[idx++];
                            statics.tune_frequency <<= 8;
                            statics.tune_frequency += b[idx++];
                            statics.rtty_txon = b[idx++];
                            //Console.WriteLine("f:" + statics.tune_frequency);
                            Byte[] b1 = new byte[b.Length - idx];
                            Array.Copy(b, idx, b1, 0, b1.Length);
                            drawFftBitmap(b1);
                        }

                        // IQ data
                        if (rxtype == statics.udp_iq)
                        {
                            Int16[] re = new Int16[b.Length / 2];
                            Int16[] im = new Int16[b.Length / 2];
                            int idx = 0;

                            for (int i = 0; i < b.Length; i+=4)
                            {
                                re[idx] = b[i+0];
                                re[idx] <<= 8;
                                re[idx] += b[i+1];

                                im[idx] = b[i+2];
                                im[idx] <<= 8;
                                im[idx] += b[i+3];
                                idx++;
                            }
                            drawBitmap(re, im);
                        }

                        if (rxtype == statics.udp_rtty_rx)
                        {
                            uq_rtty_rx.Add(b);
                        }
                    }
                }
                catch { }
            }
        }

        static int panelw = 75, panelh = 75;
        static Bitmap bm;
        const int maxsum = 5000;
        static Int16[] resum = new Int16[maxsum];
        static Int16[] imsum = new Int16[maxsum];
        static int sumidx = 0;
        static SolidBrush bgcol = new SolidBrush(Color.Silver);//FromArgb(255, (byte) 0x40, (byte) 0x00, (byte) 0x00));

        static double scaleiq(int v)
        {
            double f = v;
            f /= 15000.0;
            // f goes from -1 to +1
            // scale it to the graphics
            const int sz = 45;
            f += 1;
            f /= 2;
            f *= sz;
            f += (panelw-sz)/2;
            return f;
        }

        static void drawBitmap(Int16[] re, Int16[] im)
        {
            // collect IQ data
            for (int i = 0; i < re.Length; i++)
            {
                if (sumidx < maxsum)
                {
                    resum[sumidx] = re[i];
                    imsum[sumidx] = im[i];
                    sumidx++;
                }
            }

            // check if there is space in bitmap fifo
            // if the GUI does not process the bitmaps fast enough, just cancel it
            if (uq_iq.Count() > 2)
                return;

            // bitmap for drawing the complete picture
            bm = new Bitmap(panelw, panelh);

            using (Graphics gr = Graphics.FromImage(bm))
            {
                // background
                gr.FillRectangle(bgcol, 0,0, panelw, panelh);
                // oscilloscope screen
                gr.DrawImage(new Bitmap(Properties.Resources.screen), 2, 1);
                /*
                // screws at the 4 corners
                Bitmap screw = new Bitmap(Properties.Resources.schraube);
                gr.DrawImage(screw, 2, 2);
                gr.DrawImage(screw, panelw - 2-screw.Width, 2);
                gr.DrawImage(screw, 2, panelh - 2 - screw.Height);
                gr.DrawImage(screw, panelw - 2 - screw.Width, panelh - 2 - screw.Height);
                */
                // draw constellation points
                for (int i = 0; i < sumidx; i++)
                {
                    if (resum[i] == 0 || imsum[i] == 0) continue;
                    double dist = Math.Sqrt((resum[i] * resum[i]) + (imsum[i] * imsum[i]));
                    if (dist > 22000) continue; // do not draw outside scope

                    double x = scaleiq(resum[i]);
                    double y = scaleiq(imsum[i]);

                    double et = 1.6;
                    x -= et;
                    y -= et;
                    double w = et * 2;
                    double h = et * 2;
                    gr.FillEllipse(Brushes.Yellow, (int)x, (int)y, (int)w, (int)h);
                }
            }

            uq_iq.Add(bm);
            sumidx = 0;
        }

        static int fftw = 410, ffth = 72;
        static Bitmap bmskala = new Bitmap(fftw,ffth);
        static bool bmf = false;
        static Font fnt = new Font("Verdana", 10.0f);
        static Font smallfnt = new Font("Verdana", 8.0f);
        static Pen penyl = new Pen(Brushes.Yellow, 1);
        static Pen pengn = new Pen(Brushes.LightGreen, 3);

        static void drawFftBitmap(Byte[] b1)
        {
            int yl = ffth - 20;
            int yh = 20;

            if (!bmf)
            {
                // pre-draw background
                bmf = true;
                Pen pen = new Pen(Brushes.Navy, 1);
                Pen pensolid = new Pen(Brushes.Navy, 1);
                Pen pensolidwhite = new Pen(Brushes.White, 1);
                pen.DashPattern = new float[] { 1.0F, 2.0F, 1.0F, 2.0F };
                Pen penred = new Pen(Brushes.Red, 1);
                Pen penred2 = new Pen(Brushes.Red, 2);

                using (Graphics gr = Graphics.FromImage(bmskala))
                {
                    gr.FillRectangle(bgcol, 0, 0, fftw, ffth);
                    gr.DrawImage(new Bitmap(Properties.Resources.osci), 0, 0);

                    for (int x = 10; x <= 390; x += 10)
                        gr.DrawLine(pen, x, yl, x, yh);

                    gr.DrawLine(penred2, 11, yl, 11, yh);
                    gr.DrawLine(penred, 150, yl, 150, yh);
                    gr.DrawLine(pensolid, 20, yl, 20, yh);
                    gr.DrawLine(pensolid, 280, yl, 280, yh);
                    gr.DrawLine(pensolid, 360, yl, 360, yh);

                    gr.DrawLine(pensolidwhite, 11, 12, 11,20);
                    gr.DrawLine(pensolidwhite, 150, yl + 2, 150, yl + 7);
                    gr.DrawLine(pensolidwhite, 20, yl + 2, 20, yl + 7);
                    gr.DrawLine(pensolidwhite, 280, yl + 2, 280, yl + 7);
                    gr.DrawLine(pensolidwhite, 360, yl+2, 360, yl+7);

                    gr.DrawRectangle(penred, 15, yh, 270, yl-yh);

                    gr.DrawString("200", smallfnt, Brushes.DarkBlue, 8, yl+7);
                    gr.DrawString("1500", smallfnt, Brushes.DarkBlue, 135, yl + 7);
                    gr.DrawString("2800", smallfnt, Brushes.DarkBlue, 267, yl + 7);
                    gr.DrawString("3600", smallfnt, Brushes.DarkBlue, 345, yl + 7);

                    gr.DrawString(statics.langstr[8], fnt, Brushes.White, 100, 0);

                    gr.DrawString("Ref", smallfnt, Brushes.LightCoral, 1, 0);
                }

                bmskala.MakeTransparent(Color.White);
            }

            // check if there is space in bitmap fifo
            // if the GUI does not process the bitmaps fast enough, just cancel it
            if (uq_fft.Count() > 2)
                return;

            // bitmap for drawing the complete picture
            bm = new Bitmap(442, 76);

            int rshift = 14;
            using (Graphics gr = Graphics.FromImage(bm))
            {
                // background
                gr.FillRectangle(bgcol, 0, 0, bm.Width, bm.Height);
                // scala
                gr.DrawImage(bmskala,16,2);

                if(statics.real_datarate == 45)
                {
                    // RTTY Markers
                    int low = (statics.tune_frequency - 170 / 2)/10;
                    int high = (statics.tune_frequency + 170 / 2)/10;
                    gr.DrawLine(pengn, low + rshift, yl, low + rshift, yh + 3);
                    gr.DrawLine(pengn, high + rshift, yl, high + rshift, yh + 3);
                }
                /*
                // screws at the 4 corners
                Bitmap screw = new Bitmap(Properties.Resources.schraube);
                gr.DrawImage(screw, 2, 2);
                gr.DrawImage(screw, 442 - 2 - screw.Width, 2);
                gr.DrawImage(screw, 2, 76 - 2 - screw.Height);
                gr.DrawImage(screw, 442 - 2 - screw.Width, 76 - 2 - screw.Height);
                */
                // spectrum
                int lastus = -1;
                // values
                for (int i = 0; i < b1.Length-1; i+=2)
                {
                    int us = b1[i];
                    us <<= 8;
                    us += b1[i + 1];
                    double fus = 0;
                    if (us > 0)
                        fus = 35 * Math.Log10((double)us / 10);

                    us = (int)(fus - 5.0);
                    if(lastus != -1 && i>0)
                        gr.DrawLine(penyl, i/2+ rshift, 76-lastus, i/2+1+ rshift, 76-us); // 15 istead of 16 to get it in exact position
                    lastus = us;
                }
            }
            uq_fft.Add(bm);
        }

        // Udp TX Loop runs in its own thread
        static void Udptxloop()
        {
            DateTime dt = DateTime.UtcNow;
            UdpClient udpc = new UdpClient();

            while (statics.running)
            {
                bool wait = true;
                if(uq_ctrl.Count() > 0)
                {
                    // Control Message: send immediately
                    Byte[] b = uq_ctrl.Getarr();
                    udpc.Send(b, b.Length, statics.ModemIP, statics.UdpTXport);
                    wait = false;
                }

                if(statics.PBfifousage < 3)
                {
                    // we need to send more payload data
                    // but never send faster than 1000 Byte/s
                    // because statics.PBfifousage may be updated too slow
                    //DateTime dtact = DateTime.UtcNow;
                    //TimeSpan ts = dtact - dt;
                    //if (ts.TotalMilliseconds > statics.UdpBlocklen)
                    {
                        if (uq_tx.Count() > 0)
                        {
                            Byte[] b = uq_tx.Getarr();
                            udpc.Send(b, b.Length, statics.ModemIP, statics.UdpTXport);
                            wait = false;
                            //dt = dtact;
                        }
                    }
                }
                if (wait) Thread.Sleep(1);
            }
        }

        public static void UdpBCsend(Byte[] b, String ip, int port)
        {
            UdpClient udpc = new UdpClient();
            udpc.EnableBroadcast = true;
            udpc.Send(b, b.Length, ip, port);
        }

        // send a Byte array via UDP
        // this function can be called from anywhere in the program
        // it transfers the data to the udp-tx thread via a thread-safe pipe
        public static void UdpSendData(Byte[] b)
        {
            uq_tx.Add(b);
        }

        public static void UdpSendCtrl(Byte[] b)
        {
            uq_ctrl.Add(b);
        }

        public static int GetBufferCount()
        {
            return uq_tx.Count();
        }

        public static int GetBufferCountCtrl()
        {
            return uq_ctrl.Count();
        }

        public static Byte[] UdpReceive()
        {
            if (uq_rx.Count() == 0) return null;

            return uq_rx.Getarr();
        }

        public static qpskitem UdpGetIQ()
        {
            if (uq_iq.Count() == 0) return null;

            return uq_iq.GetQPSKitem();
        }

        public static Bitmap UdpBitmap()
        {
            if (uq_iq.Count() == 0) return null;

            return uq_iq.GetBitmap();
        }

        public static Bitmap UdpFftBitmap()
        {
            if (uq_fft.Count() == 0) return null;

            return uq_fft.GetBitmap();
        }

        public static bool IQavail()
        {
            if (uq_iq.Count() == 0) return false;
            return true;
        }

        public static Byte[] getRTTYrx()
        {
            if (uq_rtty_rx.Count() == 0) return null;
            return uq_rtty_rx.Getarr();
        }
    }

    // this class is a thread safe queue wich is used
    // to exchange data with the UDP RX/TX threads
    public class UdpQueue
    {
        Queue myQ = new Queue();

        public void Add(Byte [] b)
        {
            lock (myQ.SyncRoot)
            {
                myQ.Enqueue(b);
            }
        }

        public void Add(qpskitem b)
        {
            lock (myQ.SyncRoot)
            {
                myQ.Enqueue(b);
            }
        }

        public void Add(Bitmap bm)
        {
            lock (myQ.SyncRoot)
            {
                myQ.Enqueue(bm);
            }
        }

        public Bitmap GetBitmap()
        {
            Bitmap b;

            lock (myQ.SyncRoot)
            {
                b = (Bitmap)myQ.Dequeue();
            }
            return b;
        }

        public Byte[] Getarr()
        {
            Byte[] b;

            lock (myQ.SyncRoot)
            {
                b = (Byte[])myQ.Dequeue();
            }
            return b;
        }

        public qpskitem GetQPSKitem()
        {
            qpskitem b;

            lock (myQ.SyncRoot)
            {
                b = (qpskitem)myQ.Dequeue();
            }
            return b;
        }

        public qpskitem GetItem()
        {
            qpskitem b;

            lock (myQ.SyncRoot)
            {
                b = (qpskitem)myQ.Dequeue();
            }
            return b;
        }

        public int Count()
        {
            int result;

            lock (myQ.SyncRoot)
            {
                result = myQ.Count;
            }
            return result;
        }

        public void Clear()
        {
            lock (myQ.SyncRoot)
            {
                myQ.Clear();
            }
        }
    }

    public class qpskitem
    {
        public int re;
        public int im;
    }
}
