/*******************************************************************************
 * 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.jsf.project.capabilities;

import java.io.*;
import org.w3c.dom.*;
import org.jboss.tools.common.meta.XAttribute;
import org.jboss.tools.common.meta.action.impl.handlers.DefaultCreateHandler;
import org.jboss.tools.common.model.*;
import org.jboss.tools.common.model.engines.impl.EnginesLoader;
import org.jboss.tools.common.model.loaders.XObjectLoader;
import org.jboss.tools.common.model.loaders.impl.SerializingLoader;
import org.jboss.tools.common.model.util.*;
import org.jboss.tools.common.xml.*;
import org.jboss.tools.jsf.messages.JSFUIMessages;

public class FileAdditionPerformer extends PerformerItem {
	XModelObject fileAddition;
	XModelObject configFile;
	String xPath;
	String preferredPosition;
	String text;
	
	Element element;

	XModelObject targetObject;
	String targetAttribute;
	String attributePath;
	
	XModelObject updateObject = null;
	String attributeValue = null;

	public String getDisplayName() {
		return fileAddition.getAttributeValue("label");
	}

	public void init(XModelObject configFile, XModelObject fileAddition) {
		this.fileAddition = fileAddition;
		this.configFile = configFile;
		xPath = fileAddition.getAttributeValue("x-path");
		preferredPosition = fileAddition.getAttributeValue("preferred position");
		text = fileAddition.getAttributeValue("text");
		targetObject = configFile;
	}
	
	void setConfigFile(XModelObject configFile) {
		this.configFile = configFile;
		targetObject = configFile;
	}
	
	public boolean check() {
		if(!isSelected()) return true;
		if(!scan(configFile, "")) {
			String path = XModelObjectLoaderUtil.getResourcePath(configFile);
			String message = "Cannot find x-path " + xPath + " in config file" + path + ".\n"
						   + "Please correct file addition " + fileAddition.getAttributeValue("label") + ".";
			report(message);
			return false;
		}
		if(!parseText()) return false;
		
		return true;
	}
	
	public boolean execute(PerformerContext context) throws XModelException {
		if(!isSelected()) return true;
		if(targetAttribute != null && attributeValue != null) {
			targetObject.getModel().editObjectAttribute(targetObject, targetAttribute, attributeValue);
		} else if(updateObject != null) {
			mergeAttributes(targetObject, updateObject);
			mergeChildren(targetObject, updateObject.getChildren());
		}
		String capability = fileAddition.getParent().getAttributeValue("name");
		context.changeList.add("" + capability + ": " + "Added \"" + getDisplayName() + "\" to " + fileAddition.getAttributeValue("file name"));
		context.monitor.worked(1);
		return true;
	}
	
	private boolean scan(XModelObject object, String xp) {
		String xml = object.getModelEntity().getXMLSubPath();
		if(xml == null || xml.length() == 0) {
			return scanChildren(object, xp);
		}
		xp += "/" + xml;
		if(!xPath.startsWith(xp)) return false;
		if(xPath.equals(xp)) {
			targetObject = object;
			targetAttribute = null;
			return true;
		}
		if(scanAttributes(object, xp)) return true;
		return scanChildren(object, xp);
	}
	
	private boolean scanAttributes(XModelObject object, String xp) {
		XAttribute[] as = object.getModelEntity().getAttributes();
		for (int i = 0; i < as.length; i++) {
			String xml = as[i].getXMLName();
			if(xml == null || xml.length() == 0) continue;
			xml = xml.replace('.', '/');
			if(xml.endsWith("#text")) {
				xml = xml.substring(0, xml.length() - 5) + "text()";
			} else {
				int k = xml.lastIndexOf("/");
				if(k < 0) xml = "@" + xml; else xml = xml.substring(0, k + 1) + "@" + xml.substring(k + 1);
			}
			xp = xp + "/" + xml;
			if(xp.equals(xPath) || xp.startsWith(xPath)) {
				targetObject = object;
				targetAttribute = as[i].getName();
				attributePath = null;
				if(!xp.equals(xPath)) {
					attributePath = xp.substring(xPath.length() + 1);
					attributePath = attributePath.replace('/', '.');
					if(attributePath.endsWith("text()")) {
						attributePath = attributePath.substring(0, attributePath.length() - 6) + "#text";
					}
				}
				return true;
			}
		}
		return false;
	}
	
	private boolean scanChildren(XModelObject object, String xp) {
		XModelObject[] cs = object.getChildrenForSave();
		for (int i = 0; i < cs.length; i++) {
			if(scan(cs[i], xp)) return true;
		}
		return false;
	}
	
	private boolean parseText() {
		if(text == null) return false;
		if(text.length() == 0) return true;
		if(text.trim().startsWith("<") || targetAttribute == null) {
			String tag = targetObject.getModelEntity().getXMLSubPath();
			String body = "<" + tag + ">\n" + text + "</" + tag + ">\n";
			element = XMLUtilities.getElement(new StringReader(body), XMLEntityResolver.getInstance());
			if(element == null) {
				String[] errors = XMLUtilities.getXMLErrors(new StringReader(body), false, XMLEntityResolver.getInstance());
				String message = "Cannot load xml";
				if(errors.length > 0) message += ": " + errors[0];
				message += "\nPlease correct file addition " + fileAddition.getAttributeValue("label") + ".";
				report(message);
				return false;
			}
			if(targetAttribute == null) {
				updateObject = XModelObjectLoaderUtil.createValidObject(targetObject.getModel(), targetObject.getModelEntity().getName());
				XObjectLoader loader = XModelObjectLoaderUtil.getObjectLoader(configFile);
				if(loader instanceof SerializingLoader) {
					SerializingLoader sl = (SerializingLoader)loader;
					sl.loadFragment(updateObject, element);
				} else {
					new XModelObjectLoaderUtil().load(element, updateObject);
				}
			} else {
				attributeValue = readAttributeValue(element);
			}
		} else {
			attributeValue = text;
		}
		return true;
	}

	private void report(String message) {
		ServiceDialog d = configFile.getModel().getService();
		d.showDialog(JSFUIMessages.ERROR, message, new String[]{JSFUIMessages.CLOSE}, null, ServiceDialog.ERROR);
	}
	
	private String readAttributeValue(Element element) {
		XModelObjectLoaderUtil util = new XModelObjectLoaderUtil();
		if(attributePath != null) {
			return util.getAttribute(element, attributePath);
		} else {
			return XMLUtilities.getCDATA(element);
		}
	}
	
	private void mergeAttributes(XModelObject object, XModelObject update) throws XModelException {
		XAttribute[] as = update.getModelEntity().getAttributes();
		for (int i = 0; i < as.length; i++) {
			String xml = as[i].getXMLName();
			if(xml == null || xml.indexOf('.') < 0) continue;
			String v = update.getAttributeValue(as[i].getName());
			if(v == null || v.length() == 0) continue;
			object.getModel().editObjectAttribute(object, as[i].getName(), v);
		}
	}
	
	private void mergeChildren(XModelObject object, XModelObject[] update) throws XModelException {
		for (int i = 0; i < update.length; i++) {
			String pp = update[i].getPathPart();
			XModelObject c = object.getChildByPath(pp);
			if(c == null) {
				DefaultCreateHandler.addCreatedObject(object, update[i], -1);
				if("0".equals(preferredPosition) || "top".equals(preferredPosition)) {
					moveToPotition(object, update[i], 0);
				}
			} else {
				String xml = update[i].getModelEntity().getXMLSubPath();
				if(xml == null || xml.length() == 0) {
					mergeChildren(c, update[i].getChildren());
				} else {
					EnginesLoader.merge(c, update[i], true);
				}
			}
		}
	}
	
	private void moveToPotition(XModelObject parent, XModelObject child, int pos) {
		if(!(parent instanceof XOrderedObject)) return;
		XModelObject[] cs = parent.getChildren();
		for (int i = 0; i < cs.length; i++) {
			if(cs[i] != child) continue;
			if(i == 0) return;
			((XOrderedObject)parent).move(i, 0, true);
			return;
		}
	}

}
