/*             Code by calm-solutuions.de
 *             Licensed under MIT License
 *     Feel free to use this Software like free beer!
 * 
 */
 
using System;
using System.Collections.Generic;
using System.Text;
using System.IO.Ports;
using System.IO;
 
namespace eHajo_BlinkenLights
{
    public partial class eHaJo_BL_API
    {
 
        public class pixel
        {
            public delegate void PixelChangedEventHandler(object sender);
            public event PixelChangedEventHandler OnPixelChangedEvent;
 
            protected void OnPixelChanged()
            {
                if (OnPixelChangedEvent != null)
                    OnPixelChangedEvent(this);
            }
 
            private bool EventsAusloesen=true;
 
            public void SetPixel(bool pixel)
            {
                SetPixel(pixel, EventsAusloesen);
            }
 
            public void SetPixel(bool pixel, bool EventAusloesen)
            {
                AN_AUS=pixel;
                aEvt.Set();
                if (EventAusloesen == true)
                {
                    OnPixelChanged();
                }
            }
 
            public bool GetPixel()
            {
                return AN_AUS;
            }
 
            public void MarkDirty()
            {
                aEvt.Reset();
            }
            private bool AN_AUS;
            public System.Threading.AutoResetEvent aEvt = new System.Threading.AutoResetEvent(false);
 
            public void EnableEvent(bool Zustand)
            {
                EventsAusloesen = Zustand;
            }
 
        }
        public class BL8x8
        {
            public delegate void BufferChangedEventHandler(object sender);
            public event BufferChangedEventHandler OnBufferChangedEvent;
            private bool EventsAusloesen = true;
            private ZeroCoordinate ZP = ZeroCoordinate.UpperRight; //0 Grad
            protected void OnBufferChanged()
            {
                if (OnBufferChangedEvent != null)
                    OnBufferChangedEvent(this);
            }
 
            public void EnableEvent(bool Zustand)
            {
                EventsAusloesen = Zustand;
            }
            public BL8x8(UInt16 x, UInt16 y, ref pixel[,] Buffer)
            {
                this.x = x;
                this.y = y;
                this.Buffer = Buffer;
                for (uint xx = 0; xx < 8; xx++)
                {
                    for (uint yy = 0; yy < 8; yy++)
                    {
                        Buffer[xx, yy] = new eHaJo_BL_API.pixel();
                        Buffer[xx, yy].OnPixelChangedEvent += new pixel.PixelChangedEventHandler(PixelChangedEvent);
                    }
                }
            }
 
            private void PixelChangedEvent(object sender)
            {
                if (EventsAusloesen == true)
                {
                    OnBufferChanged();
                }
            }
 
            public void RiseBufferEvent()
            {
                OnBufferChanged();
            }
            public readonly UInt16 x;
            public readonly UInt16 y;
            public pixel[,] Buffer =null;
 
        }
        public enum ZeroCoordinate { UpperLeft, UpperRight, LowerLeft, LowerRight };
 
        private SerialPort CP = new SerialPort();
        private enum BLFeedBack { ACK, NACK, TIMEOUT};
        private Queue<BLFeedBack> Rueckmeldung = new Queue<BLFeedBack>();
        private List<BL8x8> Bildpuffer = new List<BL8x8>();
        private bool boFeedback = false;
        //Konstruktor der Klasse mit allen was der Mensch so braucht
        public eHaJo_BL_API(string COMPortName) 
        {
            CP.PortName = COMPortName;
            CP.BaudRate = 9600;
            CP.DataBits = 8;
            CP.StopBits = StopBits.One;
            CP.Parity = Parity.None;
            CP.DataReceived += new SerialDataReceivedEventHandler(RX_DATA);
 
            RXTimer = new System.Timers.Timer();
            RXTimer.Elapsed += new System.Timers.ElapsedEventHandler(CheckNewData);
            RXTimer.Interval = 25;
 
        }
 
        public void SetMatrixDimension(int x, int y)
        {
            X_Groesze = x;
            Y_Groesze = y;
        }
 
        private int X_Groesze = 0;
        private int Y_Groesze = 0;
 
        private System.Timers.Timer RXTimer = null;
        private bool Pollen = false;
        public void RX_Daten_Pollen(bool DatenPollen)
        {
            Pollen = DatenPollen;
            RXTimer.Enabled = DatenPollen;
 
        }
 
        //Einmal ein neues Blinkenlights registrieren lassen können
        public BL8x8 RegisterBL(UInt16 x, UInt16 y, ref pixel[,] Buffer)
        {
            bool found = false;
            BL8x8 nbl = null;
            foreach(BL8x8 BL in Bildpuffer)
            {
                if((BL.x==x)&&(BL.y==y))
                {
                    nbl= BL;
                    break;
                }
            }
            if (found == false)
            {
                nbl = new BL8x8(x, y, ref Buffer);
                Bildpuffer.Add(nbl);
                return nbl;
            }
            else
            {
                return nbl;
            }
 
        }
 
        public pixel[,] GetBuffer(UInt16 PosX, UInt16 PosY)
        {
                    BL8x8 result = Bildpuffer.FindLast(
                    delegate(BL8x8 BL)
                    {
                        return ((BL.x == PosX) && (BL.y == PosY));
                    });
                    if (result != null)
                    {
                        return result.Buffer;
                    }
                    else
                    {
                        return null;
                    }
        }
 
 
        public BL8x8 GetBL8x8(UInt16 PosX, UInt16 PosY)
        {
            BL8x8 result = Bildpuffer.FindLast(
                                delegate(BL8x8 BL)
                                {
                                    return ((BL.x == PosX) && (BL.y == PosY));
                                });
            if (result != null)
            {
                return result;
            }
            else
            {
                return null;
            }
        }
 
        public void EnableBufferEvents(bool Zustand)
        {
            foreach (BL8x8 x in Bildpuffer)
            {
                x.EnableEvent(Zustand);
            }
        }
 
        public void RiseBufferEvents(bool Zustand)
        {
            foreach (BL8x8 x in Bildpuffer)
            {
                x.RiseBufferEvent();
            }
        }
 
 
 
        /*----------------------------------------------------------------------------------------
         *  Funktion: SetPixle
         *  Parameter: 
         *              Uint16 x , die X Koordinate des Pixel, startend oben links
         *              Uint16 y , die Y Koordinate des Pixel, startend oben links
         *              bool AN_AUS, setzt den passenden Wert des Pixel, true = an , false = aus
         *
         *  Beschreibung:
         *  Setzt einen Pixel in dem das passenden Datenpacket erstellt wird
         * 
         * ----------------------------------------------------------------------------------------
         */
 
        public void SetPixel(UInt16 x, UInt16 y, bool Zustand)
        {
            //Okay einmal x und y in das passenden BL umrechnen
            UInt16 BLX =  (UInt16)(x / 8);
            UInt16 BLY =  (UInt16)(y / 8);
            UInt16 PosX = (UInt16)(x % 8);
            UInt16 PosY = (UInt16)(y % 8);
            //Zum Testen lokale x position spiegeln...
            PosX = (UInt16)(7 - PosX);
            SetPixleAtBL(BLX, BLY, PosX, PosY, Zustand);
            if(boFeedback==true)
            {
                BLFeedBack FB = DequeueWithTimeOut(ref Rueckmeldung, 250);
                if(FB==BLFeedBack.NACK)
                {
                    //Da is was nicht  so wie es soll..das BL kennt den Befel nicht, sollte aber nicht sein
                    Console.WriteLine("Der Befel SetPixel(x,y,Zustand) wird von der Firmware nicht erkannt");
                }
                else if (FB == BLFeedBack.TIMEOUT)
                {
                    //Da is was nicht  so wie es soll..das BL kennt den Befel nicht, sollte aber nicht sein
                    Console.WriteLine("Der Befel SetPixel(x,y,Zustand) hat einen Timeput verursacht");
                }
            }
 
 
        }
 
 
        public void SetPixleAtBL(UInt16 PosX, UInt16 PosY,UInt16 LocalX, UInt16 LocalY, bool AN_AUS)
        {
           //Okay mal sehen welche BL das ist wenn es der Master ist dann past das hier so
            if ((PosX == 0) && (PosY == 0))
            {
                    BL8x8 result = Bildpuffer.FindLast(
                    delegate(BL8x8 BL)
                    {
                        return ((BL.x == PosX) && (BL.y == PosY));
                    });
                    if (result != null)
                    {
 
                        if ((LocalX < 8) && (LocalY < 8)) //Okay das Passt
                        {
                            byte Daten = 0;
                            Daten += (byte)(LocalX << 4);
                            Daten += (byte)(LocalY << 1);
                            Daten += System.Convert.ToByte(AN_AUS);
                            TX_DATA(Daten);
                            result.Buffer[LocalX, LocalY].SetPixel(AN_AUS);
                        }
                    }
            }
            else
            {
                BL8x8 result = Bildpuffer.FindLast(
                   delegate(BL8x8 BL)
                   {
                       return ((BL.x == PosX) && (BL.y == PosY));
                   });
                if (result != null)
                {
 
                    if ((LocalX < 8) && (LocalY < 8)) //Okay das Passt
                    {
                        byte Nummer = 0;
                        Nummer = (byte)(X_Groesze * result.x + result.y + 1);
                        //Dann einmal aus der Matrix das passenden BL raussuchen
                        if (CP.IsOpen == true)
                        {
                            byte[] Daten = new byte[1];
                            Daten[0]=0x8A;
							System.Console.WriteLine("Steuerbyte: 0x{0:X}", Daten[0]);
                            CP.Write(Daten, 0, 1);
                            Daten[0] = Nummer;
							System.Console.WriteLine("Adresse: {0:D}", Daten[0]);
                            CP.Write(Daten, 0, 1);
 
                            byte Data = 0;
                            Data += (byte)(LocalX << 4);
                            Data += (byte)(LocalY << 1);
                            Data += System.Convert.ToByte(AN_AUS);
							System.Console.WriteLine("Befehl: 0x{0:X}", Daten[0]);
                            TX_DATA(Data);
 
                        }
                        result.Buffer[LocalX, LocalY].SetPixel(AN_AUS);
                    }
                }
 
            }
 
        }
 
        /*----------------------------------------------------------------------------------------
         *  Funktion: GetPixle
         *  Parameter: 
         *              Uint16 x , die X Koordinate des Pixel, startend oben links
         *              Uint16 y , die Y Koordinate des Pixel, startend oben links
         *             
         *  Rückgabe:
         *              bool, true = an und flase=aus
         *  Beschreibung:
         *  Liest einen Pixel in dem das passenden Datenpacket erstellt wird
         * 
         * ----------------------------------------------------------------------------------------
         */
        public bool GetPixel(UInt16 x, UInt16 y) //Okay das hier wird häßlich da die Software alle Blinkenlighs als ein Image behandelt
        {
            //Okay einmal x und y in das passenden BL umrechnen
            UInt16 BLX =  (UInt16)(x/8);
            UInt16 BLY =  (UInt16)(y/8);
            UInt16 PosX = (UInt16)(x%8);
            UInt16 PosY = (UInt16)(y%8);
 
            //Dann mal sehen ob es da ein Blinkenlights gibt...
            BL8x8 result = Bildpuffer.FindLast(
            delegate(BL8x8 BL)
            {
                return ((BL.x == BLX) && (BL.y == BLY));
            });
            if (result != null)
            {
 
                byte Daten = 0xC0;
                Daten += (byte)(x << 3);
                Daten += (byte)(y);
                //Okay die Daten sind da also los
                result.Buffer[x, y].MarkDirty();
                TX_DATA(Daten); //Update ist angefordert
                //Warten bis die neuen Daten da sind.
                if (result.Buffer[x, y].aEvt.WaitOne(125) == false)
                {
                    //Okay da hat es einen Timeout gegeben
                    Console.WriteLine("GetPixel hat ein Timeout verursacht...");
                    return false;
                }
                return result.Buffer[x, y].GetPixel();
            }
            else
            {
                return false;
            }
 
        }
 
        /*----------------------------------------------------------------------------------------
        *  Funktion: GetImage
        *  Parameter: 
        *              kein
        *             
        *  Rückgabe:
        *              bool Array[x][y] , true = an und flase=aus
        *  Beschreibung:
        *  Liest das ganze Bild in dem das passenden Datenpacket erstellt wird
        * 
        * ----------------------------------------------------------------------------------------
        */
        public bool[,] GetImage()
        {
            bool[,] Image = new Boolean[8, 8];
 
                for (byte x = 0; x < 8; x++)
                {
 
                    for (byte y = 0; y < 8; y++)
                    {
                        Image[x, y] = GetPixel(x, y);
                    }
 
 
                }
 
                return Image;
 
        }
 
        public void ClearScreen()
        {
            TX_DATA((byte)(0x80 | 0x01));
 
            BLFeedBack FB = DequeueWithTimeOut(ref Rueckmeldung, 250);
            if (FB == BLFeedBack.NACK)
            {
                //Da is was nicht  so wie es soll..das BL kennt den Befel nicht, sollte aber nicht sein
                Console.WriteLine("Der Befel ClearScreen() wird von der Firmware nicht erkannt");
            }
            else if (FB == BLFeedBack.TIMEOUT)
            {
                //Da is was nicht  so wie es soll..das BL kennt den Befel nicht, sollte aber nicht sein
                Console.WriteLine("Der Befel ClearScreen() hat einen Timeput verursacht");
            }
 
        }
 
        public void InvertScreen()
        {
            TX_DATA((byte)(0x80 | 0x02));
 
            BLFeedBack FB = DequeueWithTimeOut(ref Rueckmeldung, 250);
            if (FB == BLFeedBack.NACK)
            {
                //Da is was nicht  so wie es soll..das BL kennt den Befel nicht, sollte aber nicht sein
                Console.WriteLine("Der Befel InvertScreen() wird von der Firmware nicht erkannt");
            }
            else if (FB == BLFeedBack.TIMEOUT)
            {
                //Da is was nicht  so wie es soll..das BL kennt den Befel nicht, sollte aber nicht sein
                Console.WriteLine("Der Befel InvertScreen() hat einen Timeput verursacht");
            }
 
        }
 
        public void SelectZeroCoordinate(ZeroCoordinate ZP)
        {
            switch (ZP)
            {
                case ZeroCoordinate.UpperLeft:
                    {
                        TX_DATA((byte)(0x84 | 0x01));
                    }
                    break;
 
                case ZeroCoordinate.UpperRight:
                    {
                        TX_DATA((byte)(0x84 | 0x00));
                    }
                    break;
 
                case ZeroCoordinate.LowerRight:
                    {
                        TX_DATA((byte)(0x84 | 0x03));
                    }
                    break;
 
                case ZeroCoordinate.LowerLeft:
                    {
                        TX_DATA((byte)(0x84 | 0x02));
                    }
                    break;
 
                default:
                    {
                        TX_DATA((byte)(0x84 | 0x00));
                    }
                    break;
            }
 
            BLFeedBack FB = DequeueWithTimeOut(ref Rueckmeldung, 250);
            if (FB == BLFeedBack.NACK)
            {
                //Da is was nicht  so wie es soll..das BL kennt den Befel nicht, sollte aber nicht sein
                Console.WriteLine("Der Befel SelectZeroCoordinate(ZP) wird von der Firmware nicht erkannt");
            }
            else if (FB == BLFeedBack.TIMEOUT)
            {
                //Da is was nicht  so wie es soll..das BL kennt den Befel nicht, sollte aber nicht sein
                Console.WriteLine("Der Befel SelectZeroCoordinate(ZP) hat einen Timeput verursacht");
            }
 
 
        }
 
        //Routinen für den COMPort
        public void SetCOMPort(string Portname)
        {
            CP.PortName = Portname;
        }
 
 
        public bool PollenAktiv()
        {
            return Pollen;
        }
        public bool OpenCOMPort()
        {
            try
            {
                CP.Open();
 
                return true;
            }
            catch
            {
                return false;
            }
        }
 
		public bool EnumerateBL ()
		{
			try 
			{
				byte[] Daten = new byte[1];
				Daten [0] = 0x88;
				CP.Write (Daten, 0, 1);
				return true;
			} catch {
				return false;
			}
		}
 
        public bool CloseCOMPort()
        {
            try
            {
                CP.Close();
                return true;
            }
            catch
            {
                return false;
            }
        }
 
        public bool IsComOpen()
        {
            return CP.IsOpen;
        }
 
        public string GetPortname()
        {
            return CP.PortName;
        }
 
        private void RX_DATA(object sender, SerialDataReceivedEventArgs e)
        {
            //Okay einmal das Ganze zerlegen lassen
            while (CP.BytesToRead > 0)
            {
                byte RX_B = (byte)CP.ReadByte();
                if ((RX_B & 0x80)!=0) //Neues Pixel ist da
                {
                    //KLann nur vom Master sein
                    UInt16 PosX =0;
                    UInt16 PosY =0;
                    UInt16 x = (UInt16)((RX_B & 0x70)>>4);
                    UInt16 y = (UInt16)((RX_B & 0x0E) >> 1);
                    bool Pixel = System.Convert.ToBoolean(RX_B & 0x01);
                    BL8x8 result = Bildpuffer.FindLast(
                    delegate(BL8x8 BL)
                    {
                        return ((BL.x == PosX) && (BL.y == PosY));
                    });
                    if (result != null)
                    {
                        result.Buffer[x, y].SetPixel(Pixel);
                    }
 
                }
                //Es gibt nun noch 0x08 und 0x15 für ACK und NACK
                else if (RX_B == 0x08)
                {
                    //Es ist ein ACK gekommen
                    Rueckmeldung.Enqueue(BLFeedBack.ACK);
 
                }
                else if (RX_B == 0x15)
                {
                    //Es ist ein NACK angekommen
                    Rueckmeldung.Enqueue(BLFeedBack.NACK);
 
                }
 
 
 
            }
        }
 
        private void TX_DATA(byte Daten)
        {
            //Einmal die Datensenden lassen
            if (CP.IsOpen == true)
            {
                byte[] Data = new byte[1];
                Data[0] = Daten;
                try
                {
                    if (Rueckmeldung.Count != 0)
                    {
                        Console.WriteLine("Eine nicht Behandelte Rückmeldung war noch vorhanden...:-(");
                    }
                    Rueckmeldung.Clear(); //Was auch immer da Drinne war ist nun unwichtig...
                    CP.Write(Data, 0, 1);
                }
                catch (SystemException pex)
                {
                    //Okay da is was hin...
                    //Bei dem Blinkenlights kann es ein das der UART nen abgang macht
                }
            }
 
        }
 
        //Einmal ein Fix für das Pollen des Comport unter Linux
        private void CheckNewData(System.Object sender, System.Timers.ElapsedEventArgs e)
        {
            if (CP.IsOpen == true)
            {
                if (CP.BytesToRead != 0)
                {
                    RX_DATA(null, null);
                }
            }
        }
 
        private BLFeedBack DequeueWithTimeOut(ref Queue<BLFeedBack> Q, Int32 Timeout_ms)
        {
               while ((Q.Count == 0) && Timeout_ms > 0)
                {
                    if (Timeout_ms > 50)
                    {
                        System.Threading.Thread.Sleep(50);
                        Timeout_ms -= 50;
                    }
                    else
                    {
                        System.Threading.Thread.Sleep(Timeout_ms);
                        Timeout_ms = 0;
                    }
                }
                if (Q.Count != 0)
                {
                    return Q.Dequeue();
                }
                else
                {
                    return BLFeedBack.TIMEOUT;
                }
            }
 
        }
 
 
 
}

8)) Okay das Passt { byte Daten = 0; Daten += (byte)(LocalX