#define DEBUG

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

//using Microsoft.SPOT


namespace Zmodem
{
    public class Zmodem
    {
        //VERBOSITY
        private const bool Verbose = true;
        //Headers
        public const byte ZPAD = 0x2a;	/* pad character; begins frames */
        public const byte ZDLE = 0x18;	/* ctrl-x zmodem escape */
        public const byte ZDLEE = 0x58;	/* escaped ZDLE */

        public const byte ZBIN = 0x41;	/* binary frame indicator (CRC16) */
        public const byte ZHEX = 0x42;	/* hex frame indicator */
        public const byte ZBIN32 = 0x43;	/* binary frame indicator (CRC32) */
        public const byte ZBINR32 = 0x44;	/* run length encoded binary frame (CRC32) */

        public const byte ZVBIN = 0x61;	/* binary frame indicator (CRC16) */
        public const byte ZVHEX = 0x62;	/* hex frame indicator */
        public const byte ZVBIN32 = 0x63;	/* binary frame indicator (CRC32) */
        public const byte ZVBINR32 = 0x64;	/* run length encoded binary frame (CRC32) */

        public const byte ZRESC = 0x7e;	/* run length encoding flag / escape character */


        //Frame Types
        public const byte ZRQINIT = 0;	/* Request receive init */
        public const byte ZRINIT = 1;	/* Receive init */
        public const byte ZSINIT = 2;	/* Send init sequence (optional) */
        public const byte ZACK = 3;  /* ACK to above */
        public const byte ZFILE = 4;  /* File name from sender */
        public const byte ZSKIP = 5;  /* To sender: skip this file */
        public const byte ZNAK = 6;  /* Last packet was garbled */
        public const byte ZABORT = 7;  /* Abort batch transfers */
        public const byte ZFIN = 8;  /* Finish session */
        public const byte ZRPOS = 9;  /* Resume data trans at this position */
        public const byte ZDATA = 10; /* Data packet(s) follow */
        public const byte ZEOF = 11;	/* End of file */
        public const byte ZFERR = 12;	/* Fatal Read or Write error Detected */
        public const byte ZCRC = 13;	/* Request for file CRC and response */
        public const byte ZCHALLENGE = 14;	/* Receiver's Challenge */
        public const byte ZCOMPL = 15; /* Request is complete */
        public const byte ZCAN = 16;	/* Other end canned session with CAN*5 */
        public const byte ZFREECNT = 17;	/* Request for free bytes on filesystem */
        public const byte ZCOMMAND = 18;	/* Command from sending program */
        public const byte ZSTDERR = 19;	/* Output to standard error, data follows */

        /* ZDLE sequences */
        public const byte ZCRCE = 0x68;	/* CRC next, frame ends, header packet follows */
        public const byte ZCRCG = 0x69;	/* CRC next, frame continues nonstop */
        public const byte ZCRCQ = 0x6A;	/* CRC next, frame continues, ZACK expected */
        public const byte ZCRCW = 0x6B;	/* CRC next, ZACK expected, end of frame */
        public const byte ZRUB0 = 0x6C;	/* Translate to rubout 0177 */
        public const byte ZRUB1 = 0x6D;	/* Translate to rubout 0377 */

        /* Byte positions within header array */
        public const byte ZF0 = 3;	/* First flags byte */
        public const byte ZF1 = 2;
        public const byte ZF2 = 1;
        public const byte ZF3 = 0;
        public const byte ZP0 = 0;	/* Low order 8 bits of position */
        public const byte ZP1 = 1;
        public const byte ZP2 = 2;
        public const byte ZP3 = 3;	/* High order 8 bits of file position */

        /* Bit Masks for ZRINIT flags byte ZF0 */
        public const byte CANFDX = 0x01;  /* Rx can send and receive true FDX */
        public const byte CANOVIO = 0x02;  /* Rx can receive data during disk I/O */
        public const byte CANBRK = 0x04;  /* Rx can send a break signal */
        public const byte CANCRY = 0x08;  /* Receiver can decrypt */
        public const byte CANLZW = 0x10;  /* Receiver can uncompress */
        public const byte CANFC32 = 0x20;  /* Receiver can use 32 bit Frame Check */
        public const byte ESCCTL = 0x40;  /* Receiver expects ctl chars to be escaped */
        public const byte ESC8 = 0x80;  /* Receiver expects 8th bit to be escaped */

        /* zdlread return values (internal) */
        /* -1 is general error, -2 is timeout */
        private const int GOTOR = 0x100;
        public const int GOTCRCE = (ZCRCE | GOTOR);	/* ZDLE-ZCRCE received */
        public const int GOTCRCG = (ZCRCG | GOTOR);	/* ZDLE-ZCRCG received */
        public const int GOTCRCQ = (ZCRCQ | GOTOR);	/* ZDLE-ZCRCQ received */
        public const int GOTCRCW = (ZCRCW | GOTOR);	/* ZDLE-ZCRCW received */
        private const int GOTCAN = (GOTOR | 0x18);  	/* CAN*5 seen */

        /* Static Buffers for header generation and storage */
        public static byte[] Rxhdr = new byte[4];   /* Received Header */
        public static byte[] Txhdr = new byte[4];   /* Transmitted Header */
        public static long Txpos;                   /* Transmitted File Position */
        public static bool Txfcs32;		/* TRUE means send binary frames with 32 bit FCS */
        public static int Crc32t;		/* Display flag indicating 32 bit CRC being sent */
        public static int Crc32;		/* Display flag indicating 32 bit CRC being received */
        public static int Znulls = 0;		/* Number of nulls to send at beginning of ZDATA hdr */
        public static int Rxtimeout = 100; /*Tenths of Seconds to wait for a reply */
        public static int Rxframeind;
        public static int Zrwindow = 1400; /* RX window size (controls garbage count) */


        /* Types for Connections */
        /*public enum Modes
        {
            BIN16,
            HEX16,
            BIN32
        };*/

        /* Property Accessor */
        //public static Modes Mode;
        public static bool turbo_escape = false;
        public static bool Zctlesc; /*Encode Control Characters*/


        public static void Init(bool zctlesc)
        {
            //Mode = mode;
            turbo_escape = false;
            Zctlesc = false;
            Txfcs32 = true;
            zsendline_init();

        }
        /*
        public static void Header(SerialPort sp, byte type, ref byte[] flags)
        {
            if (Mode == Modes.BIN16 | Mode == Modes.BIN32) {
                zsbhdr(sp, type, ref flags);
            }

            else if (Mode == Modes.HEX16) {
                zshhdr(sp, type, ref flags);
            }

          
        }
        */
        private static byte lastsent;

        private static void AddByte(byte c, ref byte[] buff, ref int nbytes)
        {
            switch (zsendline_tab[(UInt16)(c & 0xFF)]) {
                case 0: /*don't need to escape*/
                    lastsent = c;
                    buff[nbytes++] = c;
                    break;
                case 1: /*needs escape*/
                    buff[nbytes++] = ZDLE;
                    c ^= 0x40;
                    lastsent = c;
                    buff[nbytes++] = c;
                    break;
                case 2:
                    if ((lastsent & 0x7F) != 0x40) {
                        lastsent = c;
                        buff[nbytes++] = c;
                    }
                    else {
                        buff[nbytes++] = ZDLE;
                        c ^= 0x40;
                        buff[nbytes++] = c;
                    }
                    break;
            }
        }


        public static void zsbhdr(SerialPort sp, byte type, ref byte[] flags)
        {
            //Debug.Print("Inserting Binary Header.");
            // Insert nulls
            int n = 0;
            ushort crc;

            if (type == ZDATA) {
                for (n = 0; n < Znulls; n++)
                    xsendline(sp, 0);
            }

            // Binary header initial sequence
            xsendline(sp, ZPAD);
            xsendline(sp, ZDLE);
            Crc32t = Zmodem.Txfcs32 ? 1 : 0;

            if (Zmodem.Txfcs32) {
                UInt32 crc32;
                xsendline(sp, ZBIN32);
                zsendline(sp, type);
                crc32 = 0xFFFFFFFF;
                crc32 = CRC.UPDC32(type, crc32);

                for (n = 0; n < 4; n++) {
                    zsendline(sp, flags[n]);
                    crc32 = CRC.UPDC32(flags[n], crc32);
                }

                crc32 = ~crc32;
                for (n = 0; n < 4; n++) {
                    zsendline(sp, (byte)(crc32 & 0xFF));
                   crc32 >>= 8;
                }
            }
            else {
                xsendline(sp, ZBIN);
                zsendline(sp, type);
                crc = CRC.updcrc(type, 0);

                for (n = 0; n < 4; n++) {
                    zsendline(sp, flags[n]);
                    crc = CRC.updcrc((byte)(0xFF & flags[n]), crc);
                }

                crc = CRC.updcrc(0, CRC.updcrc(0, crc));
                zsendline(sp, (byte)((crc >> 8) & 0xFF));
                zsendline(sp, (byte)(crc & 0xFF));
            }

        }

        static byte[] hbuff = new byte[30]; 

        public static void zshhdr(SerialPort sp, byte type, ref byte[] flags)
        {

            ushort crc;
            int nbytes = 0;

            hbuff[0] = ZPAD;
            hbuff[1] = ZPAD;
            hbuff[2] = ZDLE;
            hbuff[3] = ZHEX;

            zputhex(ref hbuff, 4, (byte)(type & 0x7F));
            nbytes = 6;

            crc = CRC.updcrc((byte)(type & 0x7F), 0);
            Crc32t = 0;

            for (int n = 0; n < 4; n++) {
                nbytes += zputhex(ref hbuff, nbytes, flags[n]);
                crc = CRC.updcrc((byte)(0xFF & flags[n]), crc);
            }

            //add the crc 
            crc = CRC.updcrc(0, CRC.updcrc(0, crc));
            nbytes += zputhex(ref hbuff, nbytes, ((crc >> 8) & 0xFF));
            nbytes += zputhex(ref hbuff, nbytes, ((crc) & 0xFF));

            /* Make it printable on the remote machine */
            //hbuff[nbytes++] = 13;
            //hbuff[nbytes++] = 10;

            /* 
             * Uncork the remote in case a fake XOFF has stopped data flow
             */
            if (type != ZFIN && type != ZACK)
                hbuff[nbytes++] = 17;

            sp.Write(hbuff, 0, nbytes);
            return;

        }

        public static void EncodeDataSubPacket16(ref byte[] data, int length, byte frameend, ref byte[] buff, out int nbytes)
        {
            ushort crc = 0;
            nbytes = 0;

            for (int n = 0; n < length; n++) {
                AddByte(data[n], ref buff, ref nbytes);
                crc = CRC.updcrc((byte)(data[n] & 0xFF), crc);
            }

            buff[nbytes++] = ZDLE;
            buff[nbytes++] = frameend;
            crc = CRC.updcrc(frameend, crc);
            crc = CRC.updcrc(0, CRC.updcrc(0, crc));

            AddByte((byte)((crc >> 8) & 0xFF), ref buff, ref nbytes);
            AddByte((byte)(crc & 0xFF), ref buff, ref nbytes);

            if (frameend == ZCRCW) {
                buff[nbytes++] = XON;
            }
        }


        /*                                           '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'*/
        private static byte[] digits = new byte[16] { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102 };

        private static int zputhex(ref byte[] buff, int offset, int c)
        {


#if DEBUG
            //TODO P2 Put a debug write here.
#endif
            buff[0 + offset] = digits[((c & 0xF0) >> 4)];
            buff[1 + offset] = digits[(c & 0x0F)];

            return 2;
        }


        public static int readline(SerialPort sp, int timeoutInTenths)
        {
            TimeSpan tsStart = DateTime.Now.TimeOfDay;
            TimeSpan tsTimeLimit = new TimeSpan(0, 0, timeoutInTenths / 10);
            bool timedOut = false;
            int c = ERROR;

            while (!timedOut) {
                if (sp.BytesToRead == 0) {
                    //check timer
                    if (DateTime.Now.TimeOfDay - tsStart > tsTimeLimit) {
                        timedOut = true;
                    }
                    
                    continue;
                }

                try {
                    c = sp.ReadByte();
                }
                catch (Exception e) {
                    Debug.Print("Caught Exception: "+ e.ToString());
                    c = ERROR;
                }

                break;
            }

            if (timedOut)
                c = TIMEOUT;

            //if (c >= 0)
            //    Debug.Print("Got Char {0}", c);
            return c;


        }

        private static int noxrd7(SerialPort sp)
        {
            int c;

            for (; ; ) {

                if ((c = readline(sp, Rxtimeout)) < 0)
                    return c;

                switch (c &= 0x7F) {
                    case XON:
                    case XOFF:
                        continue;
                    default:
                        if (Zctlesc && ((c & 0x60) == 0)) { //not
                            continue;
                        }
                        return c;
                    case 0x0D: //'\r'
                    case 0x0A: //'\n'
                    case ZDLE:
                        return c;
                }

            }


        }

        public static int zgeth1(SerialPort sp)
        {
            int n, c;

            //First Hex Character
            c = noxrd7(sp);
            if (c < 0)
                return c;
            n = c - 0x30;
            if (n > 9)
                n -= (0x061 - 0x3A); //'a'-':'
            if ((n & ~0xF) != 0)
                return ERROR;

            //Second Hex Char
            c = noxrd7(sp);
            if (c < 0)
                return c;
            c -= 0x30;
            if (c > 9)
                c -= (0x061 - 0x3a); //'a' - ':'
            if ((c & ~0xF) != 0)
                return ERROR;
            c += (n << 4);
            return c;
        }

        /// <summary>
        /// Decode two lower case hex digits into an 8 bit byte value
        /// </summary>
        /// <param name="sp">Serialport to operate on</param>
        /// <returns>int value communicated in two characters from the serial port buffer</returns>
        public static int zgethex(SerialPort sp)
        {
            int c;

            c = zgeth1(sp);
            //Debug.Print("zgethex: {0}", c);

            return c;
        }

        static byte[] xsc = new byte[1];
        public static void xsendline(SerialPort sp, byte c)
        {
            xsc[0] = c;
            sp.Write(xsc, 0, 1);

        }

        public static void zsendline(SerialPort sp, byte c)
        {
            switch (zsendline_tab[c]) {
                case 0:
                    xsendline(sp, lastsent = c);
                    break;
                case 1:
                    xsendline(sp, ZDLE);
                    c ^= 0x40;
                    xsendline(sp, lastsent = c);
                    break;
                case 2:
                    if ((lastsent & 0x7F) != 0x40)/*'@'*/
                        xsendline(sp, lastsent = c);
                    else {
                        xsendline(sp, ZDLE);
                        c ^= 0x40;
                        xsendline(sp, lastsent = c);
                    }
                    break;
            }

        }



        public static int zrhhdr(SerialPort sp, ref byte[] hdr)
        {
            int c;
            ushort crc;
            int n;
            int RxType;

            c = zgethex(sp);
            if (c < 0) return c;

            RxType = c;
            crc = CRC.updcrc((byte)c, 0);

            for (n = 0; n < 4; n++) {
                c = zgethex(sp);
                if (c < 0) return c;
                crc = CRC.updcrc((byte)c, crc);
                hdr[n] = (byte)c;
            }

            c = zgethex(sp);
            if (c < 0) return c;
            crc = CRC.updcrc((byte)c, crc);

            c = zgethex(sp);
            if (c < 0) return c;
            crc = CRC.updcrc((byte)c, crc);

            if ((crc & 0xFFFF) != 0) {
                //zperr(badcrc);
                Debug.Print("BAD CRC");
                return ERROR;

            }

            c = readline(sp, 1);
            switch (c) {
                case 0x8D:
                case 0x0D: // throw away possible CR/LF
                    readline(sp, 1);
                    break;
            }

            //protocol = ZM_ZMODEM;
            //zmodem_requested = true;
            return RxType;


        }

        public static void stohdr(UInt32 pos)
        {
            UInt32 lpos = (UInt32)pos;
            Txhdr[ZP0] = (byte)(lpos & 0xFF);
            Txhdr[ZP1] = (byte)((lpos >> 8) & 0xFF);
            Txhdr[ZP2] = (byte)((lpos >> 16) & 0xFF);
            Txhdr[ZP3] = (byte)((lpos >> 24) & 0xFF);

        }


        public static int zgethdr(SerialPort sp, ref byte[] hdr, int eflag, ref UInt32 Rxpos)
        {
            int c, cancount;
            UInt16 max_garbage;
            max_garbage = (UInt16)(Zrwindow + sp.BaudRate);
            UInt32 rxpos = 0;

        startover:

            cancount = 5;


            /*Return immediately if ZCRCW sequence seen */
        again:
            switch (c = readline(sp, Rxtimeout)) {
                case TIMEOUT:
                    goto fifi;
                case CAN:
                    goto gotcan;
                default:
                    goto agn2;
            }
        gotcan:

            switch (c = readline(sp, 1)) {
                case TIMEOUT:
                    goto again;
                case ZCRCW:
                    Debug.Print("zgethdr():Got ZCRCW");
                    c = ERROR;
                    goto fifi;
                case CAN:
                    if (--cancount <= 0) {
                        c = ZCAN;
                        goto fifi;
                    }
                    goto again;
            }

        agn2:
            switch (c) {
                default:
                    if (--max_garbage == 0) {
                        Debug.Print("Garbage Count Exceeded");
                        sp.DiscardInBuffer();
                        return ERROR;
                    }
                    if (eflag == 1 && ((c &= 0x7F) & 0x60) != 0 && Verbose)
                        Debug.Print("GotChar: " + c.ToString("X"));
                    else if (eflag > 1)
                        Debug.Print("GotChar: " + c.ToString("x"));
                    goto startover;

                case ZPAD: /* This is what we want */
                case (ZPAD | 0x80):
                    break;
            }
            cancount = 5;

        splat:

            switch (c = noxrd7(sp)) {
                case ZPAD:
                    goto splat;
                case TIMEOUT:
                    goto fifi;
                default:
                    goto agn2;
                case ZDLE: /* this is what we expect */
                    break;
            }

            switch (c = noxrd7(sp)) {
                case TIMEOUT:
                    goto fifi;
                case ZBIN:
                    Rxframeind = ZBIN;
                    Crc32 = 0;
                    c = zrbhdr(sp, ref hdr);
                    break;
                case ZBIN32:
                    Rxframeind = ZBIN32;
                    Crc32 = ZBIN32;
                    c = zrbhdr32(sp, ref hdr);
                    break;
                case ZHEX:
                    Rxframeind = ZHEX;
                    Crc32 = 0;
                    c = zrhhdr(sp, ref hdr);
                    break;
                case CAN:
                    goto gotcan;
                default:
                    goto agn2;
            }

            rxpos = (UInt32)(hdr[ZP3] & 0xFF);
            rxpos = (UInt32)((rxpos << 8) + (hdr[ZP2] & 0xFF));
            rxpos = (UInt32)((rxpos << 8) + (hdr[ZP1] & 0xFF));
            rxpos = (UInt32)((rxpos << 8) + (hdr[ZP0] & 0xFF));

        fifi:
            switch (c) {
                case GOTCAN:
                    c = ZCAN;
                    Debug.Print("GOT CAN");
                    break;
                case ZNAK:
                    Debug.Print("GOT ZNAK");
                    break;
                case ZCAN:
                    Debug.Print("GOT ZCAN");
                    break;
                case ERROR:
                    Debug.Print("GOT ERROR");
                    break;
                case TIMEOUT:
                    Debug.Print("GOT TIMEOUT");
                    break;

            }

            Rxpos = rxpos;
            return c;
        }

        /* Bit Masks for ZRINIT flags byze ZF1 */
        public const byte ZF1_CANVHDR = 0x01; /* Variable headers OK, unused in lrzsz */
        public const byte ZF1_TIMESYNC = 0x02;  /* nonstandard, Receiver request timesync */

        /* Parameters for ZSINIT frame */
        public const byte ZATTNLEN = 32;	/* Max length of attention string */
        /* Bit Masks for ZSINIT flags byte ZF0 */
        public const byte TESCCTL = 0x40;	/* Transmitter expects ctl chars to be escaped */
        public const byte TESC8 = 0x80;	/* Transmitter expects 8th bit to be escaped */

        /* Parameters for ZFILE frame */
        /* Conversion options one of these in ZF0 */
        public const byte ZCBIN = 1;	/* Binary transfer - inhibit conversion */
        public const byte ZCNL = 2;	/* Convert NL to local end of line convention */
        public const byte ZCRESUM = 3;	/* Resume interrupted file transfer */
        /* Management include options, one of these ored in ZF1 */
        public const byte ZF1_ZMSKNOLOC = 0x80; /* Skip file if not present at rx */
        /* Management options, one of these ored in ZF1 */
        public const byte ZF1_ZMMASK = 0x1f; /* Mask for the choices below */
        public const byte ZF1_ZMNEWL = 1; /* Transfer if source newer or longer */
        public const byte ZF1_ZMCRC = 2; /* Transfer if different file CRC or length */
        public const byte ZF1_ZMAPND = 3; /* Append contents to existing file (if any) */
        public const byte ZF1_ZMCLOB = 4; /* Replace existing file */
        public const byte ZF1_ZMNEW = 5; /* Transfer if source newer */
        /* Number 5 is alive ... */
        public const byte ZF1_ZMDIFF = 6; /* Transfer if dates or lengths different */
        public const byte ZF1_ZMPROT = 7; /* Protect destination file */
        public const byte ZF1_ZMCHNG = 8; /* Change filename if destination exists */

        /* Transport options, one of these in ZF2 */
        public const byte ZTLZW = 1;	/* Lempel-Ziv compression */
        public const byte ZTCRYPT = 2;	/* Encryption */
        public const byte ZTRLE = 3;	/* Run Length encoding */
        /* Extended options for ZF3, bit encoded */
        public const byte ZXSPARS = 64;	/* Encoding for sparse file operations */

        /* Parameters for ZCOMMAND frame ZF0 (otherwise 0) */
        public const byte ZCACK1 = 1;	/* Acknowledge, then do command */

        public const byte XOFF = 0x73 & 0x1F; /* ^s */
        public const byte XON = 0x71 & 0x1F; /* ^q */
        public const int ERROR = -1;
        public const int OK = 0;
        public const int TIMEOUT = -2;
        public const int RCDO = -3;
        public const byte CPMEOF = 0x1A;
        public const byte CAN = 0x58 & 0x1F; /* ^X */
        public const uint UNIXFILE = 0xF000;
        internal static byte[] Attn = new byte[ZATTNLEN + 1];

        private static byte[] zsendline_tab = new byte[256];

        public static void zsendline_init()
        {
            for (int i = 0; i < 256; i++) {
                if ((i & 0x60) > 0)
                    zsendline_tab[i] = 0;
                else {
                    switch (i) {
                        case ZDLE:
                        case XOFF: /* ^Q */
                        case XON:  /* ^S */
                        case (XOFF | 0x80):
                        case (XON | 0x80):
                            zsendline_tab[i] = 1;
                            break;
                        case 0x10: /* ^P */
                        case 0x90:
                            if (turbo_escape)
                                zsendline_tab[i] = 0;
                            else
                                zsendline_tab[i] = 1;
                            break;
                        case 0x0D:
                        case 0x8D:
                            if (Zctlesc)
                                zsendline_tab[i] = 1;
                            else if (!turbo_escape)
                                zsendline_tab[i] = 2;
                            else
                                zsendline_tab[i] = 0;
                            break;
                        default:
                            if (Zctlesc)
                                zsendline_tab[i] = 1;
                            else
                                zsendline_tab[i] = 0;
                            break;
                    }
                }
            }
        }
        /*
        public static int zsdata(SerialPort sp, ref byte[] buff, UInt16 length, byte frameend)
        {
            ushort crc = 0;

            for (int n = 0; n < length; n++) {
                zsendline(sp, buff[n]);

            } while (true) ;

            xsendline(sp, ZDLE);
            xsendline(sp, frameend);
            crc = CRC.updcrc(frameend, crc);
            crc = CRC.updcrc(0, CRC.updcrc(0, crc));

            zsendline(sp, (byte)((crc >> 8) & 0xFF));
            zsendline(sp, (byte)(crc & 0xFF));

            if (frameend == ZCRCW) {
                xsendline(sp, XON);
            }
        }
        */


        internal static void zsdata(SerialPort sp, ref byte[] buff, UInt16 length, byte frameend)
        {
            ushort crc;

            Debug.Print("Zmodem.zsdata(): " + length.ToString() + " " + frameend.ToString());
            crc = 0;

            for (int n = 0; n < length; n++) {
                zsendline(sp, buff[n]);
                crc = CRC.updcrc((byte)(0xFF & buff[n]), crc);
            }

            xsendline(sp, ZDLE);
            xsendline(sp, frameend);
            crc = CRC.updcrc(frameend, crc);

            crc = CRC.updcrc(0, CRC.updcrc(0, crc));

            zsendline(sp, (byte)((crc >> 8) & 0xFF));
            zsendline(sp, (byte)(crc & 0xFF));

            if (frameend == ZCRCW) {
                xsendline(sp, XON);
            }

        }

        internal static void zsda32(SerialPort sp, ref byte[] buff, UInt16 length, byte frameend)
        {
            byte c;
            UInt32 crc;

            //Debug.Print("Zmodem.zsda32(): " + length.ToString() + " " + frameend.ToString());

            crc = 0xFFFFFFFF;
            zsendline_s(sp, ref buff, length);
            for (int n = 0; n < length; n++) {
                c = (byte)(buff[n] & 0xFF);
                crc = CRC.UPDC32(c, crc);
            }
            xsendline(sp, ZDLE);
            xsendline(sp, frameend);
            crc = CRC.UPDC32(frameend, crc);

            crc = ~crc;

            for (int n = 0; n < 4; n++) {
                c = (byte)crc;
                if ((c & 0x60) != 0)
                    xsendline(sp, lastsent = c);
                else
                    zsendline(sp, c);
                crc >>= 8;
            }

            if (frameend == ZCRCW) {
                xsendline(sp, XON);
            }


        }

        private static void zsendline_s(SerialPort sp, ref byte[] buff, UInt16 length)
        {
            int last_esc = 0;
            int n, i;
            byte c;

            n = 0;
            while (n < length) {
                last_esc = 0;
                i = n;
                while (i < length) {
                    last_esc = zsendline_tab[(UInt16)(buff[i] & 0xFF)];
                    if (last_esc > 0)
                        break;
                    i++;
                }

                if (i != n) {
                    sp.Write(buff, n, i - n);
                    n = i;
                }

                if (last_esc > 0) {
                    c = buff[n];
                    switch (last_esc) {
                        case 0:
                            xsendline(sp, lastsent = c);
                            break;
                        case 1:
                            xsendline(sp, ZDLE);
                            c ^= 0x40;
                            xsendline(sp, lastsent = c);
                            break;
                        case 2:
                            if ((lastsent & 0x7F) != 0x40) {
                                xsendline(sp, lastsent = c);
                            }
                            else {
                                xsendline(sp, ZDLE);
                                c ^= 0x40;
                                xsendline(sp, lastsent = c);
                            }
                            break;
                    }
                    n++;
                }
            }
        }

        private static int zdlread(SerialPort sp)
        {
            int c;
            c = readline(sp, Rxtimeout);
            if ((c & 0x60) != 0)
                return c;
            return zdlread2(sp, c);

        }


        private static int zdlread2(SerialPort sp, int c)
        {
            goto jump_over;

        again:
            c = readline(sp, Rxtimeout);
            if ((c & 0x60) > 0)
                return c;
        jump_over:
            switch (c) {
                case ZDLE:
                    break;
                case (XON):
                case (XON | 0x80):
                case XOFF:
                case (XOFF | 0x80):
                    goto again;

                default:
                    if (Zctlesc && ((c & 0x60) == 0))
                        goto again;
                    return c;
            }
        again2:
            if ((c = readline(sp, Rxtimeout)) < 0)
                return c;
            if (c == CAN && (c = readline(sp, Rxtimeout)) < 0)
                return c;
            if (c == CAN && (c = readline(sp, Rxtimeout)) < 0)
                return c;
            if (c == CAN && (c = readline(sp, Rxtimeout)) < 0)
                return c;
            switch (c) {
                case CAN:
                    return GOTCAN;
                case ZCRCE:
                case ZCRCG:
                case ZCRCQ:
                case ZCRCW:
                    return (c | GOTOR);
                case ZRUB0:
                    return 0x7F;
                case ZRUB1:
                    return 0xFF;
                case XON:
                case (XON | 0x80):
                case XOFF:
                case (XOFF | 0x80):
                    goto again2;
                default:
                    if (Zctlesc && ((c & 0x60) == 0))
                        goto again2;
                    if ((c & 0x60) == 0x40)
                        return (c ^ 0x40);
                    break;
            }

            Debug.Print("zdlread2(): Bad escape sequence.");
            return ERROR;
        }

        internal static int zrdata(SerialPort sp, ref byte[] buff, ref UInt16 nbytes)
        {
            ushort crc;
            int c, d;

            nbytes = 0;
            if (Rxframeind == ZBIN32) {
                return zrdat32(sp, ref buff, ref nbytes);
            }
            crc = 0;

            while (nbytes < buff.Length) {
                if (((c = zdlread(sp)) & ~0xFF) != 0) {

                crcfoo:
                    switch (c) {
                        case GOTCRCE:
                        case GOTCRCG:
                        case GOTCRCQ:
                        case GOTCRCW:

                            d = c;
                            c &= 0xFF;
                            crc = CRC.updcrc((byte)c, crc);
                            if (((c = zdlread(sp)) & ~0xFF) != 0)
                                goto crcfoo;
                            crc = CRC.updcrc((byte)c, crc);
                            if (((c = zdlread(sp)) & ~0xFF) != 0)
                                goto crcfoo;
                            crc = CRC.updcrc((byte)c, crc);
                            if ((crc & 0xFFFF) > 0)
                                return ERROR;
                            return d;

                        case GOTCAN:
                            return ZCAN;
                        case TIMEOUT:
                            return c;
                        default:
                            Debug.Print("zrdata(): BAD Data Subpacket.");
                            return c;
                    }
                }
                buff[nbytes++] = (byte)c;
                crc = CRC.updcrc((byte)c, crc);


            }
            Debug.Print("zrdata(): Data subpacket too long.");
            return ERROR;
        }

        private static int zrdat32(SerialPort sp, ref byte[] buff, ref UInt16 nbytes)
        {
            int c;
            uint crc;
            int d;

            crc = 0xFFFFFFFF;

            while (nbytes < buff.Length) {
                if (((c = zdlread(sp)) & ~0xFF) != 0) {
                crcfoo:
                    switch (c) {
                        case GOTCRCE:
                        case GOTCRCG:
                        case GOTCRCQ:
                        case GOTCRCW:
                            d = c;
                            Debug.Print("Got CRC*: " + c.ToString());
                            c &= 0xFF;
                            crc = CRC.UPDC32((byte)c, crc);
                            if (((c = zdlread(sp)) & ~0xFF) != 0)
                                goto crcfoo;
                            crc = CRC.UPDC32((byte)c, crc);
                            if (((c = zdlread(sp)) & ~0xFF) != 0)
                                goto crcfoo;
                            crc = CRC.UPDC32((byte)c, crc);
                            if (((c = zdlread(sp)) & ~0xFF) != 0)
                                goto crcfoo;
                            crc = CRC.UPDC32((byte)c, crc);
                            if (((c = zdlread(sp)) & ~0xFF) != 0)
                                goto crcfoo;
                            crc = CRC.UPDC32((byte)c, crc);

                            if (crc != 0xDEBB20E3) {
                                sp.DiscardInBuffer();
                                Debug.Print("zrdat32(): Bad CRC on " + nbytes + " bytes. Bytes Remaining:"+sp.BytesToRead);
                                return ERROR;
                            }
                            //Debug.Print("zrdat32(): CRC good on " + nbytes + " bytes.");
                            return d;
                        case GOTCAN:
                            Debug.Print("Sender Canceled");
                            return ZCAN;
                        case TIMEOUT:
                            Debug.Print("TIMEOUT");
                            return c;
                        default:
                            Debug.Print("zrdat32(): Bad Data subpacket");
                            return c;

                    }
                }
                buff[nbytes++] = (byte)c;
                crc = CRC.UPDC32((byte)c, crc);
                }
            Debug.Print("zrdat32(): Data Subpacket too long");
            return ERROR;

            }


        private static int zrbhdr(SerialPort sp, ref byte[] hdr)
        {
            int c, n, Rxtype;
            ushort crc;

            if (((c = zdlread(sp)) & ~0xFF) != 0)
                return c;

            Rxtype = c;
            crc = CRC.updcrc((byte)c, 0);

            // Read in header bytes
            for (n = 0; n < 4; n++) {
                if (((c = zdlread(sp)) & ~0xFF) != 0)
                    return c;
                crc = CRC.updcrc((byte)c, crc);
                hdr[n] = (byte)c;
            }
            if (((c = zdlread(sp)) & ~0xFF) != 0)
                return c;
            crc = CRC.updcrc((byte)c, crc);
            if (((c = zdlread(sp)) & ~0xFF) != 0)
                return c;
            crc = CRC.updcrc((byte)c, crc);
            if ((crc & 0xFFFF) != 0) {
                Debug.Print("Bad CRC");
        }

            return Rxtype;
        }

        internal static int rclhdr(ref byte[] hdr) {
            Int32 l;

            l = (hdr[ZP3] & 0xFF);
            l = (l << 8) | (hdr[ZP2] & 0xFF);
            l = (l << 8) | (hdr[ZP1] & 0xFF);
            l = (l << 8) | (hdr[ZP0] & 0xFF);
            return l;
        }

        private static int zrbhdr32(SerialPort sp, ref byte[] hdr)
        {
            int c, n, Rxtype;
            UInt32 crc;
            
            if (((c = zdlread(sp)) & ~0xFF) != 0)
                return c;
            Rxtype = c;
            crc = 0xFFFFFFFF;
            crc = CRC.UPDC32((byte)c, crc);
            
            // Read in header bytes
            for (n = 0; n < 4; n++) {
                if (((c = zdlread(sp)) & ~0xFF) != 0)
                    return c;
                crc = CRC.UPDC32((byte)c, crc);
                hdr[n] = (byte)c;
            }

            // Read in crc
            for (n = 0; n < 4; n++) {
                if (((c = zdlread(sp)) & ~0xFF) != 0)
                    return c;
                crc = CRC.UPDC32((byte)c, crc);
            }

            if (crc != 0xDEBB20E3) {
                Debug.Print("BAD CRC");
                return ERROR;
            }

            return Rxtype;

        }

    }
}
