using System;

namespace Calculator
{
    public void Parse(string input, SymbolTable symbolTable)
    {
        Scanner scanner = new Scanner(input);
        scanner.NextToken();
        if (scanner.CurrentToken is PrintToken)
        {
            symbolTable.Print();
            return;
        }
        else if (scanner.CurrentToken is VariableNameToken)
        {
            VariableNameToken token = cast<VariableNameToken>(scanner.CurrentToken);
            string variableName = token.VariableName;
            scanner.NextToken();
            if (scanner.CurrentToken is OperatorToken)
            {
                OperatorToken token = cast<OperatorToken>(scanner.CurrentToken);
                if (token.Char == '=')
                {
                    scanner.NextToken();
                    double value = Expr(scanner, symbolTable);
                    Token token = scanner.CurrentToken;
                    if (token is EndToken)
                    {
                        symbolTable.SetVariable(variableName, value);
                        Console.WriteLine(variableName + " = " + value.ToString());
                        return;
                    }
                    else
                    {
                        throw new ParsingException("invalid input");
                    }
                }
            }
        }
        scanner.Rewind();
        scanner.NextToken();
        double value = Expr(scanner, symbolTable);
        Token token = scanner.CurrentToken;
        if (token is EndToken)
        {
            Console.WriteLine("= " + value.ToString());
        }
        else
        {
            throw new ParsingException("invalid input");
        }
    }

    public double Expr(Scanner scanner, SymbolTable symbolTable)
    {
        double value = Term(scanner, symbolTable);
        Token token = scanner.CurrentToken;
        OperatorToken op = token as OperatorToken;
        while (op != null && op.IsAdditiveOperator)
        {
            char opChar = op.Char;
            scanner.NextToken();
            double right = Term(scanner, symbolTable);
            if (opChar == '+')
            {
                value = value + right;
            }
            else if (opChar == '-')
            {
                value = value - right;
            }
            else
            {
                throw new LogicErrorException("logic error");
            }
            token = scanner.CurrentToken;
            op = token as OperatorToken;
        }
        return value;
    }

    public double Term(Scanner scanner, SymbolTable symbolTable)
    {
        double value = Factor(scanner, symbolTable);
        Token token = scanner.CurrentToken;
        OperatorToken op = token as OperatorToken;
        while (op != null && op.IsMultiplicativeOperator)
        {
            char opChar = op.Char;
            scanner.NextToken();
            double right = Factor(scanner, symbolTable);
            if (opChar == '*')
            {
                value = value * right;
            }
            else if (opChar == '/')
            {
                if (right == 0)
                {
                    throw new DivisionByZeroException("division by zero");
                }
                value = value / right;
            }
            else
            {
                throw new LogicErrorException("logic error");
            }
            token = scanner.CurrentToken;
            op = token as OperatorToken;
        }
        return value;
    }

    public double Factor(Scanner scanner, SymbolTable symbolTable)
    {
        Token token = scanner.CurrentToken;
        OperatorToken op = token as OperatorToken;
        bool neg = false;
        if (op != null && op.IsAdditiveOperator)
        {
            if (op.Char == '-')
            {
                neg = true;
            }
            scanner.NextToken();
        }
        double value = Primary(scanner, symbolTable);
        if (neg)
        {
            return -value;
        }
        return value;
    }

    public double Primary(Scanner scanner, SymbolTable symbolTable)
    {
        Token token = scanner.CurrentToken;
        NumberToken number = token as NumberToken;
        if (number != null)
        {
            double value = number.Value;
            scanner.NextToken();
            return value;
        }
        else
        {
            VariableNameToken variableNameToken = token as VariableNameToken;
            if (variableNameToken != null)
            {
                double value = symbolTable.GetVariableValue(variableNameToken.VariableName);
                scanner.NextToken();
                return value;
            }
            else
            {
                OperatorToken op = token as OperatorToken;
                if (op != null && op.Char == '(')
                {
                    scanner.NextToken();
                    double value = Expr(scanner, symbolTable);
                    token = scanner.CurrentToken;
                    op = token as OperatorToken;
                    if (op != null && op.Char == ')')
                    {
                        scanner.NextToken();
                        return value;
                    }
                    else
                    {
                        throw new ParsingException("')' expected");
                    }
                }
                else
                {
                    throw new ParsingException("number, variable name or subexpression expected");
                }
            }
        }
    }
}