package org.dynalang.mop.collections;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;

import org.dynalang.mop.BaseMetaobjectProtocol;
import org.dynalang.mop.ClassBasedMetaobjectProtocol;
import org.dynalang.mop.CallProtocol;

/**
 * A metaobject protocol that knows how to manipulate Java lists.
 * @author Attila Szegedi
 * @version $Id: $
 */
public class ListMetaobjectProtocol implements ClassBasedMetaobjectProtocol {
    public boolean isAuthoritativeForClass(Class clazz) {
        return List.class.isAssignableFrom(clazz);
    }

    /**
     * @return {@link BaseMetaobjectProtocol.Result#noAuthority} as this MOP has no concept of callables
     */
    public Object call(Object callable, CallProtocol callProtocol, Map args) {
        return Result.noAuthority;
    }

    /**
     * @return {@link BaseMetaobjectProtocol.Result#noAuthority} as this MOP has no concept of callables
     */
    public Object call(Object callable, CallProtocol callProtocol, Object... args) {
        return Result.noAuthority;
    }
    
    /**
     * @return {@link BaseMetaobjectProtocol.Result#noAuthority} as this MOP has no concept of callables
     */
    public Object call(Object target, Object callableId, CallProtocol callProtocol, Object... args) {
        return Result.noAuthority;
    }
    
    /**
     * @return {@link BaseMetaobjectProtocol.Result#noAuthority} as this MOP has no concept of callables
     */
    public Object call(Object target, Object callableId, CallProtocol callProtocol, Map args) {
        return Result.noAuthority;
    }
    
    /**
     * Handles the delete attempt from the list. Note that the list semantics
     * actually don't allow an element to be deleted from it.
     * @param target the list to remove element from
     * @param propertyId the numeric index of the element
     * @return If the index is within the bounds of the target list, 
     * {@link BaseMetaobjectProtocol.Result#notDeleteable} is returned. If the index is out of 
     * bounds, or the target is not a list, {@link BaseMetaobjectProtocol.Result#noAuthority} 
     * is returned.
     */
    public Result delete(Object target, long propertyId) {
	if(target instanceof List) {
	    if(isWithinBounds(propertyId, ((List)target))) {
	    	return Result.notDeleteable;
	    }
	}
        return Result.noAuthority;
    }
    
    /**
     * Handles the delete attempt from the list. Note that the list semantics
     * actually don't allow an element to be deleted from it.
     * @param target the list to remove element from
     * @param propertyId the numeric index of the element
     * @return If the index is a number, and it is within the bounds of the 
     * list, {@link BaseMetaobjectProtocol.Result#notDeleteable} is returned. 
     * If the index is out of bounds, or the target is not a list, 
     * {@link BaseMetaobjectProtocol.Result#noAuthority} is returned.
     */
    public Result delete(Object target, Object propertyId) {
	if(propertyId instanceof Number) {
	    return delete(target, ((Number)propertyId).longValue());
	}
	return Result.noAuthority;
    }
    
    /**
     * Retrieves a value from the list.
     * @param target the list to retrieve from
     * @param propertyId the numeric index of the element
     * @return If the list contains the element, returns it. If the index is
     * out of bounds, or the target is not a list, 
     * {@link BaseMetaobjectProtocol.Result#noAuthority} is returned.
     */
    public Object get(Object target, long propertyId) {
	if(target instanceof List) {
	    List l = (List)target;
	    if(isWithinBounds(propertyId, l)) {
		return l.get((int)propertyId);
	    }
	}
        return Result.noAuthority;
    }

    /**
     * Retrieves a value from the list.
     * @param target the list to retrieve from
     * @param propertyId the numeric index of the element
     * @return If the list contains the element at the index 
     * {@link Number#longValue()}, returns it. If the index is out of bounds, 
     * or it is not an instance of {@link Number}, or the target is not a list, 
     * {@link BaseMetaobjectProtocol.Result#noAuthority} is returned.
     */
    public Object get(Object target, Object propertyId) {
	if(propertyId instanceof Number) {
	    return get(target, ((Number)propertyId).longValue());
	}
	return Result.noAuthority;
    }
    
    /**
     * Tells whether the index is valid for the list.
     * @param target the list
     * @param propertyId the index
     * @return if propertyId is a valid index for this list, returns 
     * {@link Boolean#TRUE}. If it is not valid, or the target is not a list,
     * returns null (meaning, no authority). 
     */
    public Boolean has(Object target, long propertyId) {
	if(target instanceof List) {
	    return isWithinBounds(propertyId, (List)target) ? Boolean.TRUE : null;
	}
        return null;
    }

    /**
     * Tells whether the index is valid for the list.
     * @param target the list
     * @param propertyId the index
     * @return if {@link Number#longValue()} of propertyId is a valid index for
     * this list, returns {@link Boolean#TRUE}. If it is not valid, or the 
     * target is not a list, or propertyId is not a {@link Number}, returns 
     * null (meaning, no authority). 
     */
    public Boolean has(Object target, Object propertyId) {
	if(propertyId instanceof Number) {
	    return has(target, ((Number)propertyId).longValue());
	}
        return null;
    }

    /**
     * Returns mappings from indexes to elements for a list.
     * @param target the list
     * @return an iterator of map entries, keys being {@link Integer} objects.
     * The iterator does not support removal.
     */
    public Iterator<Map.Entry> properties(final Object target) {
	if(!(target instanceof List)) {
	    return Collections.EMPTY_MAP.entrySet().iterator();
	}
	
        return new Iterator<Map.Entry>() {
            int i = 0;
            final ListIterator it = ((List)target).listIterator(); 
            
            public boolean hasNext() {
                return it.hasNext();
            }
            
            public Entry<Object, Object> next() {
        	final int key = i++;
        	
                return new Entry<Object,Object>() {
            	    Object value = it.next();
                    @Override
                    public boolean equals(Object obj) {
                	if(obj instanceof Entry) {
                	    Entry e = (Entry)obj;
                	    return (value == null ? e.getValue() == null : value.equals(e.getValue()))
                	    && (Integer.valueOf(key).equals(e.getKey()));
                	}
                	return false;
                    }
                    
                    public Object getKey() {
                        return key;
                    }
                    
                    public Object getValue() {
                        // TODO Auto-generated method stub
                        return value;
                    }
                    
                    @Override
                    public int hashCode() {
                        return key ^ (value == null ? 0 : value.hashCode());
                    }
                    
                    public Object setValue(Object value) {
                        if(i == key + 1) {
                            // optimal way that doesn't invalidate iterator,
                            // used if the iterator hasn't moved yet, i.e. if
                            // this method is called from within the loop that
                            // traverses the list using the iterator
                            Object oldValue = this.value;
                            it.set(value);
                            this.value = value;
                            return oldValue;
                        }
                        else {
                            // Less optimal way that quite likely invalidates
                            // the iterator, used if the iterator already
                            // moved. Usually, this means the entry is accessed
                            // outside the traversing loop, so it fortunately 
                            // won't cause concurrent modification exception.
                            return ((List)target).set(key, value);
                        }
                    }
                };
            }
            
            public void remove() {
        	throw new UnsupportedOperationException();
            }
        };
    }
    
    /**
     * Returns an iterator over all indexes in a list
     * @param target the list
     * @return an iterator of valid indexes ({@link Integer} objects).
     * The iterator does not support removal.
     */
    public Iterator<? extends Object> propertyIds(final Object target) {
	if(!(target instanceof List)) {
	    return Collections.emptySet().iterator();
	}
        return new Iterator<Integer>() {
            int i = 0;
            Iterator it = ((List)target).iterator();
            public boolean hasNext() {
                return it.hasNext();
            }
            
            public Integer next() {
        	it.next();
                return i++;
            }
            
            public void remove() {
        	throw new UnsupportedOperationException();
            }
        };
    }

    /**
     * Puts an element into the list.
     * @param target the list to put the element into
     * @param propertyId the numeric index of the element
     * @param callProtocol not used
     * @return {@link BaseMetaobjectProtocol.Result#ok} if the put was successful. If the list is
     * read-only (throws a {@link UnsupportedOperationException} on put 
     * attempt), {@link BaseMetaobjectProtocol.Result#notWritable} is returned. If the index is
     * out of bounds, or the target is not a list, {@link BaseMetaobjectProtocol.Result#noAuthority} 
     * is returned. If the list throws {@link ClassCastException} (because it 
     * is limited in types of values it can accept), 
     * {@link BaseMetaobjectProtocol.Result#noRepresentation} is returned.
     */
    public Result put(Object target, long propertyId, Object value, CallProtocol callProtocol) {
	if(target instanceof List) {
	    List l = (List)target;
	    if(isWithinBounds(propertyId, l)) {
		try {
		    l.set((int)propertyId, value);
		    return Result.ok;
		}
		catch(ClassCastException e) {
		    return Result.noRepresentation;
		}
		catch(UnsupportedOperationException e) {
		    return Result.notWritable;
		}
	    }
	}
        return Result.noAuthority;
    }
    
    /**
     * Puts an element into the list.
     * @param target the list to put the element into
     * @param propertyId the numeric index of the element. If this argument is
     * a {@link Number}, it's {@link Number#longValue()} method is used.
     * @param callProtocol not used
     * @return {@link BaseMetaobjectProtocol.Result#ok} if the put was successful. If the list is
     * read-only (throws a {@link UnsupportedOperationException} on put 
     * attempt), {@link BaseMetaobjectProtocol.Result#notWritable} is returned. If the index is
     * out of bounds, or is not a {@link Number}, or the target is not a list, 
     * {@link BaseMetaobjectProtocol.Result#noAuthority} is returned. If the list throws 
     * {@link ClassCastException} (because it is limited in types of values it 
     * can accept), {@link BaseMetaobjectProtocol.Result#noRepresentation} is returned.
     */
    public Result put(Object target, Object propertyId, Object value, CallProtocol callProtocol) {
	if(propertyId instanceof Number) {
	    return put(target, ((Number)propertyId).longValue(), value, callProtocol);
	}
        return Result.noAuthority;
    }
    
    /**
     * @return {@link BaseMetaobjectProtocol.Result#noAuthority} as this MOP has no concept of type
     * conversion
     */
    public Object representAs(Object object, Class targetClass) {
        return Result.noAuthority;
    }
    
    private static boolean isWithinBounds(long propertyId, List<?> l) {
	return propertyId >= 0 && propertyId < l.size();
    }
}