package walker;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;

import autogenerated.CymbolBaseVisitor;
import autogenerated.CymbolParser;



public class CymbolCheckerVisitor extends CymbolBaseVisitor<Type> {
	public Map<String, Type> symbolTable = new HashMap<String,Type>(); 
	public Map<String, ArrayList<Type> > FuncTable = new HashMap<String, ArrayList<Type>>(); 
	
	
	@Override 
	public Type visitFile(CymbolParser.FileContext ctx) { 
		List<CymbolParser.FuncDeclContext> funcs = ctx.funcDecl();
		for(int j = 0; j < funcs.size(); j++){
			String varname = funcs.get(j).ID().getText();
			Type type = Type.valueOf(funcs.get(j).type( ).getText().toUpperCase());
			ArrayList <Type> a = new ArrayList<Type>();
			a.add(type);
			if(funcs.get(j).paramTypeList() != null){
				List<CymbolParser.ParamTypeContext> al = funcs.get(j).paramTypeList().paramType();
				for(int i = 0; i < al.size(); i++){
					symbolTable.put(al.get(i).ID().getText(), Type.valueOf(al.get(i).type().getText().toUpperCase()));
					a.add(Type.valueOf(al.get(i).type().getText().toUpperCase()));
				}
				
			}
			FuncTable.put(varname, a);
		}
		
		return visitChildren(ctx);
		
	}
	
	@Override 
	public Type visitFuncDecl(CymbolParser.FuncDeclContext ctx) {
		Type result;
//		String varname = ctx.ID( ).getText();
		Type type = Type.valueOf(ctx.type( ).getText().toUpperCase());
//		ArrayList <Type> a = new ArrayList<Type>();
//		a.add(type);
//		if(ctx.paramTypeList() != null){
//			List<CymbolParser.ParamTypeContext> al = ctx.paramTypeList().paramType();
//			for(int i = 0; i < al.size(); i++){
//				symbolTable.put(al.get(i).ID().getText(), Type.valueOf(al.get(i).type().getText().toUpperCase()));
//				a.add(Type.valueOf(al.get(i).type().getText().toUpperCase()));
//			}
//			
//		}
//		FuncTable.put(varname, a);
		
		
		
		Type reto = visit(ctx.block());
		
		if(reto.equals(type)){
			result = type;
		} else {
			Token token = (Token)ctx.ID().getPayload();
			int b = token.getLine();
			int c = token.getCharPositionInLine();
			System.err.format("erro: Declaração da função invalida na linha '%d', coluna '%d'\n", b, c);
			System.exit(1);
			result = Type.VOID;
		}
		return result;
	}
	
	@Override 
	public Type visitFunctionCallExpr(CymbolParser.FunctionCallExprContext ctx) {
		String varname = ctx.ID().getText();
		Type result;
		ArrayList <Type> parameterType = FuncTable.get(varname);
		if(parameterType == null){
			Token token = (Token)ctx.ID().getPayload();
			int b = token.getLine();
			int c = token.getCharPositionInLine();
			System.err.format("erro: Função inexistente na linha '%d', coluna '%d'\n", b, c);
			System.exit(1);
			result = Type.VOID;
		}
		result = parameterType.get(0);
		//System.out.println(varname);
		
		if(ctx.exprList() != null){
			List<CymbolParser.ExprContext> al = ctx.exprList().expr();
			for(int i = 0; i < al.size(); i++){
				Type varType = visit(al.get(i));
				if(!varType.equals(parameterType.get(i+1))){
					Token token = (Token)ctx.ID().getPayload();
					int b = token.getLine();
					int c = token.getCharPositionInLine();
					System.err.format("erro: Chamada de função invalida na linha '%d', coluna '%d'\n", b, c);
					System.exit(1);
					result = Type.VOID;
				}
			}
		}
		return result; 
	}

	@Override 
	public Type visitBlock(CymbolParser.BlockContext ctx) {
		Type result = Type.VOID;
		Type lastResult = null;
		int first = 0;
		//ArrayList <Type> a = new ArrayList<Type>();
		
		if(ctx.stat() != null){
			List<CymbolParser.StatContext> al = ctx.stat();
			for(int i = 0; i < al.size(); i++){
				if(al.get(i).returnStat() != null){
					result = visit(al.get(i).returnStat());
					if(first == 1){
						if(result != lastResult){
							Token token = (Token) al.get(i).returnStat().getPayload( );
							int b = token.getLine();
							System.err.format("erro: Tipo de retorno invalido na linha '%d'\n", b);
							System.exit(1);
						}
					}
					lastResult = result;
					first = 1;
				}
				else if (al.get(i).ifElseStat() != null) {
					result = visit(al.get(i).ifElseStat());
					if(first == 1){
						if(result != lastResult){
							Token token = (Token) al.get(i).ifElseStat().getPayload( );
							int b = token.getLine();
							System.err.format("erro: Tipo de retorno invalido na linha '%d'\n", b);
							System.exit(1);
						}
					}
					lastResult = result;
					first = 1;
				} else {
					if(al.get(i).assignStat() != null)
						visit(al.get(i).assignStat());
					if(al.get(i).exprStat() != null)
						visit(al.get(i).exprStat());
					if(al.get(i).varDecl() != null)
						visit(al.get(i).varDecl());
				}
			}
			
		} else {
			result = Type.VOID;
		}
		if(result == null)
			result = Type.VOID;
		return result;
	}
	
	
	@Override
	public Type visitVarDecl(CymbolParser.VarDeclContext ctx) {
		Type result;
		String varname = ctx.ID( ).getText();
		String typeName = ctx.type( ).getText();
		Type type = Type.valueOf(typeName.toUpperCase());
		
		if (type.equals(Type.VOID)) {
			Token token = (Token)ctx.ID().getPayload();
			int b = token.getLine();
			int c = token.getCharPositionInLine();
			System.err.format("erro: Operação invalida na linha '%d', coluna '%d'\n", b, c);
			System.exit(1);
			result = Type.VOID;
		} else {
			if (ctx.expr( ) != null) {
				Type init = visit(ctx.expr( ));
				if(type.equals(Type.FLOAT) && init.equals(Type.INT))
					init = Type.FLOAT;
				if (!init.equals(type)) {
					Token token = (Token)ctx.ID().getPayload();
					int b = token.getLine();
					int c = token.getCharPositionInLine();
					System.err.format("erro: Atribuição invalida na linha '%d', coluna '%d'\n", b, c);
					System.exit(1);
					result = Type.VOID;
				} 
			}
			
			symbolTable.put(varname, type);
			result = type;
		}
		
		return result;
	}
	
	@Override public Type visitComparisonExpr(CymbolParser.ComparisonExprContext ctx) { 
		Type result;
		Type left = visit(ctx.expr(0));
		Type right = visit(ctx.expr(1));
		if((left.equals(Type.INT) || left.equals(Type.FLOAT))  && (right.equals(Type.INT) || right.equals(Type.FLOAT)))	{
			result = Type.BOOLEAN;
		} else {
			int b = ctx.op.getLine();
			int c = ctx.op.getCharPositionInLine();
			System.err.format("erro: comparacao invalida na linha '%d', coluna '%d'\n", b, c);
			System.exit(1);
			result = Type.VOID;
		}
		return result;
	}
	@Override
	public Type visitAddSubExpr(CymbolParser.AddSubExprContext ctx) {
		Type result;
		Type left = visit(ctx.expr(0));
		Type right = visit(ctx.expr(1));
		
		if (left.equals(Type.INT) && right.equals(Type.INT)) {
			result = Type.INT;
		} else if (left.equals(Type.FLOAT) && right.equals(Type.INT)){
			result = Type.FLOAT;
		} else if (left.equals(Type.INT) && right.equals(Type.FLOAT)){
			result = Type.FLOAT;
		} else if (left.equals(Type.FLOAT) && right.equals(Type.FLOAT)){
			result = Type.FLOAT;
		} else if (ctx.op.getText().equals("+") && left.equals(Type.STRING) && (right.equals(Type.INT) || right.equals(Type.FLOAT)
						 || right.equals(Type.STRING) || right.equals(Type.BOOLEAN))){
			result = Type.STRING;
		} else if (ctx.op.getText().equals("+") && right.equals(Type.STRING) && (left.equals(Type.INT) || left.equals(Type.FLOAT)
						 || left.equals(Type.STRING) || left.equals(Type.BOOLEAN))){
			result = Type.STRING;
		} else {
			int b = ctx.op.getLine();
			int c = ctx.op.getCharPositionInLine();
			System.err.format("erro: Operação invalida na linha '%d', coluna '%d'\n", b, c);
			System.exit(1);
			result = Type.VOID;
		}

		
		return result;
	}
	@Override
	public Type visitMulDivExpr(CymbolParser.MulDivExprContext ctx) {
		Type result;
		Type left = visit(ctx.expr(0));
		Type right = visit(ctx.expr(1));
		
		if (left.equals(Type.INT) && right.equals(Type.INT)) {
			result = Type.INT;
		} else if (left.equals(Type.FLOAT) && right.equals(Type.INT)){
			result = Type.FLOAT;
		} else if (left.equals(Type.INT) && right.equals(Type.FLOAT)){
			result = Type.FLOAT;
		} else {
			int b = ctx.op.getLine();
			int c = ctx.op.getCharPositionInLine();
			System.err.format("erro: operacao invalida na linha '%d', coluna '%d'\n", b, c);
			System.exit(1);
			result = Type.VOID;
		}
		
		return result;
	}
	
	@Override
	public Type visitNotExpr(CymbolParser.NotExprContext ctx) {
		Type result;
		Type ttype = visit(ctx.expr());
		
		if (ttype.equals(Type.BOOLEAN)) {
			result = Type.BOOLEAN;
		} else {
			int b = ctx.op.getLine();
			int c = ctx.op.getCharPositionInLine();
			System.err.format("erro: Operação invalida na linha '%d', coluna '%d'\n", b, c);
			System.exit(1);
			result = Type.VOID;
		}
		
		return result;
	}
	@Override
	public Type visitSignedExpr(CymbolParser.SignedExprContext ctx) {
		Type result;
		Type ttype = visit(ctx.expr());
		
		if (ttype.equals(Type.INT)) {
			result = Type.INT;
		} 

		else if (ttype.equals(Type.FLOAT)){
			result = Type.FLOAT;
		}

		else {
			int b = ctx.op.getLine();
			int c = ctx.op.getCharPositionInLine();
			System.err.format("erro: Operação invalida na linha '%d', coluna '%d'\n", b, c);
			System.exit(1);
			result = Type.VOID;
		}
		
		return result;
	}
	@Override 
	public Type visitAndOrExpr(CymbolParser.AndOrExprContext ctx) {
		Type result;
		Type left = visit(ctx.expr(0));
		Type right = visit(ctx.expr(1));
		if(left.equals(Type.BOOLEAN) && right.equals(Type.BOOLEAN))	{
			result = Type.BOOLEAN;
		} else {
			int b = ctx.op.getLine();
			int c = ctx.op.getCharPositionInLine();
			System.err.format("erro: Comparação invalida na linha '%d', coluna '%d'\n", b, c);
			System.exit(1);
			result = Type.VOID;
		}
		return result;
	}

	@Override
	public Type visitEqExpr(CymbolParser.EqExprContext ctx) {
		Type result;
		Type left = visit(ctx.expr(0));
		Type right = visit(ctx.expr(1));
		if(left.equals(Type.BOOLEAN) && right.equals(Type.BOOLEAN))	{
			result = Type.BOOLEAN;
		} else if (left.equals(Type.STRING) && right.equals(Type.STRING)){
			result = Type.BOOLEAN;
		} else if (left.equals(Type.INT) && right.equals(Type.INT)){
			result = Type.BOOLEAN;
		} else if (left.equals(Type.FLOAT) && right.equals(Type.INT)){
			result = Type.BOOLEAN;
		} else if (left.equals(Type.INT) && right.equals(Type.FLOAT)){
			result = Type.BOOLEAN;
		} else if (left.equals(Type.FLOAT) && right.equals(Type.FLOAT)){
			result = Type.BOOLEAN;
		} else {
			int b = ctx.op.getLine();
			int c = ctx.op.getCharPositionInLine();
			System.err.format("erro: Comparação invalida na linha '%d', coluna '%d'\n", b, c);
			System.exit(1);
			result = Type.VOID;
		}
		return result;
	}

	@Override
	public Type visitAssignStat(CymbolParser.AssignStatContext ctx) {
		Type result;
		Type typeName = symbolTable.get(ctx.ID( ).getText( ));
		Type init = visit(ctx.expr( ));
		if(typeName.equals(Type.FLOAT)){
			if(init.equals(Type.INT) || init.equals(Type.FLOAT)){
				result = Type.FLOAT;
			} else {
				Token token = (Token) ctx.ID( ).getPayload( );
				int b = token.getLine();
				int c = token.getCharPositionInLine();
				System.err.format("erro: Atribuição invalida na linha '%d', coluna '%d'\n", b, c);
				System.exit(1);
				result = Type.VOID;
			}
		}
		else if(!init.equals(typeName)) {
			Token token = (Token) ctx.ID( ).getPayload( );
			int b = token.getLine();
			int c = token.getCharPositionInLine();
			System.err.format("erro: Atribuição invalida na linha '%d', coluna '%d'\n", b, c);
			System.exit(1);
			result = Type.VOID;		
		} 
			result = typeName;
		
		return result;
	}
	
	@Override
	public Type visitVarIdExpr(CymbolParser.VarIdExprContext ctx) {
		Type result;
		Type t = symbolTable.get(ctx.ID( ).getText());
		
		if (t == null) {
			Token token = (Token) ctx.ID( ).getPayload( );
			String a = ctx.ID().getText();
			int b = token.getLine();
			int c = token.getCharPositionInLine();
			System.err.format("erro: Variáivel '%s' indefinida na linha '%d', coluna '%d'\n", a, b, c);
			System.exit(1);
			result = Type.VOID;
		}  else {
			result = t;
		}
		return result;
	}
	
	public Type visitIfElseStat(CymbolParser.IfElseStatContext ctx) {
		Type result;
		Type ifType = visit(ctx.ifStat( ));
		Type elseType = null;
		result = ifType;
		
		if (ctx.elseStat( ) != null ){
			elseType = visit(ctx.elseStat( ));
			if (!ifType.equals(elseType) ) {
				System.err.format("erro: Retorno invalido nas condições de If_Else\n");
				System.exit(1);
				result = Type.VOID;
			}
		} 		
		return result;
	}
	
	
	@Override
	public Type visitElseStat(CymbolParser.ElseStatContext ctx) {
			Type init = visit(ctx.ifElseExprStat( ));
			if (init==null) {
				return Type.VOID;
			}
			return init;
		}
	  
	@Override 
	public Type visitIfStatmentCall(CymbolParser.IfStatmentCallContext ctx) { 
		Type result;
		Type type = visit(ctx.expr());
		if(!type.equals(Type.BOOLEAN)){
			Token b = (Token)ctx.expr().getPayload();
			System.err.format("erro: Condição inválida na linha '%d'\n", b.getLine());
			System.exit(1);
			result = Type.VOID;
		} else {
			result = visit(ctx.ifElseExprStat());
		}
		return result;
	}
	
	@Override 
	public Type visitIfElseExprStat(CymbolParser.IfElseExprStatContext ctx) { 
		Type result;
		result = visitChildren(ctx);
		if(result == null)
			result = Type.VOID;
		return  result; 
	}
	
	
	@Override
	public Type visitExprStat(CymbolParser.ExprStatContext ctx) {
		
		Type init = visit(ctx.expr( ));
		
		if (init==null) {
			return Type.VOID;
		}
		return init;
	}
	
	@Override
	public Type visitReturnStat(CymbolParser.ReturnStatContext ctx) {
		Type init = Type.VOID;;
		if(ctx.expr() != null)
			init = visit(ctx.expr( ));
		return init;
	}
	
	@Override
	public Type visitIntExpr(CymbolParser.IntExprContext ctx) {
		return Type.INT;
	}

	@Override
	public Type visitStringExpr(CymbolParser.StringExprContext ctx){
		return Type.STRING;
	}
	
	@Override 
	public Type visitFloatExpr(CymbolParser.FloatExprContext ctx) {
		return Type.FLOAT;
	}

	@Override 
	public Type visitBooleanExpr(CymbolParser.BooleanExprContext ctx){
		return Type.BOOLEAN;
	}

	@Override
	public Type visitFormTypeInt(CymbolParser.FormTypeIntContext ctx) {
		return Type.INT;
	}

	@Override
	public Type visitFormTypeVoid(CymbolParser.FormTypeVoidContext ctx) { 
		return Type.VOID;
	}

	@Override
	public Type visitFormTypeString(CymbolParser.FormTypeStringContext ctx) {
		return Type.STRING;
	}

	@Override
	public Type visitFormTypeFloat(CymbolParser.FormTypeFloatContext ctx) { 
		return Type.FLOAT;
	}

	@Override
	public Type visitFormTypeBoolean(CymbolParser.FormTypeBooleanContext ctx) { 
		return Type.BOOLEAN;
	}


	@Override
	protected Type aggregateResult(Type aggregate, Type nextResult) {
		return (nextResult != null) ? nextResult : aggregate;
	}
}
