/*
 * 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.felix.scr.integration;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.apache.felix.scr.integration.components.annoconfig.AnnoComponent;
import org.apache.felix.scr.integration.components.annoconfig.AnnoComponent.A1;
import org.apache.felix.scr.integration.components.annoconfig.AnnoComponent.A1Arrays;
import org.apache.felix.scr.integration.components.annoconfig.AnnoComponent.E1;
import org.apache.felix.scr.integration.components.annoconfig.NestedAnnoComponent;
import org.apache.felix.scr.integration.components.annoconfig.NestedAnnoComponent.A2;
import org.apache.felix.scr.integration.components.annoconfig.NestedAnnoComponent.B2;
import org.apache.felix.scr.integration.components.annoconfig.NestedAnnoComponent.E2;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.junit.PaxExam;
import org.osgi.service.cm.Configuration;
import org.osgi.service.component.runtime.dto.ComponentConfigurationDTO;

import junit.framework.TestCase;

@RunWith(PaxExam.class)
public class AnnoConfigTest extends ComponentTestBase
{

    static
    {
        // uncomment to enable debugging of this test class
        //        paxRunnerVmOption = DEBUG_VM_OPTION;

        descriptorFile = "/integration_test_annoconfig.xml";
        COMPONENT_PACKAGE = COMPONENT_PACKAGE + ".annoconfig";
    }

    @Test
    public void testAnnoConfig() throws Exception
    {
        String name = "org.apache.felix.scr.integration.components.annoconfig";
        ComponentConfigurationDTO dto = findComponentConfigurationByName(name, ComponentConfigurationDTO.SATISFIED);
        AnnoComponent ac = getServiceFromConfiguration(dto, AnnoComponent.class);
        checkA1NoValues(ac.m_a1_activate);
        checkA1ArraysNoValues(ac.m_a1Arrays_activate);

        Configuration c = configure(name, null, allValues());
        delay();

        checkA1(ac.m_a1_modified);
        checkA1Array(ac.m_a1Arrays_modified);

        ungetServiceFromConfiguration(dto, AnnoComponent.class);
        checkA1(ac.m_a1_deactivate);
        checkA1Array(ac.m_a1Arrays_deactivate);
        ac = getServiceFromConfiguration(dto, AnnoComponent.class);
        checkA1(ac.m_a1_activate);
        checkA1Array(ac.m_a1Arrays_activate);

        c.delete();
        delay();

        checkA1NoValues(ac.m_a1_modified);
        checkA1ArraysNoValues(ac.m_a1Arrays_modified);

        c = configure(name, null, arrayValues());
        delay();

        checkA1FromArray(ac.m_a1_modified);
        checkA1ArrayFromArray(ac.m_a1Arrays_modified, false);

        c.delete();
        delay();

        checkA1NoValues(ac.m_a1_modified);
        checkA1ArraysNoValues(ac.m_a1Arrays_modified);

        c = configure(name, null, collectionValues());
        delay();

        checkA1FromArray(ac.m_a1_modified);
        checkA1ArrayFromArray(ac.m_a1Arrays_modified, true);

    }

    private Hashtable<String, Object> allValues()
    {
        Hashtable<String, Object> values = new Hashtable<String, Object>();
        values.put("bool", "true");
        values.put("byt", 12l);
        values.put("clas", String.class.getName());
        values.put("e1", E1.a.toString());
        values.put("doubl", "3.14");
        values.put("floa", 500l);
        values.put("integer", 3.0d);
        values.put("lon", "12345678");
        values.put("shor", 3l);
        values.put("string", 3);
        return values;
    }

    private Hashtable<String, Object> arrayValues()
    {
        Hashtable<String, Object> values = new Hashtable<String, Object>();
        values.put("bool", new boolean[] {true, false});
        values.put("byt", new byte[] {12, 3});
        values.put("clas", new String[] {String.class.getName(), Integer.class.getName()});
        values.put("e1", new String[] {E1.a.name(), E1.b.name()});
        values.put("doubl", new double[] {3.14, 2.78, 9});
        values.put("floa", new float[] {500, 37.44f});
        values.put("integer", new int[] {3, 6, 9});
        values.put("lon", new long[] {12345678l, -1});
        values.put("shor", new short[] {3, 88});
        values.put("string", new String[] {});
        return values;
    }

    private Hashtable<String, Object> collectionValues()
    {
        Hashtable<String, Object> values = arrayValues();
        Hashtable<String, Object> collectionValues = new Hashtable<String, Object>();
        for (Map.Entry<String, Object> entry: values.entrySet())
        {
            collectionValues.put(entry.getKey(), toList(entry.getValue()));
        }
        //yuck
        collectionValues.remove("string");
        return collectionValues;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private List<?> toList(Object value)
    {
        List result = new ArrayList();
        for (int i = 0; i < Array.getLength(value); i++)
        {
            result.add(Array.get(value, i));
        }
        return result;
    }

    private void checkA1(A1 a)
    {
        TestCase.assertEquals(true, a.bool());
        TestCase.assertEquals((byte)12, a.byt());
        TestCase.assertEquals(String.class, a.clas());
        TestCase.assertEquals(E1.a, a.e1());
        TestCase.assertEquals(3.14d, a.doubl());
        TestCase.assertEquals(500f, a.floa());
        TestCase.assertEquals(3, a.integer());
        TestCase.assertEquals(12345678l,  a.lon());
        TestCase.assertEquals((short)3, a.shor());
        TestCase.assertEquals("3", a.string());
    }


    private void checkA1FromArray(A1 a)
    {
        TestCase.assertEquals(true, a.bool());
        TestCase.assertEquals((byte)12, a.byt());
        TestCase.assertEquals(String.class, a.clas());
        TestCase.assertEquals(E1.a, a.e1());
        TestCase.assertEquals(3.14d, a.doubl());
        TestCase.assertEquals(500f, a.floa());
        TestCase.assertEquals(3, a.integer());
        TestCase.assertEquals(12345678l,  a.lon());
        TestCase.assertEquals((short)3, a.shor());
        TestCase.assertEquals(null, a.string());
    }

    private void checkA1Array(A1Arrays a)
    {
        assertArrayEquals(new boolean[] {true}, a.bool());
        assertArrayEquals(new byte[] {(byte)12}, a.byt());
        assertArrayEquals(new Class<?>[] {String.class}, a.clas());
        assertArrayEquals(new E1[] {E1.a}, a.e1());
        assertArrayEquals(new double[] {3.14d}, a.doubl());
        assertArrayEquals(new float[] {500f}, a.floa());
        assertArrayEquals(new int[] {3}, a.integer());
        assertArrayEquals(new long[] {12345678l},  a.lon());
        assertArrayEquals(new short[] {(short)3}, a.shor());
        assertArrayEquals(new String[] {"3"}, a.string());
    }

    private void checkA1ArrayFromArray(A1Arrays a, boolean caBug)
    {
        assertArrayEquals(new boolean[] {true, false}, a.bool());
        assertArrayEquals(new byte[] {12, 3}, a.byt());
        assertArrayEquals(new Class<?>[] {String.class, Integer.class}, a.clas());
        assertArrayEquals(new E1[] {E1.a, E1.b}, a.e1());
        assertArrayEquals(new double[] {3.14, 2.78, 9}, a.doubl());
        assertArrayEquals(new float[] {500f, 37.44f}, a.floa());
        assertArrayEquals(new int[] {3, 6, 9}, a.integer());
        assertArrayEquals(new long[] {12345678l, -1},  a.lon());
        assertArrayEquals(new short[] {(short)3, 88}, a.shor());
        if (!caBug)
        {
            assertArrayEquals(new String[] {}, a.string());
        }
    }

    private void assertArrayEquals(Object a, Object b)
    {
        TestCase.assertTrue(a.getClass().isArray());
        TestCase.assertTrue(b.getClass().isArray());
        TestCase.assertEquals("wrong length", Array.getLength(a), Array.getLength(b));
        TestCase.assertEquals("wrong type", a.getClass().getComponentType(), b.getClass().getComponentType());
        for (int i = 0; i < Array.getLength(a); i++)
        {
            TestCase.assertEquals("different value at " + i, Array.get(a, i), Array.get(b, i));
        }

    }

    private void checkA1NoValues(A1 a)
    {
        TestCase.assertEquals(false, a.bool());
        TestCase.assertEquals((byte)0, a.byt());
        TestCase.assertEquals(null, a.clas());
        TestCase.assertEquals(null, a.e1());
        TestCase.assertEquals(0d, a.doubl());
        TestCase.assertEquals(0f, a.floa());
        TestCase.assertEquals(0, a.integer());
        TestCase.assertEquals(0l,  a.lon());
        TestCase.assertEquals((short)0, a.shor());
        TestCase.assertEquals(null, a.string());
    }

    private void checkA1ArraysNoValues(A1Arrays a)
    {
        TestCase.assertEquals(0, a.bool().length);
        TestCase.assertEquals(0, a.byt().length);
        TestCase.assertEquals(0, a.clas().length);
        TestCase.assertEquals(0, a.e1().length);
        TestCase.assertEquals(0, a.doubl().length);
        TestCase.assertEquals(0, a.floa().length);
        TestCase.assertEquals(0, a.integer().length);
        TestCase.assertEquals(0,  a.lon().length);
        TestCase.assertEquals(0, a.shor().length);
        TestCase.assertEquals(0, a.string().length);
    }

    @Test
    public void testNestedAnnoConfig() throws Exception
    {
        String name = "org.apache.felix.scr.integration.components.nestedannoconfig";
        ComponentConfigurationDTO dto = findComponentConfigurationByName(name, ComponentConfigurationDTO.SATISFIED);
        NestedAnnoComponent ac = getServiceFromConfiguration(dto, NestedAnnoComponent.class);
        checkA2NoValues(ac.m_a2_activate);

        configure(name, null, allNestedValues());
        delay();

        checkA2(ac.m_a2_modified);

        ungetServiceFromConfiguration(dto, NestedAnnoComponent.class);
        checkA2(ac.m_a2_deactivate);
        ac = getServiceFromConfiguration(dto, NestedAnnoComponent.class);
        checkA2(ac.m_a2_activate);


    }
    private Hashtable<String, Object> allNestedValues()
    {
        Hashtable<String, Object> values = new Hashtable<String, Object>();
        values.put("b2.0.bool", "true");
        values.put("b2.0.e2", E2.a.toString());
        values.put("b2s.0.bool", "true");
        values.put("b2s.0.e2", E2.a.toString());
        values.put("b2s.1.bool", "true");
        values.put("b2s.1.e2", E2.b.toString());
        values.put("b2s.2.bool", "true");
        values.put("b2s.2.e2", E2.c.toString());
        return values;
    }

    private void checkA2NoValues(A2 a)
    {
        TestCase.assertEquals(0, a.b2s().length);
    }
    private void checkA2(A2 a)
    {
        checkB2(a.b2(), E2.a);
        TestCase.assertNull(a.b2null());

        TestCase.assertEquals(3, a.b2s().length);
        checkB2(a.b2s()[0], E2.a);
        checkB2(a.b2s()[1], E2.b);
        checkB2(a.b2s()[2], E2.c);
    }

    private void checkB2(B2 b, E2 e2) {
        TestCase.assertEquals(true, b.bool());
        TestCase.assertEquals(e2, b.e2());
    }

}
