package de.uni_hamburg.fs;

import collections.CollectionEnumeration;
import collections.HashedMap;
import collections.HashedSet;
import collections.Map;
import collections.UpdatableMap;
import collections.UpdatableSet;


/**
 * Manages tag mapping for feature structure nodes.
 * This class provides functionality to create and maintain mappings between nodes
 * and their unique tag identifiers, which is essential for handling cyclic structures
 * and multiple references to the same node in feature structures.
 * <p>
 * The tag map ensures that nodes with multiple predecessors are assigned unique
 * identifiers, while nodes with single predecessors remain untagged. This mapping
 * is crucial for proper serialization and deserialization of feature structures,
 * especially when dealing with cyclic references.
 */
public class TagMap {
    private UpdatableMap _tagmap = new HashedMap();
    private UpdatableSet _visited = new HashedSet();

    //private UpdatableMap pathmap=new HashedMap();
    private Node _root;
    private int _tagcounter = 0;

    /**
     * Builds a tag map for a Feature Structure.
     * The tag map provides a Name for every node that is connected
     * with the given root node. If a node is labelled with the empty Name,
     * it has exactly one predecessor and need not be tagged.
     * All other nodes have multiple predecessors and get assigned a unique
     * Name that can be used as a tag number.
     *
     * @param node The root node of the feature structure for which to build the tag map
     */
    public TagMap(Node node) {
        _root = node;
        buildTagMap(_root); //Path.EPSILON);
        resetVisited();
    }

    /**
     * Makes a TagMap from a given root node and a Map of tags
     * that was e.g. the result of parsing a feature structure.
     *
     * @param root       The root node of the feature structure
     * @param er         The equivalence relation for node unification
     * @param parsedTags A map containing the parsed tags associated with nodes
     */
    public TagMap(Node root, EquivRelation er, Map parsedTags) {
        this._root = root;
        CollectionEnumeration ptenumeration = parsedTags.keys();
        while (ptenumeration.hasMoreElements()) {
            Name tag = (Name) ptenumeration.nextElement();
            try {
                int tagInt = Integer.parseInt(tag._name);
                _tagcounter = Math.max(_tagcounter, tagInt);
            } catch (NumberFormatException e) {
                // does not have to be a number
            }
            Node node = er.getUnificator((Node) parsedTags.at(tag));
            _tagmap.putAt(node, tag);
        }
    }

    /**
     * Returns the root node of this tag map.
     *
     * @return the root node used to construct this tag map
     */
    public Node getRoot() {
        return _root;
    }

    /**
     * Marks a node as visited and checks if it was previously visited.
     *
     * @param node the node to check and mark as visited
     * @return true if the node was already visited, false otherwise
     */
    public boolean visit(Node node) {
        if (isVisited(node)) { // visited before
            return true;
        } else {
            setVisited(node);
            return false;
        }
    }

    private Name newTag() {
        return new Name(String.valueOf(++_tagcounter));
    }

    private void buildTagMap(Node node) { // Path path) {
        if (node instanceof JavaObject
            || (node instanceof NoFeatureNode && node.getType().isExtensional())) {
            return; // JavaObjects are tagged dynamically,
        }

        // no-feature-nodes with an extensional type are not tagged.
        if (visit(node)) { // visited before
            if (!_tagmap.includesKey(node)) { // ...but not yet mapped
                Name tag = newTag();
                _tagmap.putAt(node, tag); // give node a name
                //pathmap.putAt(Tag,path); // remember path of tag


            }
        } else {
            // do this recursively for all nodes:
            CollectionEnumeration features = node.featureNames();
            while (features.hasMoreElements()) {
                Name featureName = (Name) features.nextElement();
                buildTagMap(node.delta(featureName)); // path.append(featureName));
            }
        }
    }

    /**
     * Clears all visited node markings.
     * Creates a new empty set for tracking visited nodes.
     */
    public void resetVisited() {
        _visited = new HashedSet();
    }

    /**
     * Creates a string of spaces for indentation based on the specified depth.
     *
     * @param depth the number of spaces to create
     * @return a string containing the specified number of spaces
     */
    public final static String indent(int depth) {
        StringBuffer space = new StringBuffer(depth + 1);
        for (int i = 0; i < depth; ++i) {
            space.append(' ');
        }
        return space.toString();
    }

    /**
     * Retrieves the tag associated with a given node.
     * If the node is not already tagged and is a JavaObject, a new tag is created.
     * Otherwise, returns an empty name for nodes without multiple predecessors.
     *
     * @param node the node to get the tag for
     * @return the tag associated with the node, or an empty name if not tagged
     */
    public Name getTag(Node node) {
        if (_tagmap.includesKey(node)) {
            return (Name) _tagmap.at(node);
        } else if (node instanceof JavaObject) {
            Name tag = newTag();
            _tagmap.putAt(node, tag);
            return tag;
        } else {
            return Name.EMPTY;
        }
    }

    /**
     * Checks if a node has already been visited during traversal.
     *
     * @param node the node to check
     * @return true if the node has been visited, false otherwise
     */
    public boolean isVisited(Node node) {
        return _visited.includes(node);
    }

    /**
     * Marks the specified node as visited.
     * Used during traversal to avoid infinite recursion in cyclic structures.
     *
     * @param node the node to mark as visited
     */
    public void setVisited(Node node) {
        _visited.include(node);
    }

    /**
     * Removes a node from the set of visited nodes.
     * This method allows a previously visited node to be revisited.
     *
     * @param node the node to mark as unvisited
     */
    public void setUnvisited(Node node) {
        _visited.removeOneOf(node);
    }
}