/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.commons.beanutils2;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 * <p>
 * Test Case for the {@code LazyDynaMap} implementation class.
 * </p>
 *
 */
public class LazyDynaMapTestCase extends TestCase {

    /**
     * Run thus Test
     */
    public static void main(final String[] args) {
        junit.textui.TestRunner.run(suite());
    }
    /**
     * Creates the tests included in this test suite.
     */
    public static Test suite() {
        return new TestSuite(LazyDynaMapTestCase.class);
    }
    protected LazyDynaMap dynaMap = null;
    protected String testProperty = "myProperty";
    protected String testPropertyA = "myProperty-A";
    protected String testPropertyB = "myProperty-B";
    protected String testString1 = "myStringValue-1";
    protected String testString2 = "myStringValue-2";
    protected Integer testInteger1 = Integer.valueOf(30);

    protected Integer testInteger2 = Integer.valueOf(40);

    protected String testKey = "myKey";

    /**
     * Constructs a new instance of this test case.
     *
     * @param name Name of the test case
     */
    public LazyDynaMapTestCase(final String name) {
        super(name);
    }

    /**
     * Sets up instance variables required by this test case.
     */
    @Override
    public void setUp() throws Exception {
        dynaMap = new LazyDynaMap();
        dynaMap.setReturnNull(true);
    }

    /**
     * Tear down instance variables required by this test case.
     */
    @Override
    public void tearDown() {
        dynaMap = null;
    }

    /**
     * General Tests
     */
    public void testGeneral() {
//        LazyDynaMap bean = new LazyDynaMap("TestBean");
        assertEquals("Check DynaClass name", "TestBean", new LazyDynaMap("TestBean").getName());

    }

    /**
     * Test Getting/Setting an DynaBean[] array
     */
    public void testIndexedDynaBeanArray() {

        final int index = 3;
        final Object objectArray = new LazyDynaBean[0];

        // Check the property & value doesn't exist
        assertNull("Check Indexed Property doesn't exist", dynaMap.getDynaProperty(testProperty));
        assertNull("Check Indexed Property is null", dynaMap.get(testProperty));

        // Add a DynaProperty of type String[]
        dynaMap.add(testProperty, objectArray.getClass());
        assertEquals("Check Indexed Property exists", objectArray.getClass(), dynaMap.getDynaProperty(testProperty).getType());
        assertEquals("Check Indexed Property is correct type", objectArray.getClass(), dynaMap.get(testProperty).getClass());

        // Retrieving from Array should initialize DynaBean
        for (int i = index; i >= 0; i--) {
            assertEquals("Check Array Components initialized", LazyDynaBean.class, dynaMap.get(testProperty, index).getClass());
        }

        dynaMap.add(testPropertyB, objectArray.getClass());
        final LazyDynaBean newBean = new LazyDynaBean();
        newBean.set(testPropertyB, testString2);
        dynaMap.set(testPropertyA, index, newBean);
        assertEquals("Check Indexed Value is correct(a)", testString2, ((DynaBean) dynaMap.get(testPropertyA, index)).get(testPropertyB));

    }

    /**
     * Test setting indexed property for type which is not List or Array
     */
    public void testIndexedInvalidType() {
        final int index = 3;
        dynaMap.set(testProperty, "Test String");
        assertFalse("Check Property is not indexed", dynaMap.getDynaProperty(testProperty).isIndexed());
        try {
            dynaMap.set(testProperty, index, testString1);
            fail("set(property, index, value) should have thrown IllegalArgumentException");
        } catch (final IllegalArgumentException expected) {
            // expected result
        }
    }

    /**
     * Test Getting/Setting a List 'Indexed' Property - use alternative List (LinkedList)
     */
    public void testIndexedLinkedList() {

        int index = 3;

        // Check the property & value doesn't exist
        assertNull("Check Indexed Property doesn't exist", dynaMap.getDynaProperty(testProperty));
        assertNull("Check Indexed Property is null", dynaMap.get(testProperty));

        // Add a 'LinkedList' property to the DynaClass - should instantiate a new LinkedList
        dynaMap.add(testProperty, LinkedList.class);
        assertTrue("Check Property is indexed", dynaMap.getDynaProperty(testProperty).isIndexed());
        assertEquals("Check Property is correct type", LinkedList.class, dynaMap.getDynaProperty(testProperty).getType());
        assertEquals("Check Indexed Property now exists", LinkedList.class, dynaMap.get(testProperty).getClass());

        // Set the Indexed property, should grow the list to the correct size
        dynaMap.set(testProperty, index, testString1);
        assertEquals("Check Property type is correct", LinkedList.class, dynaMap.get(testProperty).getClass());
        assertEquals("Check First Indexed Value is correct", testString1, dynaMap.get(testProperty, index));
        assertEquals("Check First Array length is correct", Integer.valueOf(index + 1), Integer.valueOf(((LinkedList<?>) dynaMap.get(testProperty)).size()));

        // Set a second indexed value, should automatically grow the LinkedList and set appropriate indexed value
        index = index + 2;
        dynaMap.set(testProperty, index, testInteger1);
        assertEquals("Check Second Indexed Value is correct", testInteger1, dynaMap.get(testProperty, index));
        assertEquals("Check Second Array length is correct", Integer.valueOf(index + 1), Integer.valueOf(((LinkedList<?>) dynaMap.get(testProperty)).size()));
    }

    /**
     * Test Getting/Setting an Object array 'Indexed' Property - use String[]
     */
    public void testIndexedObjectArray() {

        int index = 3;
        final Object objectArray = new String[0];

        // Check the property & value doesn't exist
        assertNull("Check Indexed Property doesn't exist", dynaMap.getDynaProperty(testProperty));
        assertNull("Check Indexed Property is null", dynaMap.get(testProperty));

        // Add a DynaProperty of type String[]
        dynaMap.add(testProperty, objectArray.getClass());
        assertEquals("Check Indexed Property exists", objectArray.getClass(), dynaMap.getDynaProperty(testProperty).getType());
        assertTrue("Check Indexed Property exists", dynaMap.get(testProperty).getClass().isInstance(objectArray));

        // Set an indexed value
        dynaMap.set(testProperty, index, testString1);
        assertNotNull("Check Indexed Property is not null", dynaMap.get(testProperty));
        assertEquals("Check Indexed Property is correct type", objectArray.getClass(), dynaMap.get(testProperty).getClass());
        assertEquals("Check First Indexed Value is correct(a)", testString1, dynaMap.get(testProperty, index));
        assertEquals("Check First Indexed Value is correct(b)", testString1, ((String[]) dynaMap.get(testProperty))[index]);
        assertEquals("Check Array length is correct", Integer.valueOf(index + 1), Integer.valueOf(((String[]) dynaMap.get(testProperty)).length));

        // Set a second indexed value, should automatically grow the String[] and set appropriate indexed value
        index = index + 2;
        dynaMap.set(testProperty, index, testString2);
        assertEquals("Check Second Indexed Value is correct(a)", testString2, dynaMap.get(testProperty, index));
        assertEquals("Check Second Indexed Value is correct(b)", testString2, ((String[]) dynaMap.get(testProperty))[index]);
        assertEquals("Check Second Array length is correct", Integer.valueOf(index + 1), Integer.valueOf(((String[]) dynaMap.get(testProperty)).length));
    }

    /**
     * Test Getting/Setting a primitive array 'Indexed' Property - use int[]
     */
    public void testIndexedPrimitiveArray() {

        int index = 3;
        final int[] primitiveArray = {};

        // Check the property & value doesn't exist
        assertNull("Check Indexed Property doesn't exist", dynaMap.getDynaProperty(testProperty));
        assertNull("Check Indexed Property is null", dynaMap.get(testProperty));

        // Add a DynaProperty of type int[]
        dynaMap.add(testProperty, primitiveArray.getClass());
        assertEquals("Check Indexed Property exists", primitiveArray.getClass(), dynaMap.getDynaProperty(testProperty).getType());
        assertTrue("Check Indexed Property exists", dynaMap.get(testProperty).getClass().isInstance(primitiveArray));

        // Set an indexed value
        dynaMap.set(testProperty, index, testInteger1);
        assertNotNull("Check Indexed Property is not null", dynaMap.get(testProperty));
        assertEquals("Check Indexed Property is correct type", primitiveArray.getClass(), dynaMap.get(testProperty).getClass());
        assertEquals("Check First Indexed Value is correct(a)", testInteger1, dynaMap.get(testProperty, index));
        assertEquals("Check First Indexed Value is correct(b)", testInteger1, Integer.valueOf(((int[]) dynaMap.get(testProperty))[index]));
        assertEquals("Check Array length is correct", Integer.valueOf(index + 1), Integer.valueOf(((int[]) dynaMap.get(testProperty)).length));

        // Set a second indexed value, should automatically grow the int[] and set appropriate indexed value
        index = index + 2;
        dynaMap.set(testProperty, index, testInteger2);
        assertEquals("Check Second Indexed Value is correct(a)", testInteger2, dynaMap.get(testProperty, index));
        assertEquals("Check Second Indexed Value is correct(b)", testInteger2, Integer.valueOf(((int[]) dynaMap.get(testProperty))[index]));
        assertEquals("Check Second Array length is correct", Integer.valueOf(index + 1), Integer.valueOf(((int[]) dynaMap.get(testProperty)).length));

    }

    /**
     * Test Getting/Setting an 'Indexed' Property - default ArrayList property
     */
    public void testIndexedPropertyDefault() {

        int index = 3;

        // Check the property & value doesn't exist
        assertNull("Check Indexed Property doesn't exist", dynaMap.getDynaProperty(testProperty));
        assertNull("Check Indexed Property is null", dynaMap.get(testProperty));
        assertNull("Check Indexed value is null", dynaMap.get(testProperty, index));

        // Set the property, should create new ArrayList and set appropriate indexed value
        dynaMap.set(testProperty, index, testInteger1);
        assertNotNull("Check Indexed Property is not null", dynaMap.get(testProperty));
        assertEquals("Check Indexed Property is correct type", ArrayList.class, dynaMap.get(testProperty).getClass());
        assertEquals("Check First Indexed Value is correct", testInteger1, dynaMap.get(testProperty, index));
        assertEquals("Check First Array length is correct", Integer.valueOf(index + 1), Integer.valueOf(((ArrayList<?>) dynaMap.get(testProperty)).size()));

        // Set a second indexed value, should automatically grow the ArrayList and set appropriate indexed value
        index = index + 2;
        dynaMap.set(testProperty, index, testString1);
        assertEquals("Check Second Indexed Value is correct", testString1, dynaMap.get(testProperty, index));
        assertEquals("Check Second Array length is correct", Integer.valueOf(index + 1), Integer.valueOf(((ArrayList<?>) dynaMap.get(testProperty)).size()));
    }

    /**
     * Test Setting an Indexed Property when MutableDynaClass is set to restricted
     */
    public void testIndexedPropertyRestricted() {

        final int index = 3;

        // Set the MutableDyanClass to 'restricted' (i.e. no new properties cab be added
        dynaMap.setRestricted(true);
        assertTrue("Check MutableDynaClass is restricted", dynaMap.isRestricted());

        // Check the property & value doesn't exist
        assertNull("Check Property doesn't exist", dynaMap.getDynaProperty(testProperty));
        assertNull("Check Value is null", dynaMap.get(testProperty));

        // Set the property - should fail because property doesn't exist and MutableDynaClass is restricted
        try {
            dynaMap.set(testProperty, index, testInteger1);
            fail("expected IllegalArgumentException trying to add new property to restricted MutableDynaClass");
        } catch (final IllegalArgumentException expected) {
            // expected result
        }

    }

    /**
     * Test Setting an 'Indexed' Property using PropertyUtils
     */
    public void testIndexedPropertyUtils() {

        final int index = 3;
        dynaMap.setReturnNull(false);

        // Check the property & value doesn't exist
        assertFalse("Check Indexed Property doesn't exist", dynaMap.isDynaProperty(testProperty));
        assertNull("Check Indexed Property is null", dynaMap.get(testProperty));
        assertNull("Check Indexed value is null", dynaMap.get(testProperty, index));

        // Use PropertyUtils to set the indexed value
        try {
            PropertyUtils.setProperty(dynaMap, testProperty + "[" + index + "]", testString1);
        } catch (final NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
            fail("testIndexedPropertyUtils threw " + ex);
        }

        // Check property value correctly set
        assertEquals("Check Indexed Bean Value is correct", testString1, dynaMap.get(testProperty, index));

    }

    /**
     * Test setting mapped property for type which is not Map
     */
    public void testMappedInvalidType() {
        dynaMap.set(testProperty, Integer.valueOf(1));
        assertFalse("Check Property is not mapped", dynaMap.getDynaProperty(testProperty).isMapped());
        try {
            dynaMap.set(testProperty, testKey, testInteger1);
            fail("set(property, key, value) should have thrown IllegalArgumentException");
        } catch (final IllegalArgumentException expected) {
            // expected result
        }
    }

    /**
     * Test Getting/Setting a 'Mapped' Property - default HashMap property
     */
    public void testMappedPropertyDefault() {

        // Check the property & value doesn't exist
        assertNull("Check Mapped Property doesn't exist", dynaMap.getDynaProperty(testProperty));
        assertNull("Check Map is null", dynaMap.get(testProperty));
        assertNull("Check Mapped Value is null", dynaMap.get(testProperty, testKey));

        // Set a new mapped property - should add new HashMap property and set the mapped value
        dynaMap.set(testProperty, testKey, testInteger1);
        assertEquals("Check Mapped Property exists", HashMap.class, dynaMap.get(testProperty).getClass());
        assertEquals("Check First Mapped Value is correct(a)", testInteger1, dynaMap.get(testProperty, testKey));
        assertEquals("Check First Mapped Value is correct(b)", testInteger1, ((HashMap<?, ?>) dynaMap.get(testProperty)).get(testKey));

        // Set the property again - should set the new value
        dynaMap.set(testProperty, testKey, testInteger2);
        assertEquals("Check Second Mapped Value is correct(a)", testInteger2, dynaMap.get(testProperty, testKey));
        assertEquals("Check Second Mapped Value is correct(b)", testInteger2, ((HashMap<?, ?>) dynaMap.get(testProperty)).get(testKey));
    }

    /**
     * Test Setting a Mapped Property when MutableDynaClass is set to restricted
     */
    public void testMappedPropertyRestricted() {

        // Set the MutableDyanClass to 'restricted' (i.e. no new properties cab be added
        dynaMap.setRestricted(true);
        assertTrue("Check MutableDynaClass is restricted", dynaMap.isRestricted());

        // Check the property & value doesn't exist
        assertNull("Check Property doesn't exist", dynaMap.getDynaProperty(testProperty));
        assertNull("Check Value is null", dynaMap.get(testProperty));

        // Set the property - should fail because property doesn't exist and MutableDynaClass is restricted
        try {
            dynaMap.set(testProperty, testKey, testInteger1);
            fail("expected IllegalArgumentException trying to add new property to restricted MutableDynaClass");
        } catch (final IllegalArgumentException expected) {
            // expected result
        }

    }

    /**
     * Test Getting/Setting a 'Mapped' Property - use TreeMap property
     */
    public void testMappedPropertyTreeMap() {

        // Check the property & value doesn't exist
        assertNull("Check Mapped Property doesn't exist", dynaMap.getDynaProperty(testProperty));
        assertNull("Check Map is null", dynaMap.get(testProperty));

        // Add a 'TreeMap' property to the DynaClass
        dynaMap.add(testProperty, TreeMap.class);
        assertTrue("Check Property is mapped", dynaMap.getDynaProperty(testProperty).isMapped());
        assertEquals("Check Property is correct type", TreeMap.class, dynaMap.getDynaProperty(testProperty).getType());
        assertEquals("Check Mapped Property now exists", TreeMap.class, dynaMap.get(testProperty).getClass());

        // Set a new mapped property - should instantiate a new TreeMap property and set the mapped value
        dynaMap.set(testProperty, testKey, testInteger1);
        assertEquals("Check Mapped Property exists", TreeMap.class, dynaMap.get(testProperty).getClass());
        assertEquals("Check First Mapped Value is correct(a)", testInteger1, dynaMap.get(testProperty, testKey));
        assertEquals("Check First Mapped Value is correct(b)", testInteger1, ((TreeMap<?, ?>) dynaMap.get(testProperty)).get(testKey));

        // Set the property again - should set the new value
        dynaMap.set(testProperty, testKey, testInteger2);
        assertEquals("Check Second Mapped Value is correct(a)", testInteger2, dynaMap.get(testProperty, testKey));
        assertEquals("Check Second Mapped Value is correct(b)", testInteger2, ((TreeMap<?, ?>) dynaMap.get(testProperty)).get(testKey));
    }

    /**
     * Test Setting a 'Mapped' Property using PropertyUtils
     */
    public void testMappedPropertyUtils() {

        dynaMap.setReturnNull(false);

        // Check the property & value doesn't exist
        assertFalse("Check Mapped Property doesn't exist", dynaMap.isDynaProperty(testProperty));
        assertNull("Check Map is null", dynaMap.get(testProperty));
        assertNull("Check Mapped Value is null", dynaMap.get(testProperty, testKey));

        // Set the mapped property using PropertyUtils
        try {
            PropertyUtils.setProperty(dynaMap, testProperty + "(" + testKey + ")", testString1);
        } catch (final NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
            fail("testIndexedPropertyUtils threw " + ex);
        }

        // Check property value correctly set
        assertEquals("Check Mapped Bean Value is correct", testString1, dynaMap.get(testProperty, testKey));

    }

    /**
     * Test creating using DynaClass.newInstance()
     */
    public void testNewInstance() {

        // Create LazyDynaMap using TreeMap
        // containing some properties
        final LazyDynaMap orig = new LazyDynaMap(new TreeMap<String, Object>());
        orig.set("indexProp", 0, "indexVal0");
        orig.set("indexProp", 1, "indexVal1");
        assertEquals("Index prop size", 2, ((List<?>) orig.get("indexProp")).size());

        final LazyDynaMap newOne = (LazyDynaMap) orig.newInstance();
        final Map<String, Object> newMap = newOne.getMap();
        assertEquals("Check Map type", TreeMap.class, newMap.getClass());

        final ArrayList<?> indexProp = (ArrayList<?>) newMap.get("indexProp");
        assertNotNull("Indexed Prop missing", indexProp);
        assertEquals("Index prop size", 0, indexProp.size());
    }

    /**
     * Test Getting/Setting a Simple Property
     */
    public void testSimpleProperty() {

        // Check the property & value doesn't exist
        assertNull("Check Property doesn't exist", dynaMap.getDynaProperty(testProperty));
        assertNull("Check Value is null", dynaMap.get(testProperty));

        // Set a new property - should add new property and set value
        dynaMap.set(testProperty, testInteger1);
        assertEquals("Check First Value is correct", testInteger1, dynaMap.get(testProperty));
        assertEquals("Check Property type is correct", Integer.class, dynaMap.getDynaProperty(testProperty).getType());

        // Set the property again - should set the new value
        dynaMap.set(testProperty, testInteger2);
        assertEquals("Check Second Value is correct", testInteger2, dynaMap.get(testProperty));

        // Set the property again - with a different type, should succeed
        dynaMap.set(testProperty, testString1);
        assertEquals("Check Third Value is correct", testString1, dynaMap.get(testProperty));

    }

    /**
     * Test Setting a Simple Property when MutableDynaClass is set to restricted
     */
    public void testSimplePropertyRestricted() {

        // Set the MutableDyanClass to 'restricted' (i.e. no new properties cab be added
        dynaMap.setRestricted(true);
        assertTrue("Check MutableDynaClass is restricted", dynaMap.isRestricted());

        // Check the property & value doesn't exist
        assertNull("Check Property doesn't exist", dynaMap.getDynaProperty(testProperty));
        assertNull("Check Value is null", dynaMap.get(testProperty));

        // Set the property - should fail because property doesn't exist and MutableDynaClass is restricted
        try {
            dynaMap.set(testProperty, testString1);
            fail("expected IllegalArgumentException trying to add new property to restricted DynaClass");
        } catch (final IllegalArgumentException expected) {
            // expected result
        }

    }

}