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

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Node;

import eu.dnetlib.common.interfaces.nh.IBlackboardMessage.Action;
import eu.dnetlib.common.profile.Resource;
import eu.dnetlib.common.utils.XMLUtils;
import eu.dnetlib.data.collective.manager.scheduling.profile.SchedulingConfiguration;
import eu.dnetlib.data.information.MDStoreDataSinkSourceDescriptorGenerator;

/**
 * AbstractInstance is an abstract class that holds profile and other information of an instance
 * @author jochen
 *
 */
public abstract class AbstractInstance implements IInstance{
	
	private static final Log log = LogFactory.getLog(AbstractInstance.class);

	protected Resource resource;
	private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
	private boolean ongoing;
	private boolean isStoring = false;
	private String repositoryName;
	private String datasourceNsPrefix;
	protected String storingType;
	protected MDStoreDataSinkSourceDescriptorGenerator descriptorGenerator = new MDStoreDataSinkSourceDescriptorGenerator();
	
	
	@Override
	public String getResourceId(){
		return this.resource.getValue("//RESOURCE_IDENTIFIER/@value");		
	}
	
	/**
	 * get the resource type
	 * @return the resourceType
	 */
	public String getResourceType() {
		return this.resource.getValue("//RESOURCE_TYPE/@value");
	}

	/**
	 * get the resource kind
	 * @return the resourceKind
	 */
	public String getResourceKind() {
		return this.resource.getValue("//RESOURCE_KIND/@value");
	}

	/**
	 * get the resource uri
	 * @return the resourceUri
	 */
	public String getResourceUri() {
		return this.resource.getValue("//RESOURCE_URI/@value");
	}
	
	/**
	 * get the date when the profile was created or updated
	 * @return the date of creation
	 */
	public String getDateOfCreation(){
		return this.resource.getValue("//DATE_OF_CREATION/@value");
	}
	
	@Override
	public SchedulingConfiguration getScheduling(){
		SchedulingConfiguration sc = new SchedulingConfiguration(this.resource);
		return sc;
	}
	
	/**
	 * gets the data sink descriptor
	 * @return the data sink descriptor
	 */
	public String getDataSink(){
		return this.resource.getValue("//CONFIGURATION/DATA_SINK");
	}
	
	/**
	 * gets the data sink descriptor configured for a specific storageType
	 * By calling this method the data sink will not persist in the profile.
	 * @return the data sink descriptor
	 */
	public String getDataSink(String aStorageType){
		Properties params = new Properties();
		params.setProperty("type", aStorageType);
		return descriptorGenerator.regenerateDataSinkDescriptor(this.getDataSink(), params);		
	}
	
	/**
	 * gets the identifier extracted from the data sink descriptor
	 * @return the data sink id
	 */
	public String getDataSinkId(){
		return descriptorGenerator.getParameters(getDataSink()).getId();
	}
	

	/**
	 * get the last update date that indicates the date the instance will start to operate next time, e.g. incremental operations
	 * @return the last update date
	 */
	public String getLastUpdateDate(){
		return this.resource.getValue("//STATUS/LAST_UPDATE_DATE");
	}
	
	/**
	 * get the last update status
	 * @return the last update status
	 */
	public String getLastUpdateStatus(){
		return this.resource.getValue("//STATUS/LAST_UPDATE_STATUS");
	}
	
	/**
	 * get the last update error message
	 * @return the last update error message
	 */
	public String getLastUpdateErrorMessage(){
		return this.resource.getValue("//STATUS/LAST_UPDATE_ERROR_MESSAGE");
	}

	/**
	 * get the last update as date object
	 * @return the last update as Date type
	 */
	public Date getLastUpdateAsDate() {
		
		Date date = new Date(0);
		String lastUpdate = getLastUpdateDate(); 
		if (lastUpdate.length() == 0){
			return date;
		}else{
			try {
				date = sdf.parse(lastUpdate);
			} catch (ParseException e) {
				log.error(e);
			}
		}		
		return date;
	}

	/**
	 * get the repository name
	 * @return the repository name
	 */
	public String getRepositoryName() {
		return repositoryName;
	}

	/**
	 * set the last update as date
	 * @param lastUpdate
	 */
	public void setLastUpdateAsDate(Date lastUpdate){
		String lastUpdateText = sdf.format(lastUpdate);
		XMLUtils.getNode(this.resource.getResource(), "//STATUS/LAST_UPDATE_DATE").setText(lastUpdateText);
	}
	
	/**
	 * set the last update status
	 * @param status
	 */
	public void setLastUpdateStatus(String status){
		XMLUtils.getNode(this.resource.getResource(), "//STATUS/LAST_UPDATE_STATUS").setText(status);
	}
	
	/**
	 * set the last update error message
	 * @param errMsg
	 */
	public void setLastUpdateErrorMessage(String errMsg){
		XMLUtils.getNode(this.resource.getResource(), "//STATUS/LAST_UPDATE_ERROR_MESSAGE").setText(errMsg);
	}

	/**
	 * set the 'ongoing' action status
	 * @param ongoing the ongoing to set
	 */
	public void setOngoing(boolean ongoing) {
		this.ongoing = ongoing;
		if (!isOngoing()){
			this.isStoring = false;
		}
	}
	
	/**
	 * set the repository name
	 * @param repositoryName
	 */
	public void setRepositoryName(String repositoryName) {
		this.repositoryName = repositoryName;
	}

	
	/**
	 * get the 'ongoing' status
	 * @return the ongoing
	 */
	public boolean isOngoing() {
		return ongoing;
	}

	/**
	 * return a map of this instance specific parameters, usually used for executing jobs using these parameters
	 * @param aActionId actionId used as an additional parameter
	 * @return a map
	 */
	public abstract Map<String, String> getInstanceParameters(String aActionId);

	/**
	 * @param resource the datastructure-instance resource to set
	 * @throws XMLException 
	 */
	public void setResource(Resource resource){
		this.resource = resource;
		this.storingType = descriptorGenerator.getParameters(getDataSink()).getParameters().getProperty("type");
	}
	
	/**
	 * get the profile as a resource object
	 * @return the resource
	 */
	public Resource getResource(){
		return this.resource;
	}
	
	/**
	 * @return the default blackboard message action to be performed with this instance
	 */
	public abstract Action getDefaultBlackboardMessageAction();

	/**
	 * get a list of data source descriptors
	 * @return list of data source descriptors
	 */
	public List<String> getDataSources() {
		List<String> list = new LinkedList<String>();
		List<Node> nodes = this.resource.getNodeList("//CONFIGURATION/DATA_SOURCES/*");
		for (int i = 0; i < nodes.size(); i++){
			String descriptor = this.resource.getValue("//CONFIGURATION//DATA_SOURCE[" + (i+1) + "]");
			list.add(descriptor);			
		}
		return list;
	}
	
	/**
	 * get data source ids as a list
	 * @return list of data source ids
	 */
	public List<String> getDataSourceIds(){
		List<String> list = new LinkedList<String>();
		List<Node> nodes = this.resource.getNodeList("//CONFIGURATION/DATA_SOURCES/*");
		for (int i = 0; i < nodes.size(); i++){
			String descriptor = this.resource.getValue("//CONFIGURATION//DATA_SOURCE[" + (i+1) + "]");
			list.add(descriptorGenerator.getParameters(descriptor).getId());			
		}
		return list;		
	}
	
	/**
	 * sets the storing type and - implicitly - updates the data-sink-descriptor and - if any - data-source descriptors
	 * setting the data-sink-descriptor is limited for MDStore descriptors
	 * @param storingType
	 */
	public final void setStoringType(String storingType) {
		this.storingType = storingType;
		Properties params = new Properties();
		params.setProperty("type", this.storingType);
		String descriptor = descriptorGenerator.regenerateDataSinkDescriptor(this.getDataSink(), params);
		XMLUtils.getNode(this.resource.getResource(), "//CONFIGURATION/DATA_SINK").setText(descriptor);		
	}

	public String getStoringType() {
		return storingType;
	}

	/**
	 * @param isStoring the isStoring to set
	 */
	public void setStoring(boolean isStoring) {
		this.isStoring = isStoring;
	}

	/**
	 * @return the isStoring
	 */
	public boolean isStoring() {
		return isStoring;
	}

	/**
	 * @return the datasourceNsPrefix
	 */
	public String getDatasourceNsPrefix() {
		return datasourceNsPrefix;
	}

	/**
	 * @param datasourceNsPrefix the datasourceNsPrefix to set
	 */
	public void setDatasourceNsPrefix(String datasourceNsPrefix) {
		this.datasourceNsPrefix = datasourceNsPrefix;
	}

}
