package eu.dnetlib.data.collective.manager.ui;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.annotation.Resource;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import edu.emory.mathcs.backport.java.util.Collections;
import eu.dnetlib.common.interfaces.nh.IBlackboardMessage.Action;
import eu.dnetlib.data.collective.manager.AbstractInstanceManager;
import eu.dnetlib.data.collective.manager.IInstanceService;
import eu.dnetlib.data.collective.manager.IWorkerServiceActivityConsumer;
import eu.dnetlib.data.collective.manager.log.GenericInfoService;
import eu.dnetlib.data.collective.manager.log.LogInfoService;
import eu.dnetlib.data.collective.manager.nh.ServiceActivity;
import eu.dnetlib.data.collective.manager.profile.AbstractInstance;
import eu.dnetlib.data.information.DataSourceResolverDispatcher;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.resultset.rmi.ResultSetService;
import eu.dnetlib.enabling.tools.ServiceLocator;
import eu.dnetlib.enabling.tools.ServiceResolver;

/**
 * The AbstractInstanceService is the connector between the ui controller and the application logic
 * Implementations have to link to a concrete instance, instance manager and ui controller
 * @author jochen
 *
 * @param <E> - the instance type
 * @param <T> - the instance manager type
 * @param <C> - the ui controller type
 */
public abstract class AbstractInstanceService<E  extends AbstractInstance, T extends AbstractInstanceManager<E>, C extends AbstractUiController<?, ?>> implements IInstanceService{

	private static final Log log = LogFactory.getLog(AbstractInstanceService.class);
	
	@SuppressWarnings("unchecked")
	protected Map<String, Set<C>> upDateClients = Collections.synchronizedMap(new HashMap<String, Set<C>>());
	
	protected List<IUiController> upDateSchedulingClients = new LinkedList<IUiController>();

	@Resource(name="instanceManager")
	protected T instanceManager;
	
   	@Resource(name="uiContent")
	protected UIContentImpl uiContent;
   	
   	protected LogInfoService logInfoService;
   	
   	protected GenericInfoService genericInfoService;
   	
   	
	/**
	 * @return the genericInfoService
	 */
	public GenericInfoService getGenericInfoService() {
		return genericInfoService;
	}


	/**
	 * @param genericInfoService the genericInfoService to set
	 */
	public void setGenericInfoService(GenericInfoService genericInfoService) {
		this.genericInfoService = genericInfoService;
	}


	protected DataSourceResolverDispatcher dataSourceResolver;
	@Resource(name="serviceResolver")
	protected ServiceResolver serviceResolver;
	@Resource(name = "lookupLocator")
	protected ServiceLocator<ISLookUpService> lookupLocator;
	
	@Resource(name = "serviceActivityRegistry")
	protected IWorkerServiceActivityConsumer serviceActivityConsumer;
	
	private ExecutorService updateService = Executors.newSingleThreadExecutor();

   	public void init(){
   		
   		
   	}
   	
   	
	public void addController(C client){
		synchronized (upDateClients) {
			if (!this.upDateClients.containsKey(client.getId())){
				Set<C> clientSet = new HashSet<C>();			
				this.upDateClients.put(client.getId(), clientSet);
			}
			this.upDateClients.get(client.getId()).add(client);			
		}
	}
	
	
	private void removeController(C client){
		synchronized (upDateClients) {
			if (this.upDateClients.containsKey(client.getId())){
				this.upDateClients.get(client.getId()).remove(client);
			}			
		}
	}

	/**
	 * trigger job specified with the action argument
	 * @see eu.dnetlib.data.collective.manager.IInstanceService#triggerJob(java.lang.String, eu.dnetlib.common.interfaces.nh.IBlackboardMessage.Action)
	 */
	public final boolean triggerJob(String aInstanceId, Action aAction) {
		switch (aAction){
		case CANCEL:
			return this.instanceManager.cancelJobOnce(aInstanceId, aAction);
		case EMPTY:
			return this.instanceManager.startJobOnce(aInstanceId, aAction);
		default:
			return this.instanceManager.startJobOnce(aInstanceId, aAction);
		}
	}
	
	/**
	 * get a clone of an instance identified by the given argument
	 * @param id - the resource identifier of the instance
	 * @return an instance
	 * @throws InstanceNotFoundException
	 */
	public abstract E getCloneInstance(String id) throws InstanceNotFoundException;
	
	public AbstractInstance getOriginalInstance(String id) throws InstanceNotFoundException{
		return this.instanceManager.getInstanceRegistry().getInstance(id);
	}	
	
	public List<String> getRecords(String dataSinkSourceDescriptor, String descriptorId, int quantity) throws Exception{
		W3CEndpointReference dsEpr = this.dataSourceResolver.resolve(dataSinkSourceDescriptor).retrieve();
		ResultSetService rsService = serviceResolver.getService(ResultSetService.class, dsEpr);
		String dataSourceNumberOfRecordsQuery = "collection('/db/DRIVER')//RESOURCE_PROFILE[.//RESOURCE_PROFILE/HEADER" +
		"/RESOURCE_IDENTIFIER/@value='" + descriptorId + "']//NUMBER_OF_RECORDS/string()";
		String totalNumber = this.lookupLocator.getService().quickSearchProfile(dataSourceNumberOfRecordsQuery).get(0);
		if (!NumberUtils.isNumber(totalNumber)){
			return new LinkedList<String>();
		}
		Random random = new Random();
		int from = random.nextInt(Integer.parseInt(totalNumber)) + 1;
		return rsService.getResult(serviceResolver.getResourceIdentifier(dsEpr), from, from + quantity - 1, null);
	}
		
	public void enableGlobalScheduling(boolean aIsEnabled){
		this.instanceManager.enableScheduling(aIsEnabled);
	}
	
	public boolean isGlobalSchedulingEnabled(){
		return this.instanceManager.isSchedulingEnabled();
	}
	

	public void setInstanceManager(T instanceManager) {
		this.instanceManager = instanceManager;
	}

	public T getInstanceManager() {
		return instanceManager;
	}
	
	/**
	 * @return the logInfoService
	 */
	public LogInfoService getLogInfoService() {
		return logInfoService;
	}

	/**
	 * @param logInfoService the logInfoService to set
	 */
	public void setLogInfoService(LogInfoService logInfoService) {
		this.logInfoService = logInfoService;
	}

	/**
	 * updates the original instance with the copy given as argument
	 * @param aInstance
	 */
	public abstract void update(E aInstance);
	
	/**
	 * updates ui list models with information from the instance given as argument
	 * @param aInstance
	 */
	public abstract void updateModels(E aInstance);
	
	/**
	 * updates all desktops referencing aInstanceId with information contained in aServiceActivity
	 * @param aServiceActivity
	 */
	public void update(ServiceActivity aServiceActivity){
		updateService.execute(new UpdateClients(aServiceActivity));
	}
	
	/**
	 * updates ui clients with information given in the service activity argument
	 * @param aServiceActivity - the service activity
	 */
	public abstract void updateClients(ServiceActivity aServiceActivity);
	
	/**
	 * get the UiContent helper class
	 * @return the UiContent
	 */
	public UIContentImpl getUiContent() {
		return uiContent;
	}

	/**
	 * set the UiContent helper class
	 * @param uiContent
	 */
	public void setUiContent(UIContentImpl uiContent) {
		this.uiContent = uiContent;
	}

	/**
	 * @return the dataSourceResolver
	 */
	public DataSourceResolverDispatcher getDataSourceResolver() {
		return dataSourceResolver;
	}

	/**
	 * @param dataSourceResolver the dataSourceResolver to set
	 */
	public void setDataSourceResolver(
			DataSourceResolverDispatcher dataSourceResolver) {
		this.dataSourceResolver = dataSourceResolver;
	}

	/**
	 * @return the serviceResolver
	 */
	public ServiceResolver getServiceResolver() {
		return serviceResolver;
	}

	/**
	 * @param serviceResolver the serviceResolver to set
	 */
	public void setServiceResolver(ServiceResolver serviceResolver) {
		this.serviceResolver = serviceResolver;
	}
	/**
	 * @return the lookupLocator
	 */
	public ServiceLocator<ISLookUpService> getLookupLocator() {
		return lookupLocator;
	}

	/**
	 * @param lookupLocator the lookupLocator to set
	 */
	public void setLookupLocator(ServiceLocator<ISLookUpService> lookupLocator) {
		this.lookupLocator = lookupLocator;
	}
	
	@Override
	public List<?> getAllInstances() {
		// TODO Auto-generated method stub
		return this.instanceManager.getInstanceRegistry().getInstanceList();
	}
	
	public void addUiController(IUiController aClient){
		this.upDateSchedulingClients.add(aClient);
	}

	class UpdateClients implements Runnable{

		ServiceActivity serviceActivity;
		
		public UpdateClients(ServiceActivity aServiceActivity) {
			this.serviceActivity = aServiceActivity;
		}
		
		@Override
		public void run() {
			List<C> deadClientList = new LinkedList<C>();
			synchronized (upDateClients) {
				if (upDateClients.containsKey(serviceActivity.getInstanceResourceId())){
					Set<C> clientSet = upDateClients.get(serviceActivity.getInstanceResourceId()); 
					log.debug("number of current ui-clients: " + clientSet.size());
					for (C client: clientSet){
						log.debug("update of ui-client");
						if (!client.updateUI(serviceActivity)){
							deadClientList.add(client);
						}
					}
				}				
			}
			for (C client: deadClientList){
				removeController(client);
			}			
		}
		
	}

	/**
	 * @return the serviceActivityConsumer
	 */
	public IWorkerServiceActivityConsumer getServiceActivityConsumer() {
		return serviceActivityConsumer;
	}


	/**
	 * @param serviceActivityConsumer the serviceActivityConsumer to set
	 */
	public void setServiceActivityConsumer(
			IWorkerServiceActivityConsumer serviceActivityConsumer) {
		this.serviceActivityConsumer = serviceActivityConsumer;
	}
}