package it.micegroup.voila2.cartridge.library.javaservice;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.transaction.RecordingCommand;
import org.eclipse.emf.transaction.RunnableWithResult;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.uml2.uml.Actor;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.Component;
import org.eclipse.uml2.uml.EnumerationLiteral;
import org.eclipse.uml2.uml.Model;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.PackageableElement;
import org.eclipse.uml2.uml.Profile;
import org.eclipse.uml2.uml.Type;
import org.eclipse.uml2.uml.UseCase;

public class ModelUtils {

	private static final String CATEGORY_MAIN_MS = "mainMS";

	public void doEditing(EObject element) {
		// Make sure your element is attached to a resource, otherwise this will return
		// null
		TransactionalEditingDomain domain = TransactionUtil.getEditingDomain(element);
		domain.getCommandStack().execute(new RecordingCommand(domain) {

			@Override
			protected void doExecute() {
				// Implement your write operations here,
				// for example: set a new name
				element.eSet(element.eClass().getEStructuralFeature("name"), "aNewName");
			}
		});
	}

	static Collection<Class> modelEntities = new ArrayList<>();
	static Collection<Class> generalModelEntities = new ArrayList<>();
	static Collection<Package> subjectAreas = new ArrayList<>();
	static Collection<Class> mainEntities = new ArrayList<>();

	static Collection<Component> microservices = new ArrayList<>();
	static Component gateway = null;
	static Component SPA = null;
	static Profile voila2Profile = null;

	// Check se lasciare così o fare con collection nel caso ci sia bisogno di più
	// gateway
	/**
	 * Return the component with Gateway Stereotype
	 * 
	 * @author Simone Punzo <simone.punzo@micegroup.it>
	 * @param the Model
	 * @return the gateway of the model
	 */
	public static Component getGateway() {
		return gateway;
	}

	/**
	 * Inizializza la SPA
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param aModel
	 */
	public static void initializeSpa(Model aModel) {
		if (SPA != null)
			SPA = null;
		for (Type type : aModel.getOwnedTypes()) {
			if (type instanceof Component && MicroServiceUtils.hasSPAStereotype((Component) type)) {
				SPA = (Component) type;
			}
		}
	}

	/**
	 * Ritorna la SPA dell'applicazione
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @return
	 */
	public static Component getSpa() {
		return SPA;
	}

	/**
	 * Inizializza il component gateway del modello
	 * 
	 * @param aModel
	 */
	public static void initializeGateway(Model aModel) {
		if (gateway != null)
			gateway = null;
		for (Type type : aModel.getOwnedTypes()) {
			if (type instanceof Component && MicroServiceUtils.hasGatewayStereotype((Component) type)) {
				gateway = (Component) type;
			}
		}
	}

	/**
	 * Return list of all components that have MicroService Stereotype
	 * 
	 * @author Simone Punzo <simone.punzo@micegroup.it>
	 * @param model
	 * @return set of microservices of that model
	 */
	public static Collection<Component> getMicroservices() {
		return microservices;
	}

	/**
	 * Inizializza il vettore di microservices del modello
	 * 
	 * @author Simone Punzo
	 * @param aModel
	 */
	public static void initializeMicroservices(Model aModel) {
		initializeSpa(aModel);
		if (microservices.size() != 0) {
			microservices.clear();
		}
		for (Type type : aModel.getOwnedTypes()) {
			if (type instanceof Component && MicroServiceUtils.hasMicroServiceStereotype((Component) type)) {
				microservices.add((Component) type);
			}
		}

	}

	/**
	 * Returns list of all entities belonging to the model that started the
	 * generation
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param model
	 * @return Set of classes
	 */
	public static Collection<Class> getModelEntities() {
		return modelEntities;
	}

	/**
	 * Returns list of all entities belonging to all imported models of general
	 * model. NB: General Model should include all other models.
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @return
	 */
	public static Collection<Class> getGeneralModelEntities() {
		return generalModelEntities;
	}

	/**
	 * Inizializza le entità sulle quali si procederà per la generazione
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param model
	 */
	@SuppressWarnings("unchecked")
	public static void initializeEntities(Model model) {
//		ConsoleUtils.consoleMessage(SeverityLevels.ERROR, "Initialize Entities");
		modelEntities = new ArrayList<Class>();
		generalModelEntities = new ArrayList<Class>();
		subjectAreas = new ArrayList<Package>();
		mainEntities = new ArrayList<Class>();

		TransactionalEditingDomain domain = TransactionUtil.getEditingDomain(model);

		EList<Package> importedPackages = null;

		// VN Per la lettura degli elementi importati nel modello è stato necessario
		// introdurre questa transazione di lettura. USare il metodo doEdit per
		// attivarne una di scrittura.
		try {
			importedPackages = (EList<Package>) domain.runExclusive(new RunnableWithResult.Impl() {

				/**
				 * Ritorna la lista delle entità dai modelli importati
				 * 
				 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
				 *
				 * @param model
				 * @return
				 */
				public Collection<Class> getEntitiesFromImportedPackages(Model model) {
					Collection<Class> classi = new ArrayList<>();

					for (Package pack : model.getImportedPackages()) {
						if (pack != null) {
							if (pack instanceof Model) {
								Model modello = (Model) pack;
								if (!isRootModel(modello)) {
									for (Type type : modello.getOwnedTypes()) {
										if (type instanceof Class && EntityUtils.isEntityStereotype((Class) type)) {
											classi.add((Class) type);
										}
									}
								}
							}
						}
					}
					return classi;
				}

				public void run() {

					EList<Package> packages = model.getImportedPackages();
					for (Package pack : packages) {
						if (pack != null) {
							if (pack instanceof Profile) {
								voila2Profile = (Profile) pack;
							}
							if (pack instanceof Model) {
								Model importedModel = (Model) pack;

								// Modello ROOT importato nel modello del Microservizio
								if (isRootModel(importedModel)) {
									generalModelEntities.addAll(getEntitiesFromImportedPackages(importedModel));
								}
								// Modelli delle CLASSI importate nel modello del Microservizio
								else if (isMainMSModel(importedModel)) {
									modelEntities.addAll(getEntitiesFromImportedPackages(importedModel));
								} else {
									// Classi appartenenti ai modelli importati dal modello da cui è partita la
									// generazione

									for (PackageableElement element : importedModel.getPackagedElements()) {
										// Entità del package del modello
										if (element instanceof Package) {
											if (SubjectAreaUtils.isSubjectAreaStereotype((Package) element)) {
												subjectAreas.add((Package) element);
											}
											for (PackageableElement packType : ((Package) element).getOwnedTypes()) {
												addClassToEntities(packType);
											}
										}
										// Entità del modello
										addClassToMainEntities(element);
									}
								}
							}
						}

						// MODELLO DAL QUALE E' PARTITA LA GENERAZIONE
//						modelEntities.addAll(getEntitiesFromImportedPackages(model));
					}

					setResult(model.getImportedPackages());
				}

			});
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public static Profile getVoila2Profile() {
		return voila2Profile;
	}

	private static boolean isMainMSModel(Model model) {
		return isInCategory(model, ModelCategory.mainMS);
	}

	public static void addClassToEntities(PackageableElement type) {
		if (type instanceof Class && EntityUtils.isEntityStereotype((Class) type)) {
			modelEntities.add((Class) type);
			generalModelEntities.add((Class) type);
		}
	}

	public static void addClassToMainEntities(PackageableElement type) {
		addClassToEntities(type);
		if (type instanceof Class && EntityUtils.isEntityStereotype((Class) type)) {
			mainEntities.add((Class) type);
		}
	}

	private static boolean isRootModel(Model model) {
		return isInCategory(model, ModelCategory.root);
	}

	/**
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param aClass
	 * @param category
	 * @return
	 */
	public static boolean isInCategory(Model model, ModelCategory category) {
		String modelCategory = getCategory(model);

		if (modelCategory != null && category.toString().equals(modelCategory))
			return true;
		return false;
	}

	/**
	 * Ritorna la categoria di appartenenza di un modello
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param aClass
	 * @return
	 */
	public static String getCategory(Model model) {
		EnumerationLiteral stereotypeValue = StereotypeUtils.<EnumerationLiteral, Model>getStereotypeValue(model,
				StereotypeName.MODEL, StereotypePropertyName.CATEGORY);
		if (stereotypeValue != null)
			return stereotypeValue.getName();
		return null;
	}

	/**
	 * Return business entities
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param model
	 * @return
	 */
	public static Collection<Class> getBusinessEntities(Model model) {
		return getEntityOfCategory(model, EntityCategory.business);
	}

	/**
	 * Return entities related to Mailing
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param model
	 * @return
	 */
	public static Collection<Class> getMailEntities(Model model) {
		return getEntityOfCategory(model, EntityCategory.mail);
	}

	/**
	 * Return entities related to Attachment
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param model
	 * @return
	 */
	public static Collection<Class> getAttachmentEntities(Model model) {
		return getEntityOfCategory(model, EntityCategory.attachment);
	}

	/**
	 * Return entities related to Security
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param model
	 * @return
	 */
	public static Collection<Class> getSecurityEntities(Model model) {
		return getEntityOfCategory(model, EntityCategory.security);
	}

	/**
	 * Return entities for a given category
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param model
	 * @param category
	 * @return
	 */
	public static Collection<Class> getEntityOfCategory(Model model, EntityCategory category) {
		Collection<Class> result = new ArrayList<>();
		for (Class classe : getModelEntities()) {
			if (EntityUtils.isInCategory(classe, category)) {
				result.add(classe);
			}
		}
		return result;
	}

	/**
	 * Return a set of useCases belonging to model
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param model
	 * @return Set of useCases
	 */
	public static Collection<UseCase> getUseCases(Model model) {
		Collection<UseCase> useCases = new ArrayList<>();
		List<Type> types = model.getOwnedTypes();

		for (Type type : types) {
			if (type instanceof UseCase) {
				useCases.add((UseCase) type);
			}
		}
		return useCases;
	}

	/**
	 * Return a collection of features belonging to models
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param model
	 * @return Set of useCases
	 */
	public static Collection<UseCase> getFeatures(Model model) {
		Collection<UseCase> features = new ArrayList<>();
		for (org.eclipse.uml2.uml.Package pack : model.getImportedPackages()) {
			if (pack != null) {
				for (Type type : pack.getOwnedTypes()) {
					if (type instanceof UseCase && (FeatureUtils.isGenericFeatureStereotype((UseCase) type)
							|| FeatureUtils.isEntitySpecificFeatureStereotype((UseCase) type))) {
						features.add((UseCase) type);
					}
				}
			}
		}

		for (Type type : model.getOwnedTypes()) {
			if (type instanceof UseCase && (FeatureUtils.isGenericFeatureStereotype((UseCase) type)
					|| FeatureUtils.isEntitySpecificFeatureStereotype((UseCase) type))) {
				features.add((UseCase) type);
			}
		}
		return features;
	}

	/**
	 * Method return if the current model has at least one actor
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 * @param model
	 * @return
	 */
	public static boolean hasActor(Model model) {
		for (Type type : model.getOwnedTypes()) {
			if (type instanceof Actor) {
				return true;
			}
		}
		return false;
	}

	public static boolean isRoot(Model model) {
		return ModelUtils.isInCategory(model, ModelCategory.root);
	}

	public static boolean isMainMS(Model model) {
		return ModelUtils.isInCategory(model, ModelCategory.mainMS);
	}

	/**
	 * Method that checks if the model has a security microservice and return its
	 * name if so , or an empty string if not.
	 * 
	 * @author Simone Punzo <simone.punzo@micegroup.it>
	 * @param model
	 * @return
	 */
	public static String getSecurityMSName(Model model) {
		Collection<Component> microservices = ModelUtils.getMicroservices();

		for (Component microservice : microservices) {
			if (MicroServiceUtils.isSecurityMS(microservice)) {
				return microservice.getName().toString();
			}
		}
		return "noSecurityMS";
	}

	/**
	 * Check if Model has SPA component
	 * 
	 * @author Simone Punzo <simone.punzo@micegroup.it>
	 * @param model
	 * @return FLAG --- 1 if component diagram has a SPA component ;; 0 if not
	 */
	public static boolean isFrontEndGenerationEnabled(Model model) {
		if (SPA == null)
			return false;
		return true;
	}

	/**
	 * Check if Model has GATEWAY Component
	 * 
	 * @author Simone Punzo <simone.punzo@micegroup.it>
	 * @param model
	 * @return FLAG --- 1 if component diagram has a GATEWAY component ;; 0 if not
	 */
	public static boolean isGatewayGenerationEnabled(Model model) {
		if (gateway == null)
			return false;
		return true;
	}

	public static Collection<Component> getMsInCategory(Model model, MicroServiceCategory category) {
		Collection<Component> result = new ArrayList<>();
		for (Component ms : getMicroservices()) {
			if (MicroServiceUtils.isInCategory(ms, category)) {
				result.add(ms);
			}
		}
		return result;
	}

	public static Collection<Component> getSecurityMs(Model model) {
		return getMsInCategory(model, MicroServiceCategory.security);
	}

	public static Collection<Component> getBusinessMs(Model model) {
		return getMsInCategory(model, MicroServiceCategory.business);
	}

	public static Collection<Component> getDemoMs(Model model) {
		return getMsInCategory(model, MicroServiceCategory.demo);
	}

	public static Collection<Component> getNotificationMs(Model model) {
		return getMsInCategory(model, MicroServiceCategory.notification);
	}

	/**
	 * Inizializza il component SPA del modello
	 * 
	 * @param aModel
	 */
	public static void initializeSPA(Model aModel) {
		if (SPA != null)
			SPA = null;
		for (Type type : aModel.getOwnedTypes()) {
			if (type instanceof Component && MicroServiceUtils.hasSPAStereotype((Component) type)) {
				SPA = (Component) type;
			}
		}
	}

	/**
	 * Metodo per incorporare tutti i controlli da fare sul modello previa
	 * generazione
	 * 
	 * @author Simone Punzo <simone.punzo@micegroup.it>
	 * @param aModel
	 * @return true if tutto ok , false viceversa
	 */
	public static boolean checkModel(Model aModel) {
		if (checkDuplicateNames(aModel))
			return false;

		return true;
	}

	/**
	 * Metodo per controllare se i microservizi hanno nomi uguali
	 * 
	 * @author Simone Punzo <simone.punzo@micegroup.it>
	 * @param aModel
	 * @return true se ci sono due ms con lo stesso nome , false se tutti hanno nomi
	 *         diversi
	 */
	public static boolean checkDuplicateNames(Model aModel) {
		initializeMicroservices(aModel);

		String name = null;
		ArrayList<Component> microservicesList = new ArrayList<>(microservices);
		for (int i = 0; i < microservicesList.size(); ++i) {
			Component ms = microservicesList.get(i);
			name = ms.getName().toString();
			for (int j = i + 1; j < microservicesList.size(); ++j) {
				Component ms2 = (Component) microservicesList.get(j);
				if (name.equals(ms2.getName().toString()))
					return true;
			}
		}
		return false;
	}

	/**
	 * Ritorna true se esiste almeno un microservizio noSQL nel modello in input
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @param aModel Modello sul quale eseguire il controllo
	 * @return true se esiste almeno un microservizio noSQL
	 */
	public static boolean hasNoSqlMicroservice(Model aModel) {
		for (Component microservice : getMicroservices()) {
			if (MicroServiceUtils.isNoSqlMicroservice(microservice)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Ritorna una lista dells subjectArea presenti nel modello
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @return
	 */
	public static Collection<Package> getSubjectAreas(Model aModel) {
		return subjectAreas;
	}

	/**
	 * Ritorna la lista delle entità appartenenti alla main Subject Area
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @return
	 */
	public static Collection<Class> getModelEntitiesFromMainSA(Model aModel) {
		return mainEntities;
	}

	/**
	 * Ritorna l'url della SPA
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @return
	 */
	public static String getSpaUrl() {
		String stereotypeValue = StereotypeUtils.<String, Component>getStereotypeValue(getSpa(), StereotypeName.SPA,
				StereotypePropertyName.URL);
		if (stereotypeValue != null)
			return stereotypeValue;
		return null;
	}

	/**
	 * Ritorna la porta della SPA
	 * 
	 * @author Vittorio Niespolo <vittorio.niespolo@micegroup.it>
	 *
	 * @return
	 */
	public static Integer getSpaPortNumber() {
		Integer stereotypeValue = StereotypeUtils.<Integer, Component>getStereotypeValue(getSpa(), StereotypeName.SPA,
				StereotypePropertyName.PORT_NUMBER);
		if (stereotypeValue != null)
			return stereotypeValue;
		return null;
	}

}
