package de.lcag.common;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import routines.LcagStringTools;

public class SelectStatement {
	private static Pattern COND_PATTERN = Pattern
			.compile("\\s*([\\w\\d_\\.]+)\\s*(=|<=|>=|!=)\\s*([\\w\\d\\-_'\\.]+\\s*)", Pattern.CASE_INSENSITIVE);
	private static Pattern SELECT_PATTERN = Pattern.compile("^\\s*SELECT\\s+(.+?)\\s+FROM\\s+([\\w\\-_]+)(\\s+\\w+)?",
			Pattern.CASE_INSENSITIVE);
	private static Pattern AS_PATTERN = Pattern.compile("^\\s*(.+?)\\s+AS\\s+(\\w+)", Pattern.CASE_INSENSITIVE);
	private static Pattern JOIN_PATTERN = Pattern.compile(
			"(LEFT JOIN|JOIN)\\s+([\\w\\-_]+)(\\s+\\w+)?\\s*ON\\s+(.*)\\s*(WHERE|$)", Pattern.CASE_INSENSITIVE);
	private static Pattern WHERE_PATTERN = Pattern.compile("WHERE\\s+(.*)", Pattern.CASE_INSENSITIVE);
	private static Pattern BOOL_EXPRESSION_PATTERN = Pattern.compile("\\s*(.+)\\s+(AND|OR)\\s+(.+)\\s*",
			Pattern.CASE_INSENSITIVE);

	private Map<String, String> columns = new LinkedHashMap<>();
	private Table fromTable = new Table();
	private List<Join> joinOn;
	private List<Condition> whereClause = new ArrayList<>();

	class Table {
		public String name;
		public String var = null;;

		public void setVariable(String group) {
			if (group != null)
				this.var = group.trim();
		}
	}

	class Condition {
		public String arg1;
		public String operator;
		public Object arg2;

		Condition(String arg1, String operator, Object arg2) {
			this.arg1 = arg1;
			this.operator = operator;
			this.arg2 = arg2;
		}

		Condition(String pCondition) {
			Matcher m = COND_PATTERN.matcher(pCondition);

			if (m.find()) {
				this.arg1 = m.group(1);
				this.operator = m.group(2);
				this.arg2 = convertToType(m.group(3));
			}
		}

		public String toString() {
			return String.format("%s %s %s", arg1, operator, arg2.toString());
		}
	}

	class Join {
		public Table table = new Table();
		private List<Condition> conditions = new ArrayList<>();

		Join(String pJoin) {
		}

		List<Join> getJoins(String pJoin) {
			List<Join> result = new ArrayList<>();
			Matcher m = JOIN_PATTERN.matcher(pJoin);

			while (m.find()) {
				int n = m.groupCount();
				Matcher m2 = COND_PATTERN.matcher(m.group(n));

				this.table.name = m.group(1);

				if (n > 2) {
					this.table.var = m.group(2);
				}

				extractConditions(this.conditions, m2);
			}
			return result;
		}
	}

	public SelectStatement(String pSelect) {
		Matcher m = SELECT_PATTERN.matcher(pSelect);

		if (m.find()) {
			int n = m.groupCount();
			String s = m.group(1);

			fromTable.name = m.group(2);
			fromTable.setVariable(m.group(3));

			if (fromTable.name == null) {
				fromTable.name = m.group(2);
			}

			String tableVarUpper = (fromTable.var == null) ? "" : fromTable.var.trim().toUpperCase();

			if ("".equals(tableVarUpper) || "WHERE".equals(tableVarUpper) || "JOIN".equals(tableVarUpper)) {
				fromTable.var = null;
			}

			extractColumnNames(s);

			extractWhereConditions(pSelect);
		}
	}

	public String toString() {
		String result = "SELECT ";
		List<String> aList = new ArrayList<>();

		for (String targetName : columns.keySet()) {
			String sourceName = columns.get(targetName);

			if (targetName.equals(sourceName)) {
				aList.add(sourceName);
			} else {
				aList.add(sourceName + " AS " + targetName);
			}
		}

		result += LcagStringTools.joinIgnoreNull(" AND ", aList);
		result += "FROM " + fromTable.name + (fromTable.var == null ? "" : "AS " + fromTable.var);

		if (!whereClause.isEmpty()) {
			result += "WHERE " + LcagStringTools.joinIgnoreNull(" AND ", whereClause);
		}
		return result;
	}

	private void extractWhereConditions(String pWhere) {
		Matcher m = WHERE_PATTERN.matcher(pWhere);

		if (m.find()) {
			List<Condition> whereCondList = this.whereClause;
			extractConditions(whereCondList, m);

		}

	}

	private void extractConditions(List<Condition> pResultList, Matcher pMatcher) {
		String clause = pMatcher.group(1);
		boolean boolExprFound = false;

		pMatcher = BOOL_EXPRESSION_PATTERN.matcher(clause);

		while (pMatcher.find()) {
			int n = pMatcher.groupCount();

			boolExprFound = true;

			if (n == 3) {
				String expr1 = pMatcher.group(1);
				String opr = pMatcher.group(2).toUpperCase();
				String expr2 = pMatcher.group(3);

				if ("AND".equals(opr)) {
					Condition c1 = new Condition(expr1);
					Condition c2 = new Condition(expr2);

					pResultList.add(c1);
					pResultList.add(c2);
				}

			}
		}

		if (!boolExprFound) {
			Condition c1 = new Condition(clause);
			pResultList.add(c1);
		}
	}

	public static Object convertToType(String pStringValue) {
		Object result = pStringValue;

		try {
			Double d = Double.parseDouble(pStringValue);
			final boolean isInt = (d % 1 == 0);

			if (isInt) {
				result = Integer.parseInt(pStringValue);
			} else {
				result = d;
			}
		} catch (NumberFormatException e) {
			int i1 = pStringValue.indexOf("'");
			int i2 = pStringValue.lastIndexOf("'");

			if (i1 >= 0 && i2 > i1) {
				result = pStringValue.substring(i1 + 1, i2);
			}
		}
		return result;
	}

	private void extractColumnNames(String colNameList) {
		String[] parts = colNameList.split("\\s*,\\s*");

		for (String p : parts) {
			Matcher c = AS_PATTERN.matcher(p);
			String targetColName = p;
			String srcColName = p;
			boolean doRename = c.find();

			if (doRename) {
				srcColName = c.group(1);
				targetColName = c.group(2);
			}

			if (fromTable.var != null && srcColName.startsWith(fromTable.var + ".")) {
				// Column name is addressed by a variable and it matches the table variable of
				// FROM clause

				int n = fromTable.var.length();

				if (!doRename) {
					int n2 = srcColName.indexOf('.');

					targetColName = n2 >= 0 ? srcColName.substring(n2 + 1) : srcColName;
				}

				columns.put(targetColName, srcColName);
			} else if (fromTable.var == null && srcColName.indexOf('.') < 0) {
				// Column name is NOT addressed by a variable, so take it
				columns.put(targetColName, srcColName);
			} else {
				// Column name is addressed by a variable but it does NOT match the table
				// variable of FROM clause => ignore it
			}
		}
	}

	public final Map<String, String> getColumns() {
		return columns;
	}

	public String getFromTable() {
		return fromTable.name;
	}

	public String getFromTableVariable() {
		return fromTable.var;
	}

	public final List<Join> getJoinOn() {
		return joinOn;
	}

	public final List<Condition> getWhereClause() {
		return whereClause;
	}

}
