/******************************************************************************
 * Product: Adempiere ERP & CRM Smart Business Solution                       *
 * Copyright (C) 2010 Heng Sin Low                							  *
 * This program is free software; you can redistribute it and/or modify it    *
 * under the terms version 2 of the GNU General Public License as published   *
 * by the Free Software Foundation. This program is distributed in the hope   *
 * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.           *
 * See the GNU General Public License for more details.                       *
 * You should have received a copy of the GNU General Public License along    *
 * with this program; if not, write to the Free Software Foundation, Inc.,    *
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.                     *
 *****************************************************************************/
package org.adempiere.process;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import org.adempiere.exceptions.DBException;
import org.compiere.model.MColumn;
import org.compiere.model.MProcessPara;
import org.compiere.model.MTable;
import org.compiere.model.M_Element;
import org.compiere.model.PO;
import org.compiere.process.ProcessInfoParameter;
import org.compiere.process.SvrProcess;
import org.compiere.util.AdempiereUserError;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.DisplayType;
import org.compiere.util.Env;
import org.compiere.util.Msg;
import org.compiere.util.Trx;
import org.compiere.util.Util;
import org.compiere.util.ValueNamePair;
/**
 * Add UUID column (tableName_UU) to table and update existing records with new UUID.
 * Warning: this process is only safe to run if it have exclusive access to database.
 * @author hengsin
 *
 */
@org.adempiere.base.annotation.Process
public class UUIDGenerator extends SvrProcess {
	private String tableName;
	private boolean isFillUUID = false;
	private boolean isClearUUID = false;
	/**	Logger							*/
	private static final CLogger log = CLogger.getCLogger(UUIDGenerator.class);
	/**
	 * @see org.compiere.process.SvrProcess#prepare()
	 */
	@Override
	protected void prepare() {
		for(ProcessInfoParameter param : getParameter()) {
			if (param.getParameter() == null)
				;
			else if (param.getParameterName().equals("TableName"))
				tableName = param.getParameter().toString();
			else if (param.getParameterName().equals("IsFillUUID"))
				isFillUUID = param.getParameterAsBoolean();
			else if (param.getParameterName().equals("IsClearUUID"))
				isClearUUID = param.getParameterAsBoolean();
			else
				MProcessPara.validateUnknownParameter(getProcessInfo().getAD_Process_ID(), param);
		}
	}
	/**
	 * @see org.compiere.process.SvrProcess#doIt()
	 */
	@Override
	protected String doIt() throws Exception {
		if (tableName == null || tableName.trim().length() == 0)
			tableName = "%";
		else
			tableName = tableName.trim();
		String sql = "SELECT AD_Table_ID, TableName FROM AD_Table WHERE TableName LIKE ? AND IsView = 'N' AND IsActive='Y' ORDER BY TableName";
		PreparedStatement stmt = null;
		ResultSet rs = null;
		int count = 0;
		try  {
			stmt = DB.prepareStatement(sql, null);
			stmt.setString(1, tableName);
			rs = stmt.executeQuery();
			while(rs.next()) {
				int AD_Table_ID = rs.getInt(1);
				String cTableName = rs.getString(2);
				statusUpdate(Msg.getMsg(getCtx(), "Processing") + " " + cTableName);
				String columnName = PO.getUUIDColumnName(cTableName);
				int AD_Column_ID = DB.getSQLValue(null, "SELECT AD_Column_ID FROM AD_Column Where AD_Table_ID = ? AND ColumnName = ?", AD_Table_ID, columnName);
				if (AD_Column_ID <= 0) {
					if (log.isLoggable(Level.INFO)) log.info("Adding UUID to " + cTableName);
					count++;
					//create column
					MColumn mColumn = new MColumn(getCtx(), 0, null);
					mColumn.setAD_Table_ID(AD_Table_ID);
					int AD_Element_ID = DB.getSQLValue(null, "SELECT AD_Element_ID FROM AD_Element WHERE ColumnName=?",columnName);
					if (AD_Element_ID <= 0) {
						M_Element adElement = new M_Element(getCtx(), 0, null);
						adElement.setColumnName(columnName);
						adElement.setName(columnName);
						adElement.setPrintName(columnName);
						adElement.setEntityType("U");
						adElement.saveEx();
						AD_Element_ID = adElement.getAD_Element_ID();
					}
					mColumn.setAD_Element_ID(AD_Element_ID);
					mColumn.setColumnName(columnName);
					mColumn.setAD_Reference_ID(DisplayType.UUID);
					mColumn.setEntityType("U");
					mColumn.setFieldLength(36);
					mColumn.setName(columnName);
					mColumn.setVersion(Env.ONE);
					mColumn.saveEx();
					AD_Column_ID = mColumn.getAD_Column_ID();
					syncColumn(mColumn);
					//update db
					if (isFillUUID) {
						String msg = updateUUID(mColumn, null);
						if (! Util.isEmpty(msg)) {
							addBufferLog(0, null, null, msg, 0, 0);
						}
					}
				} else {
					MColumn column = MColumn.get(AD_Column_ID);
					if (column.isActive()) {
						if (isFillUUID) {
							MColumn mColumn = MColumn.get(getCtx(), AD_Column_ID);
							String msg = updateUUID(mColumn, null);
							if (! Util.isEmpty(msg)) {
								addBufferLog(0, null, null, msg, 0, 0);
							}
						}
					} else {
						if (isClearUUID) {
							StringBuilder sqlclear = new StringBuilder("UPDATE ")
									.append(cTableName)
									.append(" SET ")
									.append(columnName)
									.append("=NULL WHERE ")
									.append(columnName)
									.append(" IS NOT NULL");
							int cnt = DB.executeUpdateEx(sqlclear.toString(), get_TrxName());
							if (cnt > 0) {
								String msg = cnt + " UUID cleared from table " + cTableName;
								addBufferLog(0, null, null, msg, 0, 0);
							}
						}
					}
				}
			}
		} finally {
			DB.close(rs,stmt);
		}
		StringBuilder msgreturn = new StringBuilder().append(count).append(" table altered");
		return msgreturn.toString();
	}
	/**
	 * Fill column with new uuid value (if it is null)
	 * @param column
	 * @param trxName
	 * @return message
	 */
	public static String updateUUID(MColumn column, String trxName) {
		MTable table = (MTable) column.getAD_Table();
		if (table.getTableName().startsWith("T_")) {
			// don't update UUID for temporary tables
			return "";
		}
		int AD_Column_ID = 0;
		StringBuilder sql = new StringBuilder("SELECT ");
		String keyColumn = null;
		String[] compositeKeys = table.getKeyColumns();
		if (compositeKeys == null || compositeKeys.length == 1) {
			keyColumn = compositeKeys[0];
			AD_Column_ID = table.getColumn(keyColumn).getAD_Column_ID();
			compositeKeys = null;
		}
		if ((compositeKeys == null || compositeKeys.length == 0) && keyColumn == null) {
			// TODO: Update using rowid for oracle or ctid for postgresql
			log.warning("Cannot update orphan table " + table.getTableName() + " (not ID neither parents)");
			return "";
		}
		if (compositeKeys == null) {
			sql.append(keyColumn);
		} else {
			for(String s : compositeKeys) {
				sql.append(s).append(",");
			}
			sql.deleteCharAt(sql.length()-1);
		}
		sql.append(" FROM ").append(table.getTableName());
		sql.append(" WHERE ").append(column.getColumnName()).append(" IS NULL ");
		StringBuilder updateSQL = new StringBuilder("UPDATE ");
		updateSQL.append(table.getTableName());
		updateSQL.append(" SET ");
		updateSQL.append(column.getColumnName());
		updateSQL.append("=? WHERE ");
		if (AD_Column_ID > 0) {
			updateSQL.append(keyColumn).append("=?");
		} else {
			for(String s : compositeKeys) {
				updateSQL.append(s).append("=? AND "); 
			}
			int length = updateSQL.length();
			updateSQL.delete(length-5, length); // delete last AND
		}
		
		boolean localTrx = false;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		Trx trx = trxName != null ? Trx.get(trxName, false) : null;
		if (trx == null) {
			trx = Trx.get(Trx.createTrxName(), true);
			trx.setDisplayName(UUIDGenerator.class.getName()+"_updateUUID");
			localTrx = true;
		}
		String msg = "";
		try {				
			if (localTrx)
				trx.start();
			stmt = DB.prepareStatement(sql.toString(), trx.getTrxName());
			stmt.setFetchSize(100);
			rs = stmt.executeQuery();
			int no = 0;
			while (rs.next()) {
				if (AD_Column_ID > 0) {
					int recordId = rs.getInt(1);
					// this line is to avoid users generating official UUIDs - comment it to do official migration script work
					if (recordId > MTable.MAX_OFFICIAL_ID) {
						UUID uuid = UUID.randomUUID();
						no += DB.executeUpdateEx(updateSQL.toString(),new Object[]{uuid.toString(), recordId}, trx.getTrxName());
					}
				} else {
					UUID uuid = UUID.randomUUID();
					List