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

namespace trxGui
{
    public static class Udp
    {
        static int bigSpecW, bigSpecH, smallSpecW, smallSpecH;
        static int bigWFW, bigWFH, smallWFW, smallWFH;

        static public UdpQueue bigspecQ = new UdpQueue();
        static public UdpQueue smallspecQ = new UdpQueue();
        static public UdpQueue bigWFQ = new UdpQueue();
        static public UdpQueue smallWFQ = new UdpQueue();
        static public UdpQueue uq_tx = new UdpQueue();
        static public UdpQueue uq_rotary = new UdpQueue();

        static Bitmap bmBigWF, bmSmallWF;

        static color col = new color();

        // this threads handle udp RX and TX
        static Thread udprx_thread;
        static Thread udptx_thread;

        public static void InitUdp()
        {
            UpdateSize();

            // create thread for UDP RX
            udprx_thread = new Thread(new ThreadStart(Udprxloop));
            udprx_thread.Start();

            // create thread for UDP TX
            udptx_thread = new Thread(new ThreadStart(Udptxloop));
            udptx_thread.Start();
        }

        public static void UpdateSize()
        {
            bigSpecW = statics.panel_bigspec_Width;
            bigSpecH = statics.panel_bigspec_Height;

            smallSpecW = statics.panel_smallspec_Width;
            smallSpecH = statics.panel_smallspec_Height;

            bigWFW = statics.panel_bigwf_Width;
            bigWFH = statics.panel_bigwf_Height;

            smallWFW = statics.panel_smallwf_Width;
            smallWFH = statics.panel_smallwf_Height;

            bmBigWF = new Bitmap(bigWFW, bigWFH);
            bmSmallWF = new Bitmap(smallWFW, smallWFH);
        }

        static void Udptxloop()
        {
            DateTime dt = DateTime.UtcNow;
            UdpClient udpc = new UdpClient();

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

                if(wait) Thread.Sleep(10);
            }
        }

        static void Udprxloop()
        {
            // 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
                        IPAddress ipad = RemoteEndpoint.Address;
                        statics.ModemIP = ipad.ToString();
                        //Console.WriteLine(statics.ModemIP);
                        // 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);

                        //Console.WriteLine(rxtype + " len " + b.Length);

                        // check bitmap queues to avoid unnecessary graphic operations
                        int q1 = bigspecQ.Count();
                        int q2 = smallWFQ.Count();
                        int q3 = bigWFQ.Count();
                        int q4 = smallspecQ.Count();

                        // for slower CPUs: allow only one graphics item in the buffer
                        // faster CPUs: allow more graphic updates
                        int maxfill = 1;
                        if (statics.cpuspeed == 0) maxfill = 2; // only for fast cpus

                        if (rxtype == 0 && q1 < maxfill)
                        {
                            // big Spectrum, mid values
                            int[] arr = getSpecArr(b);
                            drawBigSpec(arr);
                        }

                        if (rxtype == 1 && q4 < maxfill)
                        {
                            // small Spectrum
                            int[] arr = getSpecArr(b);
                            drawSmallSpec(arr);
                        }

                        if (rxtype == 2 && q3 < maxfill)
                        {
                            
                            // big WF (raw - no mid - values)
                            int[] arr = getSpecArr(b);
                            //DateTime dts = DateTime.UtcNow;
                            drawBigWF(arr);
                            //DateTime dtact = DateTime.UtcNow;
                            //TimeSpan ts = dtact - dts;
                            //Console.WriteLine("drawtime: [ms] " + ts.TotalMilliseconds);
                        }

                        if (rxtype == 3 && q2 < maxfill)
                        {
                            // small WF
                            int[] arr = getSpecArr(b);
                            drawSmallWF(arr);
                        }

                        if(rxtype == 4)
                        {
                            // b contains audio devices and init status
                            UInt16 driversn = b[0];
                            driversn <<= 8;
                            driversn += b[1];
                            //Console.WriteLine("Driver SN:" + driversn);
                            statics.driver_serno = driversn;
                            String s = statics.ByteArrayToStringUtf8(b, 6);
                            //Console.WriteLine("Audio Devices:" + s);
                            String[] sa1 = s.Split(new char[] { '^' });
                            statics.AudioPBdevs = sa1[0].Split(new char[] { '~' });
                            statics.AudioCAPdevs = sa1[1].Split(new char[] { '~' });
                            statics.GotAudioDevices = 1;
                        }

                        if (rxtype == 5)
                        {
                            int v = b[0];
                            v <<= 8;
                            v |= b[1];
                            v <<= 8;
                            v |= b[2];
                            v <<= 8;
                            v |= b[3];
                            statics.noiselevel = v * 50 / 51;

                            v = b[4];
                            v <<= 8;
                            v |= b[5];
                            v <<= 8;
                            v |= b[6];
                            v <<= 8;
                            v |= b[7];
                            statics.maxlevel = v * 55 / 50;

                            v = b[8];
                            v <<= 8;
                            v |= b[9];
                            v <<= 8;
                            v |= b[10];
                            v <<= 8;
                            v |= b[11];
                            statics.qsolevel = v;

                            v = b[12];
                            v <<= 8;
                            v |= b[13];
                            v <<= 8;
                            v |= b[14];
                            v <<= 8;
                            v |= b[15];
                            statics.maxnoiselevel = v;

                            v = b[16];
                            v <<= 8;
                            v |= b[17];
                            v <<= 8;
                            v |= b[18];
                            v <<= 8;
                            v |= b[19];
                            statics.difflevel = v;

                            statics.hwptt = (b[20] == 1);

                            //Console.WriteLine("noiselevel: " + statics.noiselevel + " maxnoiselevel: " + statics.maxnoiselevel + " difflevel: " + statics.difflevel);
                            //Console.WriteLine(statics.noiselevel + "level: " + (statics.qsolevel - statics.noiselevel));
                        }

                        if (rxtype == 6)
                        {
                            int v = b[0];
                            v <<= 8;
                            v |= b[1];
                            v <<= 8;
                            v |= b[2];
                            v <<= 8;
                            v |= b[3];

                            statics.beaconoffset = v;
                            //Console.WriteLine("beaconoffset: " + statics.beaconoffset);
                        }

                        if (rxtype == 7)
                        {
                            // number of rotary steps for frequency setting
                            int steps = (int)b[0] - 128;
                            uq_rotary.Add(steps);
                        }

                        if (rxtype == 9)
                        {
                            int v = b[0];
                            v <<= 8;
                            v |= b[1];
                            v <<= 8;
                            v |= b[2];
                            v <<= 8;
                            v |= b[3];

                            statics.corrfact = v;
                        }

                        if (rxtype == 10)
                        {
                            statics.sendtone = b[0];
                        }
                    }
                }
                catch { }
            }
        }

        static int scaleY(int val, int valmin, int valmax, int max)
        {
            // scale
            int r = max * (val - valmin) / (valmax - valmin);

            // revers up/down
            return max - r;
        }

        static int scaleX(int x, int maxx, int width)
        {
            return x * width / maxx;
        }

        static int [] getSpecArr(Byte [] arr)
        {
            int [] sa = new int [arr.Length / 2];

            int didx = 0;
            for (int i = 0; i < arr.Length; i += 2)
            {
                UInt32 uv = arr[i];
                uv <<= 8;
                uv |= arr[i + 1];

                sa[didx++] = (int)uv;
            }
            return sa;
        }

        
        static Pen penmarker = new Pen(Brushes.Green, 2);
        static Pen penmarkerTX = new Pen(Brushes.Red, 2);
        static Brush brs = new SolidBrush(Color.FromArgb(60,60,60));
        static Pen penmarkerLN = new Pen(brs, 2);
        static Font rxtx = new Font("Verdana", 8.0f);
        static private Bandplan bp = new Bandplan();

        // palette
        static Color[] col_specfill = { Color.Blue, Color.FromArgb(255, 80, 80), Color.Green, Color.LightGray };
        static Color[] col_specline = { Color.LightGreen, Color.Yellow, Color.Cyan, Color.White };

        static SolidBrush[] br_spedFill = { new SolidBrush(col_specfill[0]), new SolidBrush(col_specfill[1]), new SolidBrush(col_specfill[2]), new SolidBrush(col_specfill[3]) };
        static Pen[] penline = { new Pen(col_specline[0], 1), new Pen(col_specline[1], 1), new Pen(col_specline[2], 1), new Pen(col_specline[3], 1) };

        static void drawBigSpec(int[] arr)
        {
            int noiselevel = statics.noiselevel * 54/50;

            Bitmap bmbigspec = new Bitmap(bigSpecW, bigSpecH);
            // using gibt Ressourcen von gr nicht frei !!! 
            // mono Update auf >= 6.12.xx erforderlich !!!
            using (Graphics gr = Graphics.FromImage(bmbigspec))
            {
                // Make a Polyline
                Point[] poly = new Point[arr.Length + 2];
                poly[0] = new Point(0, bigSpecH - 1);
                poly[arr.Length + 2 - 1] = new Point(bigSpecW - 1, bigSpecH - 1);

                for (int i = 0; i < arr.Length; i++)
                {
                    int val = scaleY(arr[i], noiselevel, statics.maxlevel, bigSpecH);
                    int xi = scaleX(i, arr.Length, bigSpecW);
                    poly[i + 1] = new Point(xi, val);
                }

                gr.FillRectangle(Brushes.Black, 0, 0, bigSpecW, bigSpecH);
                gr.FillPolygon(br_spedFill[statics.palette], poly);
                gr.DrawPolygon(penline[statics.palette], poly);

                // vertical lines at specific frequencies
                penmarkerLN.DashPattern = new float[] { 1.0f, 1.0f };
                if (statics.bandplan_mode == 0)
                {
                    for (int i = 0; i < bp.be.Length; i++)
                    {
                        int xs = qrgToPixelpos(bp.be[i].from);
                        xs = xs * bigSpecW / 1120;
                        gr.DrawLine(penmarkerLN, xs, 0, xs, bigSpecH);
                    }
                }
                else
                {
                    for (int qrg = 10489500; qrg <= 10490000; qrg += 50)
                    {
                        int spos = qrgToPixelpos(qrg);
                        spos = spos * bigSpecW / 1120;
                        gr.DrawLine(penmarkerLN, spos, 0, spos, bigSpecH);
                    }
                }

                // green vertical line at RX frequency
                // red vertical line at TX frequency
                int x = statics.RXoffset * 2 / 1000;
                x = x * bigSpecW / 1120;
                int xtx = statics.TXoffset * 2 / 1000;
                xtx = xtx * bigSpecW / 1120;
                int xydiff = Math.Abs(x - xtx);

                penmarker.DashPattern = new float[] { 2.0f, 2.0f };
                gr.DrawLine(penmarker, x, 20, x, bigSpecH);

                // red vertical line at TX frequency
                penmarkerTX.DashPattern = new float[] { 2.0f, 2.0f };
                gr.DrawLine(penmarkerTX, xtx, 24, xtx, bigSpecH - 4);

                if (xydiff > 10)
                {
                    gr.DrawString("RX", rxtx, Brushes.LightGreen, x - 6, 0);
                    gr.DrawString("TX", rxtx, Brushes.LightCoral, xtx - 6, 0);
                }
                else
                {
                    gr.DrawString("RX/TX", rxtx, Brushes.White, x - 15, 0);
                }
            }
            bigspecQ.Add(bmbigspec);
        }


        static int qrgToPixelpos(int qrg)
        {
            qrg -= 10489000;   // rest is kHz
            qrg -= 470;
            return qrg * 2;
        }

        static Font dBfont = new Font("Arial Black", 9.0f);
        static Brush br = new SolidBrush(Color.FromArgb(30,30,20));

        static void drawSmallSpec(int[] arr)
        {
            Pen dotpen = new Pen(Brushes.Yellow, 1);
            dotpen.DashPattern = new float[] { 2.0f, 2.0f };

            Bitmap bmsmallspec = new Bitmap(smallSpecW, smallSpecH);
            using (Graphics gr = Graphics.FromImage(bmsmallspec))
            {
                // Make a Polyline
                Point[] poly = new Point[arr.Length + 2];
                poly[0] = new Point(0, smallSpecH - 1);
                poly[arr.Length + 2 - 1] = new Point(smallSpecW - 1, smallSpecH - 1);

                for (int i = 0; i < arr.Length; i++)
                {
                    int val = scaleY(arr[i], statics.noiselevel, statics.maxlevel, smallSpecH);
                    int xi = scaleX(i, arr.Length, smallSpecW);
                    poly[i + 1] = new Point(xi, val);
                }

                gr.FillRectangle(Brushes.Black, 0, 0, bigSpecW, smallSpecH);
               
                gr.FillRectangle(br, 14150 * bigSpecW / 28000, 0, 2750 * bigSpecW / 28000, smallSpecH);
                gr.FillPolygon(br_spedFill[statics.palette], poly);
                gr.DrawPolygon(penline[statics.palette], poly);

                // calculate level of the QSO over noise
                int dB = 0;
                int qlev = statics.difflevel; // qsolevel - statics.maxnoiselevel;
                // ignore values below 800, these are too small to evaluate
                // above 400: 120/dB
                //Console.WriteLine("qlev: " + qlev + " nl " + statics.noiselevel + " mnl" + statics.maxnoiselevel);
                if(qlev >= 800)
                {
                    qlev -= 400;
                    dB = qlev / 120 + 6; // +6 because 400 is +6dB
                    String s = "+" + dB.ToString() + " dB";
                    gr.DrawString(s, dBfont, Brushes.Yellow, smallSpecW / 2 + 4, 0);
                }

                // tuning (middle) line
                gr.DrawLine(dotpen, smallSpecW / 2, 0, smallSpecW / 2, smallSpecH);
            }
            smallspecQ.Add(bmsmallspec);
        }

        static int scaleYWF(int val, int valmin, int valmax, int max)
        {
            // scale
            return max * (val - valmin) / (valmax - valmin);
        }

        static void drawBigWF(int[] arr)
        {
            int lineincrement = statics.cpuspeed + 1;
            // create a new bitmap
            Bitmap bmnew = new Bitmap(bigWFW, bigWFH);
            using (Graphics gr = Graphics.FromImage(bmnew))
            {
                                
                // copy existing bitmap into bmnew, n lines lower
                gr.DrawImage(bmBigWF, 0, lineincrement);
                
                for (int i = 0; i < arr.Length; i++)
                {
                    // scale color
                    int v = 255 - scaleY(arr[i], statics.noiselevel, statics.maxlevel * 47 / 50, 255);
                    if (v < 0) v = 0;
                    if (v > 255) v = 255;
                    //Console.WriteLine(arr[i] + ": " + v);

                    int xi = scaleX(i, arr.Length, bigWFW);
                    gr.FillRectangle(col.getSolidBrush(v), xi, 0, xi, lineincrement);
                }
                
            }
            // copy the new bitmap back
            using (Graphics grbm = Graphics.FromImage(bmBigWF))
              grbm.DrawImage(bmnew, 0, 0);

            using (Graphics gr = Graphics.FromImage(bmnew))
            {
                // green vertical line at RX frequency
                // red vertical line at TX frequency
                int x = statics.RXoffset * 2 / 1000;
                x = x * bigWFW / 1120;
                int xtx = statics.TXoffset * 2 / 1000;
                xtx = xtx * bigWFW / 1120;
                int xydiff = Math.Abs(x - xtx);

                penmarker.DashPattern = new float[] { 2.0f, 2.0f };
                gr.DrawLine(penmarker, x, 4, x, bigWFH);

                // red vertical line at TX frequency
                penmarkerTX.DashPattern = new float[] { 2.0f, 2.0f };
                gr.DrawLine(penmarkerTX, xtx, 0, xtx, bigWFH - 4);
            }

            bigWFQ.Add(bmnew);
        }

        static void drawSmallWF(int[] arr)
        {
            int lineincrement = statics.cpuspeed + 1;

            // create a new bitmap
            Bitmap bmnew = new Bitmap(smallWFW, smallWFH);
            using (Graphics gr = Graphics.FromImage(bmnew))
            {
                // copy existing bitmap into bmnew, one line lower
                gr.DrawImage(bmSmallWF, 0, lineincrement);

                for (int i = 0; i < arr.Length; i++)
                {
                    // scale color
                    int v = 255 - scaleY(arr[i], statics.noiselevel * 48/50, statics.maxlevel * 47 / 50, 255);
                    //Console.WriteLine(arr[i] + ": " + v);

                    int xi = scaleX(i, arr.Length, smallWFW);
                    gr.FillRectangle(col.getSolidBrush(v), xi, 0, xi, lineincrement);
                }
            }

            // copy the new bitmap back
            using (Graphics grbm = Graphics.FromImage(bmSmallWF))
            {
                grbm.DrawImage(bmnew, 0, 0);
            }

            // draw scales after scrolling
            using (Graphics gr = Graphics.FromImage(bmnew))
            {
                // tuning (middle) line
                Pen dotpen = new Pen(Brushes.Yellow, 1);
                dotpen.DashPattern = new float[] { 2.0f, 2.0f };
                int xi = smallWFW / 2;
                gr.DrawLine(dotpen, xi, 0, xi, smallWFH);

                // 25Hz per pixel: 120px per 3kHz
                Pen dotgraypen = new Pen(Brushes.DarkGray, 1);
                dotgraypen.DashPattern = new float[] { 1.0f, 3.0f };
                for (int i=120; i<500; i+=120)
                {
                    xi = smallWFW / 2 + i * smallWFW / 1120;
                    gr.DrawLine(dotgraypen, xi, 0, xi, smallWFH);
                }
                for (int i = -120; i > -500; i -= 120)
                {
                    xi = smallWFW / 2 + i * smallWFW / 1120;
                    gr.DrawLine(dotgraypen, xi, 0, xi, smallWFH);
                }
            }

            smallWFQ.Add(bmnew);
        }

        public static Bitmap getBigSpecBitmap()
        {
            if (bigspecQ.Count() == 0) return null;

            return bigspecQ.GetBitmap();
        }

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

        public static Bitmap getSmallSpecBitmap()
        {
            if (smallspecQ.Count() == 0) return null;

            return smallspecQ.GetBitmap();
        }

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

        public static Bitmap getBigWFBitmap()
        {
            if (bigWFQ.Count() == 0) return null;

            return bigWFQ.GetBitmap();
        }

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

        public static Bitmap getSmallWFBitmap()
        {
            if (smallWFQ.Count() == 0) return null;

            return smallWFQ.GetBitmap();
        }

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

        public static void UdpSendData(Byte[] b)
        {
            uq_tx.Add(b);
        }

        public static int GetRotary()
        {
            if (uq_rotary.Count() == 0) return 0;
            return uq_rotary.Getint();
        }
    }

    // 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(int 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 int Getint()
        {
            int b;

            lock (myQ.SyncRoot)
            {
                b = (int)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();
            }
        }
    }
}
