package de.renew.formalism.fs;

import java.util.Iterator;

import collections.CollectionEnumeration;
import de.uni_hamburg.fs.Concept;
import de.uni_hamburg.fs.ConceptImpl;
import de.uni_hamburg.fs.ConceptSet;
import de.uni_hamburg.fs.CyclicHierarchyException;
import de.uni_hamburg.fs.Name;
import de.uni_hamburg.fs.OrderedTable;
import de.uni_hamburg.fs.ParsedConjunctiveType;
import de.uni_hamburg.fs.ParsedListType;
import de.uni_hamburg.fs.ParsedType;
import de.uni_hamburg.fs.Partition;
import de.uni_hamburg.fs.TypeException;
import de.uni_hamburg.fs.TypeSystem;
import de.uni_hamburg.fs.UnificationFailure;

import de.renew.net.NetInstance;
import de.renew.shadowcompiler.ShadowLookup;
import de.renew.shadowcompiler.ShadowPreprocessor;
import de.renew.simulatorontology.shadow.ShadowNet;
import de.renew.simulatorontology.shadow.ShadowNetElement;
import de.renew.simulatorontology.shadow.ShadowNetSystem;
import de.renew.simulatorontology.shadow.SyntaxException;


/**
 * ShadowPreprocessors's in the context of FS
 */
public class FSNetPreprocessor implements ShadowPreprocessor {
    /**
     * Default constructor for FSNetPreprocessor.
     */
    public FSNetPreprocessor() {}

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

    /**
     * Name of the default "Link" concept used in the FS
     */
    public static final String LINK = "Link";

    /**
     * Feature name representing the "receiver" of a link concept.
     */
    public static final Name RCV = new Name("rcv");

    /**
     * Feature name representing a parameter of a link concept.
     */
    public static final Name PARAM = new Name("param");
    private ShadowLookup _shadowLookup;

    @Override
    public void setShadowLookup(ShadowLookup shadowLookup) {
        this._shadowLookup = shadowLookup;
    }

    /**
     * Returns the current ShadowLookup service.
     * @return the shadow lookup service
     */
    protected ShadowLookup getShadowLookup() {
        return _shadowLookup;
    }

    @Override
    public void preprocess(ShadowNetSystem netSystem) throws SyntaxException {
        // compile the type hierarchy...
        // First, remove all (old) user concepts:
        TypeSystem ts = TypeSystem.newInstance();
        Partition topPartition = ts.getTopPartition();


        // add link concept
        ConceptImpl linkConcept = new ConceptImpl(LINK);
        linkConcept.addApprop(
            RCV, new ParsedConjunctiveType(new ConceptSet(ts.getJavaConcept(NetInstance.class))));
        linkConcept.addApprop(PARAM, ParsedType.PARSED_TOP);


        // second, compile all concepts from all drawings:
        Iterator<ShadowNet> iterator = netSystem.elements().iterator();
        while (iterator.hasNext()) {
            compileConcepts(iterator.next(), topPartition);
        }


        // then, add all "approp"-information:
        iterator = netSystem.elements().iterator();
        while (iterator.hasNext()) {
            compileApprops(iterator.next());
        }


        // then, add all "isNotA"-information:
        iterator = netSystem.elements().iterator();
        while (iterator.hasNext()) {
            compilePartitions(iterator.next());
        }


        // last, add all "isa"-information:
        iterator = netSystem.elements().iterator();
        while (iterator.hasNext()) {
            compileIsas(iterator.next());
        }

        // and now:
        try {
            ts.inheritFeatures();
        } catch (UnificationFailure uff) {
            SyntaxException ex;
            if (uff instanceof TypeException) {
                TypeException tex = (TypeException) uff;
                String msg = "Feature \"" + tex._featureName
                    + "\" has been redefined illegally in concept \"" + tex._concept.getName()
                    + "\".";
                ex = new SyntaxException(msg, uff);
                ex.addObject(
                    FSShadowLookupExtension.lookup(_shadowLookup)
                        .getApprop(tex._concept, tex._featureName));
            } else {
                ex = new SyntaxException(
                    "Unification failure while constructing feature types.", uff);
            }
            throw ex;
        }
    }

    private void compileConcepts(ShadowNet net, Partition topPartition) throws SyntaxException {
        String netName = net.getName();
        Iterator<ShadowNetElement> iterator = net.elements().iterator();
        while (iterator.hasNext()) {
            ShadowNetElement elem = iterator.next();
            if (elem instanceof ShadowConcept) {
                ShadowConcept shadowConcept = (ShadowConcept) elem;
                if (shadowConcept.getNamespace().equals(netName)) {
                    // only compile "local" concepts
                    String conceptName = shadowConcept.getFullName();
                    conceptName = withoutStereotype(conceptName);
                    LOGGER.debug("Compiling concept " + conceptName + "...");
                    // Concept with that name may already exist:
                    if (TypeSystem.instance().hasConcept(conceptName)) {
                        SyntaxException ex = new SyntaxException(
                            "The concept \"" + conceptName + "\" already exists.");
                        ex.addObject(shadowConcept);
                        throw ex;
                    }
                    ConceptImpl concept = new ConceptImpl(conceptName);
                    FSShadowLookupExtension.lookup(_shadowLookup)
                        .setConcept(shadowConcept, concept);
                    if (shadowConcept.elements().isEmpty()) {
                        // is this a top level concept?
                        try {
                            topPartition.addConcept(concept);
                        } catch (TypeException tex) {
                            // should never happen!
                            throw new RuntimeException(tex.toString());
                        }
                    }

                    //logger.debug("Created Concept "+conceptName);
                }
            }
        }
    }

    private static String withoutStereotype(String str) {
        return str.replaceAll("<<[^>]*>>", "").replaceAll("\n", "");
    }

    private Concept lookup(ShadowConcept shadowConcept) throws SyntaxException {
        // check referenced concepts:
        String conceptName = withoutStereotype(shadowConcept.getFullName());
        if (!TypeSystem.instance().hasConcept(conceptName)) {
            SyntaxException ex =
                new SyntaxException("Referenced Concept " + conceptName + " does not exist.");
            ex.addObject(shadowConcept);
            throw ex;
        }
        return TypeSystem.instance().conceptForName(conceptName);
    }

    /**
     * Default constructor for SingleFSNetCompiler
     * @return a new SingleFSNetCompiler instance
     */
    protected SingleFSNetCompiler createSingleFSNetCompiler() {
        return new SingleFSNetCompiler();
    }

    private void compileApprops(ShadowNet net) throws SyntaxException {
        Iterator<ShadowNetElement> iterator = net.elements().iterator();
        while (iterator.hasNext()) {
            ShadowNetElement elem = iterator.next();
            if (elem instanceof ShadowAssoc) {
                ShadowAssoc shadowAssoc = (ShadowAssoc) elem;
                ConceptImpl concept = (ConceptImpl) lookup(shadowAssoc.getConcept());
                ConceptSet typ = new ConceptSet(lookup(shadowAssoc.getType()));
                int mult = shadowAssoc.getMultiplicity();
                Name featureName = shadowAssoc.getFeature();
                ParsedType pt = new ParsedConjunctiveType(typ);
                if (mult != ShadowAssoc.ZERO_OR_ONE) {
                    pt = new ParsedListType(mult == ShadowAssoc.ONE_OR_MORE, pt);
                }
                concept.addApprop(featureName, pt);
                FSShadowLookupExtension.lookup(_shadowLookup)
                    .setApprop(concept, featureName, shadowAssoc);
                //logger.debug("Found Assoc: "+shadowAssoc.getFeature()+": "+typ);
            }
        }

        iterator = net.elements().iterator();
        while (iterator.hasNext()) {
            ShadowNetElement elem = iterator.next();
            if (elem instanceof ShadowConcept) {
                ShadowConcept shadowConcept = (ShadowConcept) elem;
                ConceptImpl concept = (ConceptImpl) lookup(shadowConcept);


                // parse lines:
                // ### this is a hack that allows us to use the standard
                // compiler decomposition.
                SingleFSNetCompiler compiler = createSingleFSNetCompiler();
                compiler.setShadowLookup(_shadowLookup);
                FSInscriptionParser parser =
                    (FSInscriptionParser) compiler.makeParser("\n" + shadowConcept.getApprop());
                parser.setLookup(_shadowLookup);
                ParsedFSDeclarationNode decl =
                    (ParsedFSDeclarationNode) compiler.makeDeclarationNode(net);
                try {
                    decl.addAccess(net.getName(), null);
                } catch (de.renew.formalism.java.ParseException e) {
                    // do nothing
                }
                parser.setDeclarationNode(decl);
                try {
                    OrderedTable approps = parser.parseAppropDef();

                    //concept.setApprops(approps);
                    CollectionEnumeration appropenumeration = approps.keys();
                    while (appropenumeration.hasMoreElements()) {
                        Name featureName = (Name) appropenumeration.nextElement();
                        concept.addApprop(featureName, (ParsedType) approps.at(featureName));
                        FSShadowLookupExtension.lookup(_shadowLookup)
                            .setApprop(concept, featureName, shadowConcept);
                    }
                } catch (de.renew.formalism.java.ParseException e) {
                    SyntaxException ex = FSNetCompiler.makeSyntaxException(e);
                    ex.addObject(shadowConcept);
                    throw ex;
                }
            }
        }
    }

    private void compilePartitions(ShadowNet net) throws SyntaxException {
        Iterator<ShadowNetElement> iterator = net.elements().iterator();
        while (iterator.hasNext()) {
            ShadowNetElement elem = iterator.next();
            if (elem instanceof ShadowIsa) {
                ShadowIsa shadowIsa = (ShadowIsa) elem;
                if (shadowIsa.isDisjunctive()) {
                    ConceptImpl sub = (ConceptImpl) lookup(shadowIsa.getSource());
                    ConceptImpl supi = (ConceptImpl) lookup(shadowIsa.getTarget());
                    try {
                        FSShadowLookupExtension.lookup(_shadowLookup).addToSubPartition(supi, sub);
                    } catch (TypeException tex) {
                        SyntaxException ex =
                            new SyntaxException("This disjunction leads to a contradiction.", tex);
                        ex.addObject(shadowIsa);
                        throw ex;
                    }
                }
            }
        }
    }

    private void compileIsas(ShadowNet net) throws SyntaxException {
        Iterator<ShadowNetElement> iterator = net.elements().iterator();
        while (iterator.hasNext()) {
            ShadowNetElement elem = iterator.next();
            if (elem instanceof ShadowIsa) {
                ShadowIsa shadowIsa = (ShadowIsa) elem;
                ConceptImpl sub = (ConceptImpl) lookup(shadowIsa.getSource());
                ShadowConcept supi = shadowIsa.getTarget();
                ConceptImpl sup = (ConceptImpl) lookup(supi);
                LOGGER.debug("Compiling is-a from " + sub.getName() + " to " + sup.getName());
                try {
                    sub.addIsa(sup);
                } catch (CyclicHierarchyException e) {
                    SyntaxException ex = new SyntaxException(
                        "This is-a-relation leads to a cyclic type hierarchy.", e);
                    ex.addObject(shadowIsa);
                    throw ex;
                } catch (TypeException te) {
                    SyntaxException ex =
                        new SyntaxException("This is-a-relation leads to a contradiction.", te);
                    ex.addObject(shadowIsa);
                    throw ex;
                }
            }
        }
    }

    @Override
    public int hashCode() {
        return getClass().hashCode();
    }

    @Override
    public boolean equals(Object that) {
        return that != null && this.getClass().equals(that.getClass());
    }
}