package de.uni_hamburg.fs;

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

import collections.ArrayEnumeration;


/**
 * Represents a path in a feature structure as a sequence of features.
 * A path starts at the root node and follows feature names to reach specific nodes.
 */
public class Path implements java.io.Serializable {

    /**
     * Represents an empty path (path of length zero).
     * This constant can be used when no specific path needs to be specified
     * or as a starting point for building other paths.
     */
    public static final Path EPSILON = new Path(new Name[0]);
    /**
     * Array of Name objects representing the sequence of features in the path.
     * Each element in this array represents a feature name in the path from root to leaf.
     */
    private Name[] _feature;

    /**
     * Constructs a Path from a String.
     * A path is a sequence of Features (meant to be starting at the root node).
     * The syntax of a path is feature_1:feature_2:...feature_n.
     * The empty String returns the empty Path (length zero).
     *
     * @param path the string representation of the path using colon-separated feature names
     */
    public Path(String path) {
        StringTokenizer featenumeration = new StringTokenizer(path, ":");
        _feature = new Name[featenumeration.countTokens()];
        for (int i = 0; featenumeration.hasMoreTokens(); ++i) {
            _feature[i] = new Name(featenumeration.nextToken());
        }
    }

    /**
     * Constructs a Path containing a single feature.
     *
     * @param feature the single feature name to create the path from
     */
    public Path(Name feature) {
        this._feature = new Name[] { feature };
    }

    /**
     * Constructs a Path from an array of features.
     *
     * @param feature array of feature names that make up the path
     */
    public Path(Name[] feature) {
        this._feature = feature;
    }

    /**
     * Creates a path to access the nth element in a list structure.
     *
     * @param n the index to access in the list
     * @return a Path object representing the path to the nth element
     */
    public static Path nth(int n) {
        Name[] feature = new Name[n + 1];
        for (int i = 0; i < n; ++i) {
            feature[i] = ListType.TAIL;
        }
        feature[n] = ListType.HEAD;
        return new Path(feature);
    }

    /**
     * Returns an enumeration of the features in this path.
     *
     * @return an Enumeration of the features in this path
     */
    public Enumeration<?> features() {
        return new ArrayEnumeration(_feature);
    }

    /**
     * Returns the number of features in this path.
     *
     * @return the length of the path
     */
    public int length() {
        return _feature.length;
    }

    /**
     * Checks if this path contains any features.
     *
     * @return true if the path is empty, false otherwise
     */
    public boolean isEmpty() {
        return _feature.length == 0;
    }

    /**
     * Returns the feature at the specified index.
     *
     * @param i the index of the feature to retrieve
     * @return the Name at the specified index
     * @throws ArrayIndexOutOfBoundsException if index is out of range
     */
    public Name at(int i) {
        return _feature[i]; // may throw ArrayIndexOutOfBoundsException
    }

    /**
     * Creates a new path by appending a feature to the end of this path.
     *
     * @param feat the feature to append
     * @return a new Path with the feature appended
     */
    public Path append(Name feat) {
        int size = _feature.length;
        Name[] appfeature = new Name[size + 1];
        System.arraycopy(_feature, 0, appfeature, 0, size);
        appfeature[size] = feat;
        return new Path(appfeature);
    }

    /**
     * Creates a new path by appending a feature string to the end of this path.
     * The string is converted to a Name object before being appended.
     *
     * @param feat the feature string to append
     * @return a new Path with the feature appended
     */
    public Path append(String feat) {
        return append(new Name(feat));
    }

    /**
     * Creates a new path by adding a feature to the beginning of this path.
     *
     * @param feat the feature to prepend
     * @return a new Path with the feature prepended
     */
    public Path prepend(Name feat) {
        int size = _feature.length;
        Name[] prefeature = new Name[size + 1];
        System.arraycopy(_feature, 0, prefeature, 1, size);
        prefeature[0] = feat;
        return new Path(prefeature);
    }

    /**
     * Creates a new path by adding a feature string to the beginning of this path.
     * The string is converted to a Name object before being prepended.
     *
     * @param feat the feature string to prepend
     * @return a new Path with the feature prepended
     */
    public Path prepend(String feat) {
        return prepend(new Name(feat));
    }

    private Path butOne(int shift) {
        int bosize = _feature.length - 1;
        if (bosize < 0) {
            throw new NoSuchElementException();
        }
        Name[] bofeature = new Name[bosize];
        System.arraycopy(_feature, shift, bofeature, 0, bosize);
        return new Path(bofeature);
    }

    /**
     * Returns a new path containing all features except the last one.
     *
     * @return a new Path without the last feature
     * @throws NoSuchElementException if the path is empty
     */
    public Path butLast() {
        return butOne(0);
    }

    /**
     * Returns the last feature in this path.
     *
     * @return the last Name in the path
     * @throws ArrayIndexOutOfBoundsException if the path is empty
     */
    public Name last() {
        return at(_feature.length - 1);
    }

    /**
     * Returns a new path containing all features except the first one.
     *
     * @return a new Path without the first feature
     * @throws NoSuchElementException if the path is empty
     */
    public Path butFirst() {
        return butOne(1);
    }

    /**
     * Returns the first feature in this path.
     *
     * @return the first Name in the path
     * @throws ArrayIndexOutOfBoundsException if the path is empty
     */
    public Name first() {
        return at(0);
    }

    @Override
    public String toString() {
        if (isEmpty()) {
            return "";
        }
        StringBuffer out = new StringBuffer(_feature[0].toString());
        for (int i = 1; i < _feature.length; ++i) {
            out.append(':').append(_feature[i]);
        }
        return out.toString();
    }
}