//
        // Find out what we should generate next. It can be a symbol, a distance/length pair
        // or a symbol followed by distance/length pair
        //
        internal bool GetNextSymbolOrMatch(Match match)
        {
            Debug.Assert(_bufPos >= FastEncoderWindowSize && _bufPos < (2 * FastEncoderWindowSize),
                "Invalid Buffer Position!");

            // initialise the value of the hash, no problem if locations bufPos, bufPos+1 
            // are invalid (not enough data), since we will never insert using that hash value
            var hash = HashValue(0, _window[_bufPos]);
            hash = HashValue(hash, _window[_bufPos + 1]);

            int matchLen;
            var matchPos = 0;

            this.VerifyHashes(); // Debug only code
            if (_bufEnd - _bufPos <= 3)
            {
                // The hash value becomes corrupt when we get within 3 characters of the end of the
                // input window, since the hash value is based on 3 characters.  We just stop
                // inserting into the hash table at this point, and allow no matches.
                matchLen = 0;
            }
            else
            {
                // insert string into hash table and return most recent location of same hash value
                var search = (int)InsertString(ref hash);

                // did we find a recent location of this hash value?
                if (search != 0)
                {
                    // yes, now find a match at what we'll call position X
                    matchLen = FindMatch(search, out matchPos, SearchDepth);

                    // truncate match if we're too close to the end of the input window
                    if (_bufPos + matchLen > _bufEnd)
                        matchLen = _bufEnd - _bufPos;
                }
                else
                {
                    // no most recent location found
                    matchLen = 0;
                }
            }

            if (matchLen < MinMatch)
            {
                // didn't find a match, so output unmatched char
                match.State = MatchState.HasSymbol;
                match.Symbol = _window[_bufPos];
                _bufPos++;
            }
            else
            {
                // bufPos now points to X+1
                _bufPos++;

                // is this match so good (long) that we should take it automatically without
                // checking X+1 ?
                if (matchLen <= LazyMatchThreshold)
                {
                    int nextMatchLen;
                    var nextMatchPos = 0;

                    // search at position X+1
                    var search = (int)InsertString(ref hash);

                    // no, so check for a better match at X+1
                    if (search != 0)
                    {
                        nextMatchLen = FindMatch(search, out nextMatchPos,
                            matchLen < GoodLength ? SearchDepth : (SearchDepth >> 2));

                        // truncate match if we're too close to the end of the window
                        // note: nextMatchLen could now be < MinMatch
                        if (_bufPos + nextMatchLen > _bufEnd)
                        {
                            nextMatchLen = _bufEnd - _bufPos;
                        }
                    }
                    else
                    {
                        nextMatchLen = 0;
                    }

                    // right now X and X+1 are both inserted into the search tree
                    if (nextMatchLen > matchLen)
                    {
                        // since nextMatchLen > matchLen, it can't be < MinMatch here

                        // match at X+1 is better, so output unmatched char at X
                        match.State = MatchState.HasSymbolAndMatch;
                        match.Symbol = _window[_bufPos - 1];
                        match.Position = nextMatchPos;
                        match.Length = nextMatchLen;

                        // insert remainder of second match into search tree            
                        // example: (*=inserted already)
                        //
                        // X      X+1               X+2      X+3     X+4
                        // *      *
                        //        nextmatchlen=3
                        //        bufPos
                        //
                        // If nextMatchLen == 3, we want to perform 2
                        // insertions (at X+2 and X+3).  However, first we must 
                        // inc bufPos.
                        //
                        _bufPos++; // now points to X+2
                        matchLen = nextMatchLen;
                        InsertStrings(ref hash, matchLen);
                    }
                    else
                    {
                        // match at X is better, so take it
                        match.State = MatchState.HasMatch;
                        match.Position = matchPos;
                        match.Length = matchLen;

                        // Insert remainder of first match into search tree, minus the first
                        // two locations, which were inserted by the FindMatch() calls.
                        // 
                        // For example, if matchLen == 3, then we've inserted at X and X+1
                        // already (and bufPos is now pointing at X+1), and now we need to insert 
                        // only at X+2.
                        //
                        matchLen--;
                        _bufPos++; // now bufPos points to X+2
                        InsertStrings(ref hash, matchLen);
                    }
                }
                else
                {
                    // match_length >= good_match 
                    // in assertion: bufPos points to X+1, location X inserted already
                    // first match is so good that we're not even going to check at X+1
                    match.State = MatchState.HasMatch;
                    match.Position = matchPos;
                    match.Length = matchLen;

                    // insert remainder of match at X into search tree
                    InsertStrings(ref hash, matchLen);
                }
            }

            if (_bufPos == 2 * FastEncoderWindowSize)
            {
                MoveWindows();
            }
            return true;
        }
        private readonly Match _currentMatch; // current match in history window

        public FastEncoder()
        {
            _inputWindow = new FastEncoderWindow();
            _currentMatch = new Match();
        }