/****************************************************************************
 *  This software is part of the gCube Project.
 *  Site: http://www.gcube-system.org/
 ****************************************************************************
 * The gCube/gCore software is licensed as Free Open Source software
 * conveying to the EUPL (http://ec.europa.eu/idabc/eupl).
 * The software and documentation is provided by its authors/distributors
 * "as is" and no expressed or
 * implied warranty is given for its use, quality or fitness for a
 * particular case.
 ****************************************************************************
 * Filename: ManagementUtils.java
 ****************************************************************************
 * @author <a href="mailto:daniele.strollo@isti.cnr.it">Daniele Strollo</a>
 ***************************************************************************/

package org.gcube.resourcemanagement.support.server.managers.resources;

import java.io.File;
import java.rmi.RemoteException;
import java.util.List;
import java.util.Vector;

import org.gcube.common.core.faults.GCUBEFault;
import org.gcube.common.core.informationsystem.client.AtomicCondition;
import org.gcube.common.core.informationsystem.client.queries.GCUBEServiceQuery;
import org.gcube.common.core.resources.GCUBEResource;
import org.gcube.common.core.resources.GCUBEService;
import org.gcube.common.core.scope.GCUBEScope;
import org.gcube.common.core.scope.GCUBEScope.Type;
import org.gcube.resourcemanagement.support.server.exceptions.AbstractResourceException;
import org.gcube.resourcemanagement.support.server.exceptions.ResourceAccessException;
import org.gcube.resourcemanagement.support.server.exceptions.ResourceOperationException;
import org.gcube.resourcemanagement.support.server.exceptions.ResourceParameterException;
import org.gcube.resourcemanagement.support.server.managers.scope.ScopeManager;
import org.gcube.resourcemanagement.support.server.types.AllowedResourceTypes;
import org.gcube.resourcemanagement.support.server.utils.Assertion;
import org.gcube.resourcemanagement.support.server.utils.ServerConsole;
import org.gcube.vremanagement.resourcemanager.stubs.binder.AddResourcesParameters;
import org.gcube.vremanagement.resourcemanager.stubs.binder.PackageItem;
import org.gcube.vremanagement.resourcemanager.stubs.binder.ResourceBinderPortType;
import org.gcube.vremanagement.resourcemanager.stubs.binder.ResourceItem;
import org.gcube.vremanagement.resourcemanager.stubs.binder.ResourceList;
import org.gcube.vremanagement.resourcemanager.stubs.binder.SoftwareList;

/**
 * A support class containing operations to manage multiple resources.
 * Here are provided the functionalities to delete/addToScope/deploy
 * groups of homogeneous resources.
 * @author Daniele Strollo (ISTI-CNR)
 */
public class ManagementUtils {
	private static final String LOG_PREFIX = "[MANAGEMENT-UTILS]";

	/**
	 * Applies the add to scope to multiple resources having the same type.
	 * @param type
	 * @param resources
	 * @param sourceScope
	 * @param targetScope
	 * @return the generated report ID
	 */
	public static final synchronized String addToExistingScope(
			final AllowedResourceTypes type,
			final String[] resourceIDs,
			final GCUBEScope sourceScope,
			final GCUBEScope targetScope)
					throws Exception {
		ServerConsole.trace(
				LOG_PREFIX,
				"[ADD-ToExistingScope] Adding from scope [" +
						sourceScope.toString() +
						"] to existing scope [" +
						targetScope.toString() +
						"] resources having type " + type.name());

		// 1 - If not RI or GHN and the scopes are sibling and VO copyFromToVO
		if (!(type == AllowedResourceTypes.GHN) &&
				!(type == AllowedResourceTypes.RunningInstance) &&
				sourceScope.getType() == Type.VO && targetScope.getType() == Type.VO) {
			// Phase 1. retrieve the resource to copy
			//GCUBEResource resStub = this.getGCUBEResource(sourceScope);

			// Phase 2. Before to register the resource, the scope must be
			// bound to the local GCUBEResource
			String retval = bindToScope(type, resourceIDs, targetScope);

			// Phase 3. Register to the new VO through the ISPublisher
			// applies a copy of old descriptors bound in other scopes.
			try {
				for (String id : resourceIDs) {
					AbstractResourceManager res = ResourceFactory.createResourceManager(type, id);
					GCUBEResource resStub = res.getGCUBEResource(sourceScope);
					res.getISPublisher().registerGCUBEResource(resStub, targetScope, res.getSecurityManager());
				}
			} catch (Exception e) {
				throw new ResourceAccessException(e.getMessage());
			}
			return retval;
		}

		// Add a gCube Resource to
		// (i) a VRE scope from the parent VO or
		// (ii) a VO scope from the infrastructure scope
		if (!targetScope.isEnclosedIn(sourceScope)) {
			throw new ResourceOperationException(
					"You are not allowed to apply to this scope. Target scope is not enclosed in the source one.");
		}

		// 2 - Applies the normal scope binding
		return bindToScope(type, resourceIDs, targetScope);
	}

	/**
	 * Applies the add to scope to multiple resources having the same type.
	 * @param type
	 * @param resources
	 * @param sourceScope
	 * @param targetScope
	 * @return the generated report ID
	 */
	public static final synchronized String removeFromExistingScope(final AllowedResourceTypes type, final String[] resourceIDs, 
			final GCUBEScope sourceScope, final GCUBEScope targetScope)	throws Exception {

		ServerConsole.trace(
				LOG_PREFIX,
				"[REMOVE-FromExistingScope] Removing scope [" +
						sourceScope.toString() +
						"] to existing scope [" +
						targetScope.toString() +
						"] resources having type " + type.name());
		// cannot remove a Scope if its the same
		if (targetScope.toString().compareTo(sourceScope.toString()) == 0) {
			return "You are not allowed to remove this scope. Current and Target scope are the same.";
		}
		AbstractResourceManager resource = ResourceFactory.createResourceManager(type);
		for (String id : resourceIDs) {
			try {
				resource.setID(id);
				resource.delete(targetScope);
			} catch (AbstractResourceException e) {
				ServerConsole.error(LOG_PREFIX, e);
			}
		}

		return "ACK";
	}

	/**
	 *
	 * @param type
	 * @param resourceIDs
	 * @param targetScope
	 * @return the ID of generated report
	 * @throws AbstractResourceException
	 */
	private static synchronized String bindToScope(
			final AllowedResourceTypes type,
			final String[] resourceIDs,
			final GCUBEScope targetScope)
					throws AbstractResourceException {
		AddResourcesParameters addParam = new AddResourcesParameters();
		ResourceBinderPortType manager = ResourceFactory.createResourceManager(type).getResourceManager(targetScope);
		List<ResourceItem> resToBind = new Vector<ResourceItem>();

		for (String id : resourceIDs) {
			ResourceItem toAdd = new ResourceItem();
			toAdd.setID(id);
			toAdd.setType(type.name());
			resToBind.add(toAdd);
		}
		ResourceList r = new ResourceList();
		r.setResource(resToBind.toArray(new ResourceItem[]{}));
		addParam.setResources(r);
		addParam.setTargetScope(targetScope.toString());

		try {
			String reportID =  manager.addResources(addParam);

			ServerConsole.trace(
					LOG_PREFIX,
					"[BIND-SCOPE-EXIT] Applyed Adding of resources " + type.name() + " to scope [" +
							targetScope.toString() + "]... reportID: " + reportID);

			return reportID;
		} catch (Exception e) {
			ServerConsole.trace(
					LOG_PREFIX,
					"[BIND-SCOPE-EXIT] [FAILURE]");
			throw new ResourceOperationException("During resource::addToScope: " + e.getMessage());
		}
	}

	public static synchronized void delete(
			final AllowedResourceTypes type,
			final String[] resourceIDs,
			final GCUBEScope scope)
					throws AbstractResourceException {
		AbstractResourceManager resource = ResourceFactory.createResourceManager(type);
		for (String id : resourceIDs) {
			try {
				resource.setID(id);
				resource.delete(scope);
			} catch (AbstractResourceException e) {
				ServerConsole.error(LOG_PREFIX, e);
			}
		}
	}

	/**
	 * Makes the deployment of software on a list of ghns.
	 * @param ghnsID
	 * @param servicesID
	 * @return the generated report ID
	 * @throws Exception
	 */
	public static final synchronized String deploy(
			final GCUBEScope scope,
			final String[] ghnsID,
			final String[] servicesID)
					throws Exception {
		Assertion<Exception> checker = new Assertion<Exception>();
		checker.validate(ghnsID != null && ghnsID.length != 0, new ResourceParameterException("Invalid ghnsID parameter. It cannot be null or empty."));
		checker.validate(servicesID != null && servicesID.length != 0, new ResourceParameterException("Invalid servicesID parameter. It cannot be null or empty."));
		checker.validate(scope != null, new Exception("Cannot retrieve the scope."));

		AbstractResourceManager resource = ResourceFactory.createResourceManager(AllowedResourceTypes.Service);
		ResourceBinderPortType manager = ResourceFactory.createResourceManager(AllowedResourceTypes.Service).getResourceManager(scope);

		System.out.println("\n\n**** These are the service ids to deploy on SCOPE " + scope);
		for (String sid : servicesID) {
			System.out.println(sid);
		}
		System.out.println("\n\n**** These are the gHNs ids to deploy on SCOPE " + scope);
		for (String ghn : ghnsID) {
			System.out.println(ghn);
		}

		List<PackageItem> serviceProfiles = new Vector<PackageItem>();
		// Retrieves the profiles of services
		final GCUBEServiceQuery query = resource.getISClient().getQuery(GCUBEServiceQuery.class);
		prepareServices: for (String serviceID : servicesID) {
			System.out.println("\n\n**** Query the IsClient to get the profile");
			query.clearConditions();
			query.addAtomicConditions(new AtomicCondition("/ID", serviceID));

			System.out.println("**** Query : " + query.getExpression());

			List<GCUBEService> results = resource.getISClient().execute(query, scope);

			System.out.println("**** results received : " + results.size());

			GCUBEService ret = null;
			if (results != null && results.size() > 0) {
				ret = results.get(0);
			} else {
				continue prepareServices;
			}

			if (ret == null ||
					ret.getServiceClass() == null ||
					ret.getServiceName() == null ||
					ret.getVersion() == null) {
				ServerConsole.error(LOG_PREFIX, "found an invalid service profile");
				continue;
			}

			PackageItem toAdd = new PackageItem();

			toAdd.setServiceClass(ret.getServiceClass());
			toAdd.setServiceName(ret.getServiceName());
			toAdd.setServiceVersion(ret.getVersion());
			if (ret.getPackages().size() == 1) {
				toAdd.setPackageName(ret.getPackages().get(0).getName());
				toAdd.setPackageVersion(ret.getPackages().get(0).getVersion());
			} else {
				for (org.gcube.common.core.resources.service.Package p : ret.getPackages()) {
					if (p.getClass().isAssignableFrom(org.gcube.common.core.resources.service.MainPackage.class)) {
						toAdd.setPackageName(p.getName());
						toAdd.setPackageVersion(p.getVersion());
						break;
					}
				}
			}

			serviceProfiles.add(toAdd);
		}

		SoftwareList serviceList = new SoftwareList();
		serviceList.setSuggestedTargetGHNNames(ghnsID);
		serviceList.setSoftware(serviceProfiles.toArray(new PackageItem[0]));

		AddResourcesParameters addResourcesParameters = new AddResourcesParameters();
		addResourcesParameters.setSoftware(serviceList);
		addResourcesParameters.setTargetScope(scope.toString());

		System.out.println("\n\n**** These is the ServiceList i pass to ResourceManagerPortType: ");
		for (int i = 0; i <  serviceList.getSoftware().length; i++) {
			System.out.println(serviceList.getSoftware()[i]);
		}

		String id = "";
		try {
			id = manager.addResources(addResourcesParameters);
			ServerConsole.debug(LOG_PREFIX, "Report ID = " + id);
		} catch (GCUBEFault e) {
			ServerConsole.error(LOG_PREFIX, "during deployment.", e);
			throw e;
		} catch (RemoteException e) {
			ServerConsole.error(LOG_PREFIX, "during deployment.", e);
			throw e;
		}
		System.out.println("Returning.... no exceptions");
		return id;
	}

	public static final void main(final String[] args) {
		List<String> ids = new Vector<String>();
		ids.add(null);
		ids.add("id2");
		ids.add(null);

		ScopeManager.setScopeConfigFile("test-suite" + File.separator + "scopes" + File.separator + "scopedata.xml");

		try {


			ManagementUtils.delete(AllowedResourceTypes.GenericResource,
					new String[]{
					"3f7384a0-d51c-11df-80cc-ece35605c26c",
					"975419b0-d2e1-11df-b0ed-f8e6e669b8ad",
					null,
					"test"
			},
			ScopeManager.getScope("/gcube/devsec/devVRE"));
		} catch (Exception e) {
			ServerConsole.error(LOG_PREFIX, e);
		}
	}
}
