// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;

#nullable disable

namespace Microsoft.Build.Shared.LanguageParser
{
    /*
     * Class:   VisualBasicTokenCharReader
     *
     * Reads over the contents of a vb source file (in the form of a string). 
     * Provides utility functions for dealing with VB-specific tokens.
     *
     */
    internal sealed class VisualBasicTokenCharReader : TokenCharReader
    {
        /*
         * Method:  VisualBasicTokenCharReader
         * 
         * Construct
         */
        internal VisualBasicTokenCharReader(Stream binaryStream, bool forceANSI)
            : base(binaryStream, forceANSI)
        {
        }

        /*
         * Method:  SinkSeparatorCharacter
         * 
         * Matches a vb separator character.
         */
        internal bool SinkSeparatorCharacter()
        {
            if
            (
                   CurrentCharacter == '('
                || CurrentCharacter == ')'
                || CurrentCharacter == '!'
                || CurrentCharacter == '#'
                || CurrentCharacter == ','
                || CurrentCharacter == '.'
                || CurrentCharacter == ':'
                || CurrentCharacter == '{'
                || CurrentCharacter == '}')
            {
                Skip();
                return true;
            }

            return false;
        }

        /*
         * Method:  SinkLineContinuationCharacter
         * 
         * Matches a vb line continuation character.
         */
        internal bool SinkLineContinuationCharacter()
        {
            if
            (
                CurrentCharacter == '_')
            {
                Skip();
                return true;
            }

            return false;
        }

        /*
         * Method:  SinkLineCommentStart
         * 
         * Matches a vb start of comment indicator
         */
        internal bool SinkLineCommentStart()
        {
            if (Sink("\'"))
            {
                return true;
            }
            else
            {
                int previousPosition = Position;

                if (SinkIgnoreCase("rem"))
                {
                    if (SinkWhiteSpace())
                    {
                        return true;
                    }

                    // We've probably found an Identifier that starts with "rem",
                    // so return to the previous position.
                    Position = previousPosition;
                }
            }
            return false;
        }

        /*
         * Method:  SinkHexIntegerPrefix
         * 
         * Matches a vb hex integer prefix
         */
        internal bool SinkHexIntegerPrefix()
        {
            if (SinkIgnoreCase("&H"))
            {
                return true;
            }

            return false;
        }

        /*
         * Method:  SinkOctalIntegerPrefix
         * 
         * Matches a vb octal integer prefix
         */
        internal bool SinkOctalIntegerPrefix()
        {
            if (SinkIgnoreCase("&O"))
            {
                return true;
            }

            return false;
        }

        /*
         * Method:  SinkWhiteSpace
         * 
         * Sink a single whitespace character.
         * In vb, newlines are not considered whitespace.
         */
        internal bool SinkWhiteSpace()
        {
            if (Char.IsWhiteSpace(CurrentCharacter) && !TokenChar.IsNewLine(CurrentCharacter))
            {
                Skip();
                return true;
            }
            return false;
        }

        /*
         * Method:  SinkIntegerSuffix
         * 
         * Sink a vb integer suffix.
         */
        internal bool SinkIntegerSuffix()
        {
            switch (CurrentCharacter)
            {
                case 'S':
                case 's':
                case 'I':
                case 'i':
                case 'L':
                case 'l':
                    Skip();
                    return true;
            }
            return true; // An integer suffix can be zero characters, so there's always a match.
        }

        /*
         * Method:  SinkDecimalIntegerSuffix
         * 
         * Sink a vb decimal integer suffix.
         * Couldn't find this documented anywhere, but a decimal (as opposed to hex or octal)
         * is also allowed a trailing '@', '!', '#' or '&'
         */
        internal bool SinkDecimalIntegerSuffix()
        {
            switch (CurrentCharacter)
            {
                case 'S':
                case 's':
                case 'I':
                case 'i':
                case 'L':
                case 'l':
                case '@':
                case '!':
                case '#':
                case '&':
                case '%':

                    Skip();
                    return true;
            }
            return true; // An integer suffix can be zero characters, so there's always a match.
        }


        /*
         * Method:  SinkOctalDigits
         * 
         * Sink multiple octal digits.
         */
        internal bool SinkMultipleOctalDigits()
        {
            int count = 0;
            while (TokenChar.IsOctalDigit(CurrentCharacter))
            {
                ++count;
                Skip();
            }
            return count > 0;     // Must match at least one  
        }

        /*
         * Method:  SinkOperator
         * 
         * Determine whether this is a vb operator.
         */
        internal bool SinkOperator()
        {
            const string operators = @"&|*+-/\^<=>";
            if (operators.IndexOf(CurrentCharacter) == -1)
            {
                return false;
            }
            Skip();
            return true;
        }

        /*
         * Method:  SinkTypeCharacter
         * 
         * Identifiers in vb can end with a special character to indicate type:
         *   IntegerTypeCharacter ::= %
         *   LongTypeCharacter ::= &
         *   DecimalTypeCharacter ::= @
         *   SingleTypeCharacter ::= !
         *   DoubleTypeCharacter ::= #
         *   StringTypeCharacter ::= $
         */
        internal bool SinkTypeCharacter()
        {
            const string types = @"%&@!#$";
            if (types.IndexOf(CurrentCharacter) == -1)
            {
                return false;
            }
            Skip();
            return true;
        }
    }
}
