/*
 * Copyright 2006-2019 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * https://rcenvironment.de/
 */
package de.rcenvironment.core.communication.common;

import java.util.Objects;

import org.apache.commons.logging.LogFactory;

import de.rcenvironment.core.communication.api.NodeIdentifierService;

/**
 * Utility methods for handling node identifiers and related exception. All methods that require a {@link NodeIdentifierService} instance
 * call {@link NodeIdentifierContextHolder#getDeserializationServiceForCurrentThread()} implicitly.
 * 
 * @author Robert Mischke
 */
public final class NodeIdentifierUtils {

    private NodeIdentifierUtils() {}

    /**
     * Recreates a {@link InstanceNodeId} from the string representation returned by {@link InstanceNodeSessionId#getInstanceNodeIdString()}
     * .
     * 
     * @see NodeIdentifierService#parseInstanceNodeIdString(String)
     * 
     * @param input the string representation to reconstruct the object from
     * @return the reconstructed object, if successful
     * @throws IdentifierException on failure, typically because of a null or malformed string representation
     */
    public static InstanceNodeId parseInstanceNodeIdString(String input) throws IdentifierException {
        return getNodeIdentifierServiceForCurrentThread().parseInstanceNodeIdString(input);
    }

    /**
     * Transitional method; forwards to {@link #parseInstanceNodeIdString(String)}, but maps any {@link IdentifierException} to a
     * RuntimeException.
     * 
     * TODO >= 8.0.0: replace calls to this with proper exception handling
     * 
     * @param input the string representation to reconstruct the object from
     * @return the reconstructed object, if successful
     */
    public static InstanceNodeId parseInstanceNodeIdStringWithExceptionWrapping(String input) {
        try {
            return parseInstanceNodeIdString(input);
        } catch (IdentifierException e) {
            throw wrapIdentifierException(e);
        }
    }

    /**
     * Recreates a {@link InstanceSessionIdId} from the string representation returned by
     * {@link InstanceNodeSessionId#getInstanceNodeSessionIdString()}.
     * 
     * @see NodeIdentifierService#parseInstanceNodeSessionIdString(String)
     * 
     * @param input the string representation to reconstruct the object from
     * @return the reconstructed object, if successful
     * @throws IdentifierException on failure, typically because of a null or malformed string representation
     */
    public static InstanceNodeSessionId parseInstanceNodeSessionIdString(String input) throws IdentifierException {
        return getNodeIdentifierServiceForCurrentThread().parseInstanceNodeSessionIdString(input);
    }

    /**
     * Transitional method; forwards to {@link #parseInstanceNodeIdString(String)}, but maps any {@link IdentifierException} to a
     * RuntimeException.
     * 
     * TODO >= 8.0.0: replace calls to this with proper exception handling
     * 
     * @param input the string representation to reconstruct the object from
     * @return the reconstructed object, if successful
     */
    public static InstanceNodeSessionId parseInstanceNodeSessionIdStringWithExceptionWrapping(String input) {
        try {
            return parseInstanceNodeSessionIdString(input);
        } catch (IdentifierException e) {
            throw wrapIdentifierException(e);
        }
    }

    /**
     * Recreates a {@link LogicalNodeId} from the string representation returned by {@link LogicalNodeSessionId#getLogicalNodeIdString()}.
     * 
     * @param input the string representation to reconstruct the object from
     * @return the reconstructed object, if successful
     * @throws IdentifierException on failure, typically because of a null or malformed string representation
     */
    public static LogicalNodeId parseLogicalNodeIdString(String input) throws IdentifierException {
        return getNodeIdentifierServiceForCurrentThread().parseLogicalNodeIdString(input);
    }

    /**
     * As {@link #parseLogicalNodeIdString(String)}, but with implicit exception wrapping until migration is complete.
     * 
     * TODO >= 8.0.0: replace calls to this with proper exception handling
     * 
     * @param input the string representation to reconstruct the object from
     * @return the reconstructed object, if successful
     */
    public static LogicalNodeId parseLogicalNodeIdStringWithExceptionWrapping(String input) {
        try {
            return parseLogicalNodeIdString(input);
        } catch (IdentifierException e) {
            throw wrapIdentifierException(e);
        }
    }

    /**
     * Special backwards compatibility method. Behaves as {@link #parseLogicalNodeIdString()}, but also accepts other id strings, which are
     * mapped to the default {@link LogicalNodeId} for that {@link InstanceNodeId}.
     * 
     * @param input the string representation to reconstruct the object from
     * @return the reconstructed object, if successful
     * @throws IdentifierException on failure, typically because of a null or malformed string representation
     */
    public static LogicalNodeId parseArbitraryIdStringToLogicalNodeId(String input) throws IdentifierException {
        // TODO (p2) move method into service?
        Objects.requireNonNull(input, "Cannot parse 'null' string to a node identifier"); // null ids are not allowed since 8.0
        final int length = input.length();
        if (length == CommonIdBase.INSTANCE_ID_STRING_LENGTH) {
            return parseInstanceNodeIdString(input).convertToDefaultLogicalNodeId();
        } else if (length == CommonIdBase.INSTANCE_SESSION_ID_STRING_LENGTH) {
            return parseInstanceNodeSessionIdString(input).convertToDefaultLogicalNodeId();
        } else {
            // logical node or logical node session id; determine by looking at the separator positions
            int pos = input.lastIndexOf(CommonIdBase.STRING_FORM_PART_SEPARATOR);
            if (pos == CommonIdBase.INSTANCE_ID_STRING_LENGTH) {
                // only one separator; the one after the instance part -> logical node id
                return parseLogicalNodeIdString(input);
            } else {
                if (pos < 0) {
                    throw new IdentifierException("Unexpected state while trying to parse arbitrary id string '" + input + "'");
                }
                // by exclusion, assuming a logical node id now
                return parseLogicalNodeSessionIdString(input).convertToLogicalNodeId();
            }
        }
    }

    /**
     * As {@link #parseArbitraryIdStringToLogicalNodeId(String)}, but with implicit exception wrapping until migration is complete.
     * 
     * @param input the string representation to reconstruct the object from
     * @return the reconstructed object, if successful
     */
    public static LogicalNodeId parseArbitraryIdStringToLogicalNodeIdWithExceptionWrapping(String input) {
        try {
            return parseArbitraryIdStringToLogicalNodeId(input);
        } catch (IdentifierException e) {
            throw wrapIdentifierException(e);
        }
    }

    /**
     * Recreates a {@link LogicalNodeSessionId} from the string representation returned by
     * {@link LogicalNodeSessionId#getLogicalNodeSessionIdString()}.
     * 
     * @param input the string representation to reconstruct the object from
     * @return the reconstructed object, if successful
     * @throws IdentifierException on failure, typically because of a null or malformed string representation
     */
    public static LogicalNodeSessionId parseLogicalNodeSessionIdString(String input) throws IdentifierException {
        return getNodeIdentifierServiceForCurrentThread().parseLogicalNodeSessionIdString(input);
    }

    /**
     * Central method to wrap {@link IdentifierException}s as {@link RuntimeException}s in places where proper exception handling is not
     * currently possible (as the old code did not cause such exceptions, instead failing silently or with undefined behavior). Obviously,
     * calls to this method should be eliminated better sooner than later.
     * 
     * @param e the exception to wrap
     * @return the wrapping {@link RuntimeException}
     */
    public static RuntimeException wrapIdentifierException(IdentifierException e) {
        LogFactory.getLog(NodeIdentifierUtils.class).error("Wrapping IdentifierException for backwards compatibility", e);
        return new RuntimeException("Wrapping identifier exception for backwards compatibility", e);
    }

    private static NodeIdentifierService getNodeIdentifierServiceForCurrentThread() {
        return NodeIdentifierContextHolder.getDeserializationServiceForCurrentThread();
    }
}
