/* Copyright (c) 2007 Wayne Meissner, All Rights Reserved
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 * <p/>
 * This library 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.  
 */

package com.sun.jna;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.Map;
import junit.framework.TestCase;

@SuppressWarnings("unused")
public class TypeMapperTest extends TestCase {
    public static interface TestLibrary extends Library {
        int returnInt32Argument(boolean b);
        int returnInt32Argument(String s);
        int returnInt32Argument(Number n);
    }

    public void testBooleanToIntArgumentConversion() {
        final int MAGIC = 0xABEDCF23;
        Map options = new HashMap();
        DefaultTypeMapper mapper = new DefaultTypeMapper();
        mapper.addToNativeConverter(Boolean.class, new ToNativeConverter() {
            public Object toNative(Object arg, ToNativeContext ctx) {
                return new Integer(Boolean.TRUE.equals(arg) ? MAGIC : 0);
            }
            public Class nativeType() {
                return Integer.class;
            }
        });
        options.put(Library.OPTION_TYPE_MAPPER, mapper);
        TestLibrary lib = (TestLibrary) 
            Native.loadLibrary("testlib", TestLibrary.class, options);
        assertEquals("Failed to convert Boolean argument to Int", MAGIC,
                     lib.returnInt32Argument(true));
    }
    public void testStringToIntArgumentConversion() {
        DefaultTypeMapper mapper = new DefaultTypeMapper();
        mapper.addToNativeConverter(String.class, new ToNativeConverter() {
            public Object toNative(Object arg, ToNativeContext ctx) {
                return Integer.valueOf((String) arg, 16);
            }
            public Class nativeType() {
                return Integer.class;
            }
        });
        Map options = new HashMap();
        options.put(Library.OPTION_TYPE_MAPPER, mapper);
        final int MAGIC = 0x7BEDCF23;
        TestLibrary lib = (TestLibrary) 
            Native.loadLibrary("testlib", TestLibrary.class, options);
        assertEquals("Failed to convert String argument to Int", MAGIC,
                     lib.returnInt32Argument(Integer.toHexString(MAGIC)));
    }
    public void testCharSequenceToIntArgumentConversion() {
        DefaultTypeMapper mapper = new DefaultTypeMapper();
        mapper.addToNativeConverter(CharSequence.class, new ToNativeConverter() {
            public Object toNative(Object arg, ToNativeContext ctx) {
                return Integer.valueOf(((CharSequence)arg).toString(), 16);
            }
            public Class nativeType() {
                return Integer.class;
            }
        });
        Map options = new HashMap();
        options.put(Library.OPTION_TYPE_MAPPER, mapper);
        
        final int MAGIC = 0x7BEDCF23;
        TestLibrary lib = (TestLibrary) 
            Native.loadLibrary("testlib", TestLibrary.class, options);
        assertEquals("Failed to convert String argument to Int", MAGIC,
                     lib.returnInt32Argument(Integer.toHexString(MAGIC)));
    }
    public void testNumberToIntArgumentConversion() {
        DefaultTypeMapper mapper = new DefaultTypeMapper();
        mapper.addToNativeConverter(Double.class, new ToNativeConverter() {
            public Object toNative(Object arg, ToNativeContext ctx) {
                return new Integer(((Double)arg).intValue());
            }
            public Class nativeType() {
                return Integer.class;
            }
        });
        Map options = new HashMap();
        options.put(Library.OPTION_TYPE_MAPPER, mapper);
        
        final int MAGIC = 0x7BEDCF23;
        TestLibrary lib = (TestLibrary) 
            Native.loadLibrary("testlib", TestLibrary.class, options);
        assertEquals("Failed to convert Double argument to Int", MAGIC,
                     lib.returnInt32Argument(new Double(MAGIC)));
    }
    public static interface BooleanTestLibrary extends Library {
        boolean returnInt32Argument(boolean b);
    }
    public void testIntegerToBooleanResultConversion() throws Exception {
        final int MAGIC = 0xABEDCF23;
        Map options = new HashMap();
        DefaultTypeMapper mapper = new DefaultTypeMapper();
        mapper.addToNativeConverter(Boolean.class, new ToNativeConverter() {
            public Object toNative(Object value, ToNativeContext ctx) {
                return new Integer(Boolean.TRUE.equals(value) ? MAGIC : 0);
            }
            public Class nativeType() {
                return Integer.class;
            }
        });
        mapper.addFromNativeConverter(Boolean.class, new FromNativeConverter() {
            public Object fromNative(Object value, FromNativeContext context) {
                return Boolean.valueOf(((Integer) value).intValue() == MAGIC);
            }
            public Class nativeType() { 
                return Integer.class;
            }
        });
        options.put(Library.OPTION_TYPE_MAPPER, mapper);
        BooleanTestLibrary lib = (BooleanTestLibrary) 
            Native.loadLibrary("testlib", BooleanTestLibrary.class, options);
        assertEquals("Failed to convert integer return to boolean TRUE", true,
                     lib.returnInt32Argument(true));
        assertEquals("Failed to convert integer return to boolean FALSE", false,
                     lib.returnInt32Argument(false));
    }
    public static interface StructureTestLibrary extends Library {
        public static class TestStructure extends Structure {
            public TestStructure(TypeMapper mapper) {
                setTypeMapper(mapper);
            }
            public boolean data;
        }
    }
    public void testStructureConversion() throws Exception {
        DefaultTypeMapper mapper = new DefaultTypeMapper();
        TypeConverter converter = new TypeConverter() {
            public Object toNative(Object value, ToNativeContext ctx) {
                return new Integer(Boolean.TRUE.equals(value) ? 1 : 0);
            }
            public Object fromNative(Object value, FromNativeContext context) {
                return new Boolean(((Integer)value).intValue() == 1);
            }
            public Class nativeType() {
                return Integer.class;
            }
        };
        mapper.addTypeConverter(Boolean.class, converter);
        Map options = new HashMap();
        options.put(Library.OPTION_TYPE_MAPPER, mapper);
		StructureTestLibrary lib = (StructureTestLibrary)
            Native.loadLibrary("testlib", StructureTestLibrary.class, options);
        StructureTestLibrary.TestStructure s = new StructureTestLibrary.TestStructure(mapper);
        assertEquals("Wrong native size", 4, s.size());
        
        s.data = true;
        s.write();
        assertEquals("Wrong value written", 1, s.getPointer().getInt(0));
        
        s.getPointer().setInt(0, 0);
        s.read();
        assertFalse("Wrong value read", s.data);
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public static @interface FooBoolean {}
    public static interface AnnotationTestLibrary extends Library {
        @FooBoolean
        boolean returnInt32Argument(boolean b);
    }
    public void testAnnotationsOnMethods() throws Exception {
        final int MAGIC = 0xABEDCF23;
        Map options = new HashMap();
        final boolean[] hasAnnotation = {false, false};
        DefaultTypeMapper mapper = new DefaultTypeMapper();        
        mapper.addTypeConverter(Boolean.class, new TypeConverter() {
            public Object toNative(Object value, ToNativeContext ctx) {
                MethodParameterContext mcontext = (MethodParameterContext)ctx;
                hasAnnotation[0] = mcontext.getMethod().getAnnotation(FooBoolean.class) != null;
                return new Integer(Boolean.TRUE.equals(value) ? MAGIC : 0);
            }
            public Object fromNative(Object value, FromNativeContext context) {
                MethodResultContext mcontext = (MethodResultContext)context;                
                hasAnnotation[1] = mcontext.getMethod().getAnnotation(FooBoolean.class) != null;
                return Boolean.valueOf(((Integer) value).intValue() == MAGIC);
            }
            public Class nativeType() {
                return Integer.class;
            }
        });
        
        options.put(Library.OPTION_TYPE_MAPPER, mapper);
        AnnotationTestLibrary lib = (AnnotationTestLibrary) 
            Native.loadLibrary("testlib", AnnotationTestLibrary.class, options);
        assertEquals("Failed to convert integer return to boolean TRUE", true,
                     lib.returnInt32Argument(true));
        assertTrue("Failed to get annotation from ParameterContext", hasAnnotation[0]);        
        assertTrue("Failed to get annotation from ResultContext", hasAnnotation[1]);        
    }
    public static void main(String[] args) {
        junit.textui.TestRunner.run(TypeMapperTest.class);
    }
}
