/*******************************************************************************
 * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Exadel, Inc. and Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.common.model.ui.resources;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.xml.serialize.LineSeparator;
import org.apache.xml.serialize.Method;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.osgi.util.NLS;
import org.jboss.tools.common.model.ui.ModelUIMessages;
import org.jboss.tools.common.model.ui.ModelUIPlugin;
import org.jboss.tools.common.xml.XMLUtilities;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * @author AU
 */
public class ResourceLayoutManager {
	
	// public	
	public static final String ROOT_TAG = "Layout"; //$NON-NLS-1$
	public static final String RESOURCE_ATTR = "resource"; //$NON-NLS-1$
	public static final String LAYOUT_EXT = "l4t"; //$NON-NLS-1$
	public static final String LAYOUT_PROPERTY = "layout"; //$NON-NLS-1$

	// static
	private static ResourceLayoutManager instance;
	// documents hash, key == fullPathToResource, value == document
	private HashMap<String,Document> hashMap = new HashMap<String,Document>(); 

	// AnotherResourceException class
	class AnotherResourceException extends Exception {
		private static final long serialVersionUID = 1L;
		
	}

	private ResourceLayoutManager() {}
	
	public static ResourceLayoutManager getDefault() {
		if (instance==null) instance = new ResourceLayoutManager();
		return instance;
	}
	
	// return first element with specify elementTag or create new
	public Element getLayoutElement(IResource resource, String elementTag) {
		Element element = null;
		Document document = getLayoutDocument(resource);
		if(document == null) return null;
		NodeList nodeList = document.getElementsByTagName(elementTag);
		if (nodeList==null || nodeList.getLength()==0 || nodeList.item(0)==null) {
			// create new element
			element = document.createElement(elementTag);
			Node root = document.getFirstChild();
			root.appendChild(element);
		} else {
			element = (Element)nodeList.item(0); 
		}
		return element;
	}
	
	private Document getLayoutDocument(IResource resource) {
		Document document = null;
		if (resource == null) {
			ModelUIPlugin.getPluginLog().logError(ModelUIMessages.ResourceLayoutManager_ERROR_RESOURCE_NULL);
			return null;
		}
		
		String fullResourceLocation = getFullLocation(resource).toString();
		String layoutLocation = null;
		try {
			if(resource.isAccessible()) {
				layoutLocation = resource.getPersistentProperty(new QualifiedName("",LAYOUT_PROPERTY)); //$NON-NLS-1$
			}
		} catch (CoreException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		}
		if (layoutLocation == null) layoutLocation = createNewLayoutLocation(resource);
		//String fullLayoutLocation = getFullLocation(resource).addFileExtension(LAYOUT_EXT).toString();
		
		// try get from hashMap
		document = (Document)hashMap.get(fullResourceLocation);
		if (document!=null) return document;

		// try load from file system
		try {
			document = loadDocument(resource);
		} catch (ParserConfigurationException e) {
			ModelUIPlugin.getPluginLog().logError(ModelUIMessages.ResourceLayoutManager_ERROR_UNKNOWN_EXCEPTION, e);
		} catch (SAXException e) {
			ModelUIPlugin.getPluginLog().logError(ModelUIMessages.ResourceLayoutManager_ERROR_UNKNOWN_EXCEPTION, e);
		} catch (IOException e) {
			ModelUIPlugin.getPluginLog().logError(ModelUIMessages.ResourceLayoutManager_ERROR_UNKNOWN_EXCEPTION, e);
		} catch (AnotherResourceException e) {
			ModelUIPlugin.getPluginLog().logError(ModelUIMessages.ResourceLayoutManager_ERROR_ANOTHER_HEAD, e);
		}
		if (document!=null){
			hashMap.put(fullResourceLocation, document);
			return document;
		}

		// try create new document
		try {
			document = createNewDocument(resource);
		} catch (ParserConfigurationException e) {
			//log(Status.ERROR, ModelUIMessages.getString(ERROR_CREATE_DOCUMENT, new String[] {fullLayoutLocation, fullResourceLocation}));
			ModelUIPlugin.getPluginLog().logError( NLS.bind(ModelUIMessages.ResourceLayoutManager_ERROR_CREATE_DOCUMENT, layoutLocation, fullResourceLocation));
		}
		if (document!=null){
			hashMap.put(fullResourceLocation, document);
			return document;
		}
		
		return document;
	}

	private Document createNewDocument(IResource resource) throws ParserConfigurationException {
		Document document = null;
		// try create DocumentBuilder
		DocumentBuilder builder = null;
		//DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		builder = DocumentBuilderHolder.getInstance();
		// create new document
		document = builder.newDocument();
		createRoot(resource, document);
		return document;
	}
	
	private Document loadDocument(IResource resource) throws AnotherResourceException, ParserConfigurationException, SAXException, IOException {
//		String fullResourceLocation = 
			getFullLocation(resource).toString();
		String layoutLocation = null;
		try {
			if(resource != null && resource.isAccessible()) {
				layoutLocation = resource.getPersistentProperty(new QualifiedName("",LAYOUT_PROPERTY)); //$NON-NLS-1$
			}
		} catch (CoreException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		}
		if (layoutLocation == null) layoutLocation = createNewLayoutLocation(resource);
		if(!new File(layoutLocation).isFile()) return null;
		//String fullLayoutLocation = getFullLocation(resource).addFileExtension(LAYOUT_EXT).toString();
		Document document = null;
		DocumentBuilder builder = null;
		// try create DocumentBuilder
		builder = DocumentBuilderHolder.getInstance();
		// try parse
		//document = builder.parse(fullLayoutLocation);
		document = builder.parse(layoutLocation);
		// verify head
		if (document != null) {
			NodeList nodeList = document.getElementsByTagName(ROOT_TAG);
			if (nodeList==null || nodeList.getLength()==0 || nodeList.item(0)==null) {
				// warning
				//log(ModelUIMessages.getString(WARNING_EMPTY_HEAD, new String[] {fullLayoutLocation}));
				ModelUIPlugin.getPluginLog().logInfo(NLS.bind(ModelUIMessages.ResourceLayoutManager_WARNING_HEAD_EMPTY, layoutLocation));
				// create new head
				createRoot(resource, document);
			} /*else {
				(Element)nodeList.item(0);
				String fromHead = head.getAttribute(RESOURCE_ATTR);
				String fromResource = resource.getFullPath().toString();
				if (!fromResource.equals(fromHead)) {
					throw new AnotherResourceException();
				}
			}
			*/
		}
		return document;
	}

	private IPath getFullLocation(IResource resource) {
		IProject project = resource.getProject();
		IPath layoutPath = resource.getProjectRelativePath();
		IFile layoutFile = project.getFile(layoutPath);
		return layoutFile.getLocation();
	}

	public void store(IResource resource) {
		String layoutLocation = null;
		try {
			if(resource != null && resource.isAccessible()) {
				layoutLocation = resource.getPersistentProperty(new QualifiedName("",LAYOUT_PROPERTY)); //$NON-NLS-1$
			}
		} catch (CoreException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		}
		if (layoutLocation==null) {
			layoutLocation = createNewLayoutLocation(resource);
		}
		if (layoutLocation==null) return;
		File file = new File(layoutLocation);
		if (!file.exists()) {
			layoutLocation = createNewLayoutLocation(resource);
		}
		String fullResourceLocation = getFullLocation(resource).toString();
		//String fullLayoutLocation = getFullLocation(resource).addFileExtension(LAYOUT_EXT).toString();
		Document document = (Document)hashMap.get(fullResourceLocation);
		if (document==null) return;
		try {
			//FileWriter writer = new FileWriter(fullLayoutLocation);
			FileWriter writer = new FileWriter(layoutLocation);
			XMLSerializer serial = new XMLSerializer(writer, createOutputFormat("UTF-8")); //$NON-NLS-1$
			serial.asDOMSerializer();
			serial.serialize(document);
			writer.close();
		} catch (IOException e) {
			//log(Status.ERROR, ModelUIMessages.getString(ERROR_CREATE_DOCUMENT, new String[] {fullLayoutLocation, fullResourceLocation}), e);
			ModelUIPlugin.getPluginLog().logError(NLS.bind(ModelUIMessages.ResourceLayoutManager_ERROR_CREATE_DOCUMENT, layoutLocation, fullResourceLocation), e);
		}
	}

	private OutputFormat createOutputFormat(String encoding) {
		if(encoding == null || encoding.length() == 0) encoding = "UTF-8"; //$NON-NLS-1$
		OutputFormat format = new OutputFormat(Method.XML, encoding, true);
		format.setLineSeparator(System.getProperty("line.separator", LineSeparator.Web)); //$NON-NLS-1$
		format.setIndent(1);
		return format;
	}
	
	private Element createRoot(IResource resource, Document document) {
		Element element = document.createElement(ROOT_TAG);
		Node oldRoot = document.getFirstChild();
		element.setAttribute(RESOURCE_ATTR, resource.getFullPath().toString());
		if (oldRoot!=null) {
			document.removeChild(oldRoot);
		}
		document.appendChild(element);
		return element;
	}
	
	
	private IPath getPluginMetadataPath() {
		return Platform.getStateLocation(Platform.getBundle(ModelUIPlugin.PLUGIN_ID));
	}
	
	private String createNewLayoutLocation(IResource resource) {
		String fileName = resource.getProject().getName()+"_"+resource.getName()+"_"+System.currentTimeMillis();  //$NON-NLS-1$//$NON-NLS-2$
		String layoutLocation = getPluginMetadataPath().toString();
		String result = layoutLocation+"/"+fileName+"."+LAYOUT_EXT;   //$NON-NLS-1$//$NON-NLS-2$
		try {
			if(resource != null && resource.isAccessible()) {
				resource.setPersistentProperty(new QualifiedName("", LAYOUT_PROPERTY), result); //$NON-NLS-1$
			}
		} catch (CoreException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		}
		return result; 
	}
	
	static class DocumentBuilderHolder {
		static private DocumentBuilder INSTANCE = XMLUtilities.createDocumentBuilder();
		
		public static DocumentBuilder getInstance() {
			return INSTANCE;
		}
	}

}
