/*
 * Copyright (C) Yutaka Matsuno 2010-2012 All rights reserved.
 */
package net.dependableos.dcase.diagram.edit.parts.custom;

import static net.dependableos.dcase.diagram.common.constant.SystemPropertyKeyConst.DSTAR_FILE_EXTENSION;
import net.dependableos.dcase.Argument;
import net.dependableos.dcase.BasicNode;
import net.dependableos.dcase.System;
import net.dependableos.dcase.Userdef001;
import net.dependableos.dcase.Userdef005;
import net.dependableos.dcase.impl.ParameterItem;
import net.dependableos.dcase.provider.DcaseEditPlugin;
import net.dependableos.dcase.diagram.common.util.FileUtil;
import net.dependableos.dcase.diagram.common.util.Messages;
import net.dependableos.dcase.diagram.common.util.PropertyUtil;
import net.dependableos.dcase.diagram.edit.parts.ArgumentEditPart;
import net.dependableos.dcase.diagram.edit.parts.SystemEditPart;
import net.dependableos.dcase.diagram.ui.editpolicies.BasicNodeOpenEditPolicy;
import net.dependableos.dcase.diagram.part.DcaseDiagramEditor;
import net.dependableos.dcase.diagram.part.DcaseDiagramEditorUtil;
import net.dependableos.dcase.diagram.part.PatternUtil;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.IFigure;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart;
import org.eclipse.draw2d.Shape;
import org.eclipse.gmf.runtime.diagram.ui.editparts.ShapeNodeEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editpolicies.EditPolicyRoles;
import org.eclipse.gmf.runtime.gef.ui.figures.NodeFigure;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.gmf.runtime.notation.FillStyle;
import org.eclipse.gmf.runtime.notation.NotationPackage;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.emf.workspace.util.WorkspaceSynchronizer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.ui.IActionFilter;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;
import org.eclipse.ui.part.FileEditorInput;

/**
 * An abstract class represents a node editpart.
 */
public abstract class DcaseNodeEditPart extends ShapeNodeEditPart implements
        DcaseDelegateNodeEditPart, DcaseDelegateEditPart {

	/**
	 * The module name of d*.
	 */
	private static final String DSTAR_MODULE_NAME = PatternUtil.getMainModuleName();
	
	 /**
	  * The console name.
	  */
    private static final String CONSOLE_NAME = "Console"; //$NON-NLS-1$
    
    /**
     * The key of Parameter subtype.
     */
    private static final String SUBTYPE_PARAMETER = "_UI_System_subType_param"; //$NON-NLS-1$
    private String subTypeParameter;

    /**
     * Allocates a DcaseNodeEditPart object.
     * 
     * @param view owned view by this edit part 
     */
    public DcaseNodeEditPart(View view) {
        super(view);
        subTypeParameter = DcaseEditPlugin.getPlugin().getString(SUBTYPE_PARAMETER);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    protected abstract NodeFigure createNodeFigure();

    /**
     * Returns the primary shape.
     * 
     * @return primaryShape the primary shape.
     */
    public abstract IFigure getPrimaryShape();

    /**
     * Returns the background color.
     * 
     * @return the background color.
     */
    public Color getBackgroundColorEx() {
        IFigure primaryShape = getPrimaryShape();
        if (primaryShape != null) {
            return primaryShape.getBackgroundColor();
        }
        return null;
    }

    /**
     * Sets the background color.
     * 
     * @param color the background color.
     */
    public void setBackgroundColorEx(Color color) {
        setBackgroundColor(color);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    protected void createDefaultEditPolicies() {
        super.createDefaultEditPolicies();

        // installs a edit policy to open a property input dialog.
        installEditPolicy(EditPolicyRoles.OPEN_ROLE,
                new BasicNodeOpenEditPolicy());
    }

    /**
     * Sets the foreground color.
     * 
     * @param color the foreground color.
     */
    public void setForegroundColorEx(Color color) {
        setForegroundColor(color);
    }
    
    /**
     * Returns the foreground color.
     * 
     * @return the foreground color.
     */
    public Color getForegroundColorEx() {
    	IFigure primaryShape = getPrimaryShape();
        if (primaryShape != null) {
        	return primaryShape.getForegroundColor();
        }
        return null;
    }
    
    //@SuppressWarnings("unchecked")
    @Override
    public Object getAdapter(Class adapter) {
        if (adapter == IActionFilter.class) {
            return SetParametersActionFilter.getSingleton();
        }
        return super.getAdapter(adapter);
    }

    /**
     * Returns the line width.
     * 
     * @return the line width.
     */
    public int getLineWidthEx() {
        return super.getLineWidth();
    }

    /**
     * Sets the line width.
     * 
     * @param width the line width.
     */
    public void setLineWidthEx(int width) {
        if (width < 0) {
            width = 1;
        }
        ((Shape) getPrimaryShape()).setLineWidth(width);
    }
    
    // AUTO_GENERATED:END
    
    /**
     * {@inheritDoc}
     */
    protected void addNotationalListeners() {
        super.addNotationalListeners();
        addListenerFilter("PrimaryView", this, getPrimaryView()); //$NON-NLS-1$
    }

    /**
     * {@inheritDoc}
     */
    protected void removeNotationalListeners() {
        super.removeNotationalListeners();
        removeListenerFilter("PrimaryView"); //$NON-NLS-1$
    }

    /**
     * Handles the event for changing Attachment.
     * @param event the event.
     */
    protected void handleNotificationEvent(Notification event) {
    	Object feature = event.getFeature();
    	if(event.getEventType() == Notification.SET) {
    		if(feature instanceof EAttribute) {
    	    	// process parameters
    			EAttribute attr = (EAttribute)feature;
    			if(attr.getName().equals("parameterVals") || //$NON-NLS-1$
    					attr.getName().equals("subType")) { //$NON-NLS-1$
    				this.notifyParameters();
            	}
            	// change background
            	refreshAttributeColor();
    		}
    	}
    	
    	super.handleNotificationEvent(event);
    }
    
    private void refreshAttributeColor() {
		Color color = getAttributeBackground();
		if (getBackgroundColorEx() != color) {
			setBackgroundColor(color);
			setFillColor(color);
		}
    }
    
    /**
     * Notify about changing parameters.
     */
    public void notifyParameters() {
    	// add my UUID.
    	BasicNode node = (BasicNode)getElement();
    	if(node == null) {
    		return;
    	}
		HashSet<String> uuidSet = new HashSet<String>();
		uuidSet.add(getUUID(node));
    	// notify to parent.
    	for(Object link : this.getTargetConnections()) {
    		if(link instanceof DcaseLinkEditPart) {
    			DcaseNodeEditPart parent = (DcaseNodeEditPart)((DcaseLinkEditPart)link).getSource();
    			if(parent != null) {
    				parent.notifyParameters(uuidSet);
    			}
    		}
    	}
    }
    
    /**
     * Notify about changing parameters.
     * @param uuidSet the current uuid set.
     */
    public void notifyParameters(HashSet<String> uuidSet) {
    	// check and add UUID.
    	BasicNode node = (BasicNode)getElement();
    	if(node == null) {
    		return;
    	}
    	String uuid = getUUID(node);
    	if(uuidSet.contains(uuid)) {
    		return;
    	}
    	uuidSet.add(uuid);
    	
    	// apply parameters for the element only.
    	applyParameters();
    	
    	// set notify flag to attachment.
    	String attachment = node.getAttachment();
    	if((node instanceof Userdef005 || node instanceof Userdef001)
    			&& PatternUtil.isModuleReference(attachment)) {
    		String moduleName = PatternUtil.getModuleName(attachment);
    		DcaseDiagramEditor editor = getDcaseDiagramEditor(node, moduleName);
    		if(editor != null) {
    			editor.setNotifyFlag(true);
    		}
    	}
    	
    	// notify to children.
    	for(Object link : this.getSourceConnections()) {
    		if(link instanceof DcaseLinkEditPart) {
    			DcaseNodeEditPart child = (DcaseNodeEditPart)((DcaseLinkEditPart)link).getTarget();
    			child.notifyParameters(uuidSet);
    		}
    	}
    }

    /**
     * Applies parameter values to Desc attribute.
     */
    public void applyParameters() {
    	BasicNode node = (BasicNode)getElement();
    	if(node == null) {
    		return;
    	}
    	ParameterItem.setDesc(node, getParameters(null));
    }
    
    /**
     * Returns the parameter string.
     * @param userdef007 the current parameter string...never used...
     * @return the parameter string.
     */
    public String getParameters(String userdef007) {
    	HashSet<String> uuidSet = new HashSet<String>();
    	List<ParameterData> ret = getParameters(0, userdef007, uuidSet);
    	return parameterListToString(ret);
    }

    /**
     * Converts parameter list to string.
     * @param paramList the parameter list.
     * @return the string of parameter list.
     */
    protected String parameterListToString(List<ParameterData> paramList) {
    	Collections.sort(paramList, new ParameterComparator());
    	StringBuffer ret = new StringBuffer();
    	ArrayList<String> nameList = null;
    	int prevdist = -1;
    	
    	for (int i = 0; i < paramList.size(); i++) {
    		ParameterData curData = paramList.get(i);
    		/* check duplicate parameter */
    		if (curData.getDistance() != prevdist) {
    			nameList = new ArrayList<String>();
    			prevdist = curData.getDistance();
    		}
    		String parameters = curData.getValues();
    		if (parameters == null || parameters.length() == 0) {
    			continue;
    		}
    		for (ParameterItem item : ParameterItem.getPatameterList(parameters)) {
    			String name = item.getParameterId();
    			if (nameList.contains(name)) {
    				MessageConsoleStream stream = getMessageConsoleStream();
    				stream.println(NLS.bind(Messages.ParameterDuplicated, name));
    			} else {
    				nameList.add(name);
    			}
    		}
    		ret.append(curData.getValues());
    		if (i < paramList.size() - 1) {
    			ret.append(ParameterItem.SEPARATOR);
    		}
    	}
    	return ret.toString();
    }
    
    /**
     * Returns the parameter string.
     * @param distance the distance.
     * @param userdef007 the current parameter string.
     * @param uuidSet the node set.
     * @return the parameter string.
     */
    protected List<ParameterData> getParameters(int distance, String userdef007, HashSet<String> uuidSet) {
    	// "userdef007" is used only Parameter node.
    	ArrayList<ParameterData> myParameter = new ArrayList<ParameterData>();
    	boolean isNeedDstar = (uuidSet != null && uuidSet.size() == 0);
    	
    	// check UUID
    	if (uuidSet != null) {
    		BasicNode node = (BasicNode)getElement();
        	if(node == null) {
        		return myParameter;
        	}
        	String uuid = getUUID(node);
        	if(uuidSet.contains(uuid)) {
        		return myParameter;
        	}
        	uuidSet.add(uuid);
    	}
    	
    	// get parameters of child Parameter nodes.
    	for(Object link : this.getSourceConnections()) {
    		if(link instanceof DcaseLinkEditPart) {
    			DcaseNodeEditPart child = (DcaseNodeEditPart)((DcaseLinkEditPart)link).getTarget();
    			if (child instanceof SystemEditPart) {
    				BasicNode childNode = (BasicNode)child.getElement();
    				if (subTypeParameter.equals(((System)childNode).getSubType())) {
    					String childParameter = childNode.getParameterVals();
    					myParameter.add(new ParameterData(distance, childParameter));
    				}
    			}
    		}
    	}
    	
		// if uuidSet is null, only returns the current node's parameters.
    	if (uuidSet == null) {
    		return myParameter;
    	}
    	
    	// get Ancestor's parameters
    	List<ParameterData> ret = getParentParameters(distance + 1, uuidSet);
    	ret.addAll(myParameter);
    	
    	// get parameters of d*
    	if (isNeedDstar) {
    		DcaseDiagramEditor editor = getDstarDiagramEditor((BasicNode)getElement());
    		if(editor != null) {
    			DiagramEditPart dstarEditPart = editor.getDiagramEditPart();
    			if (dstarEditPart instanceof ArgumentEditPart) {
    				Argument argument = (Argument)getElement(dstarEditPart);
    				String dstarUserdef007 = argument.getParameterVals();
    				ret.add(new ParameterData(Integer.MAX_VALUE, dstarUserdef007));
    			}
			}
		}
    	
    	return ret;
    }
    
    /**
     * Returns the parameter string of ancestors.
     * @return the parameter string of ancestors.
     */
    private List<ParameterData> getParentParameters(int distance, HashSet<String> uuidSet) {
    	ArrayList<ParameterData> ret = new ArrayList<ParameterData>();
    	getParentParameters(ret, distance, uuidSet);
    	BasicNode node = (BasicNode)getElement();
    	if(node == null) {
    		return ret;
    	}
    	// check attachment
    	for(String nodeName : getReferringSourceNodes()) { //$NON-NLS-1$
    		getAnotherParameters(ret, distance + 1, nodeName, uuidSet);
    	}
    	
    	return ret;
    }
    
    /**
     * Returns the referring source node names.
     * @return the referring source node names.
     */
    public HashSet<String> getReferringSourceNodes() {
    	HashSet<String> ret = new HashSet<String>();
    	BasicNode node = (BasicNode)getElement();
    	if(node == null) {
    		return ret;
    	}
    	Argument argument = (Argument)node.eContainer();
    	String refSource = argument.getRefSource();
    	String parent = argument.getParent();
    	if (refSource != null && refSource.length() > 0 &&
    		parent != null && parent.length() > 0) {
			// search parent module
    		for(String nodeName : refSource.split(PatternUtil.getReferenceSeparator())) {
    			if (parent.equals(PatternUtil.getModuleName(nodeName))) {
    				ret.add(nodeName);
    			}
    		}
    	}
    	return ret;
    }
    
    /**
     * Returns the another module's parameters.
     * @param parameter the current parameters.
     * @param userdef011 the module name.
     * @param uuidSet the node set.
     * @return the another module's parameters.
     */
    private void getAnotherParameters(List<ParameterData> paramList, int distance, String nodePath, HashSet<String> uuidSet) {
    	BasicNode node = (BasicNode)getElement();
    	if(node == null) {
    		return;
    	}

    	String moduleName = PatternUtil.getModuleName(nodePath);
    	String nodeName = PatternUtil.getNodeName(nodePath);
    	if(PatternUtil.isModuleReference(nodePath) && (! nodePath.equals(nodeName))) {
    		DcaseDiagramEditor editor = getDcaseDiagramEditor(node, moduleName);
    		if(editor == null) {
    			return;
    		}
    		DiagramEditPart editPart = editor.getDiagramEditPart();
    		if (!(editPart instanceof ArgumentEditPart)) {
    			return;
    		}
    		for(Object nobj : ((ArgumentEditPart)editPart).getChildren()) {
    			if(nobj instanceof DcaseNodeEditPart) {
    				BasicNode nnode = (BasicNode)getElement((DcaseNodeEditPart)nobj);
    				if(!nnode.getName().equals(nodeName)) {
    					continue;
    				}
    				List<ParameterData> nret = ((DcaseNodeEditPart)nobj).getParentParameters(distance + 1, uuidSet);
    				paramList.addAll(nret);
    			}
    		}
    	}
    }
    
    /**
     * Returns the parameter string of ancestors.
     * @param parameter the current parameter...do not use...
     * @param uuidSet the current uuid set.
     * @return the parameter string of ancestors.
     */
    private void getParentParameters(List<ParameterData> paramList, int distance, HashSet<String> uuidSet) {
    	// add parent's parameters.
    	for(Object link : this.getTargetConnections()) {
    		if(link instanceof DcaseLinkEditPart) {
    			DcaseNodeEditPart parentEditPart = (DcaseNodeEditPart)((DcaseLinkEditPart)link).getSource();
    			BasicNode parentNode = (BasicNode)parentEditPart.getElement();
    			if(parentNode == null) {
    				continue;
    			}
    			paramList.addAll(parentEditPart.getParameters(distance, null, uuidSet));
    		}
    	}
    }
    
    /**
     * Returns the element.
     * @return the element.
     */
    public EObject getElement() {
    	return getElement(this);
    }
    
    /**
     * Returns the element.
     * @param editPart the edit part.
     * @return the element.
     */
    public static EObject getElement(GraphicalEditPart editPart) {
    	Object model = editPart.getModel();
    	if(! (model instanceof View)) {
    		return null;
    	}
    	return ((View)model).getElement();
    }
    
    /**
     * Returns the UUID.
     * @param node the element.
     * @return the UUID.
     */
    public static String getUUID(BasicNode node) {
    	XMLResource resource = (XMLResource)node.eResource();
    	return resource.getID(node);
    }
   
    /**
     * Returns the diagram editor of the specified module.
     * @param node the node.
     * @param moduleName the module name.
     * @return the diagram editor.
     */
    private DcaseDiagramEditor getDcaseDiagramEditor(BasicNode node, String moduleName) {
    	return getDcaseDiagramEditor(node, moduleName, false);
    }
    
    /**
     * Returns the diagram editor of the specified module.
     * @param node the node.
     * @param moduleName the module name.
     * @return the diagram editor.
     */
    public DcaseDiagramEditor getDstarDiagramEditor(BasicNode node) {
    	return getDcaseDiagramEditor(node, DSTAR_MODULE_NAME, true);
    }
    
    /**
     * Returns the diagram editor of the specified module.
     * @param node the node.
     * @param moduleName the module name.
     * @param isDstar whether dstar or not.
     * @return the diagram editor.
     */
    private DcaseDiagramEditor getDcaseDiagramEditor(BasicNode node, String moduleName, boolean isDstar) {
		XMLResource resource = (XMLResource)node.eResource();
		IFile modelFile = WorkspaceSynchronizer.getFile(resource);
		IPath diagramPath = null;
		if (isDstar) {
			diagramPath = PatternUtil.getDstarPath(modelFile);
		} else {
			diagramPath = PatternUtil.getDiagramPath(modelFile, moduleName);
		}
    	if(diagramPath == null) {
    		return null;
    	}
    	IFile diagramFile = FileUtil.getWorksapceFileFromPath(diagramPath.toOSString());
    	IWorkbenchPage workbenchPage = PlatformUI.getWorkbench()
    			.getActiveWorkbenchWindow().getActivePage();
    	IEditorPart editor = null;
    	try {
    		editor = workbenchPage.findEditor(new FileEditorInput(diagramFile));
    		if (editor == null && diagramFile.exists()) {
    			IEditorDescriptor desc = PlatformUI.getWorkbench()
                        .getEditorRegistry().getDefaultEditor(diagramFile.getName());
    			if (desc != null) {
    		    	Diagram currentDiagram = ((DcaseDiagramEditor)workbenchPage.getActiveEditor()).getDiagram();
    				editor = workbenchPage.openEditor(new FileEditorInput(diagramFile), desc.getId(), false);
    				DcaseDiagramEditorUtil.openDiagram(currentDiagram.eResource());
    			}
    		}
    	} catch (Exception e) {
    		return null;
    	}
    	return (DcaseDiagramEditor)editor;
    }
    
    protected void setFillColor(Color color) {
    	RGB rgb = color.getRGB();
    	int c = (rgb.blue<<16) | (rgb.green<<8) | rgb.red;
    	FillStyle fillStyle =
    			(FillStyle)getPrimaryView().getStyle(NotationPackage.eINSTANCE.getFillStyle());
    	if (fillStyle != null) {
    		fillStyle.setFillColor(c);
    	}
    }

    /**
     * Sets the background for Attachment and RefSource.
     */
    public Color getAttributeBackground() {
    	BasicNode node = getBasicNode();
    	int condition = 0;
    	IWorkbenchPage workbenchPage = PlatformUI.getWorkbench()
    			.getActiveWorkbenchWindow().getActivePage();
    	DcaseDiagramEditor editor = (DcaseDiagramEditor)workbenchPage.getActiveEditor();
    	if (editor != null) {
    		Diagram diagram = editor.getDiagram();
        	String dstarDiagramName = DSTAR_MODULE_NAME + "." + PropertyUtil.getSystemProperty(DSTAR_FILE_EXTENSION); //$NON-NLS-1$
        	if (! diagram.getName().equals(dstarDiagramName)) {
            	if(node != null) {
            		String attachmentValue = node.getAttachment();
            		if(PatternUtil.isModuleReference(attachmentValue)) {
            			condition += 1;
            		}
            		String refSourceValue = node.getRefSource();
            		if(refSourceValue != null && refSourceValue.length() > 0) {
            			condition += 2;
            		}
            	}
        	}
    	}
    	switch(condition) {
    	case 1:
    		return ColorConstants.green;
    	case 2:
    		return ColorConstants.orange;
    	case 3:
    		return ColorConstants.darkGreen;
    	default:
    		return ColorConstants.white;
		}
    }
    
    /**
     * Gets the current BasicNode.
     * @return the current BasicNode
     */
    private BasicNode getBasicNode() {
		Object obj = getModel();
		if(obj instanceof View) {
			EObject eobj = ((View)obj).getElement();
			if(eobj instanceof BasicNode) {
				return ((BasicNode)eobj);
			}
		}
		return null;
    }
    
    /**
     * A class for sorting parameters.
     *
     */
    private class ParameterData {
    	private int distance;
    	private String values;
    	public ParameterData(int distance, String values) {
    		this.distance = distance;
    		this.values = values;
    	}
    	public int getDistance() {
    		return distance;
    	}
    	public String getValues() {
    		return values;
    	}
    	public String toString() {
    		return "{" + distance + "," + values + "}"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    	}
    }

    /**
     * A class for sorting parameters.
     *
     */
    private class ParameterComparator implements Comparator<ParameterData> {
    	public int compare(ParameterData d1, ParameterData d2) {
    		return d1.getDistance() - d2.getDistance();
    	}
    }
    
    /**
     * Returns the message console stream.
     * @returnthe message console stream.
     */
    public static MessageConsoleStream getMessageConsoleStream() {
		// get or create Console ... see editor.common.util.MessageWriter class
		IConsole[] consoles = ConsolePlugin.getDefault().getConsoleManager().getConsoles();
        MessageConsole console = null;
        for (int i = 0; i < consoles.length; i++) {
            if (CONSOLE_NAME.equals(consoles[i].getName())) {
                console = (MessageConsole) consoles[i];
                break;
            }
        }
        if (console == null) {
            console = new MessageConsole(CONSOLE_NAME, null);
        }
		ConsolePlugin.getDefault().getConsoleManager().addConsoles(new IConsole[] { console });
		ConsolePlugin.getDefault().getConsoleManager().showConsoleView(console);
		return console.newMessageStream();
    }

}
