package pex.parser;

import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.FileReader;
import java.io.StringReader;
import java.io.Reader;

import java.util.Collection;
import java.util.ArrayList;

import pex.core.*;

public class NewParser {

    private Program _program;
    private StreamTokenizer _tokenizer;

    // may need aditional fields

    public NewParser() {
    }

    private void initTokenizer(Reader reader) {
        _tokenizer = new StreamTokenizer(reader);
        _tokenizer.eolIsSignificant(false);
    }

    public Program parseFile(String fileName, String programName /* may need aditional parameters */) throws BadSourceException, BadNumberException, InvalidExpressionException, 
                                                                                                  MissingClosingParenthesisException, UnknownOperationException, EndOfInputException  {
        _program = new Program(programName /* may need additional parameters */);

        try (FileReader reader = new FileReader(fileName)) {
            initTokenizer(reader);

            Collection<Expression> expressions = new ArrayList<>();
            Expression exp;

            while ((exp = parseExpression()) != null)
                expressions.add(exp);

            _program.set(expressions);
        } catch (IOException ioe) {
            throw new BadSourceException(fileName, ioe);
        }

        return _program;
    }

    public Expression parseString(String expression, Program program) throws BadSourceException, BadNumberException, InvalidExpressionException,
                                                                             MissingClosingParenthesisException, UnknownOperationException, EndOfInputException {
        _program = program;

        try (StringReader reader = new StringReader(expression)) {
            initTokenizer(reader);

            return parseExpression();
        } catch (IOException ioe) {
            throw new BadSourceException(expression, ioe);
        }
    }

    private Expression parseExpression() throws IOException, BadNumberException, InvalidExpressionException, MissingClosingParenthesisException,
                                                UnknownOperationException, EndOfInputException {
        int token = _tokenizer.nextToken();

        switch (token) {
            case StreamTokenizer.TT_EOF:
                return null;
                
            case StreamTokenizer.TT_NUMBER: // Literal inteiro
                if (_tokenizer.nval < 0 || _tokenizer.nval - (int)_tokenizer.nval != 0)
                    throw new BadNumberException("" + _tokenizer.nval);

                return new IntegerLiteral((int)_tokenizer.nval);

            case '"':
                return new StringLiteral(_tokenizer.sval);

            case StreamTokenizer.TT_WORD:
                return new Identifier(_tokenizer.sval /* may need aditional parameters */);

            case '(':
                Expression exp = parseCompositeExpression();
                // process closing parenthesis
                if (_tokenizer.nextToken() != ')')
                    throw new MissingClosingParenthesisException(exp.toString());

                return exp;

            default:
                throw new InvalidExpressionException(_tokenizer.lineno());

        }
    }
    
    // Return value cannot be null
    private Expression parseArgument() throws IOException, BadNumberException, UnknownOperationException,
                                              MissingClosingParenthesisException, EndOfInputException, InvalidExpressionException {
        Expression exp = parseExpression();
        if (exp == null)
            throw new EndOfInputException();

        return exp;
    }

    // the opening '(' was already processed
    private CompositeExpression parseCompositeExpression() throws IOException, BadNumberException, UnknownOperationException, MissingClosingParenthesisException,
                                                                  EndOfInputException, InvalidExpressionException {
        int token = _tokenizer.nextToken();

        if (token != StreamTokenizer.TT_WORD)
            throw new InvalidExpressionException(_tokenizer.lineno());

        String operatorName = _tokenizer.sval;

        // The way each composite expression is inntanciated mey need to be changed since
        // this depends on the specific code of each group

        switch (operatorName) {
            
            // process no-args expressions
            case "reads":
                return new ReadS(/* may need additional parameters */);

            case "readi":
                return new ReadI(/* may need additional parameters */);
              
             // processing unary expressions
            case "neg":
                return new Neg(parseArgument());

            case "not":
                return new Not(parseArgument());
                
            case "call":
                try {
                    return new Call((StringLiteral)parseArgument() /* may need additional parameter */);
                } catch(ClassCastException cce) { // it is not a StringLiteral
                    throw new InvalidExpressionException(_tokenizer.lineno());
                }

              // processing binary expressions
            case "add":
                return new Add(parseArgument(), parseArgument());

            case "mul":
                return new Mul(parseArgument(), parseArgument());

            case "div":
                return new Div(parseArgument(), parseArgument());

             case "mod":
                return new Mod(parseArgument(), parseArgument());

            case "sub":
                return new Sub(parseArgument(), parseArgument());

            case "lt":
                return new Lt(parseArgument(), parseArgument());

            case "le":
                return new Le(parseArgument(), parseArgument());

            case "gt":
                return new Gt(parseArgument(), parseArgument());

            case "ge":
                return new Ge(parseArgument(), parseArgument());

             case "eq":
                return new Eq(parseArgument(), parseArgument());

             case "ne":
                 return new Ne(parseArgument(), parseArgument());

             case "and":
                 return new And(parseArgument(), parseArgument());

             case "or":
                 return new Or(parseArgument(), parseArgument());

             case "set":
                 return new Set(parseArgument(), parseArgument() /* may need additional parameters */);

             case "while":
                 return new While(parseArgument(), parseArgument());
        
             // processing ternary expressions
             case "if":
                 return new If(parseArgument(), parseArgument(), parseArgument());

             // processing variadic expressions
             case "seq":
             case "print":
                 // process args
                 ArrayList<Expression> args = new ArrayList<>();

                 while (true) {
                     try {
                         args.add(parseArgument());
                     } catch (InvalidExpressionException iee) { // reaching the closing parenthisis of current composite expression
                         if (_tokenizer.ttype == ')') { // no more arguments
                             _tokenizer.pushBack();
                             break;
                         }
                         throw iee;
                     }
                 }

                 if (operatorName.equals("seq"))
                     return new Seq(args);
                 else
                     return new Print(args /* may need additional parameters */);
        
             default:
                 throw new UnknownOperationException(_tokenizer.sval);
        }
    }          
}