package it.csi.demo.democomp.business.business.dao.qbe;

import it.csi.demo.democomp.business.business.dao.metadata.DAOMetadata;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

/**
 * @generated
 * @param <TEX> classe degli esempi
 */
public class QBEQueryBuilder<TEX extends AbstractExample> {

	/**
	 * @generated
	 */
	private DAOMetadata _metadata;

	/**
	 * @generated
	 * @param metadata i metadati del DAO
	 */
	public QBEQueryBuilder(DAOMetadata metadata) {
		this._metadata = metadata;
	}

	private String formatColumnNames(DAOMetadata md) {
		if (md != null) {
			String[] names = md.getColumnNames();
			if (names != null) {
				StringBuffer ris = new StringBuffer("");
				String currName = null;
				for (int i = 0; i < names.length; i++) {
					currName = names[i];
					ris.append(currName);
					if (i < names.length - 1)
						ris.append(",");
				}
				return ris.toString();
			} else
				return "????";
		} else
			return "????";
	}

	/**
	 * 
	 * @param propName
	 * @param pkClass
	 * @return true se la property <propName> e' una property di PK all'interno del DTO
	 * corrispondente alla <pkClass>
	 */
	private boolean isInPK(String propName, Class exampleClass) {
		// per future evoluzioni.
		// per il momento restituisce sempre true
		return true;
	}

	/**
	 * @generated
	 * @param posEx
	 * @param negEx
	 * @return
	 */
	public String createFromExamples(List<TEX> posEx, List<TEX> negEx,
			boolean usesDistinct) {
		String sql = "SELECT " + (usesDistinct ? "DISTINCT " : "")
				+ formatColumnNames(_metadata) + " FROM "
				+ _metadata.getTableName() + " WHERE ";
		String where = "";
		String posExClause = "";
		String negExClause = "";
		if (posEx != null && posEx.size() > 0) {
			posExClause = createFromExamples(posEx, false);
		}
		if (negEx != null && negEx.size() > 0) {
			negExClause = createFromExamples(negEx, false);
		}
		if (posExClause.length() > 0 && negExClause.length() > 0) {
			where += posExClause + " AND NOT " + negExClause;
		} else
			where = posExClause + negExClause; // uno dei due e' ""
		// rimuovo l'ultimo AND
		sql += "(" + where + ")";
		return sql;
	}

	/**
	 * 
	 * @param examples lista di esempi
	 * @param conjunctive se true le singolke clausole sono unite dall'operatore AND, 
	 * se false da OR.
	 * @return la clausola derivante dalla concatenazione delle singole espressioni
	 * relative agli esempi, concatenate con AND o OR a seconda del valore del parametro
	 * conjunctive. Tutta la clausola è racchiusa da "(" e "(".
	 * (<expr_ex_1> AND|OR <expr_ex2> AND|OR ... <expr_ex_n>)
	 */
	public String createFromExamples(List<TEX> examples, boolean conjunctive) {
		String clause = "";
		String operator = conjunctive ? "AND " : " OR ";
		for (TEX ex : examples) {
			String currExClause = createWhereClauseForSingleExample(ex);
			clause += currExClause;
			if (!(examples.get(examples.size() - 1) == ex))
				clause += operator;
		}
		return "(" + clause + ")";
	}

	/**
	 * Crea una parte di clausola where a fronte di un singolo esempio.
	 * Per ogni FieldChk valorizzato aggiunge un termine della clausola.
	 * @param example
	 * @return l'espressione associata all'esempio.
	 * (<term_chk_1> AND <term_chk_2> .... <term_chk_n>)
	 */
	public String createWhereClauseForSingleExample(TEX example) {
		return createSubClause4Example(example, null, false);
	}

	/**
	 * crea una sotto-clausola per l'esempio specificato
	 * @param example
	 * @param prefixPropName prefisso (opzionale) che permette di seguire la struttura annidata
	 *                       degli examples in caso di LookupResolver. Se il metodo e' invocato
	 *                       con questo parametro non nullo significa che si sta costruendo una 
	 *                       sotto-clausola where relativa ad un example annidato.
	 *                       Funziona solo per un livello di annidamento.
	 * @param pkFieldsOnly se true => considera solo le property dell'example che sono in PK.
	 * @return
	 */
	public String createSubClause4Example(AbstractExample example,
			String prefixPropName, boolean pkFieldsOnly) {
		String clause = "";
		try {
			BeanInfo bi = Introspector.getBeanInfo(example.getClass());
			PropertyDescriptor pd[] = bi.getPropertyDescriptors();
			for (int i = 0; i < pd.length; i++) {
				PropertyDescriptor currPD = pd[i];
				if (FieldChk.class.isAssignableFrom(currPD.getPropertyType())
						&& (!pkFieldsOnly || isInPK(currPD.getName(),
								example.getClass()))) {
					FieldChk currChk = (FieldChk) (currPD.getReadMethod()
							.invoke(example, new Object[]{}));
					if (currChk == null)
						continue;
					String term = createTerm4FieldChk(currChk, currPD,
							prefixPropName);
					clause += term + " AND ";
				} else if (AbstractExample.class.isAssignableFrom(currPD
						.getPropertyType())) {
					AbstractExample subEx = (AbstractExample) (currPD
							.getReadMethod().invoke(example, new Object[]{}));
					if (subEx == null)
						continue;
					String term = createSubClause4Example(subEx,
							currPD.getName(), true);
					clause += term + " AND ";
				}
			}
			// alla fine rimuove l'ultimo "AND"
			clause = clause.substring(0, clause.length() - 5);
		} catch (IntrospectionException e) {
			// TODO
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return "(" + clause + ")";
	}

	/**
	 * Formatta l'oggetto in modo che sia utilizzabile nella query costruita a partire
	 * dagli esempi.
	 * @param val
	 * @return
	 */
	public String formatValue(Object val) {

		String resultValue = "null";

		if (val != null) {
			if (val instanceof String) {
				return "'" + ((String) val).replaceAll("'", "''") + "'";
			}

			else if (val instanceof Number) {
				resultValue = val.toString();
			}

			else if (val instanceof java.util.Date) {
				resultValue = makeToDateTerm((java.util.Date) val);
			}

		}
		return resultValue;
	}

	/**
	 * crea l'espressione "to_date" da inserire nella query SQL
	 * @param date la data da utilizzare
	 * @return toDate('<data>','DD/MM/YYYY HH24:MI:SS')
	 */
	private static String makeToDateTerm(java.util.Date date) {
		String retvalue = "";
		java.util.Calendar cal = java.util.Calendar.getInstance();
		cal.setTime(date);
		retvalue = Integer.toString(cal.get(java.util.Calendar.DAY_OF_MONTH))
				+ "/";
		retvalue = retvalue
				+ Integer.toString(cal.get(java.util.Calendar.MONTH) + 1) + "/";
		retvalue = retvalue
				+ Integer.toString(cal.get(java.util.Calendar.YEAR)) + "-";
		retvalue = retvalue
				+ Integer.toString(cal.get(java.util.Calendar.HOUR_OF_DAY))
				+ ":";
		retvalue = retvalue
				+ Integer.toString(cal.get(java.util.Calendar.MINUTE)) + ":";
		retvalue = retvalue
				+ Integer.toString(cal.get(java.util.Calendar.SECOND));
		retvalue = "to_date('" + retvalue + "','DD/MM/YYYY HH24:MI:SS')";
		return retvalue;
	}

	/**
	 * 
	 * @param chk
	 * @return
	 */
	public String createTerm4FieldChk(FieldChk chk, PropertyDescriptor pd,
			String prefixPropName) {
		if (chk instanceof EqChk)
			return createTerm4FieldChk((EqChk) chk, pd, prefixPropName);
		else if (chk instanceof RangeChk)
			return createTerm4FieldChk((RangeChk) chk, pd, prefixPropName);
		else if (chk instanceof RegexpChk)
			return createTerm4FieldChk((RegexpChk) chk, pd, prefixPropName);
		else if (chk instanceof NullChk)
			return createTerm4FieldChk((NullChk) chk, pd, prefixPropName);
		else if (chk instanceof InSetChk)
			return createTerm4FieldChk((InSetChk) chk, pd, prefixPropName);
		else
			throw new IllegalArgumentException("field check non gestito:"
					+ chk.getClass());
	}

	public String createTerm4FieldChk(EqChk chk, PropertyDescriptor pd,
			String prefixPropName) {
		String colName = _metadata.getColumnName(prefixPropName, pd.getName());
		String term = "(";
		String lside = chk.isIgnoreCase() ? "UPPER(" + colName + ")" : colName;
		String rside = chk.isIgnoreCase() ? "UPPER("
				+ formatValue(chk.getExVal()) + ")" : formatValue(chk
				.getExVal());
		if (!chk.valueHasWildcards()) {
			term += lside + (chk.isNegate() ? " <> " : " = ") + rside;
		} else {
			term += lside + (chk.isNegate() ? " NOT LIKE( " : " LIKE( ")
					+ rside + ")";
		}
		term += ")";
		return term;
	}

	public String createTerm4FieldChk(NullChk chk, PropertyDescriptor pd,
			String prefixPropName) {
		String colName = _metadata.getColumnName(prefixPropName, pd.getName());
		String term = "(";
		term += colName + " IS " + (chk.isNegate() ? " NOT" : "") + " NULL";
		term += ")";
		return term;
	}

	public String createTerm4FieldChk(RangeChk chk, PropertyDescriptor pd,
			String prefixPropName) {
		String colName = _metadata.getColumnName(prefixPropName, pd.getName());
		String term = "(";
		if (chk.isNegate())
			term += "NOT ";
		if (chk.isLBIncluded() && chk.isUBIncluded()) {
			term += colName + " BETWEEN " + formatValue(chk.getLowerBound())
					+ " AND " + formatValue(chk.getUpperBound());
		} else {
			term += colName + (chk.isLBIncluded() ? " >= " : " > ")
					+ formatValue(chk.getLowerBound());
			term += " AND " + colName + (chk.isUBIncluded() ? " <= " : " < ")
					+ formatValue(chk.getUpperBound());
			if (chk.isNegate()) {
				term = "(NOT " + term + ")";
			}
		}
		term += ")";
		return term;
	}
}
