package transformation.su_transformationplugin;

import transformation.associationtags.AssociationTag;

import com.nomagic.magicdraw.core.Application;
import com.nomagic.magicdraw.core.Project;
import com.nomagic.magicdraw.openapi.uml.ModelElementsManager;
import com.nomagic.magicdraw.openapi.uml.PresentationElementsManager;
import com.nomagic.magicdraw.openapi.uml.ReadOnlyElementException;
import com.nomagic.magicdraw.openapi.uml.SessionManager;
import com.nomagic.magicdraw.ui.actions.DefaultDiagramAction;
import com.nomagic.magicdraw.uml.DiagramTypeConstants;
import com.nomagic.magicdraw.uml.symbols.DiagramPresentationElement;
import com.nomagic.magicdraw.uml.symbols.shapes.ShapeElement;
import com.nomagic.uml2.ext.jmi.helpers.StereotypesHelper;
import com.nomagic.uml2.ext.magicdraw.actions.mdbasicactions.CallBehaviorAction;
import com.nomagic.uml2.ext.magicdraw.activities.mdintermediateactivities.ActivityPartition;
import com.nomagic.uml2.ext.magicdraw.classes.mdassociationclasses.impl.AssociationClassImpl;
import com.nomagic.uml2.ext.magicdraw.classes.mddependencies.impl.DependencyImpl;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Class;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Comment;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Diagram;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Element;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Namespace;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Operation;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Property;
import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Relationship;
import com.nomagic.uml2.ext.magicdraw.mdprofiles.Profile;
import com.nomagic.uml2.impl.ElementsFactory;

import transformation.su_transformationplugin.listeners.CheckBoxListener;
import transformation.su_transformationplugin.listeners.JoinButtonListener;
import transformation.su_transformationplugin.listeners.OkListener;
import transformation.wizards.UMLsec_Wizard;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class SU_Transformation extends DefaultDiagramAction
{		
		ElementsFactory ef;
		Diagram activityDiagram;

	/**
	* Creates action with name "SecureUML -> UMLsec Transformation" in the diagram context menu
	*/
	public SU_Transformation()
		{
		super("", "SecureUML -> UMLsec Transformation", null, null);
		}
	
	/**
	 * Describes the actions that follow clicking the menu item in the diagram context menu. 
	 */
	public void actionPerformed(ActionEvent e)
		{
		
		Project project = Application.getInstance().getProject();
		
		// Consistency check - SU transformation can only be run on a class diagram
		if (project.getActiveDiagram().getDiagramType().getType().equals("Class Diagram") == false) {
			javax.swing.JOptionPane.showMessageDialog( null, "Diagram must be a Class Diagram");
			return;
		}
	
		Namespace parent = project.getModel();
		
		// In order to create new elements in a model, a session needs to be started
		SessionManager.getInstance().createSession("Create element");
		
		// Elements are created with the help of an elements factory
		ef = Application.getInstance().getProject().getElementsFactory();
	
			
		try {
			
			transform(project);
	
		} 
		
		catch (ReadOnlyElementException e1) {
			javax.swing.JOptionPane.showMessageDialog( null, "Element on diagram must be of class Element!");
		}
	
		// The session needs to be closed after all elements are created
		SessionManager.getInstance().closeSession();
		
		// The consistency check is run and a note with reminders is added to the diagram
		new UMLsec_Wizard();
		
		// Makes the project saveable
		project.setDirty(true);
		
		}

	private void transform(Project project) throws ReadOnlyElementException {
		// A new profile is created
		Profile profile = ef.createProfileInstance();
		profile.setName("UMLsec model");
		
		// The profile is added to the model
		ModelElementsManager.getInstance().addElement(profile, project.getModel());
		
		PresentationElementsManager PEMInstance = PresentationElementsManager.getInstance();
		
		// An activity diagram is created and added to the profile
		activityDiagram = ModelElementsManager.getInstance().
			createDiagram(DiagramTypeConstants.UML_ACTIVITY_DIAGRAM, profile);
		
		activityDiagram.setName("UMLsec diagram");
		
		// Add the activity diagram the stereotype <<rbac>>
		if (StereotypesHelper.getStereotype(project, "rbac") == null) {
			com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Class metaClass =
				StereotypesHelper.getMetaClassByName(project, "Class");

			StereotypesHelper.createStereotype(profile, "rbac", Arrays.asList(metaClass));
		}
		StereotypesHelper.addStereotypeByString(activityDiagram, "rbac");
		
		// Lists containing references to partitions and actions of the activity diagram
		List<ActivityPartition> horPartitions = new ArrayList<ActivityPartition>();
		List<ActivityPartition> verPartitions = new ArrayList<ActivityPartition>();
		List<List<CallBehaviorAction>> actions = new ArrayList<List<CallBehaviorAction>>();
		
		// The SecureUML diagram
		Diagram classDiagram = project.getActiveDiagram().getDiagram();
		Collection<Element> elements = classDiagram.getOwnerOfDiagram().getOwnedElement();
		
		// Lists containing references to partial association tags
		List<AssociationTag> roleTags = new ArrayList<AssociationTag>();
		List<AssociationTag> actionTags = new ArrayList<AssociationTag>();
		
		List<String> resources = new ArrayList<String>();
		
		boolean existsResource = false;
		
		// Iterating through all the elements of the SecureUML model
		for (Element element : elements) {
			
			try {
				
				Class classElement = (Class) element;
				
				// Checks if the element is a class with the stereotype <<secuml.resource>>
				if (StereotypesHelper.getAppliedStereotypeByString(classElement, "secuml.resource") != null
						&& classElement instanceof com.nomagic.uml2.ext.magicdraw.classes.mdkernel.impl.ClassImpl) {
					
					existsResource = addResourcePartitions(verPartitions,
							actions, resources, classElement);
				}
				
				// Checks if the element is a class with the stereotype <<secuml.role>>
				else if (StereotypesHelper.getAppliedStereotypeByString(classElement, "secuml.role") != null
						&& classElement instanceof com.nomagic.uml2.ext.magicdraw.classes.mdkernel.impl.ClassImpl) {
					
					addRolePartitions(verPartitions, actions, roleTags,
							actionTags, classElement);
				}
			}
			
			catch (ClassCastException cce) {
				continue;
			}
		}
		
		// Checks if the class diagram contains a class with the stereotype <<secuml.resource>>
		if (existsResource == false) {
			javax.swing.JOptionPane.showMessageDialog( null, "A class with the stereotype <<secuml.resource>> " +
					"must exist on the diagram");
			SessionManager.getInstance().closeSession();
			return;
		}
		
		createDiagram(project, PEMInstance, horPartitions, verPartitions,
				actions);
		
		// Joins partial association tags
		joinTags(actionTags, roleTags, resources);
	}

	private void createDiagram(Project project,
			PresentationElementsManager PEMInstance,
			List<ActivityPartition> horPartitions,
			List<ActivityPartition> verPartitions,
			List<List<CallBehaviorAction>> actions)
			throws ReadOnlyElementException {
		// Opens the activity diagram for editing
		DiagramPresentationElement diagramPE = project.getDiagram(activityDiagram);
		diagramPE.open();
		
		// Creates the graphical swimlanes
		ShapeElement swimlaneShape = PEMInstance.createSwimlane(horPartitions, verPartitions, diagramPE);
		swimlaneShape.setLocation(100, 100);
		swimlaneShape.setSize(400, 400);
		
		// Iterates through all the actions that were previously created and adds corresponding graphical elements
		for (int i=0; i<actions.size(); i++) {
			for (int j=0; j<actions.get(i).size(); j++) {
				ShapeElement actionShape = PEMInstance.createShapeElement(actions.get(i).get(j), diagramPE);
				actionShape.setLocation((i+1)*200-50, 100 + (j+1)*50);
				actionShape.setParent(swimlaneShape);
			}
		}
	}

	private void addRolePartitions(List<ActivityPartition> verPartitions,
			List<List<CallBehaviorAction>> actions,
			List<AssociationTag> roleTags, List<AssociationTag> actionTags,
			Class classElement) {
		// A swimlane with the class's name is added to the activity diagram
		verPartitions.add(ef.createActivityPartitionInstance());
		((ActivityPartition) verPartitions.get(verPartitions.size()-1))
			.setName(classElement.getName());
		
		// Adds actions to the created swimlane
		addActionsToRolePartition(ef, activityDiagram, (ActivityPartition)verPartitions.get(verPartitions.size()-1),
				classElement, actions, actionTags);
		
		// Iterates through all relationships associated with this class
		for (Relationship dependency : classElement.get_relationshipOfRelatedElement()) {
			
			// Checks if the relationships is a dependecy link with the stereotype <<assignment>>
			if ((dependency instanceof DependencyImpl) && 
					StereotypesHelper.getAppliedStereotypeByString(dependency, "assignment") != null) {
				
				// Iterates through all elements connected to the class through that dependency link
				for (Element relatedElement : dependency.getRelatedElement()) {
					
					// Checks if the element is a class with the stereotype <<secuml.user>>
					if (relatedElement instanceof Class &&
							StereotypesHelper.getAppliedStereotypeByString(relatedElement, "secuml.user") != null) {
						
						// A partial association tag is created carrying the name of the user assigned to that role
						AssociationTag roleTag = new AssociationTag();
						roleTag.setUser(((Class)relatedElement).getName());
						roleTag.setRole(classElement.getName());
						roleTags.add(roleTag);
					}
				}
			}
		}
	}

	private boolean addResourcePartitions(List<ActivityPartition> verPartitions,
			List<List<CallBehaviorAction>> actions, List<String> resources,
			Class classElement) {
		boolean existsResource;
		existsResource = true;
		
		// A swimlane with the class's name is added to the activity diagram
		verPartitions.add(ef.createActivityPartitionInstance());
		((ActivityPartition) verPartitions.get(verPartitions.size()-1))
			.setName(classElement.getName());
		
		// A list is created containing references to the swimlane's actions
		List<CallBehaviorAction> partitionActions = new ArrayList<CallBehaviorAction>();
		
		// Iterating through all of the class's operations
		// and adding an action with that operation's name to the swimlane
		for (Operation operation : classElement.getOwnedOperation()) {
			CallBehaviorAction action = ef.createCallBehaviorActionInstance();
			action.setName(operation.getName());
			action.setOwner(activityDiagram.getOwner());
			action.getInPartition().add((ActivityPartition)verPartitions.get(verPartitions.size()-1));
			partitionActions.add(action);
			if (resources.contains(classElement.getName()) == false) {
				resources.add(classElement.getName());
			}
		}
		
		actions.add(partitionActions);
		return existsResource;
	}
	
	
	/**
	 * A method for adding actions to the swimlane that was created 
	 * based on a class with the stereotype <<secuml.role>>
	 * 
	 * @param ef Elements factory
	 * @param diagram Activity diagram
	 * @param partition Swimlane
	 * @param classElement Class with the stereotype <<secuml.role>>
	 * @param actions List of actions, where the created actions will reside
	 * @param actionTags List containing references to the partial association tags 
	 */
	private void addActionsToRolePartition(ElementsFactory ef, Diagram diagram,
			ActivityPartition partition, Class classElement, List<List<CallBehaviorAction>> actions,
			List<AssociationTag> actionTags) {
		
		// Iterator for the relationships of the class with the stereotype <<secuml.role>>
		Iterator<Relationship> relationIt = classElement.get_relationshipOfRelatedElement().iterator();
		while (relationIt.hasNext()) {
			Relationship nextRel = relationIt.next();
			
			// Checks if the relationship is an association class
			if (nextRel instanceof AssociationClassImpl && 
					StereotypesHelper.getAppliedStereotypeByString(nextRel, "secuml.permission") != null) {
				
				// Sees if the association class is connected to a class with the stereotype <<secuml.resource>>
				Iterator<Element> relatedIt = nextRel.getRelatedElement().iterator();
				boolean hasResource = false;
				while (relatedIt.hasNext()) {
					if (StereotypesHelper.getAppliedStereotypeByString(relatedIt.next(), "secuml.resource") != null) {
						hasResource = true;
						break;
					}
				}
				
				// List containing references to the actions of the swimlane
				List<CallBehaviorAction> partitionActions = new ArrayList<CallBehaviorAction>();
				
				if (hasResource) {
					
					// Iterates through the attributes of the association class
					// and adds them to the previously created swimlane
					for (Property attribute : ((Class)nextRel).getOwnedAttribute()) {
						
						CallBehaviorAction action = ef.createCallBehaviorActionInstance();
						action.setName(attribute.getName());
						action.setOwner(activityDiagram.getOwner());
						action.getInPartition().add(partition);
						partitionActions.add(action);
						
						// A new association tag is created
						AssociationTag actionTag = new AssociationTag();
						// Information about the protected action is set
						actionTag.setAction(attribute.getName());
						actionTags.add(actionTag);
						
					}
					
					// Adds the list of this swimlane's actions to the overall actions list
					actions.add(partitionActions);
				}
			}
		}
	}
	
	/**
	 * Opens a wizard that enables the user to join partial association tags
	 * 
	 * @param actionTags Partial association tag containing only the "protected = [action name]" part
	 * @param roleTags Partial association tag containing only the "role = [user name], [role name]" part
	 * @param resources List containing the names of protected resources
	 */
	private void joinTags(List<AssociationTag> actionTags,
			List<AssociationTag> roleTags, List<String> resources) {
		
		JFrame frame = new JFrame();
		
		// Panel that lists the partial tags
		JPanel unjoinedTagsPanel = new JPanel();
		
		// Panel that lists the partial tags with action information
		JPanel actionTagsPanel = new JPanel();
		
		// Panel that lists the partial tags with role information
		JPanel roleTagsPanel = new JPanel();
				
		// Panel that lists the complete association tags
		JPanel joinedTagsPanel = new JPanel();
		
		JPanel upperPanel = new JPanel();
		JPanel lowerPanel = new JPanel();
		JPanel buttonPanel = new JPanel();
		JScrollPane scrollPane = new JScrollPane(joinedTagsPanel);
		
		List<AssociationTag> joinedTags = new ArrayList<AssociationTag>();
		
		actionTagsPanel.setLayout(new BoxLayout(actionTagsPanel, BoxLayout.Y_AXIS));
		roleTagsPanel.setLayout(new BoxLayout(roleTagsPanel, BoxLayout.Y_AXIS));
		unjoinedTagsPanel.setLayout(new BoxLayout(unjoinedTagsPanel, BoxLayout.Y_AXIS));
		joinedTagsPanel.setLayout(new GridLayout(0,1));
		upperPanel.setLayout(new BoxLayout(upperPanel, BoxLayout.Y_AXIS));
		lowerPanel.setLayout(new BoxLayout(lowerPanel, BoxLayout.Y_AXIS));
		frame.setLayout(new BorderLayout());
		
	
		JTextArea instructions = new JTextArea("The following association tags were generated from the diagram. " +
		"\nYou may join the protected actions with the roles \nto make up complete association tags:\n");
		instructions.setEditable(false);
		instructions.setBackground(Color.getColor("DAD8D1"));
		upperPanel.add(instructions);
		
		// For each partial action-related association tag, a checkbox is created
		for (AssociationTag actionTag : actionTags) {
			JCheckBox action = new JCheckBox();
			action.setText(actionTag.getAction());
			action.addActionListener(new CheckBoxListener(actionTagsPanel.getComponents()));
			actionTagsPanel.add(action);
		}
		
		for (Component component : actionTagsPanel.getComponents()) {
			((JCheckBox) component).addActionListener(new CheckBoxListener(actionTagsPanel.getComponents()));
		}
		
		JTextArea actionText = new JTextArea("Protected actions:");
		actionText.setEditable(false);
		actionText.setBackground(Color.getColor("DAD8D1"));
		upperPanel.add(actionText);
		upperPanel.add(actionTagsPanel);
		
		// For each partial role-related association tag, a checkbox is created
		for (AssociationTag roleTag : roleTags) {
			JCheckBox role = new JCheckBox();
			role.setText(roleTag.getUser() + ", " + roleTag.getRole());
			role.addActionListener(new CheckBoxListener(roleTagsPanel.getComponents()));
			roleTagsPanel.add(role);
		}
		
		for (Component component : roleTagsPanel.getComponents()) {
			((JCheckBox) component).addActionListener(new CheckBoxListener(roleTagsPanel.getComponents()));
		}
		
		JTextArea roleText = new JTextArea("\nUsers and roles:");
		roleText.setEditable(false);
		roleText.setBackground(Color.getColor("DAD8D1"));
		upperPanel.add(roleText);
		upperPanel.add(roleTagsPanel);
	
		JButton joinButton = new JButton();
		joinButton.setText("Join");
		joinButton.addActionListener(new JoinButtonListener(actionTagsPanel, roleTagsPanel, joinedTagsPanel,
				actionTags, roleTags, frame, joinedTags));
	
		upperPanel.add(unjoinedTagsPanel);
		upperPanel.add(joinButton);
		
		joinedTagsPanel.add(new JLabel("No complete association tags"));
		lowerPanel.add(scrollPane);
		
		JButton okButton = new JButton("OK");
		okButton.addActionListener(new OkListener(this, joinedTags, 
									resources, frame));
		buttonPanel.add(okButton);
		lowerPanel.add(buttonPanel);
		
		frame.add(upperPanel, BorderLayout.NORTH);
		frame.add(lowerPanel);
		frame.setSize(600, 400);
		frame.setLocationRelativeTo(null);
		frame.setVisible(true);
		
		
	}
	
	/**
	 * This method stores the association tags in a non-visible comment
	 * so that the information would be available when the plug-in is not running.
	 * 
	 * @param tags List containing references to association tags
	 * @param resources List containing the names of protected resources
	 */
	public void createTagComment(List<AssociationTag> tags, List<String> resources) {
		Comment comment = ef.createCommentInstance();
		String commentBody = "";
		
		for (AssociationTag tag : tags) {
			commentBody += tag.getAction() + "," + tag.getUser() +
							"," + tag.getRole() + "\n";
		}
		
		commentBody += "::";
		for (String resource : resources) {
			commentBody += resource + ",";
		}
		
		comment.setBody(commentBody);
		comment.setOwner(activityDiagram);
	}
	
	
}

