package weiss.util;


/**
 * MapImpl implements the Map on top of a set.
 * It should be extended by TreeMap and HashMap, with
 * chained calls to the constructor.
 */
abstract class MapImpl<KeyType,ValueType> implements Map<KeyType,ValueType>
{
    private Set<Map.Entry<KeyType,ValueType>> theSet;
    
    protected abstract Map.Entry<KeyType,ValueType> makePair( KeyType key, ValueType value );
    protected abstract Set<KeyType> makeEmptyKeySet( );
    protected abstract Set<Map.Entry<KeyType,ValueType>> clonePairSet( Set<Map.Entry<KeyType,ValueType>> pairSet );
    
    private Map.Entry<KeyType,ValueType> makePair( KeyType key )
    {
        return makePair( (KeyType) key, null );
    }
        
    protected MapImpl( Set<Map.Entry<KeyType,ValueType>> s )
    {
        theSet = s;
    }
    
    protected MapImpl( Map<KeyType,ValueType> m )
    {
        theSet = clonePairSet( m.entrySet( ) );
    }
    
    /**
     * Returns the number of keys in this map.
     * @return the number of keys in this collection.
     */
    public int size( )
    {
        return theSet.size( );
    }
    
    /**
     * Tests if this map is empty.
     * @return true if the size of this map is zero.
     */
    public boolean isEmpty( )
    {
        return theSet.isEmpty( );
    }

    /**
     * Tests if this map contains a given key.
     * @param key the key to search for.
     * @return true if the map contains the key.
     */
    public boolean containsKey( KeyType key )
    {
        return theSet.contains( makePair( key ) );
    }

    /**
     * Returns the value in the map associated with the key.
     * @param key the key to search for.
     * @return the value that matches the key or null
     * if the key is not found. Since null values are allowed,
     * checking if the return value is null may not
     * be a safe way to ascertain if the key is present in the map.
     */
    public ValueType get( KeyType key )
    {
        Map.Entry<KeyType,ValueType> match = theSet.getMatch( makePair( key ) );
        
        if( match == null )
            return null;
        else
            return match.getValue( );    
    }

    /**
     * Adds the key value pair to the map, overriding the
     * original value if the key was already present.
     * @param key the key to insert.
     * @param value the value to insert.
     * @return the old value associated with the key, or
     * null if the key was not present prior to this call.
     */
    public ValueType put( KeyType key, ValueType value )
    {
        Map.Entry<KeyType,ValueType> match = theSet.getMatch( makePair( key ) );
        
        if( match != null )
            return match.setValue( value );
            
        theSet.add( makePair( key, value ) );
        return null;
    }
        

    /**
     * Remove the key and its value from the map.
     * @param key the key to remove.
     * @return the previous value associated with the key,
     * or null if the key was not present prior to this call.
     */
    public ValueType remove( KeyType key )
    {
        ValueType oldValue = get( key );
        if( oldValue != null )
            theSet.remove( makePair( key ) );
        
        return oldValue;    
    }

    /**
     * Removes all key value pairs from the map. 
     */
    public void clear( )
    {
        theSet.clear( );
    }
    
    /**
     * Abstract class to model a view (either key or value view).
     * Implements size and clear methods, but not iterator method.
     * View delegates to underlying map.
     */
    private abstract class ViewClass<AnyType> extends AbstractCollection<AnyType>
    {
        public int size( )
          {  return MapImpl.this.size( ); }
        
        public void clear( )
          { MapImpl.this.clear( ); }
    }
    
    /**
     * Class to model the key set view.
     * remove is overridden (otherwise a sequential search is used).
     * iterator gives a KeySetIterator (see Figure 19.81).
     * getMatch, the nonstandard part of weiss.util.Set is not needed.
     */
    private class KeySetClass extends ViewClass<KeyType> implements Set<KeyType>
    {
        public boolean remove( Object key )
          { return MapImpl.this.remove( (KeyType) key ) != null; }
        
        public Iterator<KeyType> iterator( )
          { return new KeySetIterator( ); }
        
        public KeyType getMatch( KeyType key )
          { throw new UnsupportedOperationException( ); }
    }
    
    /**
     * Class to model the value collection view.
     * Default remove which is a sequential search is used.
     * iterator gives a ValueCollectionIterator (see Figure 19.81).
     */
    private class ValueCollectionClass extends ViewClass<ValueType>
    {        
        public Iterator<ValueType> iterator( )
          { return new ValueCollectionIterator( ); }
    }
    
    /**
     * Class used to iterate through key set view.
     * Delegates to an underlying entry set iterator.
     */
    private class KeySetIterator implements Iterator<KeyType>
    {
        private Iterator<Map.Entry<KeyType,ValueType>> itr = theSet.iterator( );
        
        public boolean hasNext( )
          { return itr.hasNext( ); }
        
        public void remove( )
          { itr.remove( ); }
        
        public KeyType next( )
          { return itr.next( ).getKey( ); }
    }

    /**
     * Class used to iterate through value collection view.
     * Delegates to an underlying entry set iterator.
     */
    private class ValueCollectionIterator implements Iterator<ValueType>
    {
        private Iterator<Map.Entry<KeyType,ValueType>> itr = theSet.iterator( );
        
        public boolean hasNext( )
          { return itr.hasNext( ); }
        
        public void remove( )
          { itr.remove( ); }
        
        public ValueType next( )
          { return itr.next( ).getValue( ); }
    }

    /**
     * Returns the keys in the map.
     * These semantics are different from those in java.util because
     * in this class, changes made to the returned key set do not cause
     * changes to be reflected in the map.
     * @return the keys in the map.
     */
    public Set<KeyType> keySet( )
    {
        return new KeySetClass( );
    }

    /**
     * Returns the values in the map. There may be duplicates.
     * These semantics are different from those in java.util because
     * in this class, changes made to the returned value collection
     * do not cause changes to be reflected in the map.
     * @return the values in the map.
     */
    public Collection<ValueType> values( )
    {
        return new ValueCollectionClass( );
    }

    /**
     * Return a set of Map.Entry objects corresponding to
     * the key/value pairs in the map.
     * @return the key/value pairs in the map.
     */ 
    public Set<Map.Entry<KeyType,ValueType>> entrySet( )
    {
        return getSet( );
    }

    /**
     * Return a reference to the underlying set.
     */
    protected Set<Map.Entry<KeyType,ValueType>> getSet( )
    {
        return theSet;
    }
    
    protected abstract static class Pair<KeyType,ValueType> implements Map.Entry<KeyType,ValueType>
    {
        public Pair( KeyType k, ValueType v )
        {
            key = k;
            value = v;
        }
        
        final public KeyType getKey( )
        {
            return key;
        }
        
        final public ValueType getValue( )
        {
            return value;
        }
        
        final public ValueType setValue( ValueType newValue )
        {
            ValueType oldValue = value;
            value = newValue;
            return oldValue;      
        }
        
        public String toString( )
        {
            return key + "=" + value;
        }
        
        private KeyType key;
        private ValueType value;
    }
    
    public String toString( )
    {
        StringBuilder result = new StringBuilder( "{" );
        
        for( Map.Entry<KeyType,ValueType> e : entrySet( ) )
            result.append( e + ", " );
        result.replace( result.length() - 2, result.length(), "}" );    
        
        return result.toString( );
    }
}