/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *  
 */
package org.teiid.deployers;

import com.datavirtuality.dv.core.interfaces.DVCaller;
import org.teiid.adminapi.impl.SessionMetadata;
import org.teiid.adminapi.impl.VDBMetaData;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.core.util.NamesUtil;
import org.teiid.dqp.internal.process.DQPWorkContext;
import org.teiid.dqp.internal.process.DataTierManagerImpl;
import org.teiid.dqp.message.RequestID;
import org.teiid.events.EventDistributor;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.metadata.*;
import org.teiid.metadata.Table.TriggerEvent;
import org.teiid.query.metadata.TransformationMetadata;
import org.teiid.query.optimizer.relational.RelationalPlanner;
import org.teiid.query.processor.DdlPlan;
import org.teiid.query.processor.ProcessorPlan;
import org.teiid.query.tempdata.GlobalTableStore;
import org.teiid.runtime.RuntimePlugin;

import java.util.List;

public abstract class EventDistributorImpl implements EventDistributor {
	
	public abstract VDBRepository getVdbRepository();
	
	@Override
	public void updateMatViewRow(String vdbName, int vdbVersion, String schema,
			String viewName, List<?> tuple, boolean delete) {
		VDBMetaData metadata = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (metadata != null) {
			GlobalTableStore gts = metadata.getAttachment(GlobalTableStore.class);
			if (gts != null) {
				try {
					gts.updateMatViewRow(NamesUtil.getCanonicalName(RelationalPlanner.MAT_PREFIX + schema + '.' + viewName), tuple, delete);
				} catch (TeiidComponentException e) {
					LogManager.logError(LogConstants.CTX_RUNTIME, e, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40013, "updateMatViewRow")); //$NON-NLS-1$
				}
			}
		}
	}
	
	@Override
	public void dataModification(String vdbName, int vdbVersion, String schema,	String... tableNames) {
		updateModified(true, vdbName, vdbVersion, schema, tableNames);
	}
	
	private void updateModified(boolean data, String vdbName, int vdbVersion, String schema,
			String... objectNames) {
		Schema s = getSchema(vdbName, vdbVersion, schema);
		if (s == null) {
			return;
		}
		long ts = System.currentTimeMillis();
		for (String name:objectNames) {
			Table table = s.getTables().get(name);
			if (table == null) {
				continue;
			}
			if (data) {
				table.setLastDataModification(ts);
			} else {
				table.setLastModified(ts);
			}
		}
	}
	
	@Override
	public void setColumnStats(String vdbName, int vdbVersion,
			String schemaName, String tableName, String columnName,
			ColumnStats stats) {
		Table t = getTable(vdbName, vdbVersion, schemaName, tableName);
		if (t == null) {
			return;
		}
		Column c = t.getColumnByName(columnName);
		if (c != null) {
			c.setColumnStats(stats);
			t.setLastModified(System.currentTimeMillis());
		}
	}
	
	@Override
	public void setTableStats(String vdbName, int vdbVersion,
			String schemaName, String tableName, TableStats stats) {
		Table t = getTable(vdbName, vdbVersion, schemaName, tableName);
		if (t == null) {
			return;
		}
		t.setTableStats(stats);
		t.setLastModified(System.currentTimeMillis());
	}

	private Table getTable(String vdbName, int vdbVersion, String schemaName,
			String tableName) {
		Schema s = getSchema(vdbName, vdbVersion, schemaName);
		if (s == null) {
			return null;
		}
		return s.getTables().get(tableName);
	}

	private Schema getSchema(String vdbName, int vdbVersion, String schemaName) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return null;
		}
		TransformationMetadata tm = vdb.getAttachment(TransformationMetadata.class);
		if (tm == null) {
			return null;
		}
		return tm.getMetadataStore().getSchemas().get(schemaName);
	}
	
	@Override
	public void setInsteadOfTriggerDefinition(String vdbName, int vdbVersion,
			String schema, String viewName, TriggerEvent triggerEvent,
			String triggerDefinition, Boolean enabled) {
		Table t = getTable(vdbName, vdbVersion, schema, viewName);
		if (t == null) {
			return;
		}
		DdlPlan.alterInsteadOfTrigger(getVdbRepository().getLiveVDB(vdbName, vdbVersion), t, triggerDefinition, enabled, triggerEvent);
	}
	
	@Override
	public void setProcedureDefinition(String vdbName, int vdbVersion,String schema, String procName, String definition) {
		Schema s = getSchema(vdbName, vdbVersion, schema);
		if (s == null) {
			return;
		}
		Procedure p = s.getProcedures().get(procName);
		if (p == null) {
			return;
		}
		DdlPlan.alterProcedureDefinition(getVdbRepository().getLiveVDB(vdbName, vdbVersion), p, definition);
	}
	
	@Override
	public void setViewDefinition(String vdbName, int vdbVersion, String schema, String viewName, String definition) {
		Table t = getTable(vdbName, vdbVersion, schema, viewName);
		if (t == null) {
			return;
		}
		DdlPlan.alterView(getVdbRepository().getLiveVDB(vdbName, vdbVersion), t, definition);
	}

	@Override
	public void setProperty(String vdbName, int vdbVersion, String uuid,
	                        String name, String value) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		TransformationMetadata tm = vdb.getAttachment(TransformationMetadata.class);
		if (tm == null) {
			return;
		}
		AbstractMetadataRecord record = DataTierManagerImpl.getByUuid(vdb, tm.getMetadataStore(), uuid);
		if (record != null) {
			record.setProperty(name, value);
		}
	}

	@Override
	public void createViewProc(String vdbName, int vdbVersion, String command, boolean view) {
		openProcessorPlan(vdbName, vdbVersion, command,
				"Error while distributing \"create " + (view ? "view" : "procedure") + "\" command.");
	}

	@Override
	public void alterViewProc(String vdbName, int vdbVersion, String command, boolean view) {
		openProcessorPlan(vdbName, vdbVersion, command,
				"Error while distributing \"alter " + (view ? "view" : "procedure") + "\" command.");
	}

	@Override
	public void dropViewProc(String vdbName, int vdbVersion, String command, Integer id, String schema, String resourceName, boolean view) {
		boolean isOk = openProcessorPlan(vdbName, vdbVersion, command,
				"Error while distributing \"drop " + (view ? "view" : "procedure") + "\" command.");
		if (isOk && id != null && schema != null && resourceName != null) {
			removeFromDependencyGraphByParams(id, schema, resourceName);
		}
	}

	@Override
	public void dropViewProc(String vdbName, int vdbVersion, String command, boolean view) {
		dropViewProc(vdbName, vdbVersion, command, null, null, null, view);
	}

	@Override
	public void importViewProc(String vdbName, int vdbVersion, String sql, boolean view) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		DVCaller.call(196, vdb, sql, view);
	}

	@Override
	public void setFunctionDefinition(String vdbName, int vdbVersion,String schema, String funcName, String definition) {
		Schema s = getSchema(vdbName, vdbVersion, schema);
		if (s == null) {
			return;
		}
		FunctionMethod p = s.getFunction(funcName);
		if (p == null) {
			return;
		}
		DdlPlan.alterFunctionDefinition(getVdbRepository().getLiveVDB(vdbName, vdbVersion), p, definition);
	}

	@Override
	public void createFunction(String vdbName, int vdbVersion, String command) {
		openProcessorPlan(vdbName, vdbVersion, command,
				"Error while distributing \"create function\" command.");
	}

	@Override
	public void alterFunction(String vdbName, int vdbVersion, String command) {
		openProcessorPlan(vdbName, vdbVersion, command,
				"Error while distributing \"alter function\" command.");
	}

	@Override
	public void dropFunction(String vdbName, int vdbVersion, String command, Integer id, String schema, String resourceName) {
		openProcessorPlan(vdbName, vdbVersion, command,
				"Error while distributing \"drop function\" command.");
	}

	@Override
	public void dropFunction(String vdbName, int vdbVersion, String command) {
		dropFunction(vdbName, vdbVersion, command, null, null, null);
	}

	@Override
	public void importFunction(String vdbName, int vdbVersion, String sql) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		DVCaller.call(197, vdb, sql);
	}

	private void removeFromDependencyGraphByParams(Integer id, String schema, String name) {
		DVCaller.call(195, id, schema, name, true);
	}

	private boolean openProcessorPlan(String vdbName, int vdbVersion, String command, String errorMessage) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return false;
		}
		try {
			ProcessorPlan processorPlan = (ProcessorPlan) DVCaller.call(190, command, vdb);
			if (processorPlan != null) {
				processorPlan.open();
			}
			return true;
		} catch (TeiidComponentException | TeiidProcessingException e) {
			LogManager.logError(LogConstants.CTX_RUNTIME, e, errorMessage);
			return false;
		}
	}

	@Override
	public void createConnection(String vdbName, int vdbVersion, String name, String template, String properties, String encryptedProperties) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		DVCaller.call(197, vdb, name, template, properties, encryptedProperties);
	}

	@Override
	public void createDatasource(String vdbName, int vdbVersion, String name, String connectionName, String translator, String modelPropsString, String translatorPropsString, String encryptedModelPropsString, String encryptedTranslatorPropsString) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		DVCaller.call(198, vdb, name, connectionName, translator, modelPropsString, translatorPropsString, encryptedModelPropsString, encryptedTranslatorPropsString);
	}

	@Override
	public void removeConnection(String vdbName, int vdbVersion, String name, String jndiName, String dropScript, String template) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		DVCaller.call(199, vdb, name, jndiName, dropScript, template);
	}

	@Override
	public void removeDatasource(String vdbName, int vdbVersion, String name) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		DVCaller.call(200, vdb, name);
	}

	@Override
	public void createVirtualSchema(String vdbName, int vdbVersion, String name, boolean isDeletable, int id) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		DVCaller.call(201, vdb, name, isDeletable, id);
	}

	@Override
	public void dropVirtualSchema(String vdbName, int vdbVersion, int id, String name) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		DVCaller.call(202, vdb, id, name);
	}

	@Override
	public void renameVirtualSchema(String vdbName, int vdbVersion, int id, String name, String oldName) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		DVCaller.call(203, vdb, id, name, oldName);
	}

	@Override
	public void createTable(String vdbName, int vdbVersion, String command) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		DVCaller.call(204, vdb, command);
	}

	@Override
	public void dropTable(String vdbName, int vdbVersion, String command) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		DVCaller.call(206, vdb, command);
	}

	@Override
	public void selectInto(String vdbName, int vdbVersion, String command, String connectionId, long executionId, String combinedId, boolean rollbackMetadata) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		RequestID requestID = connectionId != null ? new RequestID(connectionId, executionId) : null;
		DVCaller.call(207, vdb, command, requestID, combinedId, rollbackMetadata);
	}

	@Override
	public void removeTable(String vdbName, int vdbVersion, String schemaName, String tableName) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		DVCaller.call(266, vdb, schemaName, tableName);
	}

	@Override
	public void changeDWH(String vdbName, int vdbVersion, String nameInDv, String nameInSource, boolean restoreTableCounter) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		DVCaller.call(224, vdb, nameInDv, nameInSource, restoreTableCounter);
	}

	@Override
	public void distributeMatTable(String vdbName, int vdbVersion, int recOptId, String matTableName, String matNameInInternalDB) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		DVCaller.call(225, vdb, recOptId, matTableName, matNameInInternalDB);
	}

	@Override
	public void distributeGitEvent(String observedType, Integer entityId, String operationType) {
		DVCaller.call(226, observedType, entityId, operationType);
	}

	@Override
	public void createVdb(String name, Integer version) {
		DVCaller.call(227, name, version);
	}

	@Override
	public void renameVdb(String name, Integer version, String oldName, Integer oldVersion) {
		DVCaller.call(228, name, version, oldName, oldVersion);
	}

	@Override
	public void dropVdb(String name, Integer version) {
		DVCaller.call(229, name, version);
	}

	@Override
	public void createQueueHandler(String name, Integer threadCount) {
		DVCaller.call(262, name, threadCount);
	}

	@Override
	public void editQueueHandler(String name, Integer threadCount) {
		DVCaller.call(263, name, threadCount);
	}

	@Override
	public void dropQueueHandler(String name) {
		DVCaller.call(264, name);
	}

	@Override
	public void updateWorkContextMetadata() {
		DVCaller.call(265);
	}

	@Override
	public void refreshModel(String vdbName, int vdbVersion, String name) {
		VDBMetaData vdb = getVdbRepository().getLiveVDB(vdbName, vdbVersion);
		if (vdb == null) {
			return;
		}
		SessionMetadata sessionMetadata = DQPWorkContext.getWorkContext().getSession();
		sessionMetadata.setVDBName(vdb.getName());
		sessionMetadata.setVDBVersion(vdb.getVersion());
		sessionMetadata.setVdb(vdb);
		DVCaller.call(267, name);
	}

	@Override
	public void setDefaultOptionValue(String option, String initialValue, String encryptedValue) {
		DVCaller.call(268, option, initialValue, encryptedValue);
	}

	@Override
	public void deleteSchedulerJob(Integer jobId, String jobUuid) {
		DVCaller.call(269, jobId, jobUuid);
	}

	@Override
	public void cancelQuery(String sessionId, Long reqIndex) {
		DVCaller.call(270, sessionId, reqIndex);
	}

	@Override
	public void callOnCompleteJobRun(Integer scheduleId, Integer id, String status, String failureReason) {
		DVCaller.call(271, scheduleId, id, status, failureReason);
	}

    @Override
    public void installLicense(String base64LicenseFile) {
        DVCaller.call(273, base64LicenseFile);
    }
}
