using System;
using System.IO.Ports;
using System.Text;
using Microsoft.SPOT;
using Zmodem;

namespace SWModules
{
    public class PosixSession
    {
        // Constants
        public enum ERROR : int
        {
            NOLOGIN = -1,
            NOCARRIER = -2,
            NOZMODEM = -3,
            NORESP = -4
        };

        protected const int SUCCESS = 0;
        protected const ushort MAXLOGINS = 5;
        protected const ushort MAXCALLS = 2;
        protected const ushort MAXZMODEM = 5;

        protected const ushort MAXBUFF = 1024;
        /// <summary>
        /// This constant drives the effective timeout of the system, 
        /// in combination with how long a port has to respond. 
        /// </summary>
        protected ushort MAXERR = 10;

        protected const ushort READTIMEOUT = 10; //in tenths of seconds
        protected const byte CR = 13;
        protected const byte LF = 10;

        //Delegates
        public delegate void ExpectedHandler();
        public delegate void ExpectedHandlerWithOutput(string output);

        // Private members
        protected byte[] txbuff;
        protected ushort _callatt, _loginatt, _zmodematt;
        protected bool bLoggedIn = false;

        protected string _username; 
        protected string _password;

        protected bool bSendExtraCRLF = true;

        protected byte[] buff = new byte[MAXBUFF];

        //Properties

        public string Username
        {
            get { return _username; }
            set { _username = value; }
        }

        public string Password
        {
            set { _password = value; }
        }

        public string LoginPrompt { get; set; }
        public string CommandPrompt { get; set; }
        public string PasswordPrompt { get; set; }

        // Members
        protected SerialPort sp;

        public PosixSession(SerialPort sPort)
        {
            sp = sPort;
            LoginPrompt = "login:";
            CommandPrompt = "$ ";
            PasswordPrompt = "Password:";
        }

        public int ZModemSendTransaction(string filename)
        {
            writeLine("rz -t 50 -B 1024 -w 1024 -r\r");
            //writeLine("rz \r");

            while (_zmodematt < MAXZMODEM)
            {
                //GET ZPAD ZDLE sequence
                if (Expect("*\x18", () => { Debug.Print("GOT ZMODEM ZRINIT"); }) < 0)
                {
                    ++_zmodematt;
                    continue;
                }
                else break;
            }

            if (_zmodematt >= MAXZMODEM)
                return (int)ERROR.NOZMODEM;

            ZmodemSender zs = new ZmodemSender(sp, filename);
            
            // zmodem transmit on our side
            int ret = 0;
            do
            {
                ret = zs.doTasks();
            } while (ret == 0);

            return ret;
        }

        public int ZModemReceiveTransaction(string remotefilename, string localdirectory)
        {
            //writeLine("sz -r -t 50 " + remotefilename + "\r\n");
            int resp = 0;
            WaitForPrompt();

            while (_zmodematt < MAXZMODEM)
            {
                writeLine("sz " + remotefilename + "\r\n");
                if ((resp = Expect("*\x18", () => { Debug.Print("GOT ZMODEM ZRQINIT"); })) < 0)
                {
                    ++_zmodematt;
                    continue;
                }
                else break;
            }

            if (resp < 0) return resp;

            ZmodemReceiver zr = new ZmodemReceiver(sp, true);

            zr.filePrefix = localdirectory;
            int ret = 0;
            do
            {
                ret = zr.doTasks();
            } while (ret == 0);

            return ret;
        }
        
        public int Logout()
        {
            if (!WaitForPrompt()) return (int)ERROR.NORESP;

            Debug.Print("Logging Out.");
            //Temporarily removed due to nature of the RUDICS connection.
            writeLine("exit \r\n");

            return SUCCESS;
        }

        private  readonly char[] _trimChars = {'\r' ,'\n'};
        public int Broadcast(string msg)
        {
            if (!WaitForPrompt()) return (int)ERROR.NORESP;
            
            StringBuilder command = new StringBuilder("broadcast ");
            command.Append(msg.Trim(_trimChars));
            command.Append("\r\n");
            Debug.Print("Broadcasting Message: "+ command);
            writeLine(command.ToString());

            return SUCCESS;
        }

        public bool WaitForPrompt()
        {
            writeCRLF();
            int _promptatt = 0;

            while (_promptatt < MAXLOGINS)
            {
                if (Expect(CommandPrompt, () => { Debug.Print("GOT PROMPT"); }) < 0)
                {
                    ++_promptatt;
                    writeCRLF();
                }
                else
                    break;
            }

            if (_promptatt >= MAXLOGINS)
                return false;
            
            return true;
        }

        protected void writeLine(string msg)
        {
            txbuff = Encoding.UTF8.GetBytes(msg);
            sp.Write(txbuff, 0, txbuff.Length);
            Debug.Print("Sending:::");
            Debug.Print(msg);

        }

        protected void writeCRLF()
        {
            sp.WriteByte(CR);
            sp.WriteByte(LF);

        }

        public int CheckForPrompt()
        {
            writeCRLF();

            int resp = ExpectEither("NO CARRIER", CommandPrompt);
            
            switch (resp)
            {
                case 0: //NO CARRIER
                    Debug.Print("Connection Dropped");
                    return (int)ERROR.NOCARRIER;
                case 1: //Prompt
                    Debug.Print("GOT CONSOLE PROMPT, LOGGED IN.");
                    bLoggedIn = true;
                    return SUCCESS;
                default:
                    return (int)ERROR.NORESP;

            }
        }
        public int Login()
        {
            bool bLoginPrompted = false;
            int resp = 0;
            _loginatt = 0;

            while (_loginatt < MAXLOGINS && !bLoggedIn)
            {
                // Attempt to trigger a login prompt

                if (!bLoginPrompted && bSendExtraCRLF && _loginatt > 0)
                {
                    writeCRLF();
                    Debug.Print("SENDING CR LF");
                }

                resp = ExpectEither("NO CARRIER", LoginPrompt, PasswordPrompt, CommandPrompt); 


                //handle response
                switch (resp)
                {
                    case 0: // "NO CARRIER"
                        Debug.Print("Connection Dropped");
                        return (int)ERROR.NOCARRIER;

                    case 1: // "login:"
                        bLoginPrompted = true;
                        Debug.Print("LOGIN PROMPT RECEIVED");
                        // send username
                        Debug.Print("Sending UserName: " + _username);
                        writeLine(_username+"\r");
                        ++_loginatt;
                        break;

                    case 2: // "Password:"
                        Debug.Print("GOT PASSWORD PROMPT");
                        // send password
                        Debug.Print("Sending Password.");
                        writeLine(_password+"\r");
                        break;
                    
                    case 3: // "~$ prompt"
                        Debug.Print("GOT CONSOLE PROMPT, LOGGED IN.");
                        bLoggedIn = true;
                        break;
                  
                    default:
                        ++_loginatt;
                        bLoginPrompted = false;
                        Debug.Print("LOGIN ERROR: " + resp.ToString());
                        break;
                }

            }

            //Test if we're logged in
            if (!bLoggedIn)
            {
                return (int)ERROR.NOLOGIN;
            }
            else return SUCCESS;
        }

        protected ushort buffl = 0;
        public int Expect(string query, ExpectedHandler handler)
        {

            StringBuilder sb = new StringBuilder();
            int errcount = 0;
            int c;
            buffl = 0;
            Array.Clear(buff, 0, MAXBUFF);

            while (true)
            {
                if ((c = Zmodem.Zmodem.readline(sp, READTIMEOUT)) > 0)
                {
                    buff[buffl++] = (byte)(c & 0x7F); //restrict to 128 ASCII set
                    sb.Append(SafeEncoding.GetChars(buff, buffl - 1, 1));
                    //Debug.Print(sb.ToString());
                }
                else errcount++;

                // let's check for our string
                if (buffl > 0)
                {
                    if (sb.ToString().IndexOf(query) >= 0)
                    {
                        Debug.Print(sb.ToString());
                        //We got it, dump the rest of the buffer...
                        if (sp.BytesToRead > 0)
                            sp.DiscardInBuffer();

                        handler();
                        return 0;
                    }

                }

                if (buffl >= MAXBUFF)
                {
                    return -2;
                }

                if (errcount > MAXERR)
                {
                    Debug.Print("MAX Attempts reached, This came in:" + sb.ToString());
                    return -1;
                }
            }
        }

        public int ExpectEither(ushort maxattempts, params string[] queries)
        {
            ushort frmr = MAXERR;
            MAXERR = maxattempts;
            int resp = ExpectEither(queries);
            MAXERR = frmr;
            return resp;

        }

        public int ExpectEither(params string[] queries)
        {

            StringBuilder sb = new StringBuilder();
            int errcount = 0;
            int c, n;
            buffl = 0;
            Array.Clear(buff, 0, MAXBUFF);

            while (true)
            {
                if ((c = Zmodem.Zmodem.readline(sp, READTIMEOUT)) > 0)
                {
                    buff[buffl++] = (byte)(c & 0x7F); //restrict to 128 ASCII set
                    sb.Append(SafeEncoding.GetChars(buff, buffl - 1, 1));

                    Debug.Print(sb.ToString());
                }
                else errcount++;

                // let's check for our string
                if (buffl > 0)
                {
                    // input order of the queries does imply priority, as the first
                    // encountered is what's return.
                    for (n = 0; n < queries.Length; n++)
                    {
                        if (sb.ToString().IndexOf(queries[n]) >= 0)
                        {
                            Debug.Print("ExpectEither(): " + sb.ToString());
                            //We got it, dump the rest of the buffer...
                            if (sp.BytesToRead > 0)
                                sp.DiscardInBuffer();

                            //Debug.Print(sb.ToString());
                            //handler();
                            return n;
                        }
                    }


                }

                if (buffl >= MAXBUFF)
                {
                    return -2;
                }

                if (errcount > MAXERR)
                {
                    Debug.Print("MAX Attempts reached, This came in:" + sb.ToString());
                    return -1;
                }
            }
        }

        public int SendCommand(string p)
        {
            writeLine(p);
            int resp = ExpectEither("NO CARRIER", CommandPrompt);
            switch (resp)
            {
                case 0:
                    Debug.Print("NO CARRIER RECEIVED");
                    return (int)ERROR.NOCARRIER;
                case 1:
                    //command complete
                    return (int)SUCCESS;
                default:
                    return (int)ERROR.NORESP;
            }
        }
    }

}
