/*
   Copyright 2007 Attila Szegedi

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
package org.dynalang.mop.impl;

import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import org.dynalang.mop.CallProtocol;
import org.dynalang.mop.MetaobjectProtocol;

/**
 * A simple composite MOP that uses a chain of other MOPs to carry out its
 * operations. Most operations are attempted from the first MOP in chain towards
 * the last, until one returns an authoritative answer. The only exceptions to
 * this are the {@link #properties(Object)} and {@link #propertyIds(Object)}
 * methods, that return the union of all authoritative answers. In case there
 * is a conflict among property IDs returned from an earlier MOP and a later 
 * MOP, the earlier takes precedence and the later will not be returned. If you
 * have a list of MOPs that you want to use, you might want to consider using
 * {@link CompositeClassBasedMetaobjectProtocol#optimize(MetaobjectProtocol[])}
 * on them before using them to construct a composite metaobject protocol.
 * might consider 
 * @author Attila Szegedi
 * @version $Id: $
 */
public class CompositeMetaobjectProtocol implements MetaobjectProtocol, Serializable
{
    private static final long serialVersionUID = 1L;

    private final MetaobjectProtocol[] members;
    
    /**
     * Creates a new composite metaobject protocol from the specified members.
     * @param members the member metaobject protocols.
     */
    public CompositeMetaobjectProtocol(MetaobjectProtocol[] members) {
	this.members = members.clone();
    }
    
    public Object call(Object callable, CallProtocol callProtocol, Map args) {
        for (MetaobjectProtocol mop : members) {
            Object res = mop.call(callable, callProtocol, args);
            if(res != Result.noAuthority) {
                return res;
            }
        }
        return Result.noAuthority;
    }

    public Object call(Object target, Object callableId, CallProtocol callProtocol, Map args) {
        for (MetaobjectProtocol mop : members) {
            Object res = mop.call(target, callableId, callProtocol, args);
            if(res != Result.noAuthority) {
                return res;
            }
        }
        return Result.noAuthority;
    }

    public Object call(Object target, Object callableId, CallProtocol callProtocol, Object... args) {
        for (MetaobjectProtocol mop : members) {
            Object res = mop.call(target, callableId, callProtocol, args);
            if(res != Result.noAuthority) {
                return res;
            }
        }
        return Result.noAuthority;
    }

    public Object call(Object callable, CallProtocol callProtocol, Object... args) {
        for (MetaobjectProtocol mop : members) {
            Object res = mop.call(callable, callProtocol, args);
            if(res != Result.noAuthority) {
                return res;
            }
        }
        return Result.noAuthority;
    }

    public Result delete(Object target, long propertyId) {
        for (MetaobjectProtocol mop : members) {
            Result res = mop.delete(target, propertyId);
            if(res != Result.noAuthority) {
                return res;
            }
        }
        return Result.noAuthority;
    }

    public Result delete(Object target, Object propertyId) {
        for (MetaobjectProtocol mop : members) {
            Result res = mop.delete(target, propertyId);
            if(res != Result.noAuthority) {
                return res;
            }
        }
        return Result.noAuthority;
    }

    public Object get(Object target, long propertyId) {
        for (MetaobjectProtocol mop : members) {
            Object res = mop.get(target, propertyId);
            if(res != Result.noAuthority) {
                return res;
            }
        }
        return Result.noAuthority;
    }

    public Object get(Object target, Object propertyId) {
        for (MetaobjectProtocol mop : members) {
            Object res = mop.get(target, propertyId);
            if(res != Result.noAuthority) {
                return res;
            }
        }
        return Result.noAuthority;
    }

    public Boolean has(Object target, long propertyId) {
        for (MetaobjectProtocol mop : members) {
            Boolean has = mop.has(target, propertyId);
            if(has != null) {
                return has;
            }
        }
        return null;
    }

    public Boolean has(Object target, Object propertyId) {
        for (MetaobjectProtocol mop : members) {
            Boolean has = mop.has(target, propertyId);
            if(has != null) {
                return has;
            }
        }
        return null;
    }

    public Iterator<Entry> properties(Object target) {
	Iterator<Entry> compositeIt = null;
        for (MetaobjectProtocol mop : members) {
            Iterator<Entry> it = mop.properties(target);
            if(it != null) {
        	if(compositeIt == null) {
        	    compositeIt = it;
        	}
        	else if(compositeIt instanceof CompositeUniqueEntryIterator) {
        		((CompositeUniqueEntryIterator)compositeIt).add(it);
    	    	}
        	else {
        	    compositeIt = new CompositeUniqueEntryIterator(compositeIt, it);
        	}
            }
        }
        return compositeIt;
    }

    public Iterator<? extends Object> propertyIds(Object target) {
	Iterator<? extends Object> compositeIt = null;
        for (MetaobjectProtocol mop : members) {
            Iterator<? extends Object> it = mop.propertyIds(target);
            if(it != null) {
        	if(compositeIt == null) {
        	    compositeIt = it;
        	}
        	else if(compositeIt instanceof CompositeUniqueIterator) {
        		((CompositeUniqueIterator<Object>)compositeIt).add(it);
    	    	}
        	else {
        	    compositeIt = new CompositeUniqueIterator<Object>(compositeIt, it);
        	}
            }
        }
        return compositeIt;
    }

    public Result put(Object target, long propertyId, Object value, CallProtocol callProtocol) {
        for (MetaobjectProtocol mop : members) {
            Result res = mop.put(target, propertyId, value, callProtocol);
            if(res != Result.noAuthority) {
                return res;
            }
        }
        return Result.noAuthority;
    }

    public Result put(Object target, Object propertyId, Object value, CallProtocol callProtocol) {
        for (MetaobjectProtocol mop : members) {
            Result res = mop.put(target, propertyId, value, callProtocol);
            if(res != Result.noAuthority) {
                return res;
            }
        }
        return Result.noAuthority;
    }

    public Object representAs(Object object, Class targetClass) {
        for (MetaobjectProtocol mop : members) {
            Object res = mop.representAs(object, targetClass);
            if(res != Result.noAuthority) {
                return res;
            }
        }
        return Result.noAuthority;
    }
}