package de.uni_hamburg.fs;

import collections.ArrayEnumeration;
import collections.CollectionEnumeration;


/**
 * Represents a list type in the feature structure system.
 * Can represent different kinds of lists: regular lists, non-empty lists (NEList),
 * and empty lists (EList).
 */
public class ListType implements Type {
    /**
     * Constant representing a regular list type.
     */
    public static final int LIST = 0;
    /**
     * Constant representing a non-empty list type.
     */
    public static final int NELIST = 1;
    /**
     * Constant representing an empty list type.
     */
    public static final int ELIST = 2;

    // public static final String LIST_TYPE[]={"List","NEList","EList"};

    /**
     * Constant representing the head feature name for non-empty lists.
     */
    public static final Name HEAD = new Name("hd");
    /**
     * Constant representing the tail feature name for non-empty lists.
     */
    public static final Name TAIL = new Name("tl");

    /**
     * The base type for elements in this list.
     */
    private Type _baseType = null;
    /**
     * The specific type of list (LIST, NELIST, or ELIST).
     */
    private int _subtype;

    /**
     * Constructs a new ListType with the specified base type and subtype.
     *
     * @param baseType the base type of the list elements
     * @param subtype  the specific list type (LIST, NELIST, or ELIST)
     */
    public ListType(Type baseType, int subtype) {
        this._baseType = baseType;
        this._subtype = subtype;
    }

    /**
     * Constructs a new regular ListType with the specified base type.
     *
     * @param baseType the base type of the list elements
     */
    public ListType(Type baseType) {
        this(baseType, LIST);
    }

    /**
     * Creates a new regular list type.
     *
     * @param baseType the base type of the list elements
     * @return a new ListType instance representing a regular list
     */
    public static ListType getList(Type baseType) {
        return new ListType(baseType, LIST);
    }

    /**
     * Creates a new empty list type.
     *
     * @param baseType the base type of the list elements
     * @return a new ListType instance representing an empty list
     */
    public static ListType getEList(Type baseType) {
        return new ListType(baseType, ELIST);
    }

    /**
     * Creates a new non-empty list type.
     *
     * @param baseType the base type of the list elements
     * @return a new ListType instance representing a non-empty list
     */
    public static ListType getNEList(Type baseType) {
        return new ListType(baseType, NELIST);
    }

    /**
     * Returns the base type of the list elements.
     *
     * @return the base type
     */
    public Type getBaseType() {
        return _baseType;
    }

    /**
     * Returns the subtype of this list type.
     *
     * @return the subtype (LIST, NELIST, or ELIST)
     */
    public int getSubtype() {
        return _subtype;
    }

    /**
     * Return the name of this Type.
     */
    @Override
    public String getName() {
        return getName(getBaseType().getName());
    }

    private String getName(String basename) {
        switch (_subtype) {
            case ELIST:
                return "<" + basename + ">";
            case NELIST:
                return basename + "+";
            case LIST:
                return basename + "*";
            default:
                return "!!!wrong list type!!!";
        }
    }

    /**
     * Return the qualified name of this Type.
     */
    @Override
    public String getFullName() {
        return getName(getBaseType().getFullName());
    }

    /**
     * Return whether this Type is extensional.
     */
    @Override
    public boolean isExtensional() {
        return _subtype != LIST;
    }

    /**
     * Return whether this Type represents an instance.
     */
    @Override
    public boolean isInstanceType() {
        return getBaseType().isInstanceType();
    }

    @Override
    public Type getInstanceType() {
        if (isInstanceType()) {
            return this;
        }
        return new ListType(getBaseType().getInstanceType(), _subtype);
    }

    /**
     * Return whether the feature {@literal <feature>} is appropriate in this Concept.
     */
    @Override
    public boolean isApprop(Name feature) {
        return _subtype == NELIST && (feature.equals(HEAD) || feature.equals(TAIL));
    }

    /**
     * Return the required Type for the Value under the given feature.
     */
    @Override
    public Type appropType(Name feature) throws NoSuchFeatureException {
        if (_subtype == NELIST) {
            if (feature.equals(HEAD)) {
                return getBaseType();
            } else if (feature.equals(TAIL)) {
                return new ListType(getBaseType(), LIST);
            }
        }
        throw new NoSuchFeatureException(feature, this);
    }

    /**
     * Return an Enumeration of all appropriate features.
     */
    @Override
    public CollectionEnumeration appropFeatureNames() {
        if (_subtype == NELIST) {
            return new ArrayEnumeration(new Name[] { HEAD, TAIL });
        } else {
            return EmptyEnumeration.INSTANCE;
        }
    }

    /**
     * Return whether this Type subsumes {@literal <that>} Type.
     * In other words, return whether this Type is more general than {@literal <that>} Type.
     */
    @Override
    public boolean subsumes(Type that) {
        if (that instanceof ListType) {
            ListType thatList = (ListType) that;
            return (_subtype == LIST || _subtype == thatList._subtype)
                && getBaseType().subsumes(thatList.getBaseType());
        }
        return false;
    }

    /**
     * Return the unification of this Type and {@literal <that>} Type.
     * this Type is not modified!
     */
    @Override
    public Type unify(Type that) throws UnificationFailure {
        // has to be a ListType or (TOP).
        if (that.equals(Type.TOP)) {
            return this;
        }
        if (that instanceof ListType) {
            ListType thatList = (ListType) that;
            if (thatList._subtype + _subtype != ELIST + NELIST) {
                // ELIST and NELIST are never compatible
                return new ListType(
                    thatList.getBaseType().unify(getBaseType()),
                    Math.max(thatList._subtype, _subtype));
            }
        }
        throw new UnificationFailure();
    }

    /**
     * Return whether this Type and {@literal <that>} Type are compatible.
     */
    @Override
    public boolean canUnify(Type that) {
        if (that.equals(Type.TOP)) {
            return true;
        }
        if (that instanceof ListType) {
            ListType thatList = (ListType) that;
            if (thatList._subtype + _subtype != ELIST + NELIST) {
                // ELIST and NELIST are never compatible
                return thatList.getBaseType().canUnify(getBaseType());
            }
        }
        return false;
    }

    /**
     * Look for the most general common extensional supertype of this and {@literal <that>}.
     */
    @Override
    public Type mostGeneralExtensionalSupertype(Type that) {
        // TO-DO
        return null;
    }

    /**
     * Return a new node from this type.
     */
    @Override
    public Node newNode() {
        if (_subtype == NELIST) {
            return new ListNode(this);
        } else {
            return new NoFeatureNode(this);
        }
    }

    @Override
    public boolean equals(Object that) {
        if (that instanceof ListType) {
            ListType thatList = (ListType) that;
            return thatList._subtype == _subtype && thatList.getBaseType().equals(getBaseType());
        }
        return false;
    }

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

    @Override
    public String toString() {
        return getName();
    }
}