/*
   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.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

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

/**
 * This class can turn any {@link BaseMetaobjectProtocol} into a full
 * {@link MetaobjectProtocol} by implementing the MOP methods using only the 
 * Base MOP methods. The resulting MOP is fully functional (assuming the 
 * underlying Base MOP was too), however the additional MOP methods might not
 * have the most optimal implementation possible.
 * @author Attila Szegedi
 * @version $Id: $
 */
public class MetaobjectProtocolAdaptor implements MetaobjectProtocol {
    private final BaseMetaobjectProtocol bmop;

    /**
     * Constructs a new MetaobjectProtocolAdaptor that enhances the adapted
     * BaseMetaobjectProtocol with additional MetaobjectProtocol methods.
     * @param bmop the base MOP to enhance.
     */
    public MetaobjectProtocolAdaptor(BaseMetaobjectProtocol bmop) {
        this.bmop = bmop;
    }
    
    public Object call(Object callable, CallProtocol callProtocol, Map args) {
        return bmop.call(callable, callProtocol, args);
    }

    public Object call(Object target, Object callableId, CallProtocol callProtocol, Map args) {
        Object callable = callProtocol.get(target, callableId);
        if(callable instanceof Result) {
            return callable;
        }
        return call(callable, callProtocol, args);
    }

    public Object call(Object target, Object callableId, CallProtocol callProtocol, Object... args) {
        Object callable = callProtocol.get(target, callableId);
        if(callable instanceof Result) {
            return callable;
        }
        return call(callable, callProtocol, args);
    }

    public Object call(Object callable, CallProtocol callProtocol, Object... args) {
        return bmop.call(callable, callProtocol, args);
    }

    public Result delete(Object target, long propertyId) {
        return delete(target, Long.valueOf(propertyId));
    }

    public Result delete(Object target, Object propertyId) {
        return bmop.delete(target, propertyId);
    }

    public Object get(Object target, long propertyId) {
        return get(target, Long.valueOf(propertyId));
    }
    
    public Object get(Object target, Object propertyId) {
        return bmop.get(target, propertyId);
    }

    public Boolean has(Object target, long propertyId) {
        return has(target, Long.valueOf(propertyId));
    }

    public Boolean has(Object target, Object propertyId) {
        return bmop.has(target, propertyId);
    }

    public Iterator<Entry> properties(Object target) {
        return bmop.properties(target);
    }

    public Iterator<? extends Object> propertyIds(Object target) {
        final Iterator<Map.Entry> it = properties(target);
        return new Iterator<Object>() {
            public boolean hasNext() {
                return it.hasNext();
            }
            public Object next() {
                return it.next().getKey();
            }
            public void remove() {
                it.remove();
            }
        };
    }

    public Result put(Object target, long propertyId, Object value, CallProtocol callProtocol) {
        return put(target, Long.valueOf(propertyId), value, callProtocol);
    }

    public Result put(Object target, Object propertyId, Object value, CallProtocol callProtocol) {
        return bmop.put(target, propertyId, value, callProtocol);
    }
    
    public Object representAs(Object object, Class targetClass) {
        return bmop.representAs(object, targetClass);
    }

    /**
     * Takes an array of base MOPs and "upgrades" them to a full MOP. If any
     * element of the input array is already a full mop, it is left unchanged.
     * If it is a base MOP, it is wrapped in an instance of 
     * {@link MetaobjectProtocolAdaptor}.
     * @param protocols the input array of base MOPs
     * @return an array of full MOPs.
     */
    public static MetaobjectProtocol[] toMetaobjectProtocols(BaseMetaobjectProtocol[] protocols)
    {
        MetaobjectProtocol[] protocols2 = new MetaobjectProtocol[protocols.length];
        for (int i = 0; i < protocols.length; i++) {
            BaseMetaobjectProtocol protocol = protocols[i];
            if(protocol instanceof MetaobjectProtocol) {
        	protocols2[i] = (MetaobjectProtocol)protocol;
            }
            else {
        	protocols2[i] = new MetaobjectProtocolAdaptor(protocol);
            }
        }
        return protocols2;
    }
}
