/*******************************************************************************
 * 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.templates.configuration;

import java.io.*;
import java.net.URL;
import java.util.*;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.*;
import org.osgi.framework.Bundle;
import org.w3c.dom.*;
import org.jboss.tools.common.xml.*;
import org.jboss.tools.common.model.ui.ModelUIPlugin;
import org.jboss.tools.common.model.ui.templates.model.*;

public class MetaConfigurationLoader implements MetaTemplateConstants {
	static MetaConfigurationLoader instance = new MetaConfigurationLoader();
	
	static {
        try {
        	String path = FileLocator.resolve(MetaConfigurationLoader.class.getResource("/dtds/meta-templates.dtd")).toString(); //$NON-NLS-1$
            XMLEntityResolver.registerPublicEntity("-//Red Hat Inc.//DTD Meta Templates 1.0//EN", path); //$NON-NLS-1$
        } catch (IOException e) {
        	ModelUIPlugin.getPluginLog().logError(e);
        }
	}
    
	private static final String GLOBAL_FILE_NAME = "/globalTemplates.xml"; //$NON-NLS-1$
    private static final String PROJECT_FILE_NAME = "/.projectTemplates"; //$NON-NLS-1$
	
	public static final String PREFERENCE_KEY = "global_templates"; //$NON-NLS-1$
	
	public void loadExtensionConfiguration(MetaConfiguration c) {
		processExtensions(c);
	}

	public void loadGlobalConfiguration(MetaConfiguration c) {
		String text = ModelUIPlugin.getDefault().getPluginPreferences().getString(PREFERENCE_KEY);
		if(text != null && text.length() > 0) {
			Document document = XMLUtilities.getDocument(new StringReader (text), createResolver());		
			loadConfiguration(c, document);
		}
	}
	
	public void loadProjectConfiguration(MetaConfiguration c, IProject project) {
		Document document = getDocument(new Path(getProjectLocation(project)));
		if(document != null) {
			//Document is null when file PROJECT_FILE_NAME is missing in project
			//because project configuration was not edited yet.
			loadConfiguration(c, document);
		}
	}

	public void saveGlobalConfiguration(MetaConfiguration c) {
		Element element = XMLUtilities.createDocumentElement(META_TEMPLATE_GROUPS);
		saveConfiguration(c, element);
		StringWriter writer = new StringWriter();
		try {
			XMLUtilities.serialize(element, writer);			
		} catch (IOException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		}
		ModelUIPlugin.getDefault().getPluginPreferences().setValue(PREFERENCE_KEY, writer.toString());				
	}
	
	public void saveProjectConfiguration(MetaConfiguration c, IProject project) {
		saveConfiguration(c, getProjectLocation(project));
	}

	void saveConfiguration(MetaConfiguration c, String location) {
		File f = new File(location);
		if(c.isOverriding()) {
			Element element = XMLUtilities.createDocumentElement(META_TEMPLATE_GROUPS);
			saveConfiguration(c, element);
			try {
				XMLUtilities.serialize(element, location);
			} catch (IOException e) {
				ModelUIPlugin.getPluginLog().logError(e);
			}
		} else if(f.isFile()) {
				f.delete();
		}
	}
	
	void loadConfiguration(MetaConfiguration c, Document document) {
	    Element[] groups = XMLUtilities.getChildren(document.getDocumentElement(), META_TEMPLATE_GROUP);
	    for (int i = 0;i < groups.length;++i) {
			MetaGroup g = c.addGroup(groups[i].getAttribute(URI));
			loadGroup(g, groups[i], null);
		}
	}

	protected String getGlobalLocation() {
		String location = Platform.getStateLocation(Platform.getBundle(ModelUIPlugin.ID_PLUGIN)).toString();
		return location + GLOBAL_FILE_NAME; 
	}

	protected String getProjectLocation(IProject project) {
		return project.getLocation().toString() + PROJECT_FILE_NAME;
	}
    
	// extensions 
    private void processExtensions(MetaConfiguration c) {
    	IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(EXTENSION_POINT);
    	if(extensionPoint == null) {
    		ModelUIPlugin.getPluginLog().logError("Cannot find extension point \"" + EXTENSION_POINT); //$NON-NLS-1$
    	}
    	IConfigurationElement[] members = extensionPoint.getConfigurationElements();
    	for (int m = 0; m < members.length; m++) {
    		IConfigurationElement member = members[m];
    		IExtension extension = member.getDeclaringExtension();
    		String name = extension.getNamespaceIdentifier();
   			if (INCLUDE.equals(member.getName())) {
   				doInclude(member, c);
   			} else if (META_TEMPLATE_GROUP.equals(member.getName())) {
   				doMetaTemplate(member, c);
   			} else {
   				ModelUIPlugin.getPluginLog().logInfo("Error in declaring extension \"" + EXTENSION_POINT+"\" at " + name); //$NON-NLS-1$ //$NON-NLS-2$
    		}
        }
    }
    
	private void doMetaTemplate(IConfigurationElement element, MetaConfiguration c) {
		MetaGroup g = c.addGroup(element.getAttribute(URI));
		loadGroup(g, element, null);
	}

	private void doInclude(IConfigurationElement element, MetaConfiguration c) {
		URL url = null;
		String name = element.getDeclaringExtension().getNamespaceIdentifier();
		Bundle bundle = Platform.getBundle(name);
		try {
			url = FileLocator.resolve(bundle.getEntry("/")); //$NON-NLS-1$
		} catch (IOException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		}
		String file = element.getAttribute(FILE);
		String translations = element.getAttribute(TRANSLATIONS);
		
		try {
			String fullPath = FileLocator.resolve(url).getFile();
			ResourceBundle properties = null;
			if (translations!=null && translations.length()>0) {
				properties = getResourceBundle(new Path(fullPath+translations));
			}
			
			Document document = getDocument(new Path(fullPath + file));
			if (document!=null) {
				Element root = document.getDocumentElement();
				MetaGroup g = c.addGroup(root.getAttribute(URI));
				loadGroup(g, root, properties);
			}
		} catch (IOException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		}
	}

	private ResourceBundle getResourceBundle(IPath path) {
		ResourceBundle properties = null;
		File propertiesFile = path.toFile();
		if (propertiesFile.canRead()) {
			InputStream is = null;
			try {
				is = new FileInputStream(propertiesFile);
				properties = new PropertyResourceBundle(is);
			} catch (IOException e) {
				ModelUIPlugin.getPluginLog().logError(e);
			} finally {
				if (is != null) {
					try {
						is.close();
					} catch (IOException e) {
						// ignore
					}
				}
				properties = new ResourceBundle() {
					@Override
					protected Object handleGetObject(String key) {
						return null;
					}
					@Override
					public Enumeration<String> getKeys() {
						return Collections.enumeration(Collections.<String>emptyList());
					}
				};
			}
		}
		return properties;
	}
	
	Document getDocument(IPath path) {
		File file = new File(path.toString());
		if(!file.isFile()) return null;
		try {
			return XMLUtilities.getDocument(new FileReader(file), createResolver());
		} catch (FileNotFoundException e) {
			return null;
		}
	}

	XMLEntityResolver createResolver() {
		XMLEntityResolver resolver = XMLEntityResolver.getInstance();
		return resolver;		
	}
	
	void loadGroup(MetaGroup g, IConfigurationElement element, ResourceBundle properties) {
		IConfigurationElement[] cs = element.getChildren();
		for (int i = 0; i < cs.length; ++i) {
			if (META_TEMPLATE.equals(cs[i].getName())) {
				MetaClassTemplate t = g.addMetaClassTemplate(cs[i].getAttribute(AXIS));
				loadClass(t, cs[i], properties);
			}
		}
	}

	void loadGroup(MetaGroup g, Element element, ResourceBundle properties) {
		Element[] cs = XMLUtilities.getChildren(element, META_TEMPLATE);
		for (int i = 0; i < cs.length; ++i) {
			String axis = cs[i].getAttribute(AXIS);
			if (axis != null && properties != null && axis.startsWith(PREFIX))  axis = properties.getString(axis);
			MetaClassTemplate t = g.addMetaClassTemplate(axis);
			loadClass(t, cs[i], properties);
		}
	}
	
	void loadClass(MetaClassTemplate t, IConfigurationElement element, ResourceBundle properties) {
		String displayName = element.getAttribute(DISPLAY_NAME);
		if (displayName != null && properties != null && displayName.startsWith(PREFIX)) displayName = properties.getString(displayName);
		t.setDisplayName(displayName); 
		String xEntity = element.getAttribute(ENTITY);
		if (xEntity != null && properties != null && xEntity.startsWith(PREFIX)) xEntity = properties.getString(xEntity);
		t.setXEntity(xEntity);
		IConfigurationElement[] childs = element.getChildren();
		ArrayList<String> is = new ArrayList<String>();
		for (int i = 0; i < childs.length; ++i) {
			IConfigurationElement child = childs[i];
			String name = child.getAttribute(NAME);
			if (name != null && properties != null && name.startsWith(PREFIX)) {
				name = properties.getString(name);
			}
			if (SUPER_CLASS.equals(child.getName())) {
				t.getSuperClass().setValue(name);
			} else if (INTERFACE.equals(child.getName())) {
				if(name != null && name.length() > 0) is.add(name); 
			}
		}
		t.getInterfaces().setValues(is.toArray(new String[0]));
	}

	void loadClass(MetaClassTemplate t, Element element, ResourceBundle properties) {
		String displayName = element.getAttribute(DISPLAY_NAME);
		if (displayName != null && displayName.startsWith(PREFIX)) {
			displayName = properties.getString(displayName);
		}
		t.setDisplayName(displayName); 
		String xEntity = element.getAttribute(ENTITY);
		if (xEntity != null && xEntity.startsWith(PREFIX)) {
			xEntity = properties.getString(xEntity);
		}
		t.setXEntity(xEntity);
		NodeList cs = element.getChildNodes();
		ArrayList<String> is = new ArrayList<String>();
		for (int i = 0; i < cs.getLength(); ++i) {
			Node cn = cs.item(i);
			if(cn.getNodeType() != Node.ELEMENT_NODE) continue;
			Element child = (Element)cn;
			String name = child.getAttribute(NAME);
			if (name != null &&  name.startsWith(PREFIX)) {
				name = properties.getString(name);
			}
			if (SUPER_CLASS.equals(child.getNodeName())) {
				t.getSuperClass().setValue(name);
			} else if (INTERFACE.equals(child.getNodeName())) {
				if(name != null && name.length() > 0) is.add(name); 
			}
		}
		t.getInterfaces().setValues(is.toArray(new String[is.size()]));
	}

	void saveConfiguration(MetaConfiguration c, Element element) {
		Iterator<MetaGroup> gs = c.getMetaTemplateGroups().iterator();
		while(gs.hasNext()) {
			MetaGroup g = (MetaGroup)gs.next();
			if(g.isOverriding()) {
				Element ge = XMLUtilities.createElement(element, META_TEMPLATE_GROUP);
				ge.setAttribute(URI, g.getUri());
				Iterator<MetaClassTemplate> ts = g.getTemplates().iterator();
				while(ts.hasNext()) {
					MetaClassTemplate t = (MetaClassTemplate)ts.next();
					if(t.isOverriding()) {
						Element te = XMLUtilities.createElement(ge, META_TEMPLATE);
						if(t.getAxis() != null) {
							te.setAttribute(AXIS, t.getAxis());
						}
						if(t.getDisplayName() != null) {
							te.setAttribute(DISPLAY_NAME, t.getDisplayName());
						}
						if(t.getXEntity() != null) {
							te.setAttribute(ENTITY, t.getXEntity());
						}
						MetaValue superClass = t.getSuperClass();
						Element sce = XMLUtilities.createElement(te, SUPER_CLASS);
						sce.setAttribute(NAME, superClass.getValue());
						MetaValueList interfaces = t.getInterfaces();
						String[] is = interfaces.getValues();
						for (int i = 0; i < is.length; i++) {
							Element ise = XMLUtilities.createElement(te, INTERFACE);
							ise.setAttribute(NAME, is[i]);
						}
					}
				}
			}
		}
	}

}
