package asistencia.utilities;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import org.json.JSONArray;
import org.json.JSONObject;

public final class DAOHelper {

	public static JSONArray queryPS(Connection connection, String query, JSONArray... params) throws Exception {
		return queryPS(connection, query, false, params);
	}

	/**
	 * Este metodo ejecuta una sentencia sql de consulta(solamente selects) en la
	 * base de datos y devuelve como resultado en un JSONArray los registros
	 * obtenidos
	 *
	 *
	 * @param cn objeto que representa la conexion a la base de datos
	 * @param query sentencia sql que se desea ejecutar en la base de datos
	 * @param parametros parametros que vamos a pasar al query, *es opcional
	 * @return Devuelve como resultado los registros obtenidos por el query en un
	 * objeto JSONArray
	 * @throws Exception
	 */
	public static JSONArray queryPS(Connection connection, String query, boolean mode, JSONArray... params) throws Exception {
		JSONArray jsonArray = new JSONArray();//objeto que almacena todas las filas obtenidas por el query
		try {
			PreparedStatement ps = connection.prepareStatement(query);
			//validamos si existen parametros
			if (params != null && params.length > 0) {
				JSONArray _params = params[0];
				int index = 1;
				//Recorremos la lista de parametros y lo seteamos en el preparedstatement
				for (Object p : _params) {
					setPreparedStatement(ps, index++, p);
				}
			}
			ResultSet rs = ps.executeQuery();
			ResultSetMetaData rm = rs.getMetaData();
			int numCols = rm.getColumnCount();
			while (rs.next()) {
				JSONObject obj = new JSONObject();
				for (int i = 1; i <= numCols; i++) {
					setJSONObject(rs, rm, i, obj, mode);
				}
				System.out.println("objeto " + obj);
				jsonArray.put(obj);
			}
		} catch (Exception ex) {
			throw ex;
		}
		return jsonArray;
	}

	/**
	 * Este metodo nos permite ejecutar una sentencia sql en la base de datos y
	 * devuelve como resultado en un JSONObject las filas afectadas por el query.
	 *
	 * @param cn objeto que representa la conexion a la base de datos
	 * @param query sentencia sql que se desea ejecutar en la base de datos
	 * @param parametros parametros que vamos a pasar al query, *es opcional
	 * @return Devuelve la cantidad de filas afectadas por el query en un
	 * JSONObject.
	 * @throws Exception
	 */
	public static JSONObject executePS(Connection connection, String query, JSONArray... params) throws Exception {
		JSONObject obj = new JSONObject();
		try {
			PreparedStatement ps = connection.prepareStatement(query);
			if (params != null && params.length > 0) {
				JSONArray _params = params[0];
				int index = 1;
				for (Object parametro : _params) {
					setPreparedStatement(ps, index++, parametro);
				}
			}
			int filas = ps.executeUpdate();
			obj.put("status", filas > 0);
			obj.put("message", "OK");
			obj.put("data", filas);
		} catch (Exception ex) {
			obj.put("status", false);
			obj.put("message", ex);
			obj.put("data", JSONObject.NULL);
			throw ex;
		}
		return obj;
	}

	public static JSONArray queryProcedure(Connection cn, String query, JSONArray... parameters) throws Exception {
		return queryProcedure(cn, query, false, parameters);
	}

	public static JSONArray queryProcedure(Connection cn, String query, boolean mode, JSONArray... parameters) throws Exception {
		JSONArray jsonArray = new JSONArray();
		JSONObject outputParamTypes = new JSONObject();
		JSONArray params = null;
		try {
			CallableStatement cs = cn.prepareCall(query);
			if (parameters != null && parameters.length > 0) {
				params = parameters[0];
				int index = 1;
				for (Object parameter : params) {
					if (parameter instanceof Class) {
						registerOutputParameter(cs, index++, parameter, outputParamTypes);
					} else {
						setPreparedStatement(cs, index++, parameter);
					}
				}
			}
			boolean isResultSet = cs.execute();
			if (isResultSet) {
				final ResultSet rs = cs.getResultSet();
				ResultSetMetaData rm = rs.getMetaData();
				int columnCount = rm.getColumnCount();
				while (rs.next()) {
					JSONObject obj = new JSONObject();
					for (int columnIndex = 1; columnIndex <= columnCount; columnIndex++) {
						setJSONObject(rs, rm, columnIndex, obj, mode);
					}
					jsonArray.put(obj);
				}
			} else {
				int rowsAffected = cs.getUpdateCount();
				JSONObject obj = new JSONObject();
				obj.put("rowsAffected", rowsAffected);
				jsonArray.put(obj);
			}

			if (outputParamTypes.length() > 0) {
				clearJSONArray(params);
			}
			for (String key : outputParamTypes.keySet()) {
				int indexOutputParams = Integer.parseInt(key);
				int type = outputParamTypes.getInt(key);
				getOutputParameter(cs, indexOutputParams, type, params);
			}

		} catch (Exception ex) {
			throw ex;
		}
		return jsonArray;
	}

	public static JSONObject executeCS(Connection connection, String query, JSONArray... params) throws Exception {
		JSONObject obj = new JSONObject();
		try {
			CallableStatement cs = connection.prepareCall(query);
			if (params != null && params.length > 0) {
				JSONArray _params = params[0];
				int index = 1;
				for (Object p : _params) {
					setPreparedStatement(cs, index++, p);
				}
			}
			int filas = cs.executeUpdate();
			obj.put("status", filas > 0);
			obj.put("message", "OK");
			obj.put("data", filas);
		} catch (Exception ex) {
			obj.put("status", false);
			obj.put("message", ex);
			obj.put("data", JSONObject.NULL);
			throw ex;
		}
		return obj;
	}

	/**
	 * Obtiene el valor de una columna de una tabla y lo guarda en el objeto
	 * JSONObject con el tipo de dato que le corresponde.
	 *
	 * @param rs Objeto ResultSet para obtener el valor de una columna de una
	 * tabla
	 * @param rsmd Objeto ResultSetMetaData nos permite obtener el nombre y tipo
	 * de columna
	 * @param columnIndex Posicion de la columna en la sentencia sql
	 * @param obj Representa a un registro de la base de datos
	 * @throws SQLException
	 */
	private static void setJSONObject(ResultSet rs, ResultSetMetaData rm, int columnIndex, JSONObject obj, boolean mode) throws SQLException {
		int type = rm.getColumnType(columnIndex);
		switch (type) {
			case Types.VARCHAR:
				obj.put(mode ? "" + columnIndex : rm.getColumnLabel(columnIndex), rs.getString(columnIndex) == null ? JSONObject.NULL : rs.getString(columnIndex));
				break;
			case Types.CHAR:
				obj.put(mode ? "" + columnIndex : rm.getColumnLabel(columnIndex), rs.getString(columnIndex) == null ? JSONObject.NULL : rs.getString(columnIndex));
				break;
			case Types.INTEGER:
				obj.put(mode ? "" + columnIndex : rm.getColumnLabel(columnIndex), rs.getInt(columnIndex));
				break;
			case Types.BIT:
				obj.put(mode ? "" + columnIndex : rm.getColumnLabel(columnIndex), rs.getBoolean(columnIndex));
				break;
			case Types.BINARY:
				obj.put(mode ? "" + columnIndex : rm.getColumnLabel(columnIndex), rs.getBytes(columnIndex));
				break;
			default:
				obj.put(mode ? "" + columnIndex : rm.getColumnLabel(columnIndex), rs.getString(columnIndex) == null ? JSONObject.NULL : rs.getString(columnIndex));
		}
	}

	private static void getOutputParameter(CallableStatement cs, int index, int type, JSONArray parameter) throws SQLException {
		switch (type) {
			case Types.INTEGER:
				parameter.put(cs.getInt(index));
				break;
			case Types.VARCHAR:
			case Types.CHAR:
				parameter.put(cs.getString(index));
				break;
			case Types.BOOLEAN:
				parameter.put(cs.getBoolean(index));
				break;
			case Types.DOUBLE:
				parameter.put(cs.getDouble(index));
				break;
			case Types.BINARY:
				parameter.put(cs.getBytes(index));
				break;
			default:
				parameter.put(cs.getString(index));
		}
	}

	private static void registerOutputParameter(CallableStatement cs, int index, Object p, JSONObject types) throws SQLException {
		if (p.equals(Integer.class)) {
			cs.registerOutParameter(index, Types.INTEGER);
			types.put(Integer.toString(index), Types.INTEGER);
		} else if (p.equals(String.class)) {
			cs.registerOutParameter(index, Types.VARCHAR);
			types.put(Integer.toString(index), Types.VARCHAR);
		} else if (p.equals(Boolean.class)) {
			cs.registerOutParameter(index, Types.BOOLEAN);
			types.put(Integer.toString(index), Types.BOOLEAN);
		} else if (p.equals(Double.class)) {
			cs.registerOutParameter(index, Types.DOUBLE);
			types.put(Integer.toString(index), Types.DOUBLE);
		}
	}

	/**
	 * Setea en el prepared statement el valor del parametro segun su tipo de
	 * dato.
	 *
	 * @param ps representa el objeto PreparedStatement
	 * @param index indica la posicion del parametro en la consulta sql
	 * @param p parametro de la consulta sql
	 * @throws SQLException
	 */
	private static void setPreparedStatement(PreparedStatement ps, int index, Object p) throws SQLException {
		if (p instanceof Integer) {
			ps.setInt(index, (int) p);
		} else if (p instanceof String) {
			ps.setString(index, p.toString());
		} else if (p instanceof Double) {
			ps.setDouble(index, (double) p);
		} else if (p instanceof Boolean) {
			ps.setBoolean(index, (boolean) p);
		} else if (p instanceof byte[]) {
			ps.setBytes(index, (byte[]) p);
		}
	}

	private static void clearJSONArray(JSONArray jsonArray) {
		while (jsonArray != null && jsonArray.length() > 0) {
			jsonArray.remove(0);
		}
	}
}
