/*
   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.beans;


import java.util.Date;

import junit.framework.TestCase;

import org.dynalang.mop.BaseMetaobjectProtocol.Result;

public class TestBeans extends TestCase
{
    private BeansMetaobjectProtocol mop;
    private BeanForTest tb;
    
    public TestBeans(String name) {
        super(name);
    }

    @Override
    protected void setUp() throws Exception
    {
        mop = new BeansMetaobjectProtocol(true);
        tb = new BeanForTest();
    }
    
    public void testPropertyExists() {
        assertTrue(mop.has(tb, "someBool"));
    }

    public void testBooleanPropertyGet() {
        assertEquals(mop.get(tb, "someBool"), Boolean.TRUE);
    }
    
    public void testBooleanPropertySet() {
        assertSame(mop.put(tb, "someBool", Boolean.FALSE, mop), Result.ok);
        assertEquals(mop.get(tb, "someBool"), Boolean.FALSE);
    }

    public void testAutomaticWideningAtPropertySet() {
        assertSame(mop.put(tb, "someDouble", Integer.valueOf(1), mop), Result.ok);
        assertEquals(mop.get(tb, "someDouble"), Double.valueOf(1.0d));
    }

    public void testNoAutomaticNarrowingAtPropertySet() {
        assertSame(mop.put(tb, "someInteger", Double.valueOf(1.0), mop), Result.noRepresentation);
        // assert value hasn't changed
        assertEquals(mop.get(tb, "someInteger"), Integer.valueOf(0));
    }
    
    public void testPropertyUnknownOnPut() {
        assertSame(mop.put(tb, "foo", null, null), Result.noAuthority);
    }
    
    public void testNumericPropertyUnknownOnPut() {
        assertSame(mop.put(tb, 1, null, null), Result.noAuthority);
    }

    public void testPropertyUnknownOnGet() {
        assertSame(mop.get(tb, "foo"), Result.noAuthority);
    }
        
    public void testNumericPropertyUnknownOnGet() {
        assertSame(mop.get(tb, 1), Result.noAuthority);
    }

    public void testPropertyUnknownOnHas() {
        assertNull(mop.has(tb, "foo"));
    }
    
    public void testNumericPropertyUnknownOnHas() {
        assertNull(mop.has(tb, 1));
    }
    
    public void testSimpleFixArgMethodInvocation() {
        assertEquals(mop.call(tb, "simpleFixArgMethod", mop, "a", "b"), "a:b");
    }
    
    public void testSimpleFixArgMethodInvocationThroughMember() {
        Object method = mop.get(tb, "simpleFixArgMethod");
        assertNotNull(method);
        assertFalse(method instanceof Result);
        assertEquals(mop.call(method, mop, "a", "b"), "a:b");
    }

    public void testSimpleVarArgMethodInvocation() {
        assertEquals(mop.call(tb, "simpleVarArgMethod", mop, 1, "a", "b", "c"), 
                "1:[a, b, c]");
    }

    public void testVarArgMethodInvocationWithZeroVarArg() {
        assertEquals(mop.call(tb, "simpleVarArgMethod", mop, 1), "1:[]");
    }

    public void testVarArgMethodInvocationWithSingleVarArg() {
        assertEquals(mop.call(tb, "simpleVarArgMethod", mop, 1, "a"), "1:[a]");
    }

    public void testVarArgMethodInvocationWithNullVarArg() {
        assertEquals(mop.call(tb, "simpleVarArgMethod", mop, 1, null), 
                "1:null");
    }

    public void testVarArgMethodInvocationWithExplicitArray() {
        assertEquals(mop.call(tb, "simpleVarArgMethod", 
                mop, 1, new Object[] { "a", "b" }), "1:[a, b]");
    }

    public void testVarArgMethodInvocationWithExplicitArrayPlusOne() {
        Object arr = new Object[] { "a", "b" };
        assertEquals(mop.call(tb, "simpleVarArgMethod", mop, 1, arr, "c"), 
                "1:[" + arr.toString() + ", c]");
    }

    public void testVarArgMethodInvocationWithWideningPrimitiveConversion() {
        assertEquals(mop.call(tb, "simpleVarArgPrimitiveMethod", 
                mop, new Object[] { Integer.valueOf(1), Byte.valueOf((byte)2), 
                Short.valueOf((short)3), Character.valueOf('0'), 
                Integer.valueOf(5) }), "1:[2, 3, 48, 5]");
    }
    
    public void testVarArgMethodInvocationCantRepresent() {
        try {
            mop.call(tb, "simpleVarArgPrimitiveMethod", mop, new Date());
            fail();
        }
        catch(IllegalArgumentException e) {
            assertTrue(e.getMessage().startsWith("Can't represent"));
        }
    }

    public void testOverloadedMethodInvocationNoSuch() {
        try {
            mop.call(tb, "overloadedFixArgMethod", mop, new Date());
            fail();
        }
        catch(IllegalArgumentException e) {
            assertTrue(e.getMessage().startsWith("No signature"));
        }
    }

    public void testOverloadedMethodInvocationAmbiguous() {
        try {
            mop.call(tb, "overloadedAmbiguous", mop, "");
            fail();
        }
        catch(IllegalArgumentException e) {
            assertTrue(e.getMessage().startsWith("Multiple signatures"));
        }
    }

    public void testFixArgOverloadedMethod() {
        assertEquals(mop.call(tb, "overloadedFixArgMethod", mop, 2), "int:2");
        assertEquals(mop.call(tb, "overloadedFixArgMethod", mop, "a"), "string:a");
        assertEquals(mop.call(tb, "overloadedFixArgMethod", mop, "b", 3), "string/int:b3");
        assertEquals(mop.call(tb, "overloadedFixArgMethod", mop, 4, "c"), "int/string:4c");
    }

    public void testVarArgOverloadedMethod() {
        assertEquals(mop.call(tb, "overloadedVarArgMethod", mop, 2), "int/String...:2[]");
        assertEquals(mop.call(tb, "overloadedVarArgMethod", mop, 2, "a"), "int/String...:2[a]");
        assertEquals(mop.call(tb, "overloadedVarArgMethod", mop, 2, 3), "int/int/String...:23[]");
        assertEquals(mop.call(tb, "overloadedVarArgMethod", mop, 2, 3, "a"), "int/int/String...:23[a]");
        assertEquals(mop.call(tb, "overloadedVarArgMethod", mop, 2, "a", "b"), "int/String...:2[a, b]");

        assertEquals(mop.call(tb, "varArgs", mop, 1, 2, 3), "varArgs-int...");
        assertEquals(mop.call(tb, "varArgs", mop, 1, 2), "varArgs-int,int");
        assertEquals(mop.call(tb, "varArgs", mop, 1), "varArgs-int...");
        assertEquals(mop.call(tb, "varArgs", mop), "varArgs-int..."); 
        assertEquals(mop.call(tb, "moreSpecific", mop, "someString"), "moreSpecific-String"); 
}

    public void testNullArgsSameAsEmpty() {
	tb.setSomeDouble(2.0d);
        assertEquals(mop.call(tb, "getSomeDouble", mop, (Object[])null), Double.valueOf(2.0d));
    }
    
    public void testWideningPrimitiveConversionOnCall() {
        assertNull(mop.call(tb, "setSomeDouble", mop, new Object[] { Integer.valueOf(1) }));
        assertEquals(tb.getSomeDouble(), 1.0d);
    }

    public void testNoConversionOnCall() {
        try {
            mop.call(tb, "setSomeDouble", mop, "a");
            fail();
        }
        catch(IllegalArgumentException e) {
            // this is expected
        }
    }

    public void testPropertiesNotCallable() {
        assertEquals(mop.call(tb, "someBool", mop, "a", "b"), 
        	Result.noAuthority);
    }

    public void testNonexistantMethodInvocation() {
        assertEquals(mop.call(tb, "someMethodThatIsNot", mop, "a", "b"), 
                Result.noAuthority);
    }
    
    public void testConstructorOverloading() {
	assertCreatedBeanForTest(true, 0, 0d);
	assertCreatedBeanForTest(false, 0, 0d, false);
	assertCreatedBeanForTest(true, 1, 0d, 1);
	assertCreatedBeanForTest(true, 0, 1d, 1d);
	assertCreatedBeanForTest(false, 1, 0d, false, 1);
	assertCreatedBeanForTest(false, 0, 1d, false, 1d);
	assertCreatedBeanForTest(true, 1, 1d, 1, 1d);
	// No signature matches -- expected to throw an exception
	try {
	    assertCreatedBeanForTest(true, 0, 0d, new Object());
	    fail();
	}
	catch(IllegalArgumentException e) {
	    assertEquals("No signature of method <init> on class org.dynalang.mop.beans.BeanForTest matches the arguments", e.getMessage());
	}
    }
    
    public void testStaticMethodOverloading() {
	assertStaticTest(true, 0, 0d);
	assertStaticTest(false, 0, 0d, false);
	assertStaticTest(true, 1, 0d, 1);
	assertStaticTest(true, 0, 1d, 1d);
    }

    public void testStaticMethodNotFound() {
	assertEquals(Result.doesNotExist, mop.getBeanMetaobjectProtocol(
		BeanForTest.class).callStatic("foo", mop));
    }
    
    public void testInstanceVsStaticMethod() {
	// No signature matches -- expected to throw an exception. The create
	// method taking a string is an instance, not static method.
	try {
	    assertStaticTest(true, 0, 0d, "");
	    fail();
	}
	catch(IllegalArgumentException e) {
	    assertEquals("No signature of method create on class org.dynalang.mop.beans.BeanForTest matches the arguments", e.getMessage());
	}
	
	// No signature matches -- expected to throw an exception. The create
	// method taking a double is a static, not instance method.
	try {
	    mop.call(tb, "create", mop, 1d);
	    fail();
	}
	catch(IllegalArgumentException e) {
	    assertEquals("Can't represent java.lang.Double as java.lang.String for invoking public void org.dynalang.mop.beans.BeanForTest.create(java.lang.String)", e.getMessage());
	}
    }
    
    private void assertCreatedBeanForTest(boolean someBool, int someInteger, double someDouble, Object... args) {
	BeanForTest bft = mop.getBeanMetaobjectProtocol(BeanForTest.class).newInstance(mop, args);
	assertEquals(someBool, bft.isSomeBool());
	assertEquals(someInteger, bft.getSomeInteger());
	assertEquals(someDouble, bft.getSomeDouble());
    }

    private void assertStaticTest(boolean someBool, int someInteger, double someDouble, Object... args) {
	Object retval = mop.getBeanMetaobjectProtocol(BeanForTest.class).callStatic("create", mop, args);
	assertTrue(retval instanceof BeanForTest);
	BeanForTest bft = (BeanForTest)retval;
	assertEquals(someBool, bft.isSomeBool());
	assertEquals(someInteger, bft.getSomeInteger());
	assertEquals(someDouble, bft.getSomeDouble());
    }
    
}