/*******************************************************************************
 * 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.meta.action.impl.handlers;

import java.text.MessageFormat;
import java.util.Properties;

import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.jboss.tools.common.meta.XAttribute;
import org.jboss.tools.common.meta.XChild;
import org.jboss.tools.common.meta.action.XAttributeData;
import org.jboss.tools.common.meta.action.XEntityData;
import org.jboss.tools.common.meta.action.impl.AbstractHandler;
import org.jboss.tools.common.meta.constraint.XAttributeConstraint;
import org.jboss.tools.common.meta.impl.XMetaDataConstants;
import org.jboss.tools.common.meta.key.WizardKeys;
import org.jboss.tools.common.model.ServiceDialog;
import org.jboss.tools.common.model.XModelException;
import org.jboss.tools.common.model.XModelObjectConstants;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.event.ActionDeclinedException;
import org.jboss.tools.common.model.impl.RegularObjectImpl;
import org.jboss.tools.common.model.plugin.ModelMessages;
import org.jboss.tools.common.model.undo.XCreateUndo;
import org.jboss.tools.common.model.undo.XUndoManager;
import org.jboss.tools.common.model.util.FindObjectHelper;
import org.jboss.tools.common.model.util.XModelObjectLoaderUtil;

public class DefaultCreateHandler extends AbstractHandler {

    public DefaultCreateHandler() {}

    public void executeHandler(XModelObject object, Properties prop) throws XModelException {
        if(!isEnabled(object) || data == null || data.length == 0) return;
        String entity = getEntityName();
        Properties p = extractProperties(data[0]);
        setOtherProperties(object, p);
        XModelObject c = XModelObjectLoaderUtil.createValidObject(object.getModel(), entity, p);
        c = modifyCreatedObject(c);
        addCreatedObject(object, c, prop);
		checkPosition(object, c, prop);
		if(prop != null) prop.put("created", c); //$NON-NLS-1$
    }
    
    private void checkPosition(XModelObject o, XModelObject c, Properties prop) {
    	if(prop == null || !(prop.get("insertAfter") instanceof Integer)) return; //$NON-NLS-1$
    	int i = ((Integer)prop.get("insertAfter")).intValue(); //$NON-NLS-1$
    	if(i < 0) return;
    	++i;
    	RegularObjectImpl impl = (RegularObjectImpl)o;
    	impl.move(impl.getIndexOfChild(c), i, true); 
    }

    protected String getEntityName() {
        String n = action.getProperty(XMetaDataConstants.ENTITY);
        return (n == null) ? data[0].getModelEntity().getName() : n;
    }

    protected XModelObject modifyCreatedObject(XModelObject o) {
        return o;
    }

    public static Properties extractProperties(XEntityData es) {
        Properties p = new Properties();
        XAttributeData[] ads = es.getAttributeData();
        for (int i = 0; i < ads.length; i++) {
            XAttribute a = ads[i].getAttribute();
            String pn = a.getName();
            String pv = extractProperty(ads[i]);
            p.setProperty(pn, pv);
        }
        return p;
    }
    
    //TODO throw XModelException
    public static String extractProperty(XAttributeData ad) {
		XAttribute a = ad.getAttribute();
		String pn = a.getName();
		String pv = ad.getValue();
		if(a.isTrimmable()) pv = (pv == null) ? "" : pv.trim(); //$NON-NLS-1$
		if((pv == null || pv.length() == 0) && ad.getMandatoryFlag())
		  throw new RuntimeException(getReguiredMessage(pn));
		XAttributeConstraint c = ad.getAttribute().getConstraint();
		validateValue(pn, pv, c);
		return pv;
    }
    
	public static String getReguiredMessage(String attributeName) {
		return NLS.bind(ModelMessages.ATTRIBUTE_REQUIRED, new Object[]{attributeName});
	}

	public static Properties getProperties(XEntityData es) {
		Properties p = new Properties();
		XAttributeData[] ads = es.getAttributeData();
		for (int i = 0; i < ads.length; i++) {
			XAttribute a = ads[i].getAttribute();
			String pn = a.getName();
			String pv = ads[i].getValue();
			if(a.isTrimmable()) pv = (pv == null) ? "" : pv.trim(); //$NON-NLS-1$
			if(pv == null) pv = ""; //$NON-NLS-1$
			p.setProperty(pn, pv);
		}
		return p;
	}
    
    public static String validateAttribute(XAttributeData ad, String pv) {
		XAttribute a = ad.getAttribute();
		String vis = WizardKeys.getAttributeDisplayName(ad, true);
		if(a.isTrimmable()) pv = (pv == null) ? "" : pv.trim(); //$NON-NLS-1$
		if((pv == null || pv.length() == 0) && ad.getMandatoryFlag()) {
  		    return NLS.bind(ModelMessages.ATTRIBUTE_REQUIRED, new Object[]{vis});
		}
		XAttributeConstraint c = ad.getAttribute().getConstraint();
		return getConstraintMessage(vis, pv, c);
    }
    
    public static String getConstraintMessage(String name, String value, XAttributeConstraint c) {
		if(c == null || c.getError(value) == null) return null;
		return NLS.bind(ModelMessages.SET_ATTRIBUTE_FAILURE, new Object[]{name, value, c.getError(value)});
    }

    public static void validateValue(String name, String value, XAttributeConstraint c) {
        String mes = getConstraintMessage(name, value, c);
        if(mes != null) throw new RuntimeException(mes);
    }

    protected void setOtherProperties(XModelObject object, Properties p) {}

    public boolean isEnabled(XModelObject object) {
        return (object != null && object.isObjectEditable());
    }

    public static void addCreatedObject(XModelObject parent, XModelObject child, Properties whereSelect) throws XModelException {
        addCreatedObject(parent, child, true, whereSelect);
    }

    public static String getContainsMessage(XModelObject parent, XModelObject child) {
    	return getContainsMessage(parent, child, true);
    }

    private static String getContainsMessage(XModelObject parent, XModelObject child, boolean forceUnique) {
		String pathpart = child.getPathPart();
		XModelObject e = parent.getChildByPath(pathpart);
		if(e != null && e != parent && e != parent.getParent()) {
			if(child.getModelEntity().getAttribute(XModelObjectLoaderUtil.ATTR_ID_NAME) != null) {
				if(!forceUnique || !XModelObjectConstants.TRUE.equals(child.getModelEntity().getProperty("unique"))) { //$NON-NLS-1$
					return null;
				}
			}
			String tp = title(parent, true), tc = title(child, false), te = title(e, false);
			String mes = (tc.equals(te))
						 ? NLS.bind(ModelMessages.CONTAINS_OBJECT_1, new Object[]{tp, tc})
						 : NLS.bind(ModelMessages.CONTAINS_OBJECT_2, new Object[]{tp, te, "\n", tc}); //$NON-NLS-1$
			return mes;
		}
		return null;
    }

    public static void addCreatedObject(XModelObject parent, XModelObject child, boolean registerundo, Properties whereSelect) throws XModelException {
    	addCreatedObject(parent, child, registerundo, extractWhereSelect(whereSelect));
    }

    public static void addCreatedObject(XModelObject parent, XModelObject child, int whereSelect) throws XModelException {
    	addCreatedObject(parent, child, true, whereSelect);
    }

    public static void addCreatedObject(XModelObject parent, final XModelObject child, boolean registerundo, final int whereSelect) throws XModelException {
        if(child == null) throw new XModelException(ModelMessages.OBJECT_CREATION_FAILURE);
        String mes = getContainsMessage(parent, child, false);
        if(mes != null) throw new XModelException(mes);
        String ce = child.getModelEntity().getName();
        XChild c = parent.getModelEntity().getChild(ce);
        if(c == null) throw new XModelException(ModelMessages.OBJECT_ADDING_FAILURE);
        int max = c.getMaxCount();
        int cur = parent.getChildren(ce).length;
        if(cur >= max) {
			ServiceDialog d = parent.getModel().getService();
			d.showDialog(ModelMessages.WARNING, MessageFormat.format("The limit of {0} children is achieved.",
					max), new String[]{"OK"}, null, ServiceDialog.MESSAGE);
			// i18n: assumes germanic plurals
            mes = MessageFormat.format("{0} can contain only {1}{2}with entity {3}.",
					title(parent, true), max, ((max == 1) ? " child " : " children "), ce);
            throw new ActionDeclinedException(mes);
        }
        boolean b = parent.addChild(child);
		if(!b && child.getModelEntity().getAttribute(XModelObjectLoaderUtil.ATTR_ID_NAME) != null) {
			int k = 1;
			String pp = child.getPathPart();
			while(parent.getChildByPath(pp) != null) {
				child.setAttributeValue(XModelObjectLoaderUtil.ATTR_ID_NAME, "" + k); //$NON-NLS-1$
				String ppn = child.getPathPart();
				if(ppn.equals(pp)) throw new RuntimeException(ModelMessages.OBJECT_ADDING_FAILURE);
				pp = ppn;
				++k;
			}
			b = parent.addChild(child);
		}
        if(!b) {
        	throw new XModelException(ModelMessages.OBJECT_ADDING_FAILURE);
        }
        XUndoManager undo = getUndoManager(parent);
        if(registerundo && undo != null) {
            undo.addUndoable(new XCreateUndo(parent, child));
        }
        child.setModified(true);
        if(whereSelect >= 0) {
        	Display.getDefault().asyncExec(new Runnable() {
        		public void run() {
                	FindObjectHelper.findModelObject(child, whereSelect);
        		}
        	});
        }
    }

    // now undo works only for ancestors of the model root!
    static XUndoManager getUndoManager(XModelObject o) {
        XModelObject root = o.getModel().getRoot();
        while(o != null && o != root) o = o.getParent();
        return (o == null) ? null : o.getModel().getUndoManager();
    }

    public static String title(XModelObject o, boolean capitalize) {
    	String elementType = o.getAttributeValue(XModelObjectConstants.ATTR_ELEMENT_TYPE);
    	String objectTitle = o.getModelEntity().getRenderer().getTitle(o);
        String s = elementType + " " + objectTitle; //$NON-NLS-1$
    	if(objectTitle != null && objectTitle.equalsIgnoreCase(elementType)) {
    		s = objectTitle;
    	}
        return (!capitalize || s.length() < 1 || Character.isUpperCase(s.charAt(0)))
               ? s : s.substring(0, 1).toUpperCase() + s.substring(1);
    }
    
    public static int extractWhereSelect(Properties p) {
    	if(p == null) return FindObjectHelper.EVERY_WHERE;
    	String component = p.getProperty("actionSourceGUIComponentID"); //$NON-NLS-1$
    	if("navigator".equals(component)) return FindObjectHelper.IN_NAVIGATOR_AND_IN_EDITOR_IF_OPEN; //$NON-NLS-1$
    	if("editor".equals(component)) return FindObjectHelper.IN_EDITOR_ONLY; //$NON-NLS-1$
    	if("dialog".equals(component)) return FindObjectHelper.IN_NAVIGATOR_ONLY; //$NON-NLS-1$
    	return FindObjectHelper.EVERY_WHERE;
    }

}

