package nl.tudelft.serg.evosql.evaluation.query;

import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.arithmetic.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.values.ValuesStatement;

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

public class DetailedClassifierVisitor implements ExpressionVisitor, FromItemVisitor, ItemsListVisitor, SelectVisitor, SelectItemVisitor {
	private Map<String, Integer> dict;
	private List<String> visitedTables;
	private boolean outerSelect;
	
	public DetailedClassifierVisitor(Map<String, Integer> dict) {
		this.dict = dict;
		visitedTables = new ArrayList<String>();
		dict.clear();
		outerSelect = true;
		
		// Initialize values
		dict.put(DetailedClassification.TABLES, 0);
		dict.put(DetailedClassification.PREDICATES, 0);
		dict.put(DetailedClassification.JOINS, 0);
		dict.put(DetailedClassification.SUBQUERIES, 0);
		dict.put(DetailedClassification.EXISTS, 0);
		dict.put(DetailedClassification.LIKES, 0);
		dict.put(DetailedClassification.AGGFUNCTIONS, 0);
		dict.put(DetailedClassification.OTHFUNCTIONS, 0);
		dict.put(DetailedClassification.CASES, 0);
		dict.put(DetailedClassification.IFNULLS, 0);
		dict.put(DetailedClassification.LEFTJ, 0);
		dict.put(DetailedClassification.RIGHTJ, 0);
		dict.put(DetailedClassification.INNERJ, 0);
		dict.put(DetailedClassification.WHERE, 0);
		dict.put(DetailedClassification.GROUPBY, 0);
		dict.put(DetailedClassification.HAVING, 0);
		dict.put(DetailedClassification.CDGTx, 0);
		dict.put(DetailedClassification.xGTCD, 0);
		
	}

	
	private boolean isAggregate(Function arg0) {
		String funName = arg0.getName();
		return "COUNT".equalsIgnoreCase(funName)
				|| "AVG".equalsIgnoreCase(funName)
				|| "MAX".equalsIgnoreCase(funName)
				|| "MIN".equalsIgnoreCase(funName)
				|| "SUM".equalsIgnoreCase(funName)
				|| "MEDIAN".equalsIgnoreCase(funName);
	}
	
	private void increment(String metricName) {
		dict.put(metricName, dict.get(metricName)+ 1);
	}

	@Override
	public void visit(NullValue arg0) {
	}

	@Override
	public void visit(Function arg0) {
		String key = arg0.getName().toUpperCase();
		if (key.equals("IFNULL"))
			increment(DetailedClassification.IFNULLS);
		if (isAggregate(arg0))
			increment(DetailedClassification.AGGFUNCTIONS);
		else
			increment(DetailedClassification.OTHFUNCTIONS);
		if (arg0.getParameters() != null)
			arg0.getParameters().accept(this);
	}

	@Override
	public void visit(SignedExpression arg0) {
	}

	@Override
	public void visit(JdbcParameter arg0) {
	}

	@Override
	public void visit(JdbcNamedParameter arg0) {
	}

	@Override
	public void visit(DoubleValue arg0) {
	}

	@Override
	public void visit(LongValue arg0) {
	}

	@Override
	public void visit(HexValue arg0) {
	}

	@Override
	public void visit(DateValue arg0) {
	}

	@Override
	public void visit(TimeValue arg0) {
	}

	@Override
	public void visit(TimestampValue arg0) {
	}

	@Override
	public void visit(Parenthesis arg0) {
		arg0.getExpression().accept(this);
	}

	@Override
	public void visit(StringValue arg0) {
	}

	@Override
	public void visit(Addition arg0) {
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(Division arg0) {
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(Multiplication arg0) {
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(Subtraction arg0) {
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(AndExpression arg0) {
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(OrExpression arg0) {
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(Between arg0) {
		increment(DetailedClassification.PREDICATES);
		arg0.getLeftExpression().accept(this);
		arg0.getBetweenExpressionStart().accept(this);
		arg0.getBetweenExpressionEnd().accept(this);
	}

	@Override
	public void visit(EqualsTo arg0) {
		increment(DetailedClassification.PREDICATES);

		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(GreaterThan arg0) {
		increment(DetailedClassification.PREDICATES);
		if (arg0.getLeftExpression() instanceof Function) {
			Function f = (Function) arg0.getLeftExpression();
			if (f.getName().equalsIgnoreCase("COUNT") || f.isDistinct()) {
				increment(DetailedClassification.CDGTx);
			}
		}
		if (arg0.getRightExpression() instanceof Function) {
			Function f = (Function) arg0.getRightExpression();
			if (f.getName().equalsIgnoreCase("COUNT") || f.isDistinct()) {
				increment(DetailedClassification.xGTCD);
			}
		}
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(GreaterThanEquals arg0) {
		increment(DetailedClassification.PREDICATES);
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(InExpression arg0) {
		increment(DetailedClassification.PREDICATES);
		arg0.getLeftExpression().accept(this);
		arg0.getRightItemsList().accept(this);

	}

	@Override
	public void visit(IsNullExpression arg0) {
		increment(DetailedClassification.PREDICATES);
		arg0.getLeftExpression().accept(this);
	}

	@Override
	public void visit(LikeExpression arg0) {
		increment(DetailedClassification.PREDICATES);
		increment(DetailedClassification.LIKES);
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(MinorThan arg0) {
		increment(DetailedClassification.PREDICATES);
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(MinorThanEquals arg0) {
		increment(DetailedClassification.PREDICATES);
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(NotEqualsTo arg0) {
		increment(DetailedClassification.PREDICATES);
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(Column arg0) {
	}

	@Override
	public void visit(SubSelect arg0) {
		increment(DetailedClassification.SUBQUERIES);
		arg0.getSelectBody().accept(this);
	}

	@Override
	public void visit(CaseExpression arg0) {
		increment(DetailedClassification.CASES);
		if (arg0.getSwitchExpression() != null)
			arg0.getSwitchExpression().accept(this);

		if (arg0.getWhenClauses() != null)
			arg0.getWhenClauses().forEach(e -> e.accept(this));

		if (arg0.getElseExpression() != null)
			arg0.getElseExpression().accept(this);
	}

	@Override
	public void visit(WhenClause arg0) {
		arg0.getWhenExpression().accept(this);
	}

	@Override
	public void visit(ExistsExpression arg0) {
		increment(DetailedClassification.PREDICATES);
		increment(DetailedClassification.EXISTS);
		if (arg0.getRightExpression() != null)
			arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(AllComparisonExpression arg0) {
		arg0.getSubSelect().accept((ExpressionVisitor)this);
	}

	@Override
	public void visit(AnyComparisonExpression arg0) {
		arg0.getSubSelect().accept((ExpressionVisitor)this);
	}

	@Override
	public void visit(Concat arg0) {
	}

	@Override
	public void visit(Matches arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(BitwiseRightShift aThis) {
		aThis.getLeftExpression().accept(this);
		aThis.getRightExpression().accept(this);
	}

	@Override
	public void visit(BitwiseLeftShift aThis) {
		aThis.getLeftExpression().accept(this);
		aThis.getRightExpression().accept(this);
	}

	@Override
	public void visit(BitwiseAnd arg0) {
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(BitwiseOr arg0) {
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(BitwiseXor arg0) {
		arg0.getLeftExpression().accept(this);
		arg0.getRightExpression().accept(this);
	}

	@Override
	public void visit(CastExpression arg0) {
	}

	@Override
	public void visit(Modulo arg0) {
	}

	@Override
	public void visit(AnalyticExpression arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(ExtractExpression arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(IntervalExpression arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(OracleHierarchicalExpression arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(RegExpMatchOperator arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(JsonExpression arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(JsonOperator jsonExpr) {

	}

	@Override
	public void visit(RegExpMySQLOperator arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(UserVariable arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(NumericBind arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(KeepExpression arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(MySQLGroupConcat arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(ValueListExpression valueList) {

	}

	@Override
	public void visit(RowConstructor arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(OracleHint arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(TimeKeyExpression arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(DateTimeLiteralExpression arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(NotExpression aThis) {
		aThis.getExpression().accept(this);
	}

	@Override
	public void visit(NextValExpression aThis) {

	}

	@Override
	public void visit(CollateExpression aThis) {

	}

	@Override
	public void visit(SimilarToExpression aThis) {
		// FIXME: increment similar tos?
		increment(DetailedClassification.PREDICATES);
		aThis.getLeftExpression().accept(this);
		aThis.getRightExpression().accept(this);
	}

	@Override
	public void visit(Table arg0) {
		if (!visitedTables.contains(arg0.getName().toUpperCase())) {
			visitedTables.add(arg0.getName().toUpperCase());
			increment(DetailedClassification.TABLES);
		}
	}

	@Override
	public void visit(SubJoin arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void visit(LateralSubSelect arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void visit(ValuesList arg0) {
		arg0.getMultiExpressionList().accept(this);
	}

	@Override
	public void visit(TableFunction arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void visit(ParenthesisFromItem aThis) {
		aThis.getFromItem().accept(this);
	}

	@Override
	public void visit(ExpressionList arg0) {
		arg0.getExpressions().stream().forEach(x -> x.accept(this));
	}

	@Override
	public void visit(NamedExpressionList namedExpressionList) {
		namedExpressionList.getExpressions().forEach(expression -> expression.accept(this));
	}

	@Override
	public void visit(MultiExpressionList arg0) {
		arg0.getExprList().stream().forEach(x -> x.accept(this));
	}

	@Override
	public void visit(PlainSelect arg0) {
		if (!outerSelect) {
			if(arg0.getSelectItems()!=null) {
				for (SelectItem si : arg0.getSelectItems())
					si.accept(this);
			}
		}
		outerSelect = false;
		if(arg0.getFromItem()!=null) arg0.getFromItem().accept(this);
		if(arg0.getHaving()!=null) {
			arg0.getHaving().accept(this);
			increment(DetailedClassification.HAVING);
		}
		if(arg0.getJoins()!=null) arg0.getJoins().stream().forEach(x -> { 
			increment(DetailedClassification.JOINS);
			if (x.isRight()) 
				increment(DetailedClassification.RIGHTJ);
			if (x.isLeft()) 
				increment(DetailedClassification.LEFTJ);
			if (x.isInner()) 
				increment(DetailedClassification.INNERJ);
			if (x.getOnExpression() != null)
				x.getOnExpression().accept(this);
			x.getRightItem().accept(this);
		});
		if(arg0.getWhere()!=null) {
			arg0.getWhere().accept(this);
			increment(DetailedClassification.WHERE);
		}
		if(arg0.getGroupBy() != null)
			increment(DetailedClassification.GROUPBY);
	}

	@Override
	public void visit(SetOperationList arg0) {
        for (SelectBody plainSelect : arg0.getSelects()) {
            plainSelect.accept(this);
        }
	}

	@Override
	public void visit(WithItem arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void visit(ValuesStatement aThis) {

	}

	@Override
	public void visit(AllColumns arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void visit(AllTableColumns arg0) {
		arg0.getTable().accept(this);
	}

	@Override
	public void visit(SelectExpressionItem arg0) {
		arg0.getExpression().accept(this);
	}
}