package de.uni_hamburg.fs;

import java.util.Enumeration;
import java.util.NoSuchElementException;

import collections.HashedMap;
import collections.HashedSet;
import collections.UpdatableMap;

import de.renew.util.ClassSource;


/**
 * Represents a type system that manages concepts, types, and their hierarchical relationships.
 * This class implements a singleton pattern and provides functionality for managing
 * type hierarchies, features, and Java class integration.
 */
public class TypeSystem implements java.io.Serializable {

    /**
     * Logger instance for the TypeSystem class.
     * Used for logging debug and error messages throughout the type system operations.
     */
    public static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(TypeSystem.class);

    /**
     * The singleton instance of the TypeSystem.
     * This field holds the single instance of TypeSystem used throughout the application.
     */
    private static TypeSystem _instance = null;

    /**
     * The root concept of the type hierarchy.
     * Represents the most general concept from which all other concepts inherit.
     */
    private ConceptImpl _root = new ConceptImpl();

    /**
     * The top-level partition of the type system.
     * Represents the highest-level grouping of types in the system.
     */
    private Partition _topPartition;

    /**
     * Map storing all concepts in the type system.
     * Keys are concept names (Name objects) and values are the corresponding Concept objects.
     */
    private UpdatableMap _concepts = new HashedMap();

    /**
     * Constructs a new TypeSystem instance.
     * Creates an empty type system with a root concept.
     */
    public TypeSystem() {
        // logger.debug("New TypeSystem.");
    }

    /**
     * Initializes the type system by creating a new top partition and computing
     * the transitive closure of the type hierarchy.
     * This method should be called only once during the type system initialization.
     */
    private void init() {
        _topPartition = new Partition();
        transitiveClosure();
    }

    /**
     * Computes the transitive closure of the type hierarchy starting from the root.
     */
    public void transitiveClosure() {
        _root.transitiveClosure();
    }

    /**
     * Recalculates direct ISA relationships in the type hierarchy.
     *
     * @throws TypeException if a type-related error occurs during calculation
     */
    public void recalcDirectIsa() throws TypeException {
        _root.transitiveClosure();
        _root.recalcDirectIsa(new HashedSet());
    }

    /**
     * Inherits features through the type hierarchy and builds feature types.
     *
     * @throws UnificationFailure if feature unification fails during inheritance
     */
    public void inheritFeatures() throws UnificationFailure {
        recalcDirectIsa();
        _root.inherit(new HashedSet());
        _root.buildFeatureTypes(new HashedSet());
    }

    /**
     * Returns the top partition of the type system.
     *
     * @return the top partition
     */
    public Partition getTopPartition() {
        return _topPartition;
    }

    /**
     * Returns the singleton instance of the type system, creating it if necessary.
     *
     * @return the singleton TypeSystem instance
     */
    public static TypeSystem instance() {
        if (_instance == null) {
            return newInstance();
        } else {
            return _instance;
        }
    }

    /**
     * Creates and initializes a new TypeSystem instance.
     *
     * @return a new TypeSystem instance
     */
    public static TypeSystem newInstance() {
        _instance = new TypeSystem();
        _instance.init();
        return _instance;
    }

    /**
     * Sets the singleton instance of the type system.
     *
     * @param instance the TypeSystem instance to set
     */
    public static void setInstance(TypeSystem instance) {
        _instance = instance;
    }

    /**
     * Returns the root concept of the type hierarchy.
     *
     * @return the root ConceptImpl
     */
    public ConceptImpl getRoot() {
        return _root;
    }

    /**
     * Creates a new root concept and updates the type hierarchy accordingly.
     */
    public void newRoot() {
        ConceptImpl newRoot = new ConceptImpl();
        _root.basicAddIsa(newRoot);
        _topPartition = new Partition();
        _root = newRoot;
    }

    /**
     * Adds a concept to the type system.
     *
     * @param concept the concept to add
     */
    public void addConcept(Concept concept) {
        if (concept instanceof ConceptImpl) {
            ConceptImpl ci = (ConceptImpl) concept;
            ci.basicAddIsa(_root);
            if (ci.isDummy()) {
                return;
            }
        }
        _concepts.putAt(new Name(concept.getFullName()), concept);
    }

    /**
     * Removes a concept from the type system.
     *
     * @param concept the concept to remove
     */
    public void removeConcept(Concept concept) {
        _concepts.removeAt(concept.getFullName());
    }

    /**
     * Checks if a concept with the given name exists in the type system.
     *
     * @param name the name to check
     * @return true if the concept exists, false otherwise
     */
    public boolean hasConcept(String name) {
        return hasConcept(new Name(name));
    }

    /**
     * Checks if a concept with the given name exists in the type system.
     *
     * @param name the Name object to check
     * @return true if the concept exists, false otherwise
     */
    public boolean hasConcept(Name name) {
        if (_concepts.includesKey(name)) {
            return true;
        }
        try {
            getJavaClass(name.toString());
            return true;
        } catch (TypeException tee) {
            return false;
        }
    }

    /**
     * Retrieves a concept by its name.
     *
     * @param name the name of the concept
     * @return the concept with the given name
     * @throws NoSuchElementException if no concept is found
     */
    public Concept conceptForName(String name) {
        return conceptForName(new Name(name));
    }

    /**
     * Retrieves a concept by its Name object.
     *
     * @param name the Name object of the concept
     * @return the concept with the given name
     * @throws NoSuchElementException if no concept is found
     */
    public Concept conceptForName(Name name) {
        Concept concept = null;
        try {
            concept = (Concept) _concepts.at(name);
        } catch (NoSuchElementException nse) {
            try {
                concept = getJavaConcept(getJavaClass(name.toString()));
            } catch (TypeException tee) {
                LOGGER.debug("No concept found for name " + name + " in " + _concepts);
                LOGGER.debug("Interpretation of name " + name + " as Java concept failed:", tee);
                throw new NoSuchElementException("No concept found for name " + name);
            }
        }
        return concept;
    }

    /**
     * Gets or creates a JavaConcept for the given Java class.
     *
     * @param javaClass the Java class to get a concept for
     * @return the JavaConcept representing the class
     */
    public JavaConcept getJavaConcept(Class<?> javaClass) {
        Name name = new Name(javaClass.getName());
        if (_concepts.includesKey(name)) {
            Concept concept = (Concept) _concepts.at(name);
            if (concept instanceof JavaConcept) {
                return (JavaConcept) concept;
            }
        }
        JavaConcept jc = new JavaConcept(javaClass);
        addConcept(jc);
        return jc;
    }

    /**
     * Loads a Java class by its name.
     *
     * @param javaClassName the fully qualified name of the Java class
     * @return the Class object for the specified name
     * @throws TypeException if the class cannot be loaded
     */
    public Class<?> getJavaClass(String javaClassName) throws TypeException {
        try {
            return ClassSource.classForName(javaClassName);
        } catch (ClassNotFoundException cnf1) {
            throw new TypeException(cnf1);
        } catch (IllegalArgumentException iae) {
            throw new TypeException(iae);
        } catch (LinkageError le) {
            throw new TypeException(le);
        }
    }

    /**
     * Creates a Type object for the given Java class.
     *
     * @param javaClass the Java class to create a type for
     * @return the appropriate Type object for the class
     */
    public Type getType(Class<?> javaClass) {
        if (javaClass.isPrimitive() || javaClass == String.class) {
            return new BasicType(javaClass);
        } else if (javaClass.isArray()) {
            return new ListType(getType(javaClass.getComponentType()));
        } else if (Enumeration.class.isAssignableFrom(javaClass)) {
            return new ListType(getType(Object.class));
        } else {
            return new ConjunctiveType(getJavaConcept(javaClass));
        }
    }

    /**
     * Creates a ParsedType object for the given Java class.
     * This method handles different kinds of Java classes:
     * - Primitive types and String are converted directly to ParsedType
     * - Array types are converted to ParsedListType with their component type
     * - Enumeration types are converted to ParsedListType of Object
     * - Other classes are converted to ParsedConjunctiveType with their corresponding JavaConcept
     *
     * @param javaClass the Java class to create a parsed type for
     * @return the appropriate ParsedType object for the class
     */
    ParsedType getParsedType(Class<?> javaClass) {
        if (javaClass.isPrimitive() || javaClass == String.class) {
            return (ParsedType) getType(javaClass);
        }
        if (javaClass.isArray()) {
            return new ParsedListType(false, getParsedType(javaClass.getComponentType()));
        }
        if (Enumeration.class.isAssignableFrom(javaClass)) {
            return new ParsedListType(false, getParsedType(Object.class));
        }
        return new ParsedConjunctiveType(new ConceptSet(getJavaConcept(javaClass)));
    }
}