package de.uni_hamburg.fs;

import collections.CollectionEnumeration;
import collections.HashedMap;
import collections.LinkedList;
import collections.Seq;
import collections.UpdatableMap;
import collections.UpdatableSeq;


/**
 * An ordered map implementation that maintains key-value pairs in a specific sequence.
 * This class provides both efficient key-value storage through a hash table and
 * ordered key iteration through a separate sequence structure.
 * <p>
 * The key ordering can be either:
 * <ul>
 * <li>Natural ordering - keys are maintained in insertion order
 * <li>Predefined ordering - keys follow a sequence specified at construction
 * </ul>
 * Keys not found in the predefined ordering are appended to the end.
 */
public class OrderedTable implements java.io.Serializable {
    // OK: These three used to be final, but that triggers the
    // all too well-known final bug.

    /**
     * The hash table storing the key-value mappings
     */
    private UpdatableMap _table;
    /**
     * The sequence maintaining the order of keys
     */
    private UpdatableSeq _keys;
    /**
     * The source sequence defining the preferred key ordering, if any
     */
    private UpdatableSeq _orderSource;

    /**
     * Creates an empty OrderedTable with natural ordering.
     * Keys will be maintained in the order they are first added to the table.
     */
    public OrderedTable() {
        this(null);
    }

    /**
     * Creates an OrderedTable with a predefined key ordering.
     * The ordering is defined by the sequence of elements in the provided enumeration.
     * Keys not found in the enumeration will be added at the end of the sequence.
     *
     * @param orderEnum the enumeration defining key order, or null for natural ordering
     */
    public OrderedTable(CollectionEnumeration orderEnum) {
        if (orderEnum == null) {
            _orderSource = null;
        } else {
            _orderSource = new LinkedList();
            while (orderEnum.hasMoreElements()) {
                _orderSource.insertLast(orderEnum.nextElement());
            }
        }
        _table = new HashedMap();
        _keys = new LinkedList();
    }

    private OrderedTable(UpdatableMap table, UpdatableSeq keys, UpdatableSeq orderSource) {
        this._table = (UpdatableMap) table.duplicate();
        this._keys = (UpdatableSeq) keys.duplicate();
        this._orderSource = orderSource; // not modified
    }

    /**
     * Maps the specified value to the specified key in this table.
     * If the key is new, it is inserted according to the table's ordering rules:
     * either in the position defined by the ordering enumeration or at the end
     * if using natural ordering or if the key is not in the enumeration.
     *
     * @param key   the key with which the specified value is to be associated
     * @param value the value to be associated with the specified key
     */
    public void putAt(Object key, Object value) {
        if (!_keys.includes(key)) {
            if (_orderSource == null) {
                _keys.insertLast(key);
            } else {
                // find insert position according to orderSource:
                int index = 0;
                boolean found = false;
                for (int i = 0; i < _orderSource.size(); ++i) {
                    Object currkey = _orderSource.at(i);
                    if (key.equals(currkey)) {
                        found = true;
                        break;
                    }
                    if (index < _keys.size() && _keys.at(index).equals(currkey)) {
                        ++index;
                    }
                }
                if (found) {
                    _keys.insertAt(index, key);
                } else {
                    _keys.insertLast(key);
                }
            }
        }
        _table.putAt(key, value);
    }

    /**
     * Places a key-value mapping at the specified position in the sequence.
     * If the key already exists, it is first removed from its current position.
     * This method explicitly overrides any existing ordering rules.
     *
     * @param key   the key to insert
     * @param index the position at which to insert the key
     * @param value the value to associate with the key
     */
    public void insertAt(Object key, int index, Object value) {
        _keys.removeOneOf(key);
        _keys.insertAt(index, key);
        _table.putAt(key, value);
    }

    /**
     * Removes the mapping for the specified key if present.
     * The key is removed from the sequence and its value from the table.
     *
     * @param key the key whose mapping is to be removed
     */
    public void removeAt(Object key) {
        _keys.removeOneOf(key);
        _table.removeAt(key);
    }

    /**
     * Tests if this table contains the specified key.
     *
     * @param key key whose presence in this table is to be tested
     * @return true if this table contains a mapping for the specified key
     */
    public boolean includesKey(Object key) {
        return _keys.includes(key);
    }

    /**
     * Tests if this table maps any key to the specified value.
     *
     * @param element value whose presence in this table is to be tested
     * @return true if this table contains the specified value
     */
    public boolean includes(Object element) {
        return _table.includes(element);
    }

    /**
     * Returns the number of key-value mappings in this table.
     *
     * @return the number of mappings in this table
     */
    public int size() {
        return _keys.size();
    }

    /**
     * Returns an enumeration of the keys in their defined order.
     * The returned enumeration reflects the current sequence of keys.
     *
     * @return an enumeration of the keys in their proper sequence
     */
    public CollectionEnumeration keys() {
        return ((Seq) _keys.duplicate()).elements();
    }

    /**
     * Returns the value mapped to the specified key.
     *
     * @param key the key whose associated value is to be returned
     * @return the value to which the specified key is mapped
     */
    public Object at(Object key) {
        return _table.at(key);
    }

    /**
     * Tests if this table contains no mappings.
     *
     * @return true if this table contains no key-value mappings
     */
    public boolean isEmpty() {
        return _keys.isEmpty();
    }

    /**
     * Creates and returns a deep copy of this table.
     * The new table contains copies of all mappings and maintains the same ordering.
     *
     * @return a new OrderedTable with the same contents and ordering
     */
    public Object duplicate() {
        return new OrderedTable(_table, _keys, _orderSource);
    }
}