using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.IO;
using System.Security.Cryptography;
using PC_Gui;
using System.IO.Ports;
using System.Windows.Forms;



namespace EmbeddedDesktop
{
    /// <summary>
    /// Definice typu ovladani
    /// </summary>
    public enum RCMode
    {
        Peripheral = 0,
        Display = 1,

    }

    /// <summary>
    /// Definice zpusobu pripojeni
    /// </summary>
    public enum ClientConnection
    {
        Serial,
        Wifi
    }

    /// <summary>
    /// Aktualni interni stav tridy
    /// </summary>
    public enum RemoteControlState
    {
        Stopped,
        Listenning,
        Configuring,
        Active,
        Error
    }

    /// <summary>
    /// Delegat pro odeslani textove informace o internim stavu tridy
    /// </summary>
    /// <param name="e">text</param>
    public delegate void RemoteControlEvent(string e);

    /// <summary>
    /// Delegat pro odeslani definovaneho vnitrniho stavu tridy
    /// </summary>
    /// <param name="e">stav</param>
    public delegate void RemoteControlStatusEvent(RemoteControlState e);

    public class RemoteControl
    {
        #region Private variables
        private TcpListener listenerSocket;             //Naslouchaci socket
        private Thread listenerThread;                  //Naslouchaci vlakno
        private bool isCiphered;                        //Flag sifrovani
        private X509Certificate2 listenerCertificate;   //Certifikat zabezpeceni
        private RCMode clientMode;                      //Rezim pripojeni
        private int listenerPort;                       //Naslouchaci port
        public DisplayReader display;                   //Display reader
        private Stream clientStream;                    //Klientsky stream
        Thread PeripheralThread;                        //Vlakno pro ovladani periferii
        private ClientConnection clientConnection;      //TCP spojeni
        private SerialPort clientSerial;                //Seriovy port

        #endregion

        #region Private constants
        private const int CONFIGURATION_TIMEOUT = 3000000;      //Delka cekani na konfiguraci
        private const string DEVICE_NAME = "ERD";               //Definice konfiguracnich hlavicek a znaku
        private const string IDENTIFICATION_HEADER = "CT=";
        private const string TERMINATOR = "!STOP!";
        private char [] SEPARATOR = { ',' };

        public RemoteControlEvent InternalEventMessage;         //Delegat zpravy o interni udalosti

        public RemoteControlStatusEvent InternalStateChange;    //Delegat zmeny vnitrniho stavu

        #endregion

        #region Public Properties

        public DisplayReader Display
        {
            get { return display; }
            set { display = value; }
        }

        public TcpListener ListenerSocket
        {
            get { return listenerSocket; }
            set { listenerSocket = value; }
        }

        public Thread ListenerThread
        {
            get { return listenerThread; }
            set { listenerThread = value; }
        }
        

        #endregion


        /// <summary>
        /// Odeslani zpravy o udalosti
        /// </summary>
        /// <param name="e">text</param>
        private void SendEventMessage(string e)
        {
            if (InternalEventMessage != null)
                InternalEventMessage(e);
        }

        /// <summary>
        /// Odeslani informace o stavu
        /// </summary>
        /// <param name="e">stav</param>
        private void SendStatusMessage(RemoteControlState e)
        {
            if (InternalStateChange!= null)
            {
                InternalStateChange(e);
            }
        }

        /// <summary>
        /// Funkce naslouchaciho vlakna
        /// </summary>
        private void ListenerThreadHandle()
        {
            Thread.CurrentThread.Name = "ListenerThread";
            SendEventMessage("Listener started, waiting for client.");
            while (true)
            {
                //Odeslani stavove zpravy
                SendStatusMessage(RemoteControlState.Listenning);
                try
                {
                    //Pokud je typ spojeni wifi
                    if (clientConnection == ClientConnection.Wifi)
                    {
                        //Cekej na pripojeni klienta
                        clientStream = GetNetworkDeviceStream();
                        if (clientStream == null)
                            continue;
                    }
                    else
                    {
                        //Jinak vyber stream ze serioveho portu
                        clientStream = clientSerial.BaseStream;
                    }

                    display.ClientStream = clientStream;
                }
                catch (Exception)
                {

                    return;
                }

                //Pokud mame spojeni pres seriovku
                if (clientSerial != null)
                {
                    //Odesli stavovou zpravu Naslouchani
                    SendStatusMessage(RemoteControlState.Listenning);
                }
                else
                {
                    //Jinak Nastavuji
                    SendStatusMessage(RemoteControlState.Configuring);
                }

                //Odesli dotaz na konfiguraci a cekej na odpoved
                System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
                byte[] headerByte = encoding.GetBytes("?\r\n");
                clientStream.Write(headerByte, 0, headerByte.Length);

                try
                {
                    //Cekani na ziskani odpovedi s konfiguraci
                    GetDeviceConfig(clientStream);

                }
                catch (Exception e)
                {
#if (DEBUG)
                    Console.WriteLine("Configuration failed, ending connection...");
                    Console.WriteLine("Exception: " + e.Message);
#endif
                    //Pokud doslo k chybe, uzavri stavajici pripojeni
                    if (clientSerial==null)
                        return;
                    if (clientConnection==ClientConnection.Wifi)
                        clientStream.Close();
                    SendEventMessage("Session closed.");
                    continue;
                }
                //POkud jde vse hladce, odesli stav Aktivni
                SendStatusMessage(RemoteControlState.Active);
                switch (clientMode)
	            {
                    case RCMode.Display:
                        display.Start();                        //aktivuj cteni displeje
                        StartPeripheralThread(clientStream);    //Aktivuj vlakno periferii
                        display.Stop();                         //ukonci cteni z displeje

                        GC.Collect();                           //Proved rucni vytrideni dat
                        break;
		            case RCMode.Peripheral:
                        StartPeripheralThread(clientStream);    //Aktivuj pouze periferni vlakno
                        break; 
                    default:
                        break;
	            }
                SendEventMessage("Session closed.");
                if (clientConnection == ClientConnection.Wifi)   //POkud je wifi spojeni, uzavri ho
                    clientStream.Close();  

            }
        }

        /// <summary>
        /// Funkce realizuje cekani na klienta. Po prichodu spojeni spusti komunikaci s nim
        /// </summary>
        /// <returns>Objekt komunikace</returns>
        private Stream GetNetworkDeviceStream()
        {

#if (DEBUG)
            Console.WriteLine("Listenning on port " + listenerPort.ToString() + "...");
#endif
            SendEventMessage("Listenning on port " + listenerPort.ToString() + "...");
            //Kazdych 20ms kontrolujeme jestli se neobjevil cekajici klient
            while (!listenerSocket.Pending())
            {
                Thread.Sleep(20);
            }
#if (DEBUG)
            Console.WriteLine("Incomming connection...");
#endif
            //Akceptace existujiciho klienta
            TcpClient client = listenerSocket.AcceptTcpClient();
            SendEventMessage("Incomming connection from IP " + ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString());

            SslStream ssl_stream = null;
            if (isCiphered)
            {
                //Pokud je pozadavek na sifrovani, pripravime instanci objektu pro sifrovani
                ssl_stream = new SslStream(client.GetStream());
                client.SendTimeout = 2000;
                try
                {
                    //Pokusime se provest authorizaci
                    ssl_stream.AuthenticateAsServer(listenerCertificate, true, System.Security.Authentication.SslProtocols.Ssl3, false);
                }
                catch (Exception e)
                {
#if (DEBUG)

                    Console.WriteLine("Connetion failed.");
#endif
                    SendEventMessage(e.Message);
                    return null;
                }


                clientStream = ssl_stream;
                clientStream.WriteTimeout = 2000000;

            }
            else
            {
                clientStream = client.GetStream();
            }
            
            //Vraci se objekt Stream ziskaneho spojeni
            return clientStream;
        }

        /// <summary>
        /// Zjisteni konfigurace vzdaleneho zarizeni
        /// </summary>
        /// <param name="clientStream">objekt spojeni</param>
        private void GetDeviceConfig(Stream clientStream)
        {
            //Vytvoreni objektu pro cteni ze streamu
            StreamReader sr = new StreamReader(clientStream);
            sr.BaseStream.ReadTimeout = CONFIGURATION_TIMEOUT;
            
            string incomming = "";
            string[] information = null;
#if (DEBUG)
            Console.WriteLine("Waiting for configuration...");
#endif
            SendEventMessage("Waiting for configuration...");
            try
            {
                //Cekani na data
                incomming = sr.ReadLine();
            }
            catch (Exception)
            {
                Console.WriteLine("Could not read from stream.");
                throw;
            }
            //Rozdeleni prichozich dat podle separatoru
            information = incomming.Split(SEPARATOR);
            //Zpracovani konfigurace od klienta
            ProceedDeviceConfig(information);
            Console.WriteLine("Device configured: " + clientMode.ToString());
            SendEventMessage("Device configured: " + clientMode.ToString());
        }

        /// <summary>
        /// Spusteni vlakna pro rizeni periferii a cekani na jeho ukonceni
        /// </summary>
        /// <param name="stream">stream spojeni</param>
        private void StartPeripheralThread(Stream stream)
        {
            PeripheralThread = new Thread(new ParameterizedThreadStart(PeripheralHandler));
            //Spusteni vlakna
            PeripheralThread.Start((object)stream);
#if (DEBUG)
            Console.WriteLine("Session started. Waiting for termination...");
#endif
            SendEventMessage("Session started. Waiting for termination...");
            //Cekani na ukonceni
            PeripheralThread.Join();
            PeripheralThread = null;
        }

        /// <summary>
        /// Zpracovani konfiguracnich informaci od klienta
        /// </summary>
        /// <param name="information">pole s informacemi</param>
        private void ProceedDeviceConfig(string[] information)
        {
            //Prvni pole musi obsahovat jmeno klienta
            if (information[0] != DEVICE_NAME)
            {
                SendEventMessage("Failed to configure device.");
                throw new Exception("Invalid device name.");
            }
            //Identifikace musi zacinat pevne stanovenym nazvem
            if (!information[1].StartsWith(IDENTIFICATION_HEADER))
            {
                SendEventMessage("Failed to configure device.");
                throw new Exception("Invalid device configuration");
            }
            try
            {
                //Rezim spojeni se ziska z druhe casti konfiguraku
                clientMode = (RCMode)Enum.Parse(typeof(RCMode), information[1].Remove(0, IDENTIFICATION_HEADER.Length), true);
            }
            catch (Exception)
            {
                SendEventMessage("Failed to configure device.");
                throw new Exception("Invalid configuration string.");
            }
        }

        /// <summary>
        /// Callback casovace pro odesilani NOP zprav
        /// </summary>
        /// <param name="t"></param>
        private void watchTimer(object t)
        {
            try
            {
                StreamWriter writer = new StreamWriter(clientStream);
                writer.WriteLine("NOP");
                writer.Flush();

            }
            catch (Exception)
            {

            }
            
        }

        /// <summary>
        /// Handler periferniho vlakna
        /// </summary>
        /// <param name="s">Objekt streamu</param>
        private void PeripheralHandler(object s)
        {
            //Priprava objektu pro zapis a cteni
            StreamReader reader = new StreamReader((Stream)s);
            StreamWriter writer = new StreamWriter((Stream)s);
            writer.AutoFlush = true;
            
            string data = "";
            string[] fields;
            System.Threading.Timer watch = new System.Threading.Timer(new TimerCallback(watchTimer));;
            //pokud je spusten periferni mod
            if (clientMode == RCMode.Peripheral)
            {
                //Spustit watchdog pro periodicke odesilani zprav NOP zarizeni

                watch.Change(3000, 3000);
            }
            
            
            while (true)
            {
                try
                {
                    //Cteni dat z rozhrani
                    data = reader.ReadLine();
                }
                catch (Exception)
                {
#if (DEBUG)
                    Console.WriteLine("Connection interrupted by remote side.");
#endif      
                    try
                    {
                        //Pokud doslo k chybe, vymaz buffer display readeru
                        watch.Dispose();
                        display.ClearOutputBuffer();
                    }
                    catch (Exception)
                    {

                    }
                    return;
                }

                //Pokud nastala chyba pri prijmu dat
                if (data==null)
                {
#if (DEBUG)
                    Console.WriteLine("Connection interrupted by remote side.");
#endif                    
                    try
                    {
                        //Vymaz display buffer
                        watch.Dispose();
                        display.ClearOutputBuffer();
                    }
                    catch (Exception)
                    {

                    }
                    //Ukonci komunikaci
                    return;
                }

                //Pokud byl zaslan ukoncovaci paket
                if (data == "!STOP!")
                {
#if (DEBUG)
                    Console.WriteLine("Terminator received. Peripherals stopped.");
#endif
                    try
                    {
                        //Vymaz display reader buffer
                        display.ClearOutputBuffer();
                    }
                    catch (Exception)
                    {

                    }
                    
                    //Odesli potvrzeni
                    writer.WriteLine("OK");
                    watch.Dispose();
                    //Ukonci watchdog
                    watch = null;
                    return;
                }

                
                //Rozparsovani paketu podle typu
                //Zde parsovani udalosti mysi
                if (data.StartsWith("m\"") && data.EndsWith("\"m"))
                {
                    try
                    {
                        data = data.Remove(0, 2);
                        data = data.Remove(data.IndexOf("\"m"), 2);
                        fields = data.Split(SEPARATOR);
                        if (fields[0].Length != 1)
                        {
                            SendEventMessage("Invalid mouse parameter length");
                            continue;
                        }
                        //Vypocet souradnic vektoru posunuti z paketu
                        double w = ((double)Screen.PrimaryScreen.Bounds.Width)/this.Display.DisplayParams.DeviceScreen.Width;
                        double h = ((double)Screen.PrimaryScreen.Bounds.Height)/this.Display.DisplayParams.DeviceScreen.Height;

                        switch (fields[0])
                        {
                            case "R":   //posun relativni
                                MouseControl.Move((Int32)(System.Convert.ToInt32(fields[1]) * w), (Int32)(System.Convert.ToInt32(fields[2])*h));
                                break;
                            case "A":   //posun absolutni
                                MouseControl.Place((Int32)(System.Convert.ToInt32(fields[1]) * w), (Int32)(System.Convert.ToInt32(fields[2])*h));
                                break;
                            case "C":   //klik na absolutni pozici
                                MouseControl.Click((Int32)(System.Convert.ToInt32(fields[1]) * w), (Int32)(System.Convert.ToInt32(fields[2])*h));
                                break;
                            case "c":   //klik na aktualni pozici
                                MouseControl.Click();
                                break;
                            case "p":   //klik pravou mysi na aktualni pozici
                                MouseControl.RightClick();
                                break;
                            case "P":   //klik pravou mysi na absolutni pozici
                                MouseControl.RightClick((Int32)(System.Convert.ToInt32(fields[1]) * w), (Int32)(System.Convert.ToInt32(fields[2]) * h));
                                break;
                            default:    //odeslat info o neplatnem paketu.
                                SendEventMessage("Invalid action requested by mouse emulator device.");
                                break;
                        }

                    }
                    catch (Exception)
                    {
#if DEBUG
                        Console.WriteLine("Could not parse Mouse string: " + data);
#endif

                    }
                    continue;
                }
                else if (data.StartsWith("k\"") && data.EndsWith("\"k"))    //Parsovani klavesnicoveho paketu
                {
                    data = data.Remove(0, 2);
                    data = data.Remove(data.IndexOf("\"k"), 2);
                    fields = data.Split(SEPARATOR);
                    try
                    {
                        if (fields[0].Length != 1)
                        {
                            SendEventMessage("Invalid keyboard parameter length");
                            continue;
                        }
                        switch (fields[0])
                        {
                            case "P":   //Stisknuti klavesy (resp. text pro sendKeys)
                                KeyboardControl.Press(fields[1]);
                                break;
                            default:    //Odeslani zpravy o neplatnem paketu...
                                SendEventMessage("Invalid action requested by keyboard emulator device.");
                                break;
                        }
                    }
                    catch (Exception)
                    {
                        
                        throw;
                    }


                }
                else if (data.StartsWith("a\"") && data.EndsWith("\"a"))    //Parsovani ACK paketu
                {
                    if (display != null)    
                    {
                        display.sendNextFrame();                            //Povoleno odeslani dalsich dat
                        Console.WriteLine("Accept received...");
                    }
                }

            }
        }


        /// <summary>
        /// Spusteni naslouchani
        /// </summary>
        /// <param name="port">TCP port</param>
        /// <param name="cert">certifikat</param>
        /// <returns>info o uspesnosti</returns>
        public bool Start(int port, X509Certificate2 cert)
        {
            try
            {
                //Priprava portu k naslouchani
                listenerSocket = new TcpListener(IPAddress.Any, port);
                //Start naslouchani
                listenerSocket.Start();
                listenerPort = port;
                clientSerial = null;
                //Prirazeni certifikatu
                listenerCertificate = cert;
                clientConnection = ClientConnection.Wifi;
                isCiphered = true;
                //Spusteni naslouchaciho vlakna
                listenerThread = new Thread(new ThreadStart(ListenerThreadHandle));
                listenerThread.Start();
            }
            catch (Exception)
            {
                SendEventMessage("Could not start listener.");
                return false;
            }
            
            return true;
            
        }

        /// <summary>
        /// Spusteni naslouchani
        /// </summary>
        /// <param name="port">TCP port</param>
        /// <returns>info o uspesnosti</returns>
        public bool Start(int port)
        {
            try
            {
                //Init naslouchace
                listenerSocket = new TcpListener(IPAddress.Any, port);
                //Spusteni naslouchani
                listenerSocket.Start();
                //Zakazani vsech ostatnich naslouchacich zpusobu
                clientSerial = null;
                listenerPort = port;
                listenerCertificate = null;
                isCiphered = false;
                clientConnection = ClientConnection.Wifi;
                //Spusteni naslouchaciho vlakna
                listenerThread = new Thread(new ThreadStart(ListenerThreadHandle));
                listenerThread.Start();
            }
            catch (Exception)
            {
                SendEventMessage("Could not start listener.");
                return false;
            }
            return true;

        }

        /// <summary>
        /// Spusteni naslouchani
        /// </summary>
        /// <param name="port">COM port</param>
        /// <param name="speed">baudrate</param>
        /// <returns>info o uspesnosti</returns>
        public bool Start(string port, int speed)
        {
            try
            {
                //Vytvoreni instance serioveho portu
                clientSerial = new SerialPort(port, speed);
                //Otevreni portu
                clientSerial.Open();
                //Ostatni metody naslouchani zakazat
                listenerPort = 0;
                listenerSocket = null;
                clientConnection = ClientConnection.Serial;
                listenerCertificate = null;
                isCiphered = false;
                //Spusteni naslouchani
                listenerThread = new Thread(new ThreadStart(ListenerThreadHandle));
                listenerThread.Start();
            }
            catch (Exception)
            {
                SendEventMessage("Could not start listener.");
                return false;
            }
            return true;
            
        }

        /// <summary>
        /// Konstruktor tridy s defaultnimi parametry
        /// </summary>
        public RemoteControl()
        {
            display = new DisplayReader(null);
            SendStatusMessage(RemoteControlState.Stopped);
        }


        /// <summary>
        /// Konstruktor tridy ze sady parametru
        /// </summary>
        /// <param name="dispParams"></param>
        public RemoteControl(DisplayReaderParams dispParams)
        {
            display = new DisplayReader(null,dispParams);
            SendStatusMessage(RemoteControlState.Stopped);
        }

        /// <summary>
        /// Destruktor tridy
        /// </summary>
        ~RemoteControl()
        {
            Stop();

        }

        /// <summary>
        /// Korektní ukončení všech vláken aplikace
        /// </summary>
        public void Stop()
        {
            //Odeslání zprávy o korektním ukončení
            SendStatusMessage(RemoteControlState.Stopped);
            try
            {
                //Ukončení naslouchacího vlákna
                if (listenerThread != null)
                {

                    listenerThread.Abort();
                }
            }
            catch (Exception)
            {

            }

            try
            {
                //Ukonceni periferniho vlakna
                if (PeripheralThread != null)
                {
                    PeripheralThread.Abort();
                }
            }
            catch (Exception)
            {

            }


            try
            {
                //Pokud je spusteno cteni displeje, ukonci ho
                if (display.IsStarted)
                    display.Stop();
            }
            catch (Exception)
            {

            }

            try
            {
                //Pokud byl otevren seriovy port, ukonci ho
                if (clientSerial != null)
                {
                    //clientSerial.DiscardInBuffer(); 
                    //clientSerial.DiscardOutBuffer();
                    GC.SuppressFinalize(clientSerial.BaseStream);
                    //Thread.Sleep(2000);
                    clientSerial.Close();       //Tady je vyhozena vyjimka, je to problem .NET, jak jsem se docetl na forech...
                    clientSerial = null;
                }
            }
            catch (Exception)
            {

            }


            try
            {
                //Pokud probihalo naslouchani, ukonci ho
                if (listenerSocket != null)
                    listenerSocket.Stop();
            }
            catch (Exception)
            {

            }


            SendEventMessage("Listener stopped.");
        }
    }
}
