﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExpressionParser
{
    using Tokens;

    public class TokenReader
    {
        List<Token> _stack = new List<Token>();
        string _expr;
        int _index;
        Token _previusToken;

        public TokenReader(string expr)
        {
            _expr = expr;
            _index = 0;
        }

        public Token this[int index]
        {
            get
            {
                var invIndex = _stack.Count - index;
                if (invIndex < 0 || invIndex >= _stack.Count)
                    return null;
                return _stack[invIndex];
            }
        }

        public Token Read()
        {
            if (_previusToken != null)
                _stack.Add(_previusToken);

            var opToken = GetOperator(_expr[_index]);
            if (opToken != null)
            {
                // Special case where '-' is the first character of the expression.
                if (_previusToken == null && opToken is SubToken)
                {
                    _index++;
                    var nextToken = Read();
                    if (nextToken is NumberToken)
                    {
                        return _previusToken = new NumberToken(
                            -(nextToken as NumberToken).Value,
                            nextToken.Index - 1, nextToken.Length + 1);
                    }
                    else
                    {
                        throw new ParserException(nextToken);
                    }
                }
                else
                {
                    _index++;
                    return _previusToken = opToken;
                }
            }
            else if (char.IsDigit(_expr[_index]) || _expr[_index] == ',')
            {
                // Read any digits and letters (and punctuation for real numbers)
                var startIndex = _index;
                var numberString = ReadNumber();
                try
                {
                    _previusToken = new NumberToken(double.Parse(numberString), startIndex, numberString.Length);
                }
                catch(FormatException)
                {
                    throw new ParserException(_previusToken);
                }
                return _previusToken;
            }
            else if (char.IsLetter(_expr[_index]) || _expr[_index] == '_')
            {
                int startIndex = _index;
                string name = ReadName();
                _previusToken = new IdentifierToken(name, startIndex, name.Length);
                return _previusToken;
            }

            throw new ParserException(_previusToken);
        }

        private string ReadName()
        {
            StringBuilder sb = new StringBuilder();

            while (char.IsLetterOrDigit(_expr[_index]) || _expr[_index] == '_')
            {
                sb.Append(_expr[_index++]);
                if (_index >= _expr.Length)
                    break;
            }

            return sb.ToString();
        }

        private string ReadNumber()
        {
            StringBuilder sb = new StringBuilder();
            while ((char.IsLetterOrDigit(_expr[_index]) || _expr[_index] == ','))
            {
                sb.Append(_expr[_index++]);
                if (_index >= _expr.Length)
                    break;
            }
            return sb.ToString();
        }

        private OperatorToken GetOperator(char c)
        {
            switch (c)
            {
                case '+':
                    return new AddToken(_index, 1);
                case '-':
                    return new SubToken(_index, 1);
                case '*':
                    return new MulToken(_index, 1);
                case '/':
                    return new DivToken(_index, 1);
                case '(':
                    return new OpenParenthesisToken(_index, 1);
                case ')':
                    return new CloseParenthesisToken(_index, 1);
                case '^':
                    return new PowToken(_index, 1);
                default:
                    return null;
            }
        }

        public bool AtEnd()
        {
            return _index >= _expr.Length;
        }
    }
}