///
/* 
 * ZmodemReceiver - receive files using zmodem.
 * 
 * A c# class developed as a library for conducting serial interface
 * file transfers, developed by Chuck Forsberg. This code is strongly 
 * based on the lrzsz linux library, and is an expanded port of that 
 * code, released under
 * GNU GPL. 
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 * 
 * Developed by Eric Martin MBARI 2016
 * 
 */

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

namespace Zmodem
{


    public class ZmodemReceiver : Zmodem
    {

        //Constants 
        private const int ZRQ_MAX = 10;
        private const UInt16 MAX_BLOCK = 8192;
        private UInt16 MAX_TXFRAME = 1024;

        //Variables
        private SerialPort sPort;
        private byte Lzmanag; /*Local File Management Request*/
        private bool rxClob;
        private bool rxBinary;
        private bool rxASCII;
        private bool ThisBinary;
        private bool renameExistingFiles;
        private bool zctlEsc;
        private bool skipifnotfound;
        private STATES state;
        private int zrTimeout = 2;
        private byte tryzhdrtype = ZRINIT;
        private FileMode openmode;

        private byte zconv;  //ZMODEM file conversion request;
        private byte zmanag; //ZMODEM file management request;
        private byte ztrans; //ZMODEM file transport request;

        private byte[] rxbuff;


        public string filePrefix = "";


        //STATES 

        private enum STATES
        {
            INIT, // Waiting for any message from the transmitter
            RECVFILE,
            DONE,
            TIMED_OUT


        };

        public enum ManageOpts
        {
            NEWORLONGER = 1,    // Transfer if source is newer or longer
            CRC,            // Transfer  if different file CRC or length
            APPEND,         // Append Contents to Existing FIle
            CLOBBER,         // Replace Existing File
            NEWER           // Transfer if source is newer
        };
        //Properties

        /// <summary> Use 16BIT Crc in reply messages </summary>
        public bool CrcEnable;
        /// <summary> Disable timeouts during </summary>
        public bool DisableTimeouts;
        /// <summary> Enable the resume file functionality. </summary>
        public bool ResumeEnable;

        /// <summary>
        /// Enable Clobbering of the Received File, disables ResumeEnable if true.
        /// </summary>
        public bool RxClob
        {
            get { return rxClob; }
            set
            {
                if (value)
                    ResumeEnable = true;
                rxClob = value;
            }
        }
        /// <summary> Sets unit to receive data in ascii 
        /// format, disables binary transfer. </summary>
        public bool RxASCII
        {
            get { return rxASCII; }
            set
            {
                rxBinary = !value;
                rxASCII = value;
            }
        }
        /// <summary> Sets unit to receive data in binary
        /// format, disables ascii transfer. </summary>
        public bool RxBinary
        {
            get { return rxBinary; }
            set
            {
                rxASCII = (!value);
                rxBinary = value;
            }
        }
        /// <summary> Enable/Disable Escape sequences 
        /// for control characters. </summary>
        public bool ZctlEsc
        {
            get { return zctlEsc; }
            set
            {
                zctlEsc = value;
                Init(zctlEsc);
            }
        }
        /// <summary>
        /// Rename any files already existing, this disables
        /// file resume functionality.
        /// </summary>
        public bool RenameExistingFiles
        {
            get { return renameExistingFiles; }
            set
            {
                renameExistingFiles = value;
                if (value)
                    ResumeEnable = false;
            }
        }

        /// <summary> Set timeout in tenths of seconds. </summary>
        public int Timeout
        {
            get { return zrTimeout; }
            set
            {
                if (value == 0)
                    DisableTimeouts = true;
                zrTimeout = value;
            }
        }

        public ManageOpts LocalManagement
        {
            get { return (ManageOpts) Lzmanag; }
            set { Lzmanag = (byte)value; }
        }


        /// <summary>
        /// Constructor for single or multiple file transfers through serialports using
        /// the ZMODEM protocol.
        /// </summary>
        /// <param name="sp">SerialPort object. Must be open for r/w.</param>
        /// <param name="tryResume">Enable the ability to resume a file partially downloaded in a 
        /// previous session. CRC on all packets will be checked.</param>
        /// <example>
        /// This is how you conduct a receive operation. Default property settings are:
        ///     - No local management options
        ///     - No CRC enabled (reduces bandwidth. 
        ///     - Standard timeouts enabled
        ///     - Binary transmission
        ///     - Skip if not found is disabled.
        /// <code>
        /// SerialPort sp = new SerialPort("COM1", 115200, Parity.None, 8, StopBits.One);
        /// ZmodemReceiver zr = new ZmodemReceiver(sp, true); //allow resume functionality
        /// while (zr.doTasks()) {
        ///     // this does not return regularly, just between phases of the transfer.
        /// }
        /// //Transfer Complete
        /// </code></example>
        public ZmodemReceiver(SerialPort sp, bool tryResume)
        {

            this.Lzmanag = 0; // no local management options

            this.CrcEnable = false;
            this.DisableTimeouts = false;
            this.RxBinary = false;
            this.RxASCII = false;
            this.ThisBinary = false;

            this.skipifnotfound = false;

            this.rxbuff = new byte[MAX_BLOCK];

            // this sets Init(false);
            this.ZctlEsc = false;

            this.sPort = sp;

            this.state = STATES.INIT;

            this.ResumeEnable = true;

        }

        private int zrqinits_received = 0;
        private int init_loops = 0;
        private UInt32 rxpos = 0;

        private ushort nbytes = 0;
        private bool pollOnNext = true;
        private int errors = 0;

        /// <summary>
        /// Run a repeat task for zmodem reception
        /// </summary>
        /// <returns>
        /// Status of the loop:
        /// 0 = OK
        /// 1 = DONE, SUCCESS
        /// -1 = ZCAN RECV
        /// -2 = ZRINIT RECV OUT OF ORDER
        /// -3 = ERROR in RECVFILE SEQ
        /// -4 = TIMED OUT (not implemented)
        /// </returns>
        public int doTasks()
        {
            int c;
            byte cmdzack1flg;

            switch (state) {
                case STATES.INIT:
                    // Test if we're under the max zrq's and that we haven't timed out too many times.
                    if ((init_loops - zrqinits_received) < ZRQ_MAX && (zrqinits_received < ZRQ_MAX)) {

                        if (pollOnNext) {
                            stohdr(1024);

                            Txhdr[ZF0] |= 
                               CANFC32 | CANFDX| CANOVIO;
                            //Txhdr[ZF0] = 0;
                            //Txhdr[ZF1] = 0;

                            //todo P2 timesync flag

                            if (zctlEsc)
                                Txhdr[ZF0] |= TESCCTL;

                            zshhdr(sPort, tryzhdrtype, ref Txhdr);

                            init_loops++;

                            if (tryzhdrtype == ZSKIP)
                                tryzhdrtype = ZRINIT;

                            pollOnNext = false;
                        }
                        switch (c = zgethdr(sPort, ref Rxhdr, 0, ref rxpos)) {
                            case ZRQINIT:
                                zrqinits_received++;
                                pollOnNext = true;
                                return 0;
                            case ZEOF:
                                Debug.Print("GOT ZEOF");
                                pollOnNext = true;
                                return 0;
                            case TIMEOUT:
                                pollOnNext = true;
                                return 0;
                            case ZFILE: //This is what we're looking for. 
                                zconv = Rxhdr[ZF0];
                                if ((Rxhdr[ZF1] & ZF1_ZMSKNOLOC) > 0) {
                                    Rxhdr[ZF1] = (byte)(Rxhdr[ZF1] & ~ZF1_ZMSKNOLOC);
                                    skipifnotfound = true;
                                }
                                zmanag = Rxhdr[ZF1];
                                ztrans = Rxhdr[ZF2];
                                tryzhdrtype = ZRINIT;
                                // clear the rx array
                                Array.Clear(rxbuff, 0, rxbuff.Length);
                                nbytes = 0;
                                c = zrdata(sPort, ref rxbuff, ref nbytes);
                                if (c == GOTCRCW) {
                                    state = STATES.RECVFILE;
                                    return 0;
                                }
                                zshhdr(sPort, ZNAK, ref Txhdr);
                                return 0;
                            case ZSINIT:
                                zctlEsc |= (TESCCTL & Rxhdr[ZF0]) != 0;
                                nbytes = 0;
                                if (zrdata(sPort, ref Attn, ref nbytes) == GOTCRCW) {
                                    stohdr(1);
                                    zshhdr(sPort, ZACK, ref Txhdr);
                                    return 0;
                                }
                                zshhdr(sPort, ZNAK, ref Txhdr);
                                return 0;
                            case ZFREECNT:// right now this doesn't work...
                                stohdr(getfree());
                                zshhdr(sPort, ZACK, ref Txhdr);
                                return 0;
                            case ZCOMMAND:
                                //Not enabled in C# protocol
                                cmdzack1flg = Rxhdr[ZF0];

                                zshhdr(sPort, ZACK, ref Txhdr);
                                state = STATES.DONE;
                                return 0;

                            case ZCOMPL:
                                return 0;
                            case ZFIN:
                                ackbibi();
                                state = STATES.DONE;
                                return 0;
                            case ZRINIT:
                                return -2;
                            case ZCAN:
                                return -1;
                            default:
                                return 0;
                        }
                    }
                    break;

                case STATES.RECVFILE:
                    if (rzfiles(ref zi) == ERROR)
                        return -3;


                    break;
                case STATES.TIMED_OUT:
                    //not implemented as a return
                    break;
                case STATES.DONE:
                    Debug.Print("Transfer Session Complete");
                    return 1;

            }

            return 0;

        }

        private int rzfiles(ref zfileinfo zm)
        {
            int c;

            for (; ; ) {
                ///Debug.Print("rzfiles(): opening new file.");
                c = rzfile(ref zm);
                switch (c) {
                    case ZEOF:
                        //Debug.Print("rzfiles(): ZEOF received.\n");
                        state = STATES.INIT;
                        pollOnNext = true;
                        return c;
                    case ZSKIP:
                        //Some debug printing ... TODO
                        state = STATES.INIT;
                        pollOnNext = true;
                        return c;
                    default:
                        return c;
                    case ERROR:
                        return c;
                }
                //if (state != STATES.RECVFILE)
                //    break;
            }
            //TODO P2 return OK;
        }

        private int rzfile(ref zfileinfo zm)
        {
            int c, n;
            //long last_rxbytes = 0;
            //ulong last_bps;
            //long not_printed = 0;
            //uint bytes_in_block = 0;

            zm.eof_seen = false;

            n = 10;
            

            if (procheader(ref rxbuff, nbytes, ref zm) == ERROR) {
                tryzhdrtype = ZSKIP;
                return ZSKIP;
            }
            if (processZFileInfo(ref zm) == ERROR) {
                tryzhdrtype = ZSKIP;
                return ZSKIP;
            }

            for (; ; ) {
            repos:
                stohdr(zm.bytes_received);
                zshhdr(sPort, ZRPOS, ref Txhdr);
            nxthdr:
                //wait for reply
                c = zgethdr(sPort, ref Rxhdr, 0, ref rxpos);
                switch (c) {
                    default:
                        Debug.Print("rzfile(): zgethdr [ERROR]");
                        return ERROR;
                    case ZNAK:
                    case TIMEOUT:
                        if (--n < 0) {
                            Debug.Print("rzfile(): [ERROR] ZNAK or TIMEOUT");
                            return ERROR;
                        }
                        continue;
                    case ZFILE:
                        nbytes = 0;
                        if (zrdata(sPort, ref rxbuff, ref nbytes) == ERROR)
                            Debug.Print("rzfile(): [ERROR] on zrdata from ZFILE header.");
                        continue;
                    case ZEOF:
                        if (rclhdr(ref Rxhdr) != zm.bytes_received) {
                            errors = 0; goto nxthdr;
                        }

                        //close file if ()
                        if (!closeit(ref zm)) {
                            return ERROR;
                        }
                        return c;
                    case ERROR:
                        if (--n < 0) {
                            return ERROR;
                        }
                        zmputs(ref Attn);
                        continue;
                    case ZSKIP:
                        if (!closeit(ref zm)) return ERROR;
                        Debug.Print("rzfile(): sender skipped file\r\n");
                        return c;
                    case ZDATA:
                        if (rclhdr(ref Rxhdr) != zm.bytes_received) {
                            if (--n < 0) {
                                Debug.Print("rzfile(): out of sync\r\n");
                                return ERROR;
                            }
                            zmputs(ref Attn);
                            continue;
                        }
                    moredata:
                        Debug.Print("BYTES SO FAR: " + zm.bytes_received + 
                            " of "+zm.bytes_total);

                        // skipped a bunch of bps checks
                        nbytes = 0;
                        switch (c = zrdata(sPort, ref rxbuff, ref nbytes)) {
                            case ZCAN:
                                Debug.Print("rzfile(): zrdata returned ZCAN\r\n");
                                return ERROR;
                            case ERROR:
                                if (--n < 0) {
                                    Debug.Print("rzfile(): zrdata returned ERROR\r\n");
                                    return ERROR;
                                }
                                else {
                                    //Dump the buffer
                                    Debug.Print("rzfile(): dumping buffer and sending new ZRPOS.");
                                    sPort.DiscardInBuffer();
                                    zmputs(ref Attn);
                                    goto repos;
                                }
                            case TIMEOUT:
                                if (--n < 0) {
                                    Debug.Print("rzfile(): zrdata returned TIMEOUT\r\n");
                                    return ERROR;
                                }
                                else continue;
                            case GOTCRCW:
                                n = 20;
                                if (putsec(ref zm, ref rxbuff, nbytes) == OK) {
                                    zm.bytes_received += nbytes;
                                    stohdr(zm.bytes_received);
                                    zshhdr(sPort, (ZACK | 0x80), ref Txhdr);
                                    goto nxthdr;

                                }
                                else { return ERROR; }
                            case GOTCRCQ:
                                n = 20;
                                if (putsec(ref zm, ref rxbuff, nbytes) == OK) {
                                    zm.bytes_received += nbytes;
                                    stohdr(zm.bytes_received);
                                    zshhdr(sPort, (ZACK), ref Txhdr);
                                    goto moredata;
                                }
                                else { return ERROR; }

                            case GOTCRCG:
                                n = 20;
                                if (putsec(ref zm, ref rxbuff, nbytes) == OK) {
                                    zm.bytes_received += nbytes;
                                    goto moredata;
                                }
                                else { return ERROR; }


                            case GOTCRCE:
                               
                                n = 20;
                                if (putsec(ref zm, ref rxbuff, nbytes) == OK) {
                                    zm.bytes_received += nbytes;
                                    stohdr(zm.bytes_received);
                                    zshhdr(sPort, (ZACK), ref Txhdr);
                                    goto nxthdr;
                                }
                                else { return ERROR; }
                            default:
                                Debug.Print("rzfile(): unhandled Reply");
                                break;

                        }
                        break;
                }
            }
        }

        /// <summary>
        /// Assumes the file is open, and writes a sector to it.
        /// </summary>
        /// <param name="zm"></param>
        /// <param name="buff"></param>
        /// <param name="nbytes"></param>
        /// <returns></returns>
        private int putsec(ref zfileinfo zm, ref byte[] buff, ushort nbytes)
        {
            if (nbytes == 0)
                return OK;
            if (zm.fs == null) {
                Debug.Print("putsec(): File not open.\r\n");
                return ERROR;
            }
            if (ThisBinary) {
                try {
                    zm.fs.Write(buff, 0, nbytes);
                }
                catch (Exception e) {
                    Debug.Print("Putsec(): writing binary data: " + e.ToString());
                    return ERROR;
                }
            }
            else {
                if (zm.eof_seen)
                    return OK;
                for (int n = 0; n < nbytes; n++) {
                    if (buff[n] == 0x0D) // \r
                        continue;
                    if (buff[n] == CPMEOF) {
                        zm.eof_seen = true;
                        return OK;
                    }

                    try {
                        zm.fs.WriteByte(buff[n]);
                    }
                    catch (Exception e) {
                        Debug.Print("Putsec(): writing ascii byte. " + e.ToString());
                    }
                }
            }
            return OK;
        }

        private void zmputs(ref byte[] s)
        {
            for (int n = 0; n < s.Length; n++ ) {
                if (s[n] == 0xDD | s[n] == 0xDE)
                    sPort.Write(s, n, 1);
                else if (s[n] == 0xDE)
                    Thread.Sleep(1000);
                else if (s[n] == 0xDD) {
                    //sPort.BreakState = true;
                    //Thread.Sleep(10);
                    //sPort.BreakState = false;
                }
            }
        }


        private bool closeit(ref zfileinfo zm)
        {
            try {
                zm.fs.Close();
                zm.fs = null;
            }
            catch {
                return false;
            }

            Debug.Print("Closing File:" + zm.fname);
            return true;

        }
        private ushort Readnum = MAX_BLOCK;
        private void ackbibi()
        {
            //Debug.Print("Ackbibi:");
            Readnum = 1;
            stohdr(0);
            for (int n = 0; n < 3; n++) {
                //purgeline(0);
                zshhdr(sPort, ZFIN, ref Txhdr);
                switch (readline(sPort, 100)) {
                    case 'O':
                        readline(sPort, 1); /*Discard second 'O'*/
                        //Debug.Print("Transfer complete");
                        return;
                    case RCDO:
                        return;
                    case TIMEOUT:
                    default:
                        break;
                }
            }
        }

        internal struct zfileinfo
        {
            public string fname;
            public DateTime modtime;
            public uint mode;
            public uint bytes_total;
            public uint bytes_sent;
            public uint bytes_received;
            public uint bytes_skipped;
            public uint bytes_remaining;
            public uint files_remaining;
            public uint serial_number;
            public bool eof_seen;
            public FileStream fs;
        }


        private zfileinfo zi;

        /// <summary>
        /// Process incoming file information header
        /// </summary>
        /// <param name="buff"></param>
        /// <param name="zm"></param>
        /// <returns></returns>
        private int procheader(ref byte[] buff, uint nbytes, ref zfileinfo zm)
        {
            //string flags;
            int nullidx = Array.IndexOf(buff, (byte)0);
            if (nullidx <= 0) return ERROR;
            StringBuilder sb = new StringBuilder();
            zm.fname = sb.Clear().Append(this.filePrefix).Append(Encoding.UTF8.GetChars(buff, 0, nullidx)).ToString() ;
            Debug.Print("Receiving File: " + zm.fname + "\r\n");

            int nullidx2 = Array.IndexOf(buff, (byte)0, nullidx + 1);

            StringBuilder flags = new StringBuilder();
            flags.Append(Encoding.UTF8.GetChars(buff, nullidx + 1, nullidx2 - nullidx - 1));
            Debug.Print("GOT FLAGS: [" + flags.ToString() + "]\r\n");

            //Parse the flags
            if (nullidx2 <= 0) {
                return OK;
            }


            int tokstart = nullidx + 1;
            int toklen = bytesToNextSpace(buff, tokstart);
            try {

                //Parse Length
                zm.bytes_total = uint.Parse(sb.Clear().Append(Encoding.UTF8.GetChars(buff, tokstart, toklen)).ToString());
                sb.Clear();
                tokstart += toklen + 1;

                if (tokstart >= nullidx2) return OK;

                //Parse Modification Date
                toklen = bytesToNextSpace(buff, tokstart);
                int modtime = convertOctalStringToDec(ref buff, tokstart, toklen);
                // convert from octal

                zm.modtime = UnixEpoch.AddSeconds(modtime);
                tokstart += toklen + 1;
                if (tokstart >= nullidx2) return OK;

                //Parse File Mode
                toklen = bytesToNextSpace(buff, tokstart);
                zm.mode = (UInt32)convertOctalStringToDec(ref buff, tokstart,toklen);
                tokstart += toklen + 1;
                if (tokstart >= nullidx2) return OK;

                //Parse Serial Number
                toklen = bytesToNextSpace(buff, tokstart);
                zm.serial_number = uint.Parse(sb.Clear().Append(Encoding.UTF8.GetChars(buff, tokstart, toklen)).ToString());
                tokstart += toklen + 1;
                if (tokstart >= nullidx2) return OK;

                // Parse Files Remaining
                toklen = bytesToNextSpace(buff, tokstart);
                zm.files_remaining = uint.Parse(sb.Clear().Append(Encoding.UTF8.GetChars(buff, tokstart, toklen)).ToString());
                tokstart += toklen + 1;
                if (tokstart >= nullidx2) return OK;

                // Parse Bytes Remaining
                toklen = bytesToNextSpace(buff, tokstart);
                zm.bytes_remaining = uint.Parse(sb.Clear().Append(Encoding.UTF8.GetChars(buff, tokstart, toklen)).ToString());

                //There is one more field (file type, but that is not really used. 



            }
            catch {
                return ERROR;
            }

            return OK;

        }

        private int convertOctalStringToDec(ref byte [] buff, int offset, int len)
        {
            int result=0;
            int pow = 0;
            int n;
            StringBuilder sb = new StringBuilder();
            

            //Debug.Print("CONVERTING :" + sb.Clear().Append(Encoding.UTF8.GetChars(buff, offset, len)).ToString());


            for (n = len-1; n >= 0 ; n--)
            {
                result += (int)System.Math.Pow(8, pow++) * int.Parse(sb.Clear().Append(Encoding.UTF8.GetChars(buff, n + offset, 1)).ToString());
            }

            //Debug.Print("RESULT: " + result.ToString());
            return result;
        }


        private int buffersize = 32768;

        private int processZFileInfo(ref zfileinfo zm)
        {

            //Set default parameters
            openmode = FileMode.OpenOrCreate;
            ThisBinary = (!rxASCII) || rxBinary;

            if (Lzmanag > 0)
                zmanag = Lzmanag;

            if (!rxBinary && (zconv == ZCNL))
                ThisBinary = false;
            if (zconv == ZCBIN)
                ThisBinary = true;
            if (ThisBinary && (zconv == ZCBIN) && ResumeEnable)
                zconv = ZCRESUM;
            if ((zmanag == ZF1_ZMAPND && zconv != ZCRESUM))
                openmode = FileMode.Append;
            if (skipifnotfound)
                openmode = FileMode.Open;

            if ((zm.mode & UNIXFILE) != 0)
                ThisBinary = true;

            // Check for existing file
            if (zconv != ZCRESUM && !RxClob &&
                (zmanag & ZF1_ZMMASK) != ZF1_ZMCLOB &&
                (zmanag & ZF1_ZMMASK) != ZF1_ZMAPND &&
                File.Exists(zm.fname)
                ) {

                //Look for newer or not
                if (zmanag == ZF1_ZMNEW || zmanag == ZF1_ZMNEWL) {

                    bool isLonger = new System.IO.FileInfo(zm.fname).LastWriteTimeUtc > zm.modtime;
                    if (zmanag == ZF1_ZMNEW) {

                        if (isLonger) {
                            Debug.Print("Skipping: " + zm.fname + ", newer file exists.");
                            return ERROR;
                        }
                    }
                    else { // newer or longer
                        long fLength = new System.IO.FileInfo(zm.fname).Length;
                        if (isLonger && fLength > zm.bytes_total) {
                            Debug.Print("Skipping: " + zm.fname + ", newer and longer file exists.");
                            return ERROR;
                        }
                    }

                }
                else if (zmanag == ZF1_ZMCRC) {
                    int r = do_crc_check(ref zm, 0);
                    if (r == ERROR)
                        return ERROR;
                    if (r != ZCRC_DIFFERS) {
                        Debug.Print("Skipping, CRC match on local and remote files.");
                        return ERROR; // skips
                    }
                    else {
                        openmode = FileMode.Create;
                        Debug.Print("CRC Mismatch, redownloading entire file.");
                    }
                }
                else {
                    if ((zmanag & ZF1_ZMMASK) != ZF1_ZMCHNG) {
                        Debug.Print("Skipping: " + zm.fname + ", file exists");
                        return ERROR;
                    }
                    // try to rename
                    int i = 1;
                    do {
                        if (!File.Exists(zm.fname + i.ToString())) {
                            zm.fname += i.ToString();
                            break;
                        }
                    } while (i < 1000);
                    if (File.Exists(zm.fname)) {
                        Debug.Print("Skipping: " + zm.fname + ", file could not be renamed.");
                        return ERROR;
                    }
                }
            }

            // NOT IMPLEMENTING TIMESYNC
            // NOT IMPLEMENTING LOWERCASING
            // NOT IMPLEMENTING PIPE
            try {
                if (zm.fs == null)
                    zm.fs = File.Open(zm.fname, openmode, FileAccess.ReadWrite);
            }
            catch {
                Debug.Print("processZFileInfo(): File open failed.\r\n");
            }
            if (ThisBinary && zconv == ZCRESUM) {
                if (zm.fs != null) {
                    bool can_resume = true;
                    if (zmanag == ZF1_ZMCRC) {
                        int r = do_crc_check(ref zm, zm.fs.Length);
                        if (r == ERROR) {
                            zm.fs.Close();
                            return ZFERR;
                        }
                        if (r == ZCRC_DIFFERS) {
                            can_resume = false;
                        }
                    }
                    if (zm.fs.Length > zm.bytes_total) {
                        can_resume = false;
                    }
                    /* Retransfer Whole Blocks */
                    zm.bytes_skipped = (uint)((zm.fs.Length) & ~(1023));
                    if (can_resume) {
                        try {
                            zm.fs.Seek(zm.bytes_skipped, SeekOrigin.Begin);
                        }
                        catch  {
                            zm.fs.Close();
                            return ZFERR;
                        }
                    }
                    else
                        zm.bytes_skipped = 0; /*resume impossible, file has changed */
                    goto buffer_it;
                }
                zm.bytes_skipped = 0;

            }

            try {
                //Deal with directory structure if required
                string directoryName = Path.GetDirectoryName(zm.fname);
                if ((directoryName.Length) > 0 && (!Directory.Exists(directoryName)))
                    Directory.CreateDirectory(directoryName);


            }
            catch { Debug.Print("processZfileInfo(): Cannot Create Directory for file, " + zm.fname + " \r \n"); }

            if (zm.fs == null) {
                Debug.Print("processZfileInfo(): Cannot open file, " + zm.fname + " \r \n");
                return ERROR;
            }

            /* 
            uint last_length = 0;
            if (buffersize == -1) {
                if (zm.bytes_total > last_length) {
                    last_length = 0;
                }
            }
            if (buffersize != 0) {
                last_length = 32768;
                if (buffersize == -1) {
                    if (zm.bytes_total > 0)
                        last_length = zm.bytes_total;
                }
                else last_length = (uint)buffersize;
                //buffer '4096' byte space
                last_length = (last_length + 4095) & 0xFFFFF000;

            }
            */

           
            //  zm.fs = File.Open(zm.fname, openmode, FileAccess.ReadWrite);
            //}
            buffer_it:

                zm.bytes_received = zm.bytes_skipped;
                return OK;
            
        }

        private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0);


        private const int ZCRC_DIFFERS = ERROR + 1;
        private const int ZCRC_EQUAL = ERROR + 2;

        private int do_crc_check(ref zfileinfo zm, long check_bytes)
        {
            UInt32 crc, rcrc;
            long n;
            int c;
            int t1 = 0, t2 = 0;
            bool iOpened;

            if (check_bytes > UInt32.MaxValue) {
                Debug.Print("do_crc_check(): filesize too big");
                return ERROR;
            }
            //Check File size
            long fLength = new FileInfo(zm.fname).Length;
            if (check_bytes == 0 && fLength != zm.bytes_total)
                return ZCRC_DIFFERS;

            //open if it's not already
            try {
                if (zm.fs == null) {
                    zm.fs = File.Open(zm.fname, FileMode.Open, FileAccess.ReadWrite);
                    iOpened = true;
                }
                else iOpened = false;

            }
            catch { return ERROR; }

            crc = 0xFFFFFFFF;
            n = check_bytes;
            if (n == 0)
                n = fLength;

            while ((n-- > 0) && ((c = zm.fs.ReadByte()) != -1)) {
                crc = CRC.UPDC32((byte)c, crc);
            }
            crc = ~crc;

            zm.fs.Seek(0, 0);

            if (iOpened) {
                zm.fs.Close();
                zm.fs = null;
            }

            while (t1 < 3) {
                stohdr((uint)check_bytes);
                zshhdr(sPort, ZCRC, ref Txhdr);
                while (t2 < 3) {
                    uint tmp = 0;
                    c = zgethdr(sPort, ref Rxhdr, 0, ref tmp);
                    rcrc = tmp;
                    switch (c) {
                        default: //ignore
                            break;
                        case ZFIN:
                            return ERROR;
                        case ZRINIT:
                            return ERROR;
                        case ZCAN:
                            Debug.Print("do_crc_check(): got ZCAN\r\n");
                            return ERROR;
                        case ZCRC:
                            if (crc != rcrc)
                                return ZCRC_DIFFERS;
                            return ZCRC_EQUAL;
                    }
                }
            }
            return ERROR;
        }

        private static int bytesToNextSpace(byte[] buff, int tokstart)
        {
            int toklen = Array.IndexOf(buff, (byte)32, tokstart) - tokstart;

            // can't find another space... check for null, maybe its the last field.
            if (toklen < 0) {
                toklen = Array.IndexOf(buff, (byte)0, tokstart) - tokstart;
            }
            return toklen;
        }
        /*private int rzfile()
        {
            zi.eof_seen = false;

            

        }
         */
        private ushort getfree()
        {
            return (UInt16)(0xFFFF);
        }

        /*
        private int sys2(byte c)
        {
            return 0;// currently not implemented
        }
        */

        private int wcreceive()
        {


            return 0;
        }


    }
}
