using System;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
using System.Timers;
//using System.Windows.Forms;

namespace EpsonC3Com
{
    public partial class TcpC3Com
    {
        public enum PowerState { Off = 0, Low = 1, High = 2 }
        
        public class InitPar    //struktura pro inicicializaci robotu
        {
            public double Speed = 0;
            public double Accel = 0;
            public double Decel = 0;

            public double SpeedS = 0;
            public double AccelS = 0;
            public double DecelS = 0;
        }

        public class ReceiveBlock
        {
            //public class RecieveArgs : System.EventArgs 
            //{
            //    public Is
            //} 
            
            public enum RecState { Done, Waiting, ValOK, ValError, Error, Timeout, Disconnect }

            public string ReceiveData = String.Empty;

            public RecState State;
            
            public RobCoord.XYZ ReceivedXYZ = null;
            public RobCoord.JntAngl ReceivedJntAngl = null;
            public bool RecBool = false;
            public int RecError = 0;

            public IAsyncResult asyncResult;
            public int ReceiveLength = 0;
            public byte[] ReadBuffer;
            public object lockObj = 0;
            public byte ReceivedState = 0;

            public ReceiveBlock()
            {
                ReadBuffer = new byte[180]; 
            }
        }

#region Paramaters

        //delegate for event method used for incoming data signal
        public delegate void DataReadHandler();
        public delegate void CloseHandler();
            
        static int numInst = 0;
        
        //settings parameters
        public String IP_Addr = "10.0.41.10";
        public Int32 PortNum = 2000;

        public double TimeoutVal = 25000;
        public double TimeoutValLTransp = 2000;
        public bool TryAlive = false;
        public double TryAliveVal = 10000;
        public bool CheckSendCoord = false;
        public bool GetResponse = true;
        public bool GetTimeout = true;

        public enum MessType { None, Console, MessBox }
        public MessType ErrMessage = MessType.MessBox;
        public MessType InfoMessage = MessType.Console;

        //flags
        public bool Connected = false;
        public bool Initialized = false;
        public bool Validated = false;
        private bool nValidatedTransp = false;

        public string SentData = String.Empty;

        public event DataReadHandler DataRead;
        public event CloseHandler ClosingConnection;
        
        public ReceiveBlock RecBlock;
        
        String dEnd = "\n";
        String dDel = " ";

        TcpClient clientInst;
        NetworkStream streamInst;
        System.Timers.Timer trAliveTimer;
        System.Timers.Timer trReadTimeout;
        SynchronizationContext contextThread;
#endregion

        public TcpC3Com()
        {
            numInst++;
            RecBlock = new ReceiveBlock();
            trReadTimeout = null;
            CreateContext();
        }

        public TcpC3Com(String inIP, int inPort)
        {
            numInst++;
            IP_Addr = inIP;
            PortNum = inPort;

            RecBlock = new ReceiveBlock();
            trReadTimeout = null;
            CreateContext();
        }

        ~TcpC3Com()
        {
            if (numInst > 0)
            {
                //Close();
                numInst--;
            }
        }

        private void CreateContext()
        {
            contextThread = SynchronizationContext.Current;
            if (contextThread == null)
            {
                throw new Exception("SynchronizationContext Err");
            }
        }
        
        public bool Connect()
        {
            try
            {
                clientInst = new TcpClient(IP_Addr, PortNum);
                streamInst = clientInst.GetStream();

                TryAliveSet(TryAlive, TryAliveVal);

                InfoShow("Communication channel opened");
                Connected = true;

                return true;
            }
            //catch (ArgumentNullException)
            //{
            //    MessageBox.Show("Prazdny argument");

            //    return false;
            //    //Console.WriteLine("ArgumentNullException: {0}", e);
            //}
            catch (SocketException e)
            {
                if (ErrMessage == MessType.MessBox)
                    Console.WriteLine("Chyba soketu");
                else if (ErrMessage == MessType.Console)
                    Console.WriteLine("SocketException: {0}", e);

                Connected = false;
                return false;
            }
        }

        public bool Close()
        {
                     
            //Jestlize neni otevren 
            if (!Connected)
                return false;

            try
            {
                if (clientInst != null)
                    clientInst.Close();

                //zastavi timer pro testovani pruchodnosti spojeni
                if (trAliveTimer != null)
                    trAliveTimer.Stop();

                InfoShow("Communication channel closed");
                
                Connected = false;
                Initialized = false;

                InvokeCloseEvent();
                return true;
            }
            catch (SocketException e)
            {

                ErrorShow("SocketException during closing: " + e.Message);

                Connected = false;
                InvokeCloseEvent();
            }
            return false;
        }

        private void InvokeCloseEvent()
        {
            //vyslani eventu, ze se zavira spojeni
            contextThread.Post(new SendOrPostCallback(delegate(object state)
            {
                CloseHandler handler = ClosingConnection;
                if (handler != null)
                {
                    handler();
                }
            }), null);
        }

        private void InfoShow(string message)
        {
            if (InfoMessage == MessType.Console)
                Console.WriteLine(message);

            else if (InfoMessage == MessType.MessBox)
                Console.WriteLine(message);
        }

        private void ErrorShow(string message)
        {
            if (ErrMessage == MessType.Console)
                Console.WriteLine(message);

            else if (ErrMessage == MessType.MessBox)
                Console.WriteLine(message);
        }

        private void SendingError(Exception err)
        {
            ErrorShow("Error during sending: " + err.Message);
            Close();
        }
        
        private void StartSendingProc(string from)
        {
            RecBlock.ReceiveData = String.Empty;
        }

        //metoda, ktera se zavola po odeslani ramce pro kontrolu komunikace, zavisle na parametru povoleni
        private void ReadResponse(string from)
        {
            if (GetResponse)
            {
                SentData = from;
                Read();
            }
        }

        public String Read()
        {
            //Jestlize neni otevren 
            if (!this.Connected)
                return "";

            try
            {
                String responseData = String.Empty;

                RecBlock.ReceivedState = 0;
                RecBlock.State = ReceiveBlock.RecState.Waiting;
                
                if (SentData == "L")
                    nValidatedTransp = true;

                RecBlock.asyncResult = streamInst.BeginRead(RecBlock.ReadBuffer, 0, RecBlock.ReadBuffer.Length, new System.AsyncCallback(ReadCallback), null);
            
                //zapina timer pro timeout
                if (GetTimeout)
                {
                    if (trReadTimeout == null)
                    {
                        //pro overeni pruchodnosti linky nastavuje delku timeoutu na mensi hodnotu
                        if (SentData == "L")
                            trReadTimeout = new System.Timers.Timer(TimeoutValLTransp);
                        else
                            trReadTimeout = new System.Timers.Timer(TimeoutVal);
                        trReadTimeout.Elapsed += new ElapsedEventHandler(trReadTimeout_Elapsed);
                    }
                    else
                    {
                        if (SentData == "L")
                            trReadTimeout.Interval = TimeoutValLTransp;
                        else
                            trReadTimeout.Interval = TimeoutVal;
                    }

                    trReadTimeout.Start();
                }

                //zastavuje testovani pruchodnosti pri odeslani pozadavku, znovu test otevre az po uspesnem prijmuti dat
                if (trAliveTimer != null)
                    trAliveTimer.Stop();

            }
            catch (SocketException e)
            {
                ErrorShow("Error during sending: " + e.Message);
                Close();
            }
            return String.Empty;
        }
        //timeout pro Read
        private void trReadTimeout_Elapsed(object sender, ElapsedEventArgs e)
        {
            lock (RecBlock.lockObj)
            {
                
                if (RecBlock.ReceivedState == 0)
                {
                    //streamInst.EndRead(RecBlock.asyncResult);
                    Close();

                    RecBlock.ReceiveLength = 0;
                    RecBlock.ReceiveData = String.Empty;
                    RecBlock.ReceivedState = 2;
                    trReadTimeout.Stop();

                    ErrorShow("Timeout");
                    Received(1);
                }
            }
        }

        //metoda volana vlaknem od BeginReadu pro vystup z read vlakna
        private void ReadCallback(System.IAsyncResult ar)
        {
            lock (RecBlock.lockObj)
            {
                // End pending asynchronous request.
                if (ar.IsCompleted && (RecBlock.ReceivedState == 0))
                {
                    try
                    {
                        //v pripade, ze jeste nedosla odpoved o pruchodnosti kanalu a uz je vyslany jiny pozadavek, tak jeste neukoncuje cteci vlakno
                        if ((SentData != "L") && nValidatedTransp)
                        { }
                        else
                            if (streamInst != null)
                                RecBlock.ReceiveLength = streamInst.EndRead(ar);
                    }
                    catch (SocketException e)
                    {
                        ErrorShow("Error during recieving: " + e.Message);
                        Close();
                    }

                    //zastavi timer s timeoutem, jestlize existuje jeho instance
                    if (trReadTimeout != null)
                        trReadTimeout.Stop();

                    //pro jistotu si pro sebe uzamce readbuffer, aby se o pristup k nemu nehadalo s vlaknem
                    lock (RecBlock.ReadBuffer)
                    {
                        RecBlock.ReceivedState = 1;
                        RecBlock.ReceiveData = System.Text.Encoding.ASCII.GetString(RecBlock.ReadBuffer, 0, RecBlock.ReceiveLength);
                    }

                    //v pripade, ze jeste nedosla odpoved o pruchodnosti kanalu a uz je vyslany jiny pozadavek, tak ani nevola parser a pocka na dalsi data
                    if ((SentData != "L") && nValidatedTransp)
                        nValidatedTransp = false;
                    else
                        Received(0);    //vola funkci pro zpracovani nactenych dat, informuje, ze se jedna o volani z Callback funkce
                    
                }
                //v pripade, ze ukoncovani cteciho threadu je zavolano z timeoutu, 
                if (ar.IsCompleted && (RecBlock.ReceivedState == 2))
                {
                    try
                    {
                        //RecBlock.RecieveLength = streamInst.EndRead(ar);
                    }
                    catch (SocketException e)
                    {
                        ErrorShow("Socket error during timeout: " + e.Message);
                    }
                }
            }
        }

        private void Received(int callFrom)
        {
            if (callFrom == 0)  //read from CallBack
            {
                RecBlock.ReceiveData = RecBlock.ReceiveData.TrimEnd();
                nValidatedTransp = false;    //jestlize je zavolano z Callbacku, znamena to, ze uz data prosla

                if (SentData == "L")
                {
                    //neprisla zadna data zpet
                    if (RecBlock.ReceiveData == "")
                    {
                        Close();
                        return;
                    }
                }
                else
                    InfoShow("Recieved data:\n" + RecBlock.ReceiveData);
                               
                RecBlock.State = ReceiveParser();
                
                //in case of succesfull recieve message from robot, restart timer of TryAlived - link is clear
                if ((trAliveTimer != null) && TryAlive) 
                {
                    trAliveTimer.Start();
                }

            }
            else if (callFrom == 1) //read from timeout
            {
                //v pripade, ze timeout je vyvolan pri testovani pruchodnosti kanalu nahlasi jako Disconnect
                if (SentData == "L")
                    RecBlock.State = ReceiveBlock.RecState.Disconnect;
                else
                    RecBlock.State = ReceiveBlock.RecState.Timeout;
            }
            if (SentData != "L")
                InvokeReceiveEvent();
        }

        private void InvokeReceiveEvent()
        {
            contextThread.Post(new SendOrPostCallback(delegate(object state)
            {
                DataReadHandler handler = DataRead;
                if (handler != null)
                {
                    handler();
                }
            }), null);
        }

        ReceiveBlock.RecState ReceiveParser()
        { 
            string dataParse = RecBlock.ReceiveData;
            //nalezeni indexu zacatku posledni prijate zpravy
            int iLastAns = dataParse.LastIndexOf("<");

            if (iLastAns == -1)
            {
                ErrorShow("Bad answer recieved [E1]");
                return ReceiveBlock.RecState.ValError;
            }
            //odstrani zacatek, ktery byl pred posledni prijatou zpravou + znak <
            dataParse = dataParse.Substring(iLastAns + 1);

            Validated = true;
            if (dataParse.StartsWith("E_"))
            {
                //prijata chyba z robotu
                ErrorShow("Recieved error: " + dataParse);

                string splittedData = dataParse.Replace("E_", "");
                RecBlock.RecError = Convert.ToInt32(splittedData, System.Globalization.CultureInfo.InvariantCulture);
                
                //Validated = true;
                return ReceiveBlock.RecState.Error;
            }
            else if (dataParse.StartsWith(SentData))    //po inicializacnim znaku rozeznava prikaz
            {
                //Validated = true;

                if ((SentData == "CurPosP") || (SentData == "CurPosJ"))
                {
                    char[] delRecv = new char[6] {' ', ' ', ' ', ' ', ' ', ' '};
                    string[] splittedData = dataParse.Split(delRecv);

                    if (splittedData.Length == 7)
                    {
                        if (splittedData[0] == "CurPosP:")
                        {
                            try
                            {
                                RecBlock.ReceivedXYZ = new RobCoord.XYZ(Double.Parse(splittedData[1], System.Globalization.CultureInfo.InvariantCulture), Double.Parse(splittedData[2], System.Globalization.CultureInfo.InvariantCulture), Double.Parse(splittedData[3], System.Globalization.CultureInfo.InvariantCulture), Double.Parse(splittedData[4], System.Globalization.CultureInfo.InvariantCulture), Double.Parse(splittedData[5], System.Globalization.CultureInfo.InvariantCulture), Double.Parse(splittedData[6], System.Globalization.CultureInfo.InvariantCulture));
                            }
                            catch (FormatException)
                            {
                                ErrorShow("Bad answer recieved [E3] - bad format of values");
                                return ReceiveBlock.RecState.ValError;
                            }
                            catch (OverflowException)
                            {
                                ErrorShow("Bad answer recieved [E4] - long values");
                                return ReceiveBlock.RecState.ValError;
                            }
                        }
                        else if (splittedData[0] == "CurPosJ:")
                        {
                            try
                            {
                                RecBlock.ReceivedJntAngl = new RobCoord.JntAngl(Double.Parse(splittedData[1], System.Globalization.CultureInfo.InvariantCulture), Double.Parse(splittedData[2], System.Globalization.CultureInfo.InvariantCulture), Double.Parse(splittedData[3], System.Globalization.CultureInfo.InvariantCulture), Double.Parse(splittedData[4], System.Globalization.CultureInfo.InvariantCulture), Double.Parse(splittedData[5], System.Globalization.CultureInfo.InvariantCulture), Double.Parse(splittedData[6], System.Globalization.CultureInfo.InvariantCulture));
                            }
                            catch (FormatException)
                            {
                                ErrorShow("Bad answer recieved [E3] - bad format of values"); 
                                return ReceiveBlock.RecState.ValError;
                            }
                            catch (OverflowException)
                            {
                                ErrorShow("Bad answer recieved [E4] - long values");
                                return ReceiveBlock.RecState.ValError;
                            }
                        }
                    }
                    else
                    {
                        //jiny pocet vracenych argumentu
                        ErrorShow("Bad answer recieved [E2] - bad values");
                        return ReceiveBlock.RecState.ValError;
                    }
                }
                //prijimani prectene hodnoty DI
                else if (SentData == "DI:")
                {
                    //Validated = true;

                    string splittedData = dataParse.Replace("DI:", "");
                    splittedData = splittedData.TrimStart();

                    try
                    {
                        RecBlock.RecBool = Convert.ToBoolean(splittedData, System.Globalization.CultureInfo.InvariantCulture);
                    }
                    catch (FormatException)
                    {
                        ErrorShow("Bad answer recieved [E3] - bad format of values");
                        return ReceiveBlock.RecState.ValError;
                    }
                    
                }
                //prijimani prectene hodnoty DO
                else if (SentData == "DOR:")
                {
                    //Validated = true;

                    string splittedData = dataParse.Replace("DOR:", "");
                    splittedData = splittedData.TrimStart();

                    try
                    {
                        RecBlock.RecBool = Convert.ToBoolean(splittedData, System.Globalization.CultureInfo.InvariantCulture);
                    }
                    catch (FormatException)
                    {
                        ErrorShow("Bad answer recieved [E3] - bad format of values");
                        return ReceiveBlock.RecState.ValError;
                    }
                }

                //prijimani, zda-li elektromagnet opravdu uchytil predmet
                else if (SentData == "GoM:")
                {
                    //Validated = true;

                    string splittedData = dataParse.Replace("GoM:", "");
                    splittedData = splittedData.TrimStart();

                    try
                    {
                        RecBlock.RecBool = Convert.ToBoolean(splittedData, System.Globalization.CultureInfo.InvariantCulture);
                    }
                    catch (FormatException)
                    {
                        ErrorShow("Bad answer recieved [E3] - bad format of values");
                        return ReceiveBlock.RecState.ValError;
                    }
                }
                //prijimani, zda-li elektromagnet opravdu uchytil predmet
                else if (SentData == "PullM:")
                {
                    //Validated = true;

                    string splittedData = dataParse.Replace("PullM:", "");
                    splittedData = splittedData.TrimStart();

                    try
                    {
                        RecBlock.RecBool = Convert.ToBoolean(splittedData, System.Globalization.CultureInfo.InvariantCulture);
                    }
                    catch (FormatException)
                    {
                        ErrorShow("Bad answer recieved [E3] - bad format of values");
                        return ReceiveBlock.RecState.ValError;
                    }
                }
                

            }
            else
            {
                //nekompatibilni prikaz
                Validated = false;
                return ReceiveBlock.RecState.ValError;
            }
            return ReceiveBlock.RecState.ValOK;
        }

        //metoda pro kontrolu aktualnosti spojeni
        public void TryAliveSet(bool stat, double time)
        {
            if (stat)   //jestlize je pozadovana funkce timeru, iniciazizuje timer, jeho callback metoda kontroluje socket
            {
                if (trAliveTimer == null)       //otevira pouze v pripade prvotni inicializace
                {
                    trAliveTimer = new System.Timers.Timer(time);
                    trAliveTimer.Elapsed += new ElapsedEventHandler(trAliveTimer_Elapsed);
                }
                trAliveTimer.Enabled = true;
                trAliveTimer.Start();
                TryAlive = true;
            }
            else
            {
                trAliveTimer = null;
                TryAlive = false;
            }
        }

        private bool SendL()
        {
            try
            {
                String dataMessage;

                string command = "L";
                dataMessage = "!" + command + dEnd;

                Byte[] data = System.Text.Encoding.ASCII.GetBytes(dataMessage);

                StartSendingProc(command);
                streamInst.Write(data, 0, data.Length);
                
                //Console.WriteLine(dataMessage);

                //vola metodu pro kontrolu odesilani
                ReadResponse(command);

                return true;
            }
            catch (SocketException e)
            {
                SendingError(e);
            }
            return false;
        }
        
        //callback pri vyprseni casovace pro kontrolu spojeni
        private void trAliveTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            Connected = clientInst.Connected;

            if ((Connected) && (RecBlock.State != ReceiveBlock.RecState.Waiting))
                SendL();
        }
    }

  
}