package com.capgemini.framework.teamcenter.authent;

import com.capgemini.framework.common.security.AesEncoded;
import com.capgemini.framework.teamcenter.ServiceDataHelper;
import com.capgemini.framework.teamcenter.exceptions.ConnectionMisconguredException;
import com.capgemini.framework.teamcenter.exceptions.FailedRelationshipActionException;
import com.capgemini.framework.teamcenter.exceptions.InvalidConfigurationException;
import com.capgemini.framework.teamcenter.exceptions.KurtException;
import com.capgemini.framework.teamcenter.exceptions.MalformedDataSetException;
import com.capgemini.framework.teamcenter.exceptions.NoSuchRevisionRuleException;
import com.capgemini.framework.teamcenter.exceptions.NoTeamcenterConnectionException;
import com.capgemini.framework.teamcenter.exceptions.runtime.ConnectionLostException;
import com.capgemini.framework.teamcenter.object.pom.workspace.bom.TcRevisionRule;
import com.capgemini.framework.teamcenter.object.runtime.TcBomWindow;
import com.capgemini.framework.teamcenter.object.runtime.TcBomWindowConfiguration;
import com.capgemini.framework.teamcenter.object.runtime.TcBomWindowFactory;
import com.capgemini.framework.teamcenter.policies.PolicyLocation;
import com.teamcenter.schemas.soa._2006_03.exceptions.InvalidCredentialsException;
import com.teamcenter.schemas.soa._2006_03.exceptions.ServiceException;
import com.teamcenter.services.loose.core._2006_03.FileManagement.DatasetFileInfo;
import com.teamcenter.services.loose.core._2006_03.FileManagement.GetDatasetWriteTicketsInputData;
import com.teamcenter.services.strong.cad.StructureManagementService;
import com.teamcenter.services.strong.cad._2007_01.StructureManagement.GetRevisionRulesResponse;
import com.teamcenter.services.strong.cad._2007_01.StructureManagement.RevisionRuleInfo;
import com.teamcenter.services.strong.core.DataManagementService;
import com.teamcenter.services.strong.core.SessionService;
import com.teamcenter.services.strong.core._2006_03.DataManagement.CreateRelationsResponse;
import com.teamcenter.services.strong.core._2006_03.DataManagement.Relationship;
import com.teamcenter.services.strong.core._2007_01.DataManagement.WhereReferencedInfo;
import com.teamcenter.services.strong.core._2007_01.DataManagement.WhereReferencedOutput;
import com.teamcenter.services.strong.core._2007_01.DataManagement.WhereReferencedResponse;
import com.teamcenter.services.strong.core._2007_12.Session.StateNameValue;
import com.teamcenter.services.strong.core._2010_04.DataManagement.CreateDatasetsResponse;
import com.teamcenter.services.strong.core._2010_04.DataManagement.DatasetInfo;
import com.teamcenter.services.strong.core._2010_09.DataManagement.NameValueStruct1;
import com.teamcenter.services.strong.core._2010_09.DataManagement.PropInfo;
import com.teamcenter.services.strong.core._2010_09.DataManagement.SetPropertyResponse;
import com.teamcenter.services.strong.core._2011_06.Session.Credentials;
import com.teamcenter.services.strong.core._2011_06.Session.LoginResponse;
import com.teamcenter.services.strong.core._2012_02.DataManagement.WhereUsedConfigParameters;
import com.teamcenter.services.strong.core._2012_02.DataManagement.WhereUsedInputData;
import com.teamcenter.services.strong.core._2012_02.DataManagement.WhereUsedParentInfo;
import com.teamcenter.services.strong.core._2012_02.DataManagement.WhereUsedResponse;
import com.teamcenter.services.strong.query.FinderService;
import com.teamcenter.services.strong.query.SavedQueryService;
import com.teamcenter.services.strong.workflow.WorkflowService;
import com.teamcenter.soa.SoaConstants;
import com.teamcenter.soa.client.Connection;
import com.teamcenter.soa.client.FileManagementUtility;
import com.teamcenter.soa.client.model.ErrorStack;
import com.teamcenter.soa.client.model.ModelObject;
import com.teamcenter.soa.client.model.ServiceData;
import com.teamcenter.soa.client.model.strong.ImanRelation;
import com.teamcenter.soa.client.model.strong.ItemRevision;
import com.teamcenter.soa.client.model.strong.RevisionRule;
import com.teamcenter.soa.client.model.strong.User;
import com.teamcenter.soa.client.model.strong.WorkspaceObject;
import com.teamcenter.soaictstubs.ICCTRevisionRule;
import com.teamcenter.soaictstubs.ICCTSession;
import com.teamcenter.soaictstubs.StringHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;


/**
 * Teamcenter connection management.
 **/
public class TcSession implements Closeable {
    /**
     * Default user login.
     **/
    private static final String USERNAME = System.getProperty("teamcenter.session.login"); // Prefix is required
    private static final Logger LOGGER = LoggerFactory.getLogger(TcSession.class);
    /**
     * Teamcenter server URL.
     **/
    private static final String URL = System.getProperty("teamcenter.url");
    private static final String ERROR_MSG_UNABLE_TO_OBTAIN_SESSION_INFO_TC_SERVER_ID = "Unable to obtain usual session info: TcServerID";
    private static final String TC_SERVER_ID = "TcServerID";
    private static final int TIMEOUT_SEC = 120;
    private static final String ERROR_MSG_LOGIN_TIMEOUT = "Timed out during login to Tc Servers after " + TIMEOUT_SEC + "sec";
    /**
     * Default user password.
     **/
    private static final String PASSWORD = System.getProperty("teamcenter.session.password"); // Prefix is required

    private static final String FSC_CLIENT_IP = System.getProperty("fsc.clientip"); // Prefix is required
    private static final String FSC_CACHE_DIR = System.getProperty("fsc.cachedir"); // Prefix is required
    private static final String[] FSC_ASSIGNED_URI = AuthentPropLoader.getStringArray("fsc.assignedURI", ","); // Prefix is required
    private static final String[] FSC_BOOTSTRAP_URI = AuthentPropLoader.getStringArray("fsc.bootstrapURI", ","); // Prefix is required
    private static final FscParameters DEFAULT_FSC_PARAMS = new FscParameters(FSC_CLIENT_IP, FSC_ASSIGNED_URI, FSC_BOOTSTRAP_URI, FSC_CACHE_DIR);
    private final FscParameters fscParams;
    /**
     * Single instance of the Connection object that is shared throughout the application.<br/>
     * This Connection object is needed whenever a Service stub is instantiated.
     */
    private Connection connection;

    /**
     * Connection status. Helps prevent double-logout, double login, or logout without prior login.
     */
    private LoginResponse loggedIn;
    /**
     * Common service for structure resolutions. Lazily initialized, use getter method.
     **/
    private StructureManagementService structureManagementService;
    /**
     * Common service for finding records in Teamcenter. Lazily initialized, use getter method.
     **/
    private FinderService finderService;
    /**
     * Saved Query Service. Lazily initialized, use getter method.
     */
    private SavedQueryService savedQueryService;
    /**
     * file Management Utility. Lazily initialized, use getter method.
     */
    private FileManagementUtility fileManagementUtility;
    /**
     * Worfklow service. Lazily initialized, use getter method.
     */
    private WorkflowService workflowService;
    private String discriminator;

    /**
     * Default constructor using settings from properties.
     **/
    public TcSession() {
        this(URL, DEFAULT_FSC_PARAMS);
    }

    /**
     * Create an instance of the Session with a connection to the specified server.
     * <p>
     * Add implementations of the ExceptionHandler, PartialErrorListener, ChangeListener, and DeleteListeners.
     *
     * @param aConnectionUrl Address of the host to connect to, http://serverName:port/tc
     * @param fscParameters  the FSC connection parameters
     */
    public TcSession(final String aConnectionUrl, final FscParameters fscParameters) {
        this.fscParams = fscParameters;
        initHostConnection(aConnectionUrl);
    }

    /**
     * Connection status.
     *
     * @return true if the connection is logged in.
     */
    private boolean isLoggedIn() {
        return loggedIn != null;
    }

    /**
     * Create an instance of the CredentialManager.<br/>
     * This is used by the SOA Framework to findAll the user's credentials when challenged by the server<br/>
     * (session timeout on the web tier).
     **/
    private void initHostConnection(final String aConnectionUrl) {
        TcCredentialManager credentialManager = new TcCredentialManager();
        LOGGER.debug("Initialize connection to URL: {}", aConnectionUrl);
        String connectionUrl;
        String protocol;
        String envNameTccs = null;
        if (aConnectionUrl.startsWith("http")) {
            protocol = SoaConstants.HTTP;
            connectionUrl = aConnectionUrl;
        } else if (aConnectionUrl.startsWith("tccs")) {
            protocol = SoaConstants.TCCS;
            connectionUrl = aConnectionUrl.trim();
            int envNameStart = connectionUrl.indexOf('/') + 2;
            envNameTccs = connectionUrl.substring(envNameStart, connectionUrl.length());
            connectionUrl = "";
        } else {
            protocol = SoaConstants.IIOP;
            connectionUrl = aConnectionUrl;
        }

        // Create the Connection object, no contact is made with the server until a service request is made
        connection = new Connection(connectionUrl, credentialManager, SoaConstants.REST, protocol);

        if (protocol.equals(SoaConstants.TCCS)) {
            connection.setOption(Connection.TCCS_ENV_NAME, envNameTccs);
        }

        connection.getModelManager().addModelEventListener(new KurtModelEventListener());

        // Add an ExceptionHandler to the Connection, this will handle any InternalServerException, communication errors, XML marshaling errors
        connection.setExceptionHandler(new TcExceptionHandler());
    }

    /**
     * Login to the Teamcenter Server. This method is guaranteed to terminate.
     *
     * @return a {@link LoginResponse} object
     * @throws NoTeamcenterConnectionException if unable to setup the connection (Tc might not be responding)
     */
    public LoginResponse login() throws NoTeamcenterConnectionException {
        if (loggedIn != null) {
            return loggedIn;
        }
        // Timeout the logout in async thread
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<LoginResponse> future = executor.submit(new LoginTask()); // Start performing the logout...
        try {
            loggedIn = future.get(TIMEOUT_SEC, TimeUnit.SECONDS); // Check if successful, do not wait more than this
            emptyPolicy();
            enableBypass();
            LOGGER.info("TcSession logged in {}/{}", USERNAME, discriminator);
        } catch (TimeoutException e) {
            // More than xxx seconds for a login? the Tc servers must be hanging, findAll out
            future.cancel(true);
            throw new NoTeamcenterConnectionException(ERROR_MSG_LOGIN_TIMEOUT, e);
        } catch (InterruptedException e) {
            throw new NoTeamcenterConnectionException("Login interrupted", e);
        } catch (Exception e) {
            Throwable rootCause = e.getCause();
            if (rootCause instanceof InvalidCredentialsException) {
                NoTeamcenterConnectionException noTeamcenterConnectionException =
                        new NoTeamcenterConnectionException("Failed connection to Tc services", rootCause);
                noTeamcenterConnectionException.addSuppressed(e);
                throw noTeamcenterConnectionException;
            } else if (rootCause instanceof ConnectionLostException) { // Connections as made, but no session were available
                throw (ConnectionLostException) rootCause;
            } else if (rootCause instanceof ServiceException) {
                throw new NoTeamcenterConnectionException("Service Exception encountered during login", rootCause);
            } else {
                throw new NoTeamcenterConnectionException("Connection to Teamcenter failed", e);
            }
        }
        executor.shutdown();
        return loggedIn;
    }

    /**
     * Terminate the session with the Teamcenter Server gracefully.
     */
    @Override
    public void close() {
        if (isLoggedIn()) {
            // Terminate an optional FileManagementUtility separately
            if (fileManagementUtility != null) {
                fileManagementUtility.term();
            }

            // Timeout the logout in async thread
            ExecutorService executor = Executors.newSingleThreadExecutor();
            Future<String> future = executor.submit(new LogoutTask()); // Start performing the logout...
            try {
                LOGGER.debug(future.get(TIMEOUT_SEC, TimeUnit.SECONDS)); // Check if successful, do not wait more than that
                LOGGER.info("TcSession logged out {}/{}", USERNAME, discriminator);
            } catch (TimeoutException e) {
                // More than xxx seconds for a logout? the Tc servers must be hanging, findAll out
                future.cancel(true);
                LOGGER.info("Timed out during logout of Tc Servers", e);
            } catch (InterruptedException e) {
                LOGGER.info("Logout interrupted", e);
            } catch (ExecutionException e) {
                Throwable rootCause = e.getCause();
                if (rootCause instanceof ConnectionLostException) {
                    // This is ok, we can't LOGGER out because lost connection in the first place
                    LOGGER.trace("Unable to logout of Tc session", e);
                } else {
                    LOGGER.info("Unable to logout of Tc session", e);
                }
            }
            executor.shutdown();
            loggedIn = null;
        } else {
            LOGGER.warn("Trying to logout of the session, but the session is not logged in");
        }
    }

    public void setObjectPropPolicy(final PolicyLocation policy) throws InvalidConfigurationException {
        boolean success = setPolicy(policy.getIdentifier());
        LOGGER.info(success ? "Policy \"{}\" file is now the active policy" : "Policy \"{}\" file could not be loaded ({})", policy.getIdentifier());
    }

    public void emptyPolicy() throws InvalidConfigurationException {
        setPolicy("Empty");
    }

    /**
     * Changes the policy on the server, for this session.
     */
    private boolean setPolicy(String identifier) throws InvalidConfigurationException {
        try {
            return SessionService.getService(connection).setObjectPropertyPolicy(identifier);
        } catch (ServiceException e) {
            throw new InvalidConfigurationException("Teamcenter could not load Object Property Policy file \"" + identifier + "\"", e);
        }
    }

    /**
     * Enable bypass for dba user.
     */
    private void enableBypass() {
        StateNameValue property = new StateNameValue();
        property.name = "bypassFlag";
        property.value = Boolean.TRUE.toString();
        SessionService sessionService = SessionService.getService(connection);
        ServiceData response = sessionService.setUserSessionState(new StateNameValue[]{property});
        if (ServiceDataHelper.hasErrors(response)) {
            ServiceDataHelper.displayErrors(response);
        }
    }

    /**
     * Getter for structureManagementService.
     *
     * @return Service instance for structure management
     **/
    public StructureManagementService getStructureManagementService() {
        if (structureManagementService == null) {
            structureManagementService = StructureManagementService.getService(connection);
        }
        return structureManagementService;
    }

    /**
     * Getter for finderService.
     *
     * @return Service instance for finder
     **/
    public FinderService getFinderService() {
        if (finderService == null) {
            finderService = FinderService.getService(connection);
        }
        return finderService;
    }

    /**
     * Returns a {@link SavedQueryService} to perform searches using Saved Queries.
     *
     * @return a SavedQueryService
     */
    public SavedQueryService getSavedQueryService() {
        if (savedQueryService == null) {
            savedQueryService = SavedQueryService.getService(connection);
        }
        return savedQueryService;
    }

    /**
     * Returns a new instance of {@link WorkflowService}.
     *
     * @return a new instance of the service
     */
    public WorkflowService getWorkflowService() {
        if (workflowService == null) {
            workflowService = WorkflowService.getService(connection);
        }
        return workflowService;
    }

    /**
     * Returns a {@link FileManagementUtility} instance.
     * <p>
     * There is no need to {@link FileManagementUtility#term() term()} this instance after usage.<br>
     * Cleanup will be performed by this {@link TcSession} upon being {@link TcSession#close() closed}.<br>
     * This instance is created on-demand, and cached for later use, as the object is rather heavy (server-side).
     *
     * @return a {@link FileManagementUtility} instance
     * @throws ConnectionMisconguredException if FSC parameters are incorrect
     */
    public FileManagementUtility getFileManagementUtility() throws ConnectionMisconguredException {
        if (fileManagementUtility == null) {
            String clientIPAddress = fscParams.getClientIPAddress();
            String[] assignedFscURIs = fscParams.getAssignedFscURIs();
            String[] bootstrapFscURIs = fscParams.getBootstrapFscURIs();
            String cacheDir = fscParams.getCacheDir();
            try {
                fileManagementUtility = new FileManagementUtility(connection, clientIPAddress, assignedFscURIs, bootstrapFscURIs, cacheDir);
            } catch (FileNotFoundException e) {
                throw new ConnectionMisconguredException("Failed to start FileManagementUtility, FSC parameters might not be set properly", e);
            }
        }
        return fileManagementUtility;
    }

    public TcBomWindow createBomWindow(final TcBomWindowConfiguration bomWindowConfiguration) throws KurtException {
        return new TcBomWindowFactory(this, bomWindowConfiguration).create();
    }

    public TcRevisionRule findRevRule(final String revRuleNameToFind) throws KurtException {
        try {
            GetRevisionRulesResponse revisionRulesResponse = getStructureManagementService().getRevisionRules();
            if (ServiceDataHelper.hasErrors(revisionRulesResponse.serviceData)) {
                ServiceDataHelper.displayErrors(revisionRulesResponse.serviceData);
                throw new NoSuchRevisionRuleException("Error while loading revision rules.");
            }
            for (RevisionRuleInfo revisionRuleInfo: revisionRulesResponse.output) {
                RevisionRule revRule = revisionRuleInfo.revRule;
                loadProperties(revRule, "object_name");
                String revRuleName = revRule.get_object_name();
                if (revRuleNameToFind.equals(revRuleName)) {
                    LOGGER.debug("Revision rule {} found.", revRuleName);
                    return new TcRevisionRule(this, revRule);
                }
            }
        } catch (ServiceException e) {
            throw new NoSuchRevisionRuleException("Teamcenter could not load the revision rules.", e);
        }
        throw new NoSuchRevisionRuleException("Unable to find the Revision Rule: " + revRuleNameToFind);
    }

    public List<RevisionRule> findR26RevRule(String reportRevRule) throws NoSuchRevisionRuleException {
        List<RevisionRule> revisionRules = new ArrayList<>();
        try {
            GetRevisionRulesResponse revisionRulesResponse = getStructureManagementService().getRevisionRules();
            if (ServiceDataHelper.hasErrors(revisionRulesResponse.serviceData)) {
                ServiceDataHelper.displayErrors(revisionRulesResponse.serviceData);
                throw new NoSuchRevisionRuleException("Error while loading revision rules.");
            }
            for (RevisionRuleInfo revisionRuleInfo: revisionRulesResponse.output) {
                RevisionRule revRule = revisionRuleInfo.revRule;
                loadProperties(revRule, "object_name");
                String revRuleName = revRule.get_object_name();
                if (revRuleName.startsWith(reportRevRule + "-")) {
                    LOGGER.debug("Revision rule {} found.", revRuleName);
                    revisionRules.add(revRule);
                }
            }
        } catch (ServiceException e) {
            throw new NoSuchRevisionRuleException("Teamcenter could not load the revision rules.", e);
        }
        return revisionRules;
    }

    public void loadProperties(ModelObject modelObjects, String... properties) {
        loadProperties(new ModelObject[]{modelObjects}, properties);
    }

    public void loadProperties(ModelObject[] modelObjects, String... properties) {
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("Loading {} properties on objects {}", properties.length == 0 ? "all" : Arrays.toString(properties), Arrays.toString(modelObjects));
        }
        ServiceData serviceData = DataManagementService.getService(connection).getProperties(modelObjects, properties);
        if (ServiceDataHelper.hasErrors(serviceData)) {
            ServiceDataHelper.displayErrors(serviceData);
        }
    }

    public void setProperties(ModelObject modelObject, Map<String, Object> map) {
        PropInfo propInfo = new PropInfo();
        propInfo.object = modelObject;
        propInfo.vecNameVal = new NameValueStruct1[map.size()];

        int i = 0;
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            NameValueStruct1 nameValueStruct1 = new NameValueStruct1();
            nameValueStruct1.name = entry.getKey();

            Object propertyValue = entry.getValue();
            if (propertyValue != null) {
                if (propertyValue.getClass().equals(String.class)) {
                    nameValueStruct1.values = new String[1];
                    nameValueStruct1.values[0] = (String) propertyValue;

                    propInfo.vecNameVal[i] = nameValueStruct1;
                    i++;
                } else if (propertyValue.getClass().equals(String[].class)) {
                    nameValueStruct1.values = (String[]) propertyValue;

                    propInfo.vecNameVal[i] = nameValueStruct1;
                    i++;
                }
            }
        }

        SetPropertyResponse setPropertyResponse = DataManagementService.getService(connection).setProperties(new PropInfo[]{propInfo}, new String[]{});
        if (ServiceDataHelper.hasErrors(setPropertyResponse.data)) {
            ServiceDataHelper.displayErrors(setPropertyResponse.data);
        }
    }

    public <T> List<T> loadObjects(String... uids) {
        List<T> objectLoaded = new ArrayList<>();
        ServiceData serviceData = DataManagementService.getService(connection).loadObjects(uids);
        if (ServiceDataHelper.hasErrors(serviceData)) {
            ServiceDataHelper.displayErrors(serviceData);
        }
        for (int objectIndex = 0; objectIndex < serviceData.sizeOfPlainObjects(); objectIndex++) {
            T object = (T) serviceData.getPlainObject(objectIndex);
            objectLoaded.add(object);
        }
        return objectLoaded;
    }

    public List<ItemRevision> whereReferenced(final WorkspaceObject workspaceObject, final int level, final Map<String, List<String>> relationAndTypesFilter) throws KurtException {
        WhereReferencedResponse whereReferencedResponse = DataManagementService.getService(connection).whereReferenced(new WorkspaceObject[]{workspaceObject}, level);
        if (ServiceDataHelper.hasErrors(whereReferencedResponse.serviceData)) {
            ServiceDataHelper.displayErrors(whereReferencedResponse.serviceData);
            throw new KurtException("Some errors were encountered while performing 'referencedBy' request");
        }
        List<ItemRevision> referencedObjects = new ArrayList<>();
        WhereReferencedOutput[] response = whereReferencedResponse.output;
        for (WhereReferencedOutput whereReferencedOutput: response) {
            LOGGER.debug("InputObject client : {}", whereReferencedOutput.inputObject);
            for (WhereReferencedInfo outputInfo: whereReferencedOutput.info) {
                String objectType = workspaceObject.getTypeObject().getName();
                LOGGER.debug("object candidate {} relation {}", objectType, outputInfo.relation);
                List<String> listRelationTypes = relationAndTypesFilter.get(objectType);
                if (listRelationTypes != null && listRelationTypes.contains(outputInfo.relation)) {
                    referencedObjects.add((ItemRevision) outputInfo.referencer);
                }
            }
        }
        return referencedObjects;
    }

    public GetDatasetWriteTicketsInputData createDatasets(final DatasetInfo[] datasetProperties, final DatasetFileInfo[] currFiles) throws MalformedDataSetException {
        // Perform dataset request using properties
        CreateDatasetsResponse resp = DataManagementService.getService(connection).createDatasets(datasetProperties);
        if (ServiceDataHelper.hasErrors(resp.servData)) {
            ServiceDataHelper.displayErrors(resp.servData);
            throw new MalformedDataSetException("Unable to perform the request, dataset creation failed");
        }

        // dataset accepted, now fill the response with dataset data
        GetDatasetWriteTicketsInputData dataSetInfoAndData = new GetDatasetWriteTicketsInputData();
        dataSetInfoAndData.dataset = resp.datasetOutput[0].dataset;
        dataSetInfoAndData.createNewVersion = false;
        dataSetInfoAndData.datasetFileInfos = currFiles;
        return dataSetInfoAndData;
    }

    public ImanRelation createRelation(final ModelObject primary, final ModelObject secondary, final String relationType) throws FailedRelationshipActionException {
        Relationship relation = new Relationship();
        relation.clientId = "KURT-reports";
        relation.primaryObject = primary;
        relation.secondaryObject = secondary;
        relation.relationType = relationType;
        CreateRelationsResponse relationCreationResult = DataManagementService.getService(connection).createRelations(new Relationship[]{relation});
        if (ServiceDataHelper.hasErrors(relationCreationResult.serviceData)) {
            ServiceDataHelper.displayErrors(relationCreationResult.serviceData);
        }
        if (relationCreationResult.output.length == 0) {
            throw new FailedRelationshipActionException("The requested relationship could not be created.");
        }
        return relationCreationResult.output[0].relation;
    }

    public void deleteRelations(final ModelObject primary, final ModelObject secondary, final String relationType) throws FailedRelationshipActionException {
        Relationship relation = new Relationship();
        relation.clientId = "KURT-reports";
        relation.primaryObject = primary;
        relation.secondaryObject = secondary;
        relation.relationType = relationType;
        ServiceData relationDeletionResult = DataManagementService.getService(connection).deleteRelations(new Relationship[]{relation});
        if (ServiceDataHelper.hasErrors(relationDeletionResult)) {
            ServiceDataHelper.displayErrors(relationDeletionResult);
        }
        if (relationDeletionResult.sizeOfDeletedObjects() == 0) {
            throw new FailedRelationshipActionException("The requested relationship could not be destroyed.");
        }
    }

    public void deleteObjects(final ModelObject... modelObjects) {
        ServiceData serviceData = DataManagementService.getService(connection).deleteObjects(modelObjects);
        if (ServiceDataHelper.hasErrors(serviceData)) {
            ServiceDataHelper.displayErrors(serviceData);
        }
    }


    public List<ItemRevision> whereUsed(final ItemRevision inputObject, final String typeWanted) throws KurtException {

        WhereUsedConfigParameters configParameters = new WhereUsedConfigParameters();
        configParameters.stringMap.put("Type", typeWanted);
        configParameters.intMap.put("numLevels", BigInteger.valueOf(1));

        WhereUsedInputData inputData = new WhereUsedInputData();
        inputData.inputObject = inputObject;
        inputData.inputParams = configParameters;

        WhereUsedResponse allResponses = DataManagementService.getService(connection).whereUsed(new WhereUsedInputData[]{inputData}, configParameters);

        List<ItemRevision> results = new ArrayList<>();

        if (ServiceDataHelper.hasErrors(allResponses.serviceData)) {
            ServiceDataHelper.displayErrors(allResponses.serviceData);
            throw new KurtException("Error while retrieving whereUsed on " + inputObject.get_item_id() );
        }



        for (WhereUsedParentInfo info: allResponses.output[0].info) {

            if (info.level == 1) {
                ItemRevision result = (ItemRevision) info.parentObject;
                if (typeWanted.equals(result.getTypeObject().getName())) {

                    results.add(result);
                }
            }
        }

        return results;
    }



    public List<ItemRevision> whereUsed(final WorkspaceObject inputObject, final int level, final RevisionRule revisionRule) {
        WhereUsedConfigParameters configParameters = new WhereUsedConfigParameters();
        configParameters.intMap.put("numLevels", BigInteger.valueOf(level));
        if (revisionRule != null) {
            configParameters.tagMap.put("revision_rule", revisionRule);
        }

        WhereUsedInputData inputData = new WhereUsedInputData();
        inputData.inputObject = inputObject;
        inputData.inputParams = configParameters;

        WhereUsedResponse allResponses = DataManagementService.getService(connection).whereUsed(new WhereUsedInputData[]{inputData}, configParameters);
        List<ItemRevision> results = new ArrayList<>();
        for (WhereUsedParentInfo info: allResponses.output[0].info) {
            ItemRevision itemRevision = (ItemRevision) info.parentObject;

            loadProperties(itemRevision, "active_seq");
            int activeSeq = itemRevision.get_active_seq();

            // only add item last sequence (means the active sequence)
            if (activeSeq != 0) {
                results.add((ItemRevision) info.parentObject);
            }
        }
        return results;
    }

    /**
     * Login task. This {@link Callable} can be executed concurrently to perform login. Outcome is the {@link User} logged in.
     */
    private final class LoginTask implements Callable<LoginResponse> {
        @Override
        public LoginResponse call() throws IOException, KurtException {
            try {
                SessionService sessionService = SessionService.getService(connection);
                Credentials crd = new Credentials();
                crd.user = USERNAME;
                crd.password = new AesEncoded(PASSWORD).decode();
                discriminator = UUID.randomUUID().toString();
                crd.descrimator = discriminator;
                crd.locale = "FR";
                LoginResponse loginResponse = sessionService.login(crd);
                for (Object obj: loginResponse.serverInfo.entrySet()) {
                    @SuppressWarnings("unchecked")
                    Map.Entry<String, String> entry = (Map.Entry<String, String>) obj;
                    LOGGER.info(entry.getKey() + " : " + entry.getValue());
                }
                return loginResponse;
            } finally {
                if (Thread.interrupted()) { // The Future got cancelled (probably because of Timeout)...
                    close(); // ...so we make sure to close() the session (the caller has given up and won't do it)
                }
            }
        }
    }

    /**
     * Logout task. This {@link Callable} can be executed concurrently to perform logout. Outcome is the {@link User} logged out.
     */
    private final class LogoutTask implements Callable<String> {
        @Override
        public String call() throws ServiceException {
            SessionService sessionService = SessionService.getService(connection);
            if (LOGGER.isDebugEnabled()) {
                try {
                    LOGGER.debug("[LOGOUT] Showing TCInfo content for session {} : {}", discriminator, sessionService.getTCSessionInfo().extraInfo.get(TC_SERVER_ID).toString());
                } catch (ServiceException e) {
                    LOGGER.warn(ERROR_MSG_UNABLE_TO_OBTAIN_SESSION_INFO_TC_SERVER_ID, e);
                }
            }
            sessionService.logout();
            return "Logout successful";
        }
    }


    /**
     * Clone revision rule: a new Revision Rule is created with the name defined in parameter.
     */
    public void cloneRevisionRule(RevisionRule revRuleToCopy, String name) throws Exception {
        ICCTSession icctSession = new ICCTSession(connection);
        int markpoint = icctSession.placeMarkpoint(new String[0]);
        try {
            ICCTRevisionRule icctRevisionRule = new ICCTRevisionRule(connection, revRuleToCopy.getTypeObject().getName(), revRuleToCopy.getTypeObject().getUid());
            StringHolder newRevRule = new StringHolder();
            StringHolder revisionRuleType = new StringHolder();
            icctRevisionRule.create(name, name, newRevRule, revisionRuleType);
            icctRevisionRule.cloneInstance(revRuleToCopy.getUid(), newRevRule.value);
            icctRevisionRule.save(newRevRule.value);
            icctSession.forgetMarkpoint(markpoint);
        }
        catch (Exception e) {
            icctSession.rollToMarkpoint(markpoint);
        }
    }
}
