/*
 * 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
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 LazyDynaList}class.
 * </p>
 *
 */
public class LazyDynaListTestCase extends TestCase {

    private static final String BASIC_PROP1 = "BasicDynaClass_Property1";
    private static final String BASIC_PROP2 = "BasicDynaClass_Property2";

    /**
     * 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(LazyDynaListTestCase.class);
    }
    protected DynaProperty[] properties = { new DynaProperty(BASIC_PROP1, String.class), new DynaProperty(BASIC_PROP2, HashMap.class) };
    protected DynaClass treeMapDynaClass = new LazyDynaMap(new TreeMap<String, Object>());
    protected DynaClass hashMapDynaClass = new LazyDynaMap(new HashMap<String, Object>());

    protected DynaClass pojoDynaClass = new WrapDynaBean(new TestBean()).getDynaClass();

    protected DynaClass basicDynaClass = new BasicDynaClass("test", BasicDynaBean.class, properties);

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

    /**
     * Test DynaBean Create
     */
    private void dynaBeanTest(final LazyDynaList list, final Class<?> testClass, final DynaClass testDynaClass, final Object wrongBean) {

        // Test get(index) created correct DynaBean - Second
        Object dynaBean = list.get(1);
        assertNotNull("1. DynaBean Not Created", dynaBean);
        assertEquals("2. Wrong Type", testClass, dynaBean.getClass());

        // Test toArray() creates correct Array - Second
        Object array = list.toArray();
        assertNotNull("3. Array Not Created", array);
        assertEquals("4. Not DynaBean[]", testClass, array.getClass().getComponentType());
        DynaBean[] dynaArray = (DynaBean[]) array;
        assertEquals("5. Array Size Wrong", 2, dynaArray.length);

        // Test get(index) created correct DynaBean - Fourth
        dynaBean = list.get(3);
        assertNotNull("6. DynaBean Not Created", dynaBean);
        assertEquals("7. Wrong type", testClass, dynaBean.getClass());

        // Test toArray() creates correct Array - Fourth
        array = list.toArray();
        assertNotNull("8. Array Not Created", array);
        assertEquals("9. Not DynaBean[]", testClass, array.getClass().getComponentType());
        dynaArray = (DynaBean[]) array;
        assertEquals("10. Array Size Wrong", 4, dynaArray.length);

        // Test fail if different type added
        try {
            list.add(2, wrongBean);
            fail("Expected IllegalArgumentException");
        } catch (final IllegalArgumentException ignore) {
            // expected result
        }

        // find a String property to set
        final String testProperty = findStringProperty(testDynaClass);
        assertNotNull("Test Property Not Found", testProperty);
        dynaArray = list.toDynaBeanArray();
        for (int i = 0; i < dynaArray.length; i++) {
            dynaArray[i].set(testProperty, "orig_pos" + i);
        }

        // Create Collection
        final List<Object> collection = new ArrayList<>();
        try {
            collection.add(testDynaClass.newInstance());
            collection.add(testDynaClass.newInstance());
            collection.add(testDynaClass.newInstance());
        } catch (final Exception ex) {
            fail("1. FAILED: " + ex);
        }
        final int expectedSize = dynaArray.length + collection.size();
        final String origValue = (String) ((DynaBean) collection.get(0)).get(testProperty);
        ((DynaBean) collection.get(0)).set(testProperty, origValue + "_updated_" + 0);
        ((DynaBean) collection.get(1)).set(testProperty, origValue + "_updated_" + 1);
        ((DynaBean) collection.get(2)).set(testProperty, origValue + "_updated_" + 2);

        // Test Insert - addAll(index, Collection)
        list.addAll(1, collection);
        dynaArray = list.toDynaBeanArray();

        // Check array after insert
        dynaArray = list.toDynaBeanArray();
        assertEquals("11. Array Size Wrong", expectedSize, dynaArray.length);

        // Check Beans have inserted correctly - by checking the property values
        assertEquals("12. Wrong Value", "orig_pos0", dynaArray[0].get(testProperty));
        assertEquals("13. Wrong Value", origValue + "_updated_" + 0, dynaArray[1].get(testProperty));
        assertEquals("14. Wrong Value", origValue + "_updated_" + 1, dynaArray[2].get(testProperty));
        assertEquals("15. Wrong Value", origValue + "_updated_" + 2, dynaArray[3].get(testProperty));
        assertEquals("16. Wrong Value", "orig_pos1", dynaArray[4].get(testProperty));

        // Test Insert - add(index, Object)
        try {
            final DynaBean extraElement = testDynaClass.newInstance();
            extraElement.set(testProperty, "extraOne");
            list.add(2, extraElement);
            dynaArray = list.toDynaBeanArray();
            assertEquals("17. Wrong Value", origValue + "_updated_" + 0, dynaArray[1].get(testProperty));
            assertEquals("18. Wrong Value", "extraOne", dynaArray[2].get(testProperty));
            assertEquals("19. Wrong Value", origValue + "_updated_" + 1, dynaArray[3].get(testProperty));
        } catch (final Exception ex) {
            fail("2. FAILED: " + ex);
        }

    }

    /**
     * Test Map Create
     */
    private String findStringProperty(final DynaClass dynaClass) {
        final DynaProperty[] properties = dynaClass.getDynaProperties();
        for (final DynaProperty property : properties) {
            if (property.getType() == String.class) {
                return property.getName();
            }
        }
        return null;
    }

    /**
     * Test Map Create
     */
    private void mapTest(final LazyDynaList list, final Class<?> testClass, final Object wrongBean) {

        // Test get(index) created correct DynaBean - First
        Object dynaBean = list.get(0);
        assertNotNull("1. DynaBean Not Created", dynaBean);
        assertEquals("2. Not LazyDynaMap", LazyDynaMap.class, dynaBean.getClass());

        // Test get(index) created correct Map - First
        Object map = ((LazyDynaMap) dynaBean).getMap();
        assertNotNull("3. Map Not Created", map);
        assertEquals("4. Wrong Map", testClass, map.getClass());

        // Test toArray() creates correct Array - First
        Object array = list.toArray();
        assertNotNull("5. Array Not Created", array);
        assertEquals("6. Not Map[]", testClass, array.getClass().getComponentType());
        Map<?, ?>[] mapArray = (Map[]) array;
        assertEquals("7. Array Size Wrong", 1, mapArray.length);

        // Test get(index) created correct DynaBean - Third
        dynaBean = list.get(2);
        assertNotNull("8. DynaBean Not Created", dynaBean);
        assertEquals("9. Not LazyDynaMap", LazyDynaMap.class, dynaBean.getClass());

        // Test get(index) created correct Map - Third
        map = ((LazyDynaMap) dynaBean).getMap();
        assertNotNull("10. Map Not Created", map);
        assertEquals("11. Wrong Map", testClass, map.getClass());

        // Test toArray() creates correct Array - Third
        array = list.toArray();
        assertNotNull("12. Array Not Created", array);
        assertEquals("13. Not Map[]", testClass, array.getClass().getComponentType());
        mapArray = (Map[]) array;
        assertEquals("14. Array Size Wrong", 3, mapArray.length);

        // Test fail if different type added
        try {
            list.add(2, wrongBean);
            fail("Expected IllegalArgumentException");
        } catch (final IllegalArgumentException ignore) {
            // expected result
        }

    }

    /**
     * Test Pojo Create
     */
    private void pojoTest(final LazyDynaList list, final Class<?> testClass, final Object wrongBean) {

        // Test get(index) created correct DynaBean - First
        Object dynaBean = list.get(0);
        assertNotNull("1. DynaBean Not Created", dynaBean);
        assertEquals("2. Not WrapDynaBean", WrapDynaBean.class, dynaBean.getClass());

        // Test get(index) created correct POJO - First
        Object pojoBean = ((WrapDynaBean) dynaBean).getInstance();
        assertNotNull("3. POJO Not Created", pojoBean);
        assertEquals("4. Not WrapDynaBean", testClass, pojoBean.getClass());

        // Test toArray() creates correct Array - First
        Object array = list.toArray();
        assertNotNull("5. Array Not Created", array);
        assertEquals("6. Wrong array", testClass, array.getClass().getComponentType());
        Object[] pojoArray = (Object[]) array;
        assertEquals("7. Array Size Wrong", 1, pojoArray.length);

        // Test get(index) created correct DynaBean - Second
        dynaBean = list.get(1);
        assertNotNull("8. DynaBean Not Created", dynaBean);
        assertEquals("9. Not WrapDynaBean", WrapDynaBean.class, dynaBean.getClass());

        // Test get(index) created correct POJO - Second
        pojoBean = ((WrapDynaBean) dynaBean).getInstance();
        assertNotNull("10. POJO Not Created", pojoBean);
        assertEquals("11. Not WrapDynaBean", testClass, pojoBean.getClass());

        // Test toArray() creates correct Array - Second
        array = list.toArray();
        assertNotNull("12. Array Not Created", array);
        assertEquals("13. Wrong array", testClass, array.getClass().getComponentType());
        pojoArray = (Object[]) array;
        assertEquals("14. Array Size Wrong", 2, pojoArray.length);

        // Test fail if different type added
        try {
            list.add(2, wrongBean);
            fail("Expected IllegalArgumentException");
        } catch (final IllegalArgumentException ignore) {
            // expected result
        }

    }

    /**
     * Do serialization and deserialization.
     */
    private Object serializeDeserialize(final Object target, final String text) {

        // Serialize the test object
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            final ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(target);
            oos.flush();
            oos.close();
        } catch (final Exception e) {
            fail(text + ": Exception during serialization: " + e);
        }

        // Deserialize the test object
        Object result = null;
        try {
            final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            final ObjectInputStream ois = new ObjectInputStream(bais);
            result = ois.readObject();
            bais.close();
        } catch (final Exception e) {
            fail(text + ": Exception during deserialization: " + e);
        }
        return result;

    }

    /**
     * Sets up instance variables required by this test case.
     */
    @Override
    public void setUp() throws Exception {
    }

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

    /**
     * Test Collection
     */
    public void testCollection(final LazyDynaList list, final Class<?> testClass, final DynaClass testDynaClass, final Object wrongBean) {

        // ----- Create Collection & Array of Maps -----
        final int size = 5;
        final List<Object> testList = new ArrayList<>(size);
        final TreeMap<?, ?>[] testArray = new TreeMap[size];
        for (int i = 0; i < size; i++) {
            final TreeMap<String, Object> map = new TreeMap<>();
            map.put("prop" + i, "val" + i);
            testArray[i] = map;
            testList.add(testArray[i]);
        }

        // ----- Create LazyArrayList from Collection -----
        LazyDynaList lazyList = new LazyDynaList(testList);
        assertEquals("1. check size", size, lazyList.size());

        DynaBean[] dynaArray = lazyList.toDynaBeanArray();
        TreeMap<?, ?>[] mapArray = (TreeMap[]) lazyList.toArray();

        // Check values
        assertEquals("2. check size", size, dynaArray.length);
        assertEquals("3. check size", size, mapArray.length);
        for (int i = 0; i < size; i++) {
            assertEquals("4." + i + " DynaBean error ", "val" + i, dynaArray[i].get("prop" + i));
            assertEquals("5." + i + " Map error ", "val" + i, mapArray[i].get("prop" + i));
        }

        // ----- Create LazyArrayList from Array -----
        lazyList = new LazyDynaList(testArray);
        assertEquals("6. check size", size, lazyList.size());

        dynaArray = lazyList.toDynaBeanArray();
        mapArray = (TreeMap[]) lazyList.toArray();

        // Check values
        assertEquals("7. check size", size, dynaArray.length);
        assertEquals("8. check size", size, mapArray.length);
        for (int i = 0; i < size; i++) {
            assertEquals("9." + i + " DynaBean error ", "val" + i, dynaArray[i].get("prop" + i));
            assertEquals("10." + i + " Map error ", "val" + i, mapArray[i].get("prop" + i));
        }

    }

    /**
     * Test DynaBean Create
     */
    public void testDynaBeanDynaClass() {

        // Create LazyArrayList for DynaBeans
        final LazyDynaList list = new LazyDynaList(basicDynaClass);

        // test
        dynaBeanTest(list, BasicDynaBean.class, basicDynaClass, new BenchBean());
    }

    /**
     * Test DynaBean Create
     */
    public void testDynaBeanType() {

        // Create LazyArrayList for DynaBeans
        final LazyDynaList list = new LazyDynaList(LazyDynaBean.class);

        final LazyDynaBean bean = new LazyDynaBean();
        bean.set("prop1", "val");

        // test
        dynaBeanTest(list, LazyDynaBean.class, bean.getDynaClass(), new BenchBean());
    }

    /**
     * Test Map Create
     */
    public void testMapDynaClass() {

        // Create LazyArrayList for TreeMap's
        final LazyDynaList list = new LazyDynaList(treeMapDynaClass);

        // test
        mapTest(list, TreeMap.class, new BenchBean());

    }

    /**
     * Test Map Create
     */
    public void testMapType() {

        // Create LazyArrayList for HashMap's
        final LazyDynaList list = new LazyDynaList(HashMap.class);

        // test
        mapTest(list, HashMap.class, new BenchBean());

    }

    /**
     * Test adding a map to List with no type set.
     */
    public void testNullType() {
        final LazyDynaList lazyList = new LazyDynaList();
        lazyList.add(new HashMap<String, Object>());
    }

    /**
     * Test Pojo Create
     */
    public void testPojoDynaClass() {

        // Create LazyArrayList for POJO's
        final LazyDynaList list = new LazyDynaList(pojoDynaClass);

        // test
        pojoTest(list, TestBean.class, new BenchBean());

    }

    /**
     * Test Pojo Create
     */
    public void testPojoType() {

        // Create LazyArrayList for POJO's
        final LazyDynaList list = new LazyDynaList(TestBean.class);

        // test
        pojoTest(list, TestBean.class, new BenchBean());

    }

    /**
     * Test DynaBean serialization.
     */
    public void testSerializationDynaBean() {

        // Create LazyArrayList for DynaBeans
        LazyDynaList target = new LazyDynaList(basicDynaClass);
        BasicDynaBean bean = (BasicDynaBean) target.get(0);

        // Set a Property
        assertNull("pre-set check", bean.get(BASIC_PROP1));
        bean.set(BASIC_PROP1, "value1");
        assertEquals("post-set check", "value1", bean.get(BASIC_PROP1));

        // Serialize/Deserialize
        final LazyDynaList result = (LazyDynaList) serializeDeserialize(target, "DynaBean");
        target = null;
        bean = null;

        // Confirm property value
        bean = (BasicDynaBean) result.get(0);
        assertEquals("post-serialize check", "value1", bean.get(BASIC_PROP1));

    }

    /**
     * Test DynaBean serialization.
     */
    public void testSerializationLazyDynaBean() {

        // Create LazyArrayList for DynaBeans
        LazyDynaList target = new LazyDynaList();
        LazyDynaBean bean = (LazyDynaBean) target.get(0);

        // Set a Property
        assertNull("pre-set check", bean.get(BASIC_PROP1));
        bean.set(BASIC_PROP1, "value1");
        assertEquals("post-set check", "value1", bean.get(BASIC_PROP1));

        // Serialize/Deserialize
        final LazyDynaList result = (LazyDynaList) serializeDeserialize(target, "DynaBean");
        target = null;
        bean = null;

        // Confirm property value
        bean = (LazyDynaBean) result.get(0);
        assertEquals("post-serialize check", "value1", bean.get(BASIC_PROP1));

    }

    /**
     * Test Map serialization.
     */
    public void testSerializationMap() {

        // Create LazyArrayList for DynaBeans
        LazyDynaList target = new LazyDynaList(treeMapDynaClass);
        LazyDynaMap bean = (LazyDynaMap) target.get(0);

        // Set a Property
        assertNull("pre-set check", bean.get(BASIC_PROP1));
        bean.set(BASIC_PROP1, "value1");
        assertEquals("post-set check", "value1", bean.get(BASIC_PROP1));

        // Serialize/Deserialize
        final LazyDynaList result = (LazyDynaList) serializeDeserialize(target, "Map");
        target = null;
        bean = null;

        // Confirm property value
        bean = (LazyDynaMap) result.get(0);
        assertEquals("post-serialize check", "value1", bean.get(BASIC_PROP1));

    }

    /**
     * Test POJO (WrapDynaBean) serialization.
     */
    public void testSerializationPojo() {

        // Create LazyArrayList for DynaBeans
        LazyDynaList target = new LazyDynaList(pojoDynaClass);
        WrapDynaBean bean = (WrapDynaBean) target.get(0);

        // Set a Property
        assertEquals("pre-set check", "This is a string", bean.get("stringProperty"));
        bean.set("stringProperty", "value1");
        assertEquals("post-set check", "value1", bean.get("stringProperty"));

        // Serialize/Deserialize
        final LazyDynaList result = (LazyDynaList) serializeDeserialize(target, "POJO");
        target = null;
        bean = null;

        // Test BEANUTILS-300
        result.add(null);

        // Confirm property value
        bean = (WrapDynaBean) result.get(0);
        assertEquals("post-serialize check", "value1", bean.get("stringProperty"));

    }

    /**
     * Tests toArray() if the list contains DynaBean objects.
     */
    public void testToArrayDynaBeans() {
        final LazyDynaList list = new LazyDynaList(LazyDynaBean.class);
        final LazyDynaBean elem = new LazyDynaBean();
        list.add(elem);
        final LazyDynaBean[] beans = new LazyDynaBean[1];
        assertSame("Wrong array", beans, list.toArray(beans));
        assertSame("Wrong element", elem, beans[0]);
    }

    /**
     * Tests toArray() if the list contains maps.
     */
    public void testToArrayMapType() {
        final LazyDynaList list = new LazyDynaList(HashMap.class);
        final HashMap<String, Object> elem = new HashMap<>();
        list.add(elem);
        final Map<?, ?>[] array = new Map[1];
        assertSame("Wrong array", array, list.toArray(array));
        assertEquals("Wrong element", elem, array[0]);
    }

    /**
     * Tests toArray() for other bean elements.
     */
    public void testToArrayOtherType() {
        final LazyDynaList list = new LazyDynaList(TestBean.class);
        final TestBean elem = new TestBean();
        list.add(elem);
        final TestBean[] array = new TestBean[1];
        assertSame("Wrong array", array, list.toArray(array));
        assertEquals("Wrong element", elem, array[0]);
    }

    /**
     * Tests toArray() if the array's size does not fit the collection size.
     */
    public void testToArrayUnsufficientSize() {
        final LazyDynaList list = new LazyDynaList(LazyDynaBean.class);
        final LazyDynaBean elem = new LazyDynaBean();
        list.add(elem);
        final LazyDynaBean[] array = list.toArray(LazyDynaBean.EMPTY_ARRAY);
        assertEquals("Wrong array size", 1, array.length);
        assertEquals("Wrong element", elem, array[0]);
    }
}
