/*
 * Copyright 2007, 2008, 2009 Duncan McGregor
 *
 * This file is part of Rococoa, a library to allow Java to talk to Cocoa.
 *
 * Rococoa is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Rococoa is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Rococoa.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * 
 */
package org.rococoa.internal;

import org.rococoa.Foundation;
import org.rococoa.ID;
import org.rococoa.ObjCObject;
import org.rococoa.Rococoa;
import org.rococoa.Selector;

import com.sun.jna.FromNativeContext;
import com.sun.jna.FunctionResultContext;
import com.sun.jna.NativeMapped;
import com.sun.jna.ToNativeContext;
import com.sun.jna.TypeConverter;

/**
 * A JNA TypeConverter that gets the ID from an NSObject when passing it into 
 * native code, and creates an instance of subclass of NSObject to wrap an id
 * when receiving one from native code.
 */
class ObjCObjectTypeConverter<T extends ObjCObject> implements TypeConverter {
    
    private static final NativeMapped nativeLongConverter = new ID();

    private final Class<T> javaType;

    public ObjCObjectTypeConverter(Class<T> javaType) {
        this.javaType = javaType;
    }

    public Class<?> nativeType() {
        // we can't return NativeLong here - has to be a primitive type, so
        // delegate so that we are 32/64 correct
        return nativeLongConverter.nativeType();
    }

    // Takes an Integer or Long representing id (32 or 64 bit respectively)
    // and returns an NSObject of javaType with that id.
    public T fromNative(Object nativeValue, FromNativeContext context) {
        Number nativeValueAsNumber = (Number) nativeValue;
        if (nativeValueAsNumber == null) {
            return null;
        }
        ID id = ID.fromLong(nativeValueAsNumber.longValue());
        if (id.isNull()) {
            return null;
        }
        boolean shouldRetain = shouldRetainFor(context);        
        return Rococoa.wrap(id, javaType, shouldRetain);
    }
    
    // Takes an NSObject and returns its id as Integer or Long
    public Object toNative(Object value, ToNativeContext context) {
        if (value == null) {
            return null;
        }
        ObjCObject valueAsNSObject = (ObjCObject) value;
        ID idToReturn = valueAsNSObject.id();
        return idToReturn.toNative();
    }
    
    // For tests only
    boolean convertsJavaType(Class<?> javaType) {
        return this.javaType == javaType;
    }
    
    private boolean shouldRetainFor(FromNativeContext context) {
        // Generally we should default to retaining, as by default NSObjects that
        // are returned from methods are owned by the current autorelease pool and
        // unless we retain will be dealloc'ed when is is drained.
        if (!(context instanceof FunctionResultContext)) {
            return true;
        }
        // The exception is if this conversion is for an object that we own, because
        // the selector name matches those
        FunctionResultContext resultContext = (FunctionResultContext) context;
        Object[] arguments = resultContext.getArguments();
        if (arguments.length < 2) {
            return true;
        }
        if (!(arguments[1] instanceof Selector)) {
            return true;
        }
        boolean dontRetain = Foundation.selectorNameMeansWeOwnReturnedObject(((Selector) arguments[1]).getName());
        return !dontRetain; // OK Smartarse, you express it better.
    }
}