package eu.dnetlib.enabling.aas.ismock;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.log4j.Logger;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.Unmarshaller;
import org.exolab.castor.xml.ValidationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import eu.dnetlib.common.rmi.UnimplementedException;
import eu.dnetlib.enabling.aas.service.A2Constants;
import eu.dnetlib.enabling.aas.utils.ResourceUtils;
import eu.dnetlib.enabling.aas.wrappers.CommonProfileWrapper;
import eu.dnetlib.enabling.aas.wrappers.SecurityContextWrapper;
import eu.dnetlib.enabling.aas.wrappers.SecurityPolicyWrapper;
import eu.dnetlib.enabling.aas.wrappers.SecurityProfileWrapper;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryException;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryService;
import eu.dnetlib.enabling.is.store.rmi.ISStoreException;
import eu.dnetlib.enabling.is.store.rmi.ISStoreService;


/**
 * Mock class implementing ISRegistry service functionalities.
 * <p>
 * Notification mechanizm is not implemented.
 * @author mhorst
 *
 */
public class MockISRegistry implements ISRegistryService {
	
	public static String TAG_NAME_RESOURCE_IDENTIFIER = "RESOURCE_IDENTIFIER";
	public static String TAG_NAME_HEADER = "HEADER";
	
	protected static final Logger log = Logger.getLogger(MockISRegistry.class);
	
	private ISStoreService storeService;
	
	/**
	 * Bindings used for mapping xmls to objects. 
	 */
	private List<String> bindings = new ArrayList<String>();

	/**
	 * Mapping xmls to SecurityContext objects.
	 */
	private Mapping mapping = new Mapping();
	
	
	/**
	 * Init method to setup and initialize MockISRegistry container. 
	 */
	public void init() {
//		Load mapping files
        if (bindings.size()>0) {
        	for (Iterator<String> iter = bindings.iterator(); iter.hasNext();) {
        		String currentBinding = null;
        		try {
					currentBinding = iter.next();
					mapping.loadMapping(currentBinding);
				} catch (IOException e) {
					log.error("Exception occured while reading binding definition for '"
							+currentBinding+"' file!",e);
				} catch (MappingException e) {
					log.error("Exception occured while reading binding definition for '"
							+currentBinding+"' file!",e);
				}
			}
        } else
        	log.error("No binding definitions found for xml mappings!");
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#addResourceType(java.lang.String, java.lang.String)
	 */
	public boolean addResourceType(String resourceType, String resourceSchema) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#deleteProfile(java.lang.String)
	 */
	public boolean deleteProfile(String profId) throws ISRegistryException {
		String[] decodedId = ISMockUtils.decodeProfileId(profId);
		if (decodedId==null)
			return false;
		String fileColl = decodedId[0];
		String fileName = decodedId[1];
		try {
			return storeService.deleteXML(fileName, fileColl);
		} catch (ISStoreException e) {
			throw new ISRegistryException(e);
		}
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#deleteProfiles(java.util.List)
	 */
	public boolean deleteProfiles(List<String> arrayprofId) {
		if(arrayprofId==null)
			return false;
		boolean check = true;
		Iterator<String> it = arrayprofId.iterator();
		while (it.hasNext()) {
			String currentId = it.next(); 
			try {
				if (!deleteProfile(currentId)) {
					check = false;
				}
			} catch (ISRegistryException e) {
				log.error("exception occured when deleting single profile: " + 
						currentId, e);
				check = false;
			}
		}
		return check;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#deleteResourceType(java.lang.String, java.lang.Boolean)
	 */
	public boolean deleteResourceType(String resourceType, Boolean hierarchical) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.common.rmi.BaseService#notify(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
	 */
	public void notify(String subscrId, String topic, String isId,
			String message) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#registerProfile(java.lang.String)
	 */
	public String registerProfile(String resourceProfile) throws ISRegistryException {
		if (resourceProfile==null || resourceProfile.length()==0)
			return null;
		
		try {
			Unmarshaller unmarshaller = new Unmarshaller(CommonProfileWrapper.class);
            unmarshaller.setIgnoreExtraElements(true);
			unmarshaller.setMapping(mapping);
			CommonProfileWrapper retrievedSecCtxWrapper = (CommonProfileWrapper)unmarshaller.unmarshal(new StringReader(resourceProfile));
			String resourceType = retrievedSecCtxWrapper.getHeader().getResourceType();
			if (resourceType==null)
				return null;
			String fileColl = (String) ISMockUtils.colTypeToColName.get(resourceType);
			if (fileColl==null)
				return null;
			String fileName = getNewFileName(fileColl);
			String profId = ISMockUtils.createProfileId(fileColl, fileName);
//			insert profId before inserting resourceProfile
			resourceProfile = insertProfId(profId,resourceType,resourceProfile);
			if (resourceProfile==null) {
				log.error("Null resourceProfile after inserting profId and marshalling! Cannot insert into ISStore!");
				return null;
			}
			try {
				boolean inserted = storeService.insertXML(fileName, fileColl, resourceProfile);
				if (inserted)
					return profId;
				else
					return null;
			} catch (ISStoreException e) {
				throw new ISRegistryException(e);
			}
			
		} catch (MappingException e) {
			log.error("Exception occured when setting mappings for marshaller!",e);
			return null;
		} catch (MarshalException e) {
			log.error("Exception occured when (un)marshalling profile!",e);
			return null;
		} catch (ValidationException e) {
			log.error("Exception occured when (un)marshalling profile!",e);
			return null;
		} 
		
	}
	

	/**
	 * Returns new file name.
	 * @param fileColl
	 * @return new file name
	 */
	public String getNewFileName(String fileColl) {
		if (fileColl==null)
			return null;
		StringBuffer strBuff = new StringBuffer();
		strBuff.append(fileColl.substring("db/DRIVER/".length()).replace('/', '-'));
		strBuff.append('-');
		strBuff.append(System.currentTimeMillis());
		return strBuff.toString();
	}
	
	private String insertProfId(String profId, String resourceType, String resourceProfile) {
		if (profId==null || resourceType==null ||resourceProfile==null || resourceProfile.length()==0)
			return null;
		
		try {
			if (A2Constants.RESOURCE_TYPE_SECURITY_CONTEXT.equals(resourceType)) {
				Unmarshaller unmarshaller = new Unmarshaller(SecurityContextWrapper.class);
				unmarshaller.setIgnoreExtraElements(true);
				unmarshaller.setMapping(mapping);
				SecurityContextWrapper retrievedSecCtxWrapper = (SecurityContextWrapper)unmarshaller.unmarshal(new StringReader(resourceProfile));
				retrievedSecCtxWrapper.getHeader().setProfId(profId);
				StringWriter strWriter = new StringWriter();
				Marshaller marshaller = new Marshaller(strWriter);
				marshaller.setMapping(mapping);
				marshaller.marshal(retrievedSecCtxWrapper);
				return strWriter.toString();
			} else if (A2Constants.RESOURCE_TYPE_SECURITY_PROFILE.equals(resourceType)) {
				Unmarshaller unmarshaller = new Unmarshaller(SecurityProfileWrapper.class);
				unmarshaller.setIgnoreExtraElements(true);
				unmarshaller.setMapping(mapping);
				SecurityProfileWrapper retrievedSecProfWrapper = (SecurityProfileWrapper)unmarshaller.unmarshal(new StringReader(resourceProfile));
				retrievedSecProfWrapper.getHeader().setProfId(profId);
				StringWriter strWriter = new StringWriter();
				Marshaller marshaller = new Marshaller(strWriter);
				marshaller.setMapping(mapping);
				marshaller.marshal(retrievedSecProfWrapper);
				return strWriter.toString();
			} else if (A2Constants.RESOURCE_TYPE_SECURITY_POLICY.equals(resourceType)) {
				Unmarshaller unmarshaller = new Unmarshaller(SecurityPolicyWrapper.class);
				unmarshaller.setIgnoreExtraElements(true);
				unmarshaller.setMapping(mapping);
				SecurityPolicyWrapper retrievedSecPolicyWrapper = (SecurityPolicyWrapper)unmarshaller.unmarshal(new StringReader(resourceProfile));
				retrievedSecPolicyWrapper.getHeader().setProfId(profId);
				StringWriter strWriter = new StringWriter();
				Marshaller marshaller = new Marshaller(strWriter);
				marshaller.setMapping(mapping);
				marshaller.marshal(retrievedSecPolicyWrapper);
				return strWriter.toString();
			} else {
				log.error("Unsupported resourceType! Trying to add header...");
				return addHeaderToUnrecoginzedProfile(profId, resourceProfile);
			}
            
		} catch (MappingException e) {
			log.error("Exception occured when setting mappings for marshaller!",e);
			return null;
		} catch (MarshalException e) {
			log.error("Exception occured when (un)marshalling profile "+profId+"!",e);
			return null;
		} catch (ValidationException e) {
			log.error("Exception occured when (un)marshalling profile "+profId+"!",e);
			return null;
		} catch (IOException e) {
			log.error("Exception occured when (un)marshalling profile "+profId+"!",e);
			return null;
		}
	}

	/**
	 * Tries to add profId header to a profile.
	 * @param profId
	 * @param resourceProfile
	 * @return modified resource profile or null if couldn't add header
	 */
	String addHeaderToUnrecoginzedProfile(String profId, String resourceProfile) {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	        factory.setIgnoringComments(true);
	        DocumentBuilder db = null;
	        factory.setNamespaceAware(true);
	        factory.setValidating(false);
	    	db = factory.newDocumentBuilder();
	    	Document doc = db.parse(new InputSource(new StringReader(resourceProfile)));
	    	NodeList nodeList = doc.getElementsByTagName(TAG_NAME_HEADER);
	    	if (nodeList.getLength()==1) {
	    		Node node = nodeList.item(0);
	    		if (node instanceof Element) {
	    			Element headerElement = (Element) node;
	    			NodeList headeNodes = headerElement.getElementsByTagName(TAG_NAME_RESOURCE_IDENTIFIER);
	    			if (headeNodes.getLength()>0) {
	    				for(int i=0; i<headeNodes.getLength(); i++) {
	    					headerElement.removeChild(headeNodes.item(i));
	    				}
	    			}
	    			Element profIdEl = doc.createElement(TAG_NAME_RESOURCE_IDENTIFIER);
	    			profIdEl.setAttribute("value", profId);
	    			headerElement.appendChild(profIdEl);
	    			return ResourceUtils.convertDocumentToString(doc);
	    		} else
	    			return null;
	    		
	    	} else {
	    		return null;
	    	}
		} catch (Exception e) {
			log.error("Excpetion occured when adding profId header to DOM representation of profile: "+resourceProfile,e);
			return null;
		}
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#updateProfile(java.lang.String, java.lang.String, java.lang.String)
	 */
	public boolean updateProfile(String profId, String resourceProfile,
			String resourceType) throws ISRegistryException {
		if (profId==null || resourceProfile==null || resourceProfile.length()==0)
			return false;
		String[] decodedId = ISMockUtils.decodeProfileId(profId);
		String fileColl = decodedId[0];
		String fileName = decodedId[1];
		if (A2Constants.RESOURCE_TYPE_SECURITY_CONTEXT.equals(resourceType)) {
//			setting profId to the new profile
			try {
				Unmarshaller unmarshaller = new Unmarshaller(SecurityContextWrapper.class);
	            unmarshaller.setIgnoreExtraElements(true);
				unmarshaller.setMapping(mapping);

				SecurityContextWrapper retrievedSecCtxWrapper = (SecurityContextWrapper)unmarshaller.unmarshal(new StringReader(resourceProfile));
				retrievedSecCtxWrapper.getHeader().setProfId(profId);
				StringWriter strWriter = new StringWriter();
				Marshaller marshaller = new Marshaller(strWriter);
				marshaller.setMapping(mapping);
				marshaller.marshal(retrievedSecCtxWrapper);
				resourceProfile = strWriter.toString();
				
			} catch (MappingException e) {
				log.error("Exception occured when setting mappings for marshaller!",e);
				return false;
			} catch (MarshalException e) {
				log.error("Exception occured when (un)marshalling profile "+profId+"!",e);
				return false;
			} catch (ValidationException e) {
				log.error("Exception occured when (un)marshalling profile "+profId+"!",e);
				return false;
			} catch (IOException e) {
				log.error("Exception occured when (un)marshalling profile "+profId+"!",e);
				return false;
			}
			
			
		} else if (A2Constants.RESOURCE_TYPE_SECURITY_PROFILE.equals(resourceType)) {
//			do nothing
		} else if (A2Constants.RESOURCE_TYPE_SECURITY_POLICY.equals(resourceType)) {
//			do nothing
		} else throw new RuntimeException("Unsupported resourceType!");
		
		try {
			return storeService.updateXML(fileName, fileColl, resourceProfile);
		} catch (ISStoreException e) {
			throw new ISRegistryException(e);
		}
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#updateProfileDHN(java.lang.String)
	 */
	public String updateProfileDHN(String resourceProfile) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#updateRegionDescription(java.lang.String, java.lang.String)
	 */
	public boolean updateRegionDescription(String profId, String resourceProfile) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#validateProfile(java.lang.String)
	 */
	public String validateProfile(String profId) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#insertProfileForValidation(java.lang.String, java.lang.String)
	 */
	public String insertProfileForValidation(String resourceType, String resourceProfile) {
		throw new UnimplementedException();
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#executeXUpdate(java.lang.String)
	 */
	public boolean executeXUpdate(String XQuery) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#registerSecureProfile(java.lang.String, java.lang.String)
	 */
	public String registerSecureProfile(String resourceProfile, String secureProfile) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#validateProfiles(java.util.List)
	 */
	public List<String> validateProfiles(List<String> profIds) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#addOrUpdateResourceType(java.lang.String, java.lang.String)
	 */
	public boolean addOrUpdateResourceType(String resourceType, String resourceSchema) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#addProfileNode(java.lang.String, java.lang.String, java.lang.String)
	 */
	public boolean addProfileNode(String profId, String xpath, String node) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#deleteBlackBoardMessage(java.lang.String, java.lang.String)
	 */
	public void deleteBlackBoardMessage(String profId, String messageId) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.common.rmi.BaseService#identify()
	 */
	public String identify() {
		return "IS_Registry-mock-0.9.0";
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#invalidateProfile(java.lang.String)
	 */
	public String invalidateProfile(String profId) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#refreshProfile(java.lang.String, java.lang.String)
	 */
	public boolean refreshProfile(String profId, String resourceType) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#removeProfileNode(java.lang.String, java.lang.String)
	 */
	public boolean removeProfileNode(String profId, String nodeId) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#replyBlackBoardMessage(java.lang.String, java.lang.String)
	 */
	public void replyBlackBoardMessage(String profId, String message) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#addBlackBoardMessage(java.lang.String, java.lang.String, java.lang.String)
	 */
	public void addBlackBoardMessage(String profId, String messageId, String message) {
		throw new UnimplementedException();
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.enabling.is.registry.rmi.ISRegistryService#updateProfileNode(java.lang.String, java.lang.String, java.lang.String)
	 */
	public boolean updateProfileNode(String profId, String xpath, String node) {
		throw new UnimplementedException();
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.common.rmi.BaseService#start()
	 */
	public void start() {
	}
	
	/**
	 * Gets ISStore service.
	 * @return
	 */
	public ISStoreService getStoreService() {
		return storeService;
	}

	/**
	 * Sets up ISStore service.
	 * @param storeService
	 */
	public void setStoreService(ISStoreService storeService) {
		this.storeService = storeService;
	}

	public List<String> getBindings() {
		return bindings;
	}

	public void setBindings(List<String> bindings) {
		this.bindings = bindings;
	}
	
}