package de.renew.formalism.efsnet;

import collections.CollectionEnumeration;
import collections.HashedSet;
import collections.UpdatableSet;
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.UnificationFailure;

import de.renew.expression.Function;
import de.renew.unify.Impossible;


public class ProcessRuleFunction implements Function, EFSNetConstants {
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(ProcessRuleFunction.class);
    private boolean _valueSem;
    private FeatureStructure _processRule;

    public ProcessRuleFunction(boolean valueSem, FeatureStructure processRule) {
        this._valueSem = valueSem;
        this._processRule = processRule;
    }

    @Override
    public Object function(Object param) throws Impossible {
        if (param instanceof FeatureStructure) {
            scut((FeatureStructure) param);
            return applyRule(_valueSem, (FeatureStructure) param, _processRule);
        } else {
            throw new Impossible("Argument of ProcessRuleFunction was not a Feature Structure!");
        }
    }

    public static FeatureStructure applyRule(
        boolean valueSem, FeatureStructure proc, FeatureStructure rule) throws Impossible
    {
        FeatureStructure nextProc = null;
        try {
            nextProc = scut(proc).unify(rule); // may fail
        } catch (UnificationFailure uff) {
            throw new Impossible();
        }

        // the following should not fail:
        try {
            if (valueSem) {
                nextProc = nextProc
                    .unify(ValueMarkingFunction.valmark(nextProc.at(PATH_POST)), PATH_POSTC);
            } else {
                nextProc = nextProc.equate(PATH_POST, PATH_POSTC);
            }
            return nextProc.at(PATH_PROC);
        } catch (UnificationFailure uff) {
            LOGGER.error("Unexpected unification failure in ProcessRuleFunction.applyRule().");
            return null;
        }
    }

    public static FeatureStructure scut(FeatureStructure p) {
        String netSpace = SingleEFSNetCompiler.getNamespace(p.getType());
        Node scut = null;
        try {
            scut = new FSNode(netSpace + "PEff");
            scut.setFeature(FEAT_PRE, new FSNode(netSpace + "M"));
        } catch (UnificationFailure uff) {
            LOGGER.error("Type " + netSpace + "PEff or M not found!");
            return null;
        }
        scut.setFeature(FEAT_PROC, p.getRoot());
        findTokens(p.getRoot(), scut, new HashedSet());
        return new FeatureStructure(scut);
    }

    private static void findTokens(Node proc, Node scut, UpdatableSet visited) {
        // look for proc nodes which contain a feature m:s
        // but do not contain the feature s (s is not consumed).
        // Then, this proc node is assigned as a value for the
        // feature s in scut.
        if (visited.includes(proc)) {
            return;
        }
        visited.include(proc);
        Node procM = proc.delta(FEAT_M);
        Node scutPre = scut.delta(FEAT_PRE);
        CollectionEnumeration prodTokens = procM.featureNames();

        // get all produced tokens:
        while (prodTokens.hasMoreElements()) {
            Name prodToken = (Name) prodTokens.nextElement();
            if (!proc.hasFeature(prodToken)) {
                // token is not cosumed
                if (scut.hasFeature(prodToken)) {
                    // has been assigned before!
                    throw new RuntimeException("More than one token on place " + prodToken + "!");
                }
                scut.setFeature(prodToken, proc);
                scutPre.setFeature(prodToken, procM.delta(prodToken));
            }
        }
        CollectionEnumeration consTokens = proc.featureNames();
        while (consTokens.hasMoreElements()) {
            Name consToken = (Name) consTokens.nextElement();
            if (!consToken.equals(FEAT_M)) {
                findTokens(proc.delta(consToken), scut, visited);
            }
        }
    }
}