package de.renew.formalism.fs;

import java.util.Hashtable;
import java.util.Vector;

import collections.CollectionEnumeration;
import de.uni_hamburg.fs.ConjunctiveType;
import de.uni_hamburg.fs.FSNode;
import de.uni_hamburg.fs.FeatureStructure;
import de.uni_hamburg.fs.Name;
import de.uni_hamburg.fs.Node;
import de.uni_hamburg.fs.Type;
import de.uni_hamburg.fs.TypeException;
import de.uni_hamburg.fs.TypeSystem;
import de.uni_hamburg.fs.UnificationFailure;

import de.renew.simulator.api.SimulationManager;
import de.renew.simulatorontology.loading.NetNotFoundException;
import de.renew.unify.Impossible;
import de.renew.unify.Tuple;


/**
 * Represents a net instance with a feature-structure-based marking.
 * Provides methods to extract, process, and fire transitions on
 * the marking stored in a feature structure.
 */
public class MarkingNetInstance extends ObjectNetInstance {

    /**
     * Logger for the {@link MarkingNetInstance} class.
     */
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(MarkingNetInstance.class);

    /**
     * Creates a new MarkingNetInstance from a feature structure.
     *
     * @param fs the feature structure containing the net name and initial marking
     * @param confirm if {@code true}, confirms the instance immediately after creation
     * @throws Impossible if the unification cannot be completed with the current values
     * @throws NetNotFoundException if no definition for the specified net could be found
     */
    public MarkingNetInstance(FeatureStructure fs, boolean confirm)
        throws Impossible, NetNotFoundException
    {
        super((String) fs.at("net").getJavaObject(), extractMarking(fs));
        if (confirm) {
            confirm();
        }
    }

    /**
     * Creates a new MarkingNetInstance from a feature structure.
     *
     * @param fs the feature structure containing the net name and initial marking
     * @throws Impossible if the unification cannot be completed with the current values
     * @throws NetNotFoundException if no definition for the specified net could be found
     */
    public MarkingNetInstance(FeatureStructure fs) throws Impossible, NetNotFoundException {
        this(fs, true);
    }

    /**
     * Confirms the marking net instance for the current simulation step.
     *
     * @return this instance
     */
    public MarkingNetInstance confirm() {
        createConfirmation(SimulationManager.getCurrentSimulator().currentStepIdentifier());
        return this;
    }

    /**
     * Creates a new Vector containing a single object.
     *
     * @param o the object to add to the vector
     * @return a {@code Vector} containing only the given object
     */
    private static Vector<Object> vector(Object o) {
        Vector<Object> v = new Vector<Object>();
        v.addElement(o);
        return v;
    }

    /**
     * Unifies a transition into a process feature structure and equates
     * multiple paths to the first unified path.
     *
     * @param proc the process feature structure
     * @param transition the transition feature structure to fire
     * @param paths the paths in the process to equate
     * @return the updated process feature structure after unification
     * @throws UnificationFailure if unification fails
     */
    public FeatureStructure equateAndFire(
        FeatureStructure proc, FeatureStructure transition, String[] paths)
        throws UnificationFailure
    {
        String path0 = "proc" + paths[0];
        proc = proc.unify(transition, path0);
        for (int i = 1; i < paths.length; ++i) {
            proc = proc.equate(path0, "proc" + paths[i]);
        }
        return proc;
    }

    /**
     * Fires a transition into the given process feature structure.
     * If multiple paths are provided as a Tuple, they are equated to the first path.
     *
     * @param proc the process feature structure
     * @param transition the transition feature structure to fire
     * @param paths a single path ({@code String}) or multiple paths ({@link Tuple}) to update
     * @return the updated process feature structure after firing the transition
     * @throws UnificationFailure if unification fails
     */
    public FeatureStructure fire(FeatureStructure proc, FeatureStructure transition, Object paths)
        throws UnificationFailure
    {
        //((FSNode)transition.getRoot()).totallyWellType();
        if (paths instanceof Tuple) {
            return equateAndFire(
                proc, transition, (String[]) ((Tuple) paths).asArray(String.class));
        } else {
            return proc.unify(transition, "proc" + paths);
        }
    }

    private static Hashtable<String, Vector<Object>> extractMarking(FeatureStructure fs) {
        Hashtable<String, Vector<Object>> placeMap = new Hashtable<String, Vector<Object>>();
        FSNode proc = (FSNode) fs.getRoot().delta(new Name("proc"));


        //proc.totallyWellType();
        extractMarking(placeMap, new Vector<Node>(), "", proc);
        placeMap.put("Process", vector(fs));
        return placeMap;
    }

    /**
     * Recursively traverses the given feature structure node and collects
     * all leaf feature paths into the provided place map.
     * Each entry maps a feature name to the list of full paths leading
     * to its leaf occurrences.
     *
     * @param placeMap the map to fill with feature names and their paths
     * @param visited the list of already visited nodes to prevent cycles
     * @param path the current feature path prefix
     * @param curr the current node to process
     */
    private static void extractMarking(
        Hashtable<String, Vector<Object>> placeMap, Vector<Node> visited, String path, Node curr)
    {
        if (visited.contains(curr)) { // already visited?
            return;
        }
        visited.addElement(curr);
        CollectionEnumeration featenumeration = curr.featureNames();
        while (featenumeration.hasMoreElements()) {
            Name featureName = (Name) featenumeration.nextElement();
            String feature = featureName.toString();
            String newpath = path + ":" + feature;
            Node next = curr.delta(featureName);
            if (next.featureNames().hasMoreElements()) {
                extractMarking(placeMap, visited, newpath, next);
            } else {
                // leaf found
                if (placeMap.containsKey(feature)) {
                    placeMap.get(feature).addElement(newpath);
                } else {
                    placeMap.put(feature, vector(newpath));
                }
            }
        }
    }

    /**
     * Processes the marking of a net represented by the given feature structure.
     *
     * @param fs the feature structure representing the net
     * @return a new feature structure representing the processed marking
     */
    public static FeatureStructure processMarking(FeatureStructure fs) {
        Type leafType = null;
        Type nodeType = null;
        Type emptyType = null;
        TypeSystem ts = TypeSystem.instance();
        if (!ts.hasConcept("E") || !ts.hasConcept("T")) {
            LOGGER.error("Type E or T not found!");
            return null;
        }
        FSNode vals = null;
        try {
            emptyType = ConjunctiveType.getType("E");
            nodeType = ConjunctiveType.getType("T");
            if (!ts.hasConcept("Token")) {
                LOGGER.error("Type Token not found - using type T.");
                leafType = nodeType;
            } else {
                leafType = ConjunctiveType.getType("Token");
                vals = new FSNode("Marking");
            }
        } catch (UnificationFailure uff) {
            LOGGER.error(uff.getMessage(), uff);
        }

        try {
            FSNode proc = (FSNode) fs.getRoot();
            FSNode state = new FSNode("State");
            state.setFeature(new Name("proc"), proc);
            FSNode mark = new FSNode("Consumers");


            //logger.debug("Type(mark)="+mark.getType());
            state.setFeature(new Name("mark"), mark);
            if (vals != null) {
                state.setFeature(new Name("values"), vals);
            }

            processMarking(new Vector<Node>(), proc, mark, vals, leafType, nodeType);

            CollectionEnumeration featenumeration = mark.getType().appropFeatureNames();
            while (featenumeration.hasMoreElements()) {
                Name feat = (Name) featenumeration.nextElement();
                if (!mark.hasFeature(feat)) {
                    mark.setFeature(feat, new FSNode(emptyType));
                }
            }

            return new FeatureStructure(state);
        } catch (TypeException tee) {
            LOGGER.error("TypeException while processing marking!");
            return null;
        } catch (UnificationFailure uff) {
            return null;
        }
    }

    private static void processMarking(
        Vector<Node> visited, Node curr, FSNode mark, FSNode vals, Type leafType, Type nodeType)
        throws TypeException
    {
        if (visited.contains(curr)) { // already visited?
            return;
        }
        visited.addElement(curr);
        CollectionEnumeration featenumeration = curr.featureNames();
        while (featenumeration.hasMoreElements()) {
            Name featureName = (Name) featenumeration.nextElement();
            if (curr.getType().equals(leafType) && featureName.toString().equals("val")) {
                continue; // do not recurse into tokens!
            }
            Node next = curr.delta(featureName);
            Type nextType = next.getType();
            if (nextType.equals(leafType)) {
                Name valName = new Name("val");
                if (next.hasFeature(valName)) {
                    // coloured leaf:
                    Name consName = new Name("cons");
                    if (!next.hasFeature(consName)) {
                        ((FSNode) next).setFeature(consName, new FSNode(nodeType));
                    }
                    Node free = next.delta(consName);
                    if (free.getType().equals(nodeType)) {
                        // real leaf found
                        mark.setFeature(featureName, free);


                        // make deep copy of token value:
                        //  Node valCopy=null;
                        //  try {
                        //      valCopy=next.delta(valName);
                        //      valCopy=EquivRelation.unify
                        //      (valCopy,new FSNode(Type.TOP));
                        //      vals.setFeature(featureName,valCopy);
                        //  } catch (UnificationFailure uff) {
                        //      logger.error("***Unexpected unification failure: Trying to add "+featureName+": "+valCopy);
                        //  }
                        vals.setFeature(featureName, next.delta(valName));
                    }
                } else {
                    // leaf of B/E net found:
                    mark.setFeature(featureName, next);
                }
            }
            if (leafType.subsumes(nextType) || nodeType.subsumes(nextType)) {
                processMarking(visited, next, mark, vals, leafType, nodeType);
            }
        }
    }
}