/* Copyright (c) 2007-2014 Timothy Wall, All Rights Reserved
 *
 * The contents of this file is dual-licensed under 2 
 * alternative Open Source/Free licenses: LGPL 2.1 or later and 
 * Apache License 2.0. (starting with JNA version 4.0.0).
 * 
 * You can freely decide which license you want to apply to 
 * the project.
 * 
 * You may obtain a copy of the LGPL License at:
 * 
 * http://www.gnu.org/licenses/licenses.html
 * 
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "LGPL2.1".
 * 
 * You may obtain a copy of the Apache License at:
 * 
 * http://www.apache.org/licenses/
 * 
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "AL2.0".
 */
package com.sun.jna.win32;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;

import junit.framework.TestCase;

import com.sun.jna.FunctionMapper;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.NativeLibrary;
import com.sun.jna.NativeLong;
import com.sun.jna.Structure;

/**
 * @author twall@users.sf.net
 */
public class W32StdCallTest extends TestCase {

    public static interface TestLibrary extends StdCallLibrary {
        public static class Inner extends Structure {
            public static final List<String> FIELDS = createFieldsOrder("value");
            public double value;
            @Override
            protected List<String> getFieldOrder() {
                return FIELDS;
            }
        }
        public static class TestStructure extends Structure {
            public static class ByValue extends TestStructure implements Structure.ByValue { }
            public static final List<String> FIELDS = createFieldsOrder("c", "s", "i", "j", "inner");
            public byte c;
            public short s;
            public int i;
            public long j;
            public Inner inner;
            @Override
            protected List<String> getFieldOrder() {
                return FIELDS;
            }
        }
        int returnInt32ArgumentStdCall(int arg);
        TestStructure.ByValue returnStructureByValueArgumentStdCall(TestStructure.ByValue arg);
        interface Int32Callback extends StdCallCallback {
            int callback(int arg, int arg2);
        }
        int callInt32StdCallCallback(Int32Callback c, int arg, int arg2);
        interface ManyArgsStdCallCallback extends StdCallCallback {
            void callback(NativeLong arg1, int arg2, double arg3,
                          String arg4, String arg5,
                          double arg6, NativeLong arg7,
                          double arg8, NativeLong arg9,
                          NativeLong arg10, NativeLong arg11);
        }
        int callManyArgsStdCallCallback(ManyArgsStdCallCallback c, NativeLong arg1, int arg2,
                                        double arg3, String arg4, String arg5,
                                        double arg6, NativeLong arg7,
                                        double arg8, NativeLong arg9,
                                        NativeLong arg10, NativeLong arg11);
    }

    public static void main(java.lang.String[] argList) {
        junit.textui.TestRunner.run(W32StdCallTest.class);
    }

    private TestLibrary testlib;

    @Override
    protected void setUp() {
        testlib = Native.loadLibrary("testlib", TestLibrary.class,
                Collections.singletonMap(Library.OPTION_FUNCTION_MAPPER, StdCallLibrary.FUNCTION_MAPPER));
    }

    @Override
    protected void tearDown() {
        testlib = null;
    }

    public void testFunctionMapper() throws Exception {
        FunctionMapper mapper = StdCallLibrary.FUNCTION_MAPPER;
        NativeLibrary lib = NativeLibrary.getInstance("testlib");

        Method[] methods = {
            TestLibrary.class.getMethod("returnInt32ArgumentStdCall", int.class),
            TestLibrary.class.getMethod("returnStructureByValueArgumentStdCall", TestLibrary.TestStructure.ByValue.class),
            TestLibrary.class.getMethod("callInt32StdCallCallback", TestLibrary.Int32Callback.class, int.class, int.class)
        };

        for (Method m : methods) {
            String name = mapper.getFunctionName(lib, m);
            assertTrue("Function name not decorated for method "
                       + m.getName()
                       + ": " + name, name.indexOf("@") != -1);
            assertEquals("Wrong name in mapped function",
                         name, lib.getFunction(name, StdCallLibrary.STDCALL_CONVENTION).getName());
        }
    }

    public void testStdCallReturnInt32Argument() {
        final int MAGIC = 0x12345678;
        assertEquals("Expect zero return", 0, testlib.returnInt32ArgumentStdCall(0));
        assertEquals("Expect magic return", MAGIC, testlib.returnInt32ArgumentStdCall(MAGIC));
    }

    public void testStdCallReturnStructureByValueArgument() {
        TestLibrary.TestStructure.ByValue s = new TestLibrary.TestStructure.ByValue();
        assertTrue("Wrong struct value", s.dataEquals(testlib.returnStructureByValueArgumentStdCall(s)));
    }

    public void testStdCallCallback() {
        final int MAGIC = 0x11111111;
        final boolean[] called = { false };
        TestLibrary.Int32Callback cb = new TestLibrary.Int32Callback() {
            @Override
            public int callback(int arg, int arg2) {
                called[0] = true;
                return arg + arg2;
            }
        };
        final int EXPECTED = MAGIC*3;
        int value = testlib.callInt32StdCallCallback(cb, MAGIC, MAGIC*2);
        assertTrue("stdcall callback not called", called[0]);
        if (value == -1) {
            fail("stdcall callback did not restore the stack pointer");
        }
        assertEquals("Wrong stdcall callback value", Integer.toHexString(EXPECTED),
                     Integer.toHexString(value));

        value = testlib.callInt32StdCallCallback(cb, -1, -2);
        if (value == -1) {
            fail("stdcall callback did not restore the stack pointer");
        }
        assertEquals("Wrong stdcall callback return", -3, value);
    }

    public void testStdCallCallbackStackAlignment() {
        final boolean[] called = { false };
        TestLibrary.ManyArgsStdCallCallback cb = new TestLibrary.ManyArgsStdCallCallback() {
            @Override
            public void callback(NativeLong arg1, int arg2, double arg3,
                                 String arg4, String arg5,
                                 double arg6, NativeLong arg7,
                                 double arg8, NativeLong arg9,
                                 NativeLong arg10, NativeLong arg11) {
                called[0] = true;
            }
        };
        int value = testlib.callManyArgsStdCallCallback(cb, new NativeLong(1),
                                                        2, 3, "four", "five",
                                                        6, new NativeLong(7),
                                                        8, new NativeLong(9),
                                                        new NativeLong(10),
                                                        new NativeLong(11));
        assertTrue("stdcall callback not called", called[0]);
        if (value == -1) {
            fail("stdcall callback did not restore the stack pointer");
        }
    }
}
