
package org.jruby.ext.ffi.jffi;

import com.kenai.jffi.CallingConvention;
import com.kenai.jffi.Function;
import com.kenai.jffi.HeapInvocationBuffer;
import com.kenai.jffi.InvocationBuffer;
import com.kenai.jffi.Invoker;
import com.kenai.jffi.ArrayFlags;
import org.jruby.RubyHash;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyString;
import org.jruby.ext.ffi.AbstractMemory;
import org.jruby.ext.ffi.ArrayMemoryIO;
import org.jruby.ext.ffi.Buffer;
import org.jruby.ext.ffi.CallbackInfo;
import org.jruby.ext.ffi.Enums;
import org.jruby.ext.ffi.MappedType;
import org.jruby.ext.ffi.MemoryIO;
import org.jruby.ext.ffi.MemoryPointer;
import org.jruby.ext.ffi.NativeType;
import org.jruby.ext.ffi.Platform;
import org.jruby.ext.ffi.Struct;
import org.jruby.ext.ffi.StructByValue;
import org.jruby.ext.ffi.StructLayout;
import org.jruby.ext.ffi.Type;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

import static org.jruby.api.Convert.asFloat;
import static org.jruby.api.Convert.toDouble;
import static org.jruby.api.Create.dupString;
import static org.jruby.api.Error.*;


public final class DefaultMethodFactory extends MethodFactory {

    private static final class SingletonHolder {
        private static final DefaultMethodFactory INSTANCE = new DefaultMethodFactory();
    }
    
    public static DefaultMethodFactory getFactory() {
        return SingletonHolder.INSTANCE;
    }

    private DefaultMethodFactory() {}

    
    @Override
    boolean isSupported(Type returnType, Type[] parameterTypes, CallingConvention convention) {
        return true;
    }
    
    DynamicMethod createMethod(RubyModule module, Function function, 
            Type returnType, Type[] parameterTypes, CallingConvention convention, IRubyObject enums, boolean ignoreError) {

        FunctionInvoker functionInvoker = getFunctionInvoker(returnType);

        ParameterMarshaller[] marshallers = new ParameterMarshaller[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; ++i)  {
            marshallers[i] = getMarshaller(parameterTypes[i], convention, enums);
            if (marshallers[i] == null) {
                throw typeError(module.getRuntime().getCurrentContext(), "Could not create marshaller for " + parameterTypes[i]);
            }
        }

        Signature signature = new Signature(returnType, parameterTypes, convention, 
                ignoreError, enums instanceof RubyHash ? (RubyHash) enums : null);

        BufferNativeInvoker fallbackInvoker = new BufferNativeInvoker(module, function, signature, functionInvoker, marshallers);
        return parameterTypes.length <= 6
            ? new DefaultMethod(module, function, signature, fallbackInvoker)
            : fallbackInvoker;
    }

    static FunctionInvoker getFunctionInvoker(Type returnType) {
        try {
            if (returnType instanceof Type.Builtin) {
                return getFunctionInvoker(returnType.getNativeType());

            } else if (returnType instanceof CallbackInfo) {
                return new ConvertingInvoker(getFunctionInvoker(NativeType.POINTER),
                        DataConverters.getResultConverter(returnType));

            } else if (returnType instanceof StructByValue) {
                return new StructByValueInvoker((StructByValue) returnType);

            } else if (returnType instanceof MappedType) {
                MappedType ctype = (MappedType) returnType;
                return new ConvertingInvoker(getFunctionInvoker(ctype.getRealType()),
                        DataConverters.getResultConverter(ctype));
            }
        } catch (IllegalArgumentException e) {
            // throw an exception
        }

        throw typeError(returnType.getRuntime().getCurrentContext(), "Cannot get FunctionInvoker for " + returnType);
    }

    static FunctionInvoker getFunctionInvoker(NativeType returnType) {
        switch (returnType) {
            case VOID:
                return VoidInvoker.INSTANCE;
            case BOOL:
                return BooleanInvoker.INSTANCE;
            case POINTER:
                return Platform.getPlatform().addressSize() == 32 ? Pointer32Invoker.INSTANCE : Pointer64Invoker.INSTANCE;
            case CHAR:
                return Signed8Invoker.INSTANCE;
            case SHORT:
                return Signed16Invoker.INSTANCE;
            case INT:
                return Signed32Invoker.INSTANCE;
            case UCHAR:
                return Unsigned8Invoker.INSTANCE;
            case USHORT:
                return Unsigned16Invoker.INSTANCE;
            case UINT:
                return Unsigned32Invoker.INSTANCE;
            case LONG_LONG:
                return Signed64Invoker.INSTANCE;
            case ULONG_LONG:
                return Unsigned64Invoker.INSTANCE;
//            case LONGDOUBLE:
//                return Float128Invoker.INSTANCE;
            case LONG:
                return Platform.getPlatform().longSize() == 32
                        ? Signed32Invoker.INSTANCE
                        : Signed64Invoker.INSTANCE;
            case ULONG:
                return Platform.getPlatform().longSize() == 32
                        ? Unsigned32Invoker.INSTANCE
                        : Unsigned64Invoker.INSTANCE;
            case FLOAT:
                return Float32Invoker.INSTANCE;
            case DOUBLE:
                return Float64Invoker.INSTANCE;
            case STRING:
            case TRANSIENT_STRING:
                return StringInvoker.INSTANCE;
                
            default:
                throw new IllegalArgumentException("Invalid return type: " + returnType);
        }
    }
    /**
     * Gets a marshaller to convert from a ruby type to a native type.
     *
     * @param type The native type to convert to.
     * @return A new <code>Marshaller</code>
     */
    static ParameterMarshaller getMarshaller(Type type, CallingConvention convention, IRubyObject enums) {
        if (enums != null && !enums.isNil() && !(enums instanceof RubyHash || enums instanceof Enums)) {
            typeError(type.getRuntime().getCurrentContext(), enums, "Hash or Enums");
        }

        if (type instanceof Type.Builtin) {
            return enums != null && !enums.isNil() ? getEnumMarshaller(type, convention, enums) : getMarshaller(type.getNativeType());

        } else if (type instanceof org.jruby.ext.ffi.CallbackInfo) {
            return new ConvertingMarshaller(getMarshaller(type.getNativeType()), 
                    DataConverters.getParameterConverter(type, (Enums)null));

        } else if (type instanceof org.jruby.ext.ffi.StructByValue) {
            return new StructByValueMarshaller((org.jruby.ext.ffi.StructByValue) type);
        
        } else if (type instanceof org.jruby.ext.ffi.MappedType) {
            MappedType ctype = (MappedType) type;
            if (enums == null || enums instanceof Enums)
                return new ConvertingMarshaller(
                    getMarshaller(ctype.getRealType(), convention, (Enums)enums), 
                    DataConverters.getParameterConverter(type, (Enums)enums));
            else
                return newConvertingMarshallerWithHash(type, convention, enums, ctype);

        } else {
            return null;
        }
    }

    @SuppressWarnings("deprecation")
    private static ParameterMarshaller newConvertingMarshallerWithHash(Type type, CallingConvention convention, IRubyObject enums, MappedType ctype) {
        return new ConvertingMarshaller(
            getMarshaller(ctype.getRealType(), convention, enums.isNil() ? null : (RubyHash)enums),
            DataConverters.getParameterConverter(type, enums.isNil() ? null : (RubyHash)enums));
    }

    /**
     * Gets a marshaller to convert from a ruby type to a native type.
     *
     * @param type The native type to convert to.
     * @param enums The enum map
     * @return A new <code>ParameterMarshaller</code>
     */
    static ParameterMarshaller getEnumMarshaller(Type type, CallingConvention convention, IRubyObject enums) {
        if (!(enums instanceof Enums)) typeError(type.getRuntime().getCurrentContext(), enums, "Enums");

        NativeDataConverter converter = DataConverters.getParameterConverter(type, (Enums)enums);
        ParameterMarshaller marshaller = getMarshaller(type.getNativeType());
        return converter != null ? new ConvertingMarshaller(marshaller, converter) : marshaller;
    }

    /**
     * Gets a marshaller to convert from a ruby type to a native type.
     *
     * @param type The native type to convert to.
     * @return A new <code>ParameterMarshaller</code>
     */
    static ParameterMarshaller getMarshaller(NativeType type) {
        switch (type) {
            case BOOL:
                return BooleanMarshaller.INSTANCE;
            case CHAR:
                return Signed8Marshaller.INSTANCE;
            case UCHAR:
                return Unsigned8Marshaller.INSTANCE;
            case SHORT:
                return Signed16Marshaller.INSTANCE;
            case USHORT:
                return Unsigned16Marshaller.INSTANCE;
            case INT:
                return Signed32Marshaller.INSTANCE;
            case UINT:
                return Unsigned32Marshaller.INSTANCE;
            case LONG_LONG:
                return Signed64Marshaller.INSTANCE;
            case ULONG_LONG:
                return Unsigned64Marshaller.INSTANCE;
//            case LONGDOUBLE:
//                return Float128Marshaller.INSTANCE;
            case LONG:
                return Platform.getPlatform().longSize() == 32
                        ? Signed32Marshaller.INSTANCE
                        : Signed64Marshaller.INSTANCE;
            case ULONG:
                return Platform.getPlatform().longSize() == 32
                        ? Signed32Marshaller.INSTANCE
                        : Unsigned64Marshaller.INSTANCE;
            case FLOAT:
                return Float32Marshaller.INSTANCE;
            case DOUBLE:
                return Float64Marshaller.INSTANCE;
            case STRING:
            case TRANSIENT_STRING:
                return StringMarshaller.INSTANCE;
            case POINTER:
                return BufferMarshaller.INOUT;
            case BUFFER_IN:
                return BufferMarshaller.IN;
            case BUFFER_OUT:
                return BufferMarshaller.OUT;
            case BUFFER_INOUT:
                return BufferMarshaller.INOUT;
    
            default:
                throw new IllegalArgumentException("Invalid parameter type: " + type);
        }
    }
    
    private static abstract class BaseInvoker implements FunctionInvoker {
        static final Invoker invoker = Invoker.getInstance();
    }
    /**
     * Invokes the native function with no return type, and returns nil to ruby.
     */
    private static final class VoidInvoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new VoidInvoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            invoker.invokeInt(function, args);
            return context.nil;
        }
    }

    /**
     * Invokes the native function with a boolean return value.
     * Returns a Boolean to ruby.
     */
    private static final class BooleanInvoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new BooleanInvoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return JITRuntime.newBoolean(context, invoker.invokeInt(function, args));
        }
    }

    /**
     * Invokes the native function with n signed 8 bit integer return value.
     * Returns a Fixnum to ruby.
     */
    private static final class Signed8Invoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new Signed8Invoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return JITRuntime.newSigned8(context, invoker.invokeInt(function, args));
        }
    }

    /**
     * Invokes the native function with an unsigned 8 bit integer return value.
     * Returns a Fixnum to ruby.
     */
    private static final class Unsigned8Invoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new Unsigned8Invoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return JITRuntime.newUnsigned8(context, invoker.invokeInt(function, args));
        }
    }

    /**
     * Invokes the native function with n signed 8 bit integer return value.
     * Returns a Fixnum to ruby.
     */
    private static final class Signed16Invoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new Signed16Invoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return JITRuntime.newSigned16(context, invoker.invokeInt(function, args));
        }
    }

    /**
     * Invokes the native function with an unsigned 32 bit integer return value.
     * Returns a Fixnum to ruby.
     */
    private static final class Unsigned16Invoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new Unsigned16Invoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return JITRuntime.newUnsigned16(context, invoker.invokeInt(function, args));
        }
    }
    /**
     * Invokes the native function with a 32 bit integer return value.
     * Returns a Fixnum to ruby.
     */
    private static final class Signed32Invoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new Signed32Invoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return JITRuntime.newSigned32(context, invoker.invokeInt(function, args));
        }
    }

    /**
     * Invokes the native function with an unsigned 32 bit integer return value.
     * Returns a Fixnum to ruby.
     */
    private static final class Unsigned32Invoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new Unsigned32Invoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return JITRuntime.newUnsigned32(context, invoker.invokeInt(function, args));
        }
    }

    /**
     * Invokes the native function with a 64 bit integer return value.
     * Returns a Fixnum to ruby.
     */
    private static final class Signed64Invoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new Signed64Invoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return JITRuntime.newSigned64(context, invoker.invokeLong(function, args));
        }
    }

    /**
     * Invokes the native function with a 64 bit unsigned integer return value.
     * Returns a ruby Fixnum or Bignum.
     */
    private static final class Unsigned64Invoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new Unsigned64Invoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return JITRuntime.newUnsigned64(context, invoker.invokeLong(function, args));
        }
    }

    /**
     * Invokes the native function with a 32 bit float return value.
     * Returns a Float to ruby.
     */
    private static final class Float32Invoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new Float32Invoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return asFloat(context, invoker.invokeFloat(function, args));
        }
    }

    /**
     * Invokes the native function with a 64 bit float return value.
     * Returns a Float to ruby.
     */
    private static final class Float64Invoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new Float64Invoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return asFloat(context, invoker.invokeDouble(function, args));
        }
    }
    
    /**
     * Invokes the native function with a 128 bit float return value.
     * Returns a Float to ruby.
     */
    private static final class Float128Invoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new Float128Invoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return asFloat(context, 0); // not implemented
        }
    }

    /**
     * Invokes the native function with a native pointer return value.
     * Returns a {@link MemoryPointer} to ruby.
     */
    private static final class Pointer32Invoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new Pointer32Invoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return JITRuntime.newPointer32(context, invoker.invokeAddress(function, args));
        }
    }

    /**
     * Invokes the native function with a native pointer return value.
     * Returns a {@link MemoryPointer} to ruby.
     */
    private static final class Pointer64Invoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new Pointer64Invoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return JITRuntime.newPointer64(context, invoker.invokeAddress(function, args));
        }
    }

    /**
     * Invokes the native function with a native string return value.
     * Returns a {@link RubyString} to ruby.
     */
    private static final class StringInvoker extends BaseInvoker {
        public static final FunctionInvoker INSTANCE = new StringInvoker();
        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return JITRuntime.newString(context, invoker.invokeAddress(function, args));
        }
    }

    /**
     * Invokes the native function with a native struct return value.
     * Returns a FFI::Struct instance to ruby.
     */
    private static final class StructByValueInvoker extends BaseInvoker {
        private final StructByValue info;

        public StructByValueInvoker(StructByValue info) {
            this.info = info;
        }

        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            int size = info.getStructLayout().getSize();
            Buffer buf = new Buffer(context.runtime, size);
            MemoryIO mem = buf.getMemoryIO();
            byte[] array;
            int arrayOffset;
            if (mem instanceof ArrayMemoryIO arrayMemoryIO) {
                array = arrayMemoryIO.array();
                arrayOffset = arrayMemoryIO.arrayOffset();
            } else {
                array = new byte[size];
                arrayOffset = 0;
            }

            invoker.invokeStruct(function, args, array, arrayOffset);

            if (!(mem instanceof ArrayMemoryIO)) mem.put(0, array, 0, array.length);

            return info.getStructClass().newInstance(context, buf, Block.NULL_BLOCK);
        }
    }

    /**
     * Invokes the native function, then passes the return value off to a
     * conversion method to massage it to a custom ruby type.
     */
    private static final class ConvertingInvoker extends BaseInvoker {
        private final FunctionInvoker nativeInvoker;
        private final NativeDataConverter converter;

        public ConvertingInvoker(FunctionInvoker nativeInvoker, NativeDataConverter converter) {
            this.nativeInvoker = nativeInvoker;
            this.converter = converter;
        }

        public final IRubyObject invoke(ThreadContext context, Function function, HeapInvocationBuffer args) {
            return converter.fromNative(context, nativeInvoker.invoke(context, function, args));
        }
    }

    /*------------------------------------------------------------------------*/
    static abstract class NonSessionMarshaller implements ParameterMarshaller {
        public final boolean requiresPostInvoke() {
            return false;
        }

        public final boolean requiresReference() {
            return false;
        }

        public final void marshal(Invocation invocation, InvocationBuffer buffer, IRubyObject parameter) {
            marshal(invocation.getThreadContext(), buffer, parameter);
        }
    }

    /**
     * Converts a ruby Boolean into an 32 bit native integer.
     */
    static final class BooleanMarshaller extends NonSessionMarshaller {
        public static final ParameterMarshaller INSTANCE = new BooleanMarshaller();

        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            buffer.putByte(JITRuntime.boolValue32(parameter));
        }
    }

    /**
     * Converts a ruby Fixnum into an 8 bit native integer.
     */
    static final class Signed8Marshaller extends NonSessionMarshaller {
        public static final ParameterMarshaller INSTANCE = new Signed8Marshaller();

        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            buffer.putByte(JITRuntime.s8Value32(parameter));
        }
    }

    /**
     * Converts a ruby Fixnum into an 8 bit native unsigned integer.
     */
    static final class Unsigned8Marshaller extends NonSessionMarshaller {
        public static final ParameterMarshaller INSTANCE = new Unsigned8Marshaller();

        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            buffer.putByte(JITRuntime.u8Value32(parameter));
        }
    }

    /**
     * Converts a ruby Fixnum into a 16 bit native signed integer.
     */
    static final class Signed16Marshaller extends NonSessionMarshaller {
        public static final ParameterMarshaller INSTANCE = new Signed16Marshaller();
        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            buffer.putShort(JITRuntime.s16Value32(parameter));
        }
    }

    /**
     * Converts a ruby Fixnum into a 16 bit native unsigned integer.
     */
    static final class Unsigned16Marshaller extends NonSessionMarshaller {
        public static final ParameterMarshaller INSTANCE = new Unsigned16Marshaller();
        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            buffer.putShort(JITRuntime.u16Value32(parameter));
        }
    }

    /**
     * Converts a ruby Fixnum into a 32 bit native signed integer.
     */
    static final class Signed32Marshaller extends NonSessionMarshaller {
        public static final ParameterMarshaller INSTANCE = new Signed32Marshaller();
        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            buffer.putInt(JITRuntime.s32Value32(parameter));
        }
    }

    /**
     * Converts a ruby Fixnum into a 32 bit native unsigned integer.
     */
    static final class Unsigned32Marshaller extends NonSessionMarshaller {
        public static final ParameterMarshaller INSTANCE = new Unsigned32Marshaller();
        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            buffer.putInt(JITRuntime.u32Value32(parameter));
        }
    }

    /**
     * Converts a ruby Fixnum into a 64 bit native signed integer.
     */
    static final class Signed64Marshaller extends NonSessionMarshaller {
        public static final ParameterMarshaller INSTANCE = new Signed64Marshaller();
        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            buffer.putLong(JITRuntime.s64Value64(parameter));
        }
    }

    /**
     * Converts a ruby Fixnum into a 64 bit native unsigned integer.
     */
    static final class Unsigned64Marshaller extends NonSessionMarshaller {
        public static final ParameterMarshaller INSTANCE = new Unsigned64Marshaller();
        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            buffer.putLong(JITRuntime.u64Value64(parameter));
        }
    }

    /**
     * Converts a ruby Float into a 32 bit native float.
     */
    static final class Float32Marshaller extends NonSessionMarshaller {
        public static final ParameterMarshaller INSTANCE = new Float32Marshaller();
        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            buffer.putFloat((float) toDouble(context, parameter));
        }
    }

    /**
     * Converts a ruby Float into a 64 bit native float.
     */
    static final class Float64Marshaller extends NonSessionMarshaller {
        public static final ParameterMarshaller INSTANCE = new Float64Marshaller();
        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            buffer.putDouble(toDouble(context, parameter));
        }
    }
    
    /**
     * Converts a ruby Float into a 128 bit native float.
     */
    static final class Float128Marshaller extends NonSessionMarshaller {
        public static final ParameterMarshaller INSTANCE = new Float64Marshaller();
        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            return; // not implemented
        }
    }

    static abstract class PointerParameterMarshaller extends NonSessionMarshaller {
        private final int flags;
        public PointerParameterMarshaller(int flags) {
            this.flags = flags;
        }

        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter,
                                  PointerParameterStrategy strategy) {
            if (strategy.isDirect()) {
                buffer.putAddress(strategy.address(parameter));

            } else {
                if (ArrayFlags.isOut(flags)) {
                    if (parameter instanceof RubyString parmStr) {
                        if (parmStr.isFrozen()) parmStr = dupString(context, parmStr);
                        parmStr.modify(); // ensure string is modifiable
                    }
                }

                buffer.putArray(byte[].class.cast(strategy.object(parameter)), strategy.offset(parameter),
                        strategy.length(parameter), flags);
            }
        }
    }
    /**
     * Converts a ruby Buffer into a native address.
     */
    static final class BufferMarshaller extends PointerParameterMarshaller {
        static final ParameterMarshaller IN = new BufferMarshaller(ArrayFlags.IN);
        static final ParameterMarshaller OUT = new BufferMarshaller(ArrayFlags.OUT);
        static final ParameterMarshaller INOUT = new BufferMarshaller(ArrayFlags.IN | ArrayFlags.OUT);

        public BufferMarshaller(int flags) {
            super(flags);
        }

        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            marshal(context, buffer, parameter, JITRuntime.pointerParameterStrategy(parameter));
        }
    }

    /**
     * Converts a ruby String into a native pointer.
     */
    static final class StringMarshaller extends PointerParameterMarshaller {
        public static final ParameterMarshaller INSTANCE = new StringMarshaller(ArrayFlags.IN | ArrayFlags.NULTERMINATE);

        StringMarshaller(int flags) {
            super(flags);
        }

        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            marshal(context, buffer, parameter, JITRuntime.stringParameterStrategy(parameter));
        }
    }

    /**
     * Converts a ruby String into a native pointer.
     */
    static final class StructByValueMarshaller extends NonSessionMarshaller {
        private final StructLayout layout;
        public StructByValueMarshaller(org.jruby.ext.ffi.StructByValue sbv) {
            layout = sbv.getStructLayout();
        }


        public final void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            if (!(parameter instanceof Struct struct)) throw typeError(context, parameter, "instance of FFI::Struct");

            final AbstractMemory memory = struct.getMemory();
            if (memory.getSize() < layout.getSize()) throw argumentError(context, "struct memory too small for parameter");

            final MemoryIO io = memory.getMemoryIO();
            if (io.isDirect()) {
                if (io.isNull()) throw runtimeError(context, "Cannot use a NULL pointer as a struct by value argument");
                buffer.putStruct(io.address());
            } else if (io instanceof ArrayMemoryIO aio) {
                buffer.putStruct(aio.array(), aio.arrayOffset());
            } else {
                throw runtimeError(context, "invalid struct memory");
            }
        }
    }
    
    static final class ConvertingMarshaller implements ParameterMarshaller {
        private final ParameterMarshaller nativeMarshaller;
        private final NativeDataConverter converter;

        public ConvertingMarshaller(ParameterMarshaller nativeMarshaller, NativeDataConverter converter) {
            this.nativeMarshaller = nativeMarshaller;
            this.converter = converter;
        }


        public void marshal(Invocation invocation, InvocationBuffer buffer, IRubyObject parameter) {
            ThreadContext context = invocation.getThreadContext();
            final IRubyObject nativeValue = converter.toNative(context, parameter);

            // keep a hard ref to the converted value if needed
            if (converter.isReferenceRequired()) {
                invocation.addReference(nativeValue);
            }
            nativeMarshaller.marshal(context, buffer, nativeValue);
        }

        public void marshal(ThreadContext context, InvocationBuffer buffer, IRubyObject parameter) {
            nativeMarshaller.marshal(context, buffer, converter.toNative(context, parameter));
        }

        public boolean requiresPostInvoke() {
            return converter.isPostInvokeRequired();
        }

        public boolean requiresReference() {
            return converter.isReferenceRequired();
        }
    }
}
