/*
*
 * 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.qpid.management.domain.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;

import junit.framework.TestCase;

import org.apache.qpid.management.TestConstants;
import org.apache.qpid.management.configuration.ConfigurationException;
import org.apache.qpid.management.configuration.Configurator;
import org.apache.qpid.management.domain.handler.impl.MethodOrEventDataTransferObject;
import org.apache.qpid.management.domain.model.QpidClass.QpidManagedObject;

/**
 * Test case for Qpid Class.
 * 
 * @author Andrea Gazzarini
 */
public class QpidClassTest extends TestCase
{
    private QpidClass _class;
    private QpidPackage _package;
    
    @Override
    protected void setUp () throws ConfigurationException
    {
    	Configurator configurator = new Configurator();
    	configurator.configure();
    	_package = new QpidPackage(TestConstants.QPID_PACKAGE_NAME,TestConstants.DOMAIN_MODEL);
        _class = new QpidClass(TestConstants.EXCHANGE_CLASS_NAME,TestConstants.HASH,_package);
    }
        
    /**
     * Tests the execution of the getObjectInstance() method.
     * Basically it tests the addition of a new object instance.
     * 
     * <br>precondition: class has no object instances.
     * <br>precondition : class contains the added object instance.
     */
    public void testGetObjectInstance() 
    {            
        assertFalse (
                "Nobody set instance #"+TestConstants.OBJECT_ID+" into this class so why is it there?", 
        		_class._objectInstances.containsKey(TestConstants.OBJECT_ID));

        QpidManagedObject instance = _class.getObjectInstance(TestConstants.OBJECT_ID, false);
        
        assertTrue (
                "Now the instance #"+TestConstants.OBJECT_ID+" should be there...",
                _class._objectInstances.containsKey(TestConstants.OBJECT_ID));
        
        assertEquals(instance,_class.getObjectInstance(TestConstants.OBJECT_ID, false));
    }
       
    /**
     * Tests the injection of instrumentation and configuration data (related to a specific object instance) before the 
     * schema is installed.
     * 
     * <br>precondition : the schema hasn't yet installed on this class.
     * <br>postcondition : incoming configuration & instrumentation data is stored into the corresponding object instance.
     */
    public void testAddInstrumentationAndConfigurationDataBeforeSchemaInstallation() 
    {
        _class._state = _class._schemaRequestedButNotYetInjected;
        QpidManagedObject objectInstance = _class.getObjectInstance(TestConstants.OBJECT_ID,false);
        
        assertTrue(
                "This object instance is a new one so how is it possible that it has already instrumentation data? ",
                objectInstance._rawInstrumentationData.isEmpty());
        assertTrue(
                "This object instance is a new one so how is it possible that it has already configuration data? ",
                objectInstance._rawConfigurationData.isEmpty());
        
        byte [] dummyConfigurationData = {1,2,3,4,5,6,7,8};
        byte [] dummyInstrumentationData = {11,21,31,41,51,61,71,81};
        
        _class.addConfigurationData(TestConstants.OBJECT_ID, dummyConfigurationData);
        _class.addInstrumentationData(TestConstants.OBJECT_ID, dummyInstrumentationData);
        
        assertEquals("Now configuration data should be there...",1,objectInstance._rawConfigurationData.size());
        assertEquals("Now instrumentation data should be there...",1,objectInstance._rawInstrumentationData.size());
        
        assertTrue(
                "Object instance configuration data should be the previously set...",
                Arrays.equals(objectInstance._rawConfigurationData.get(0), 
                dummyConfigurationData));
        
        assertTrue(
                "Object instance instrumentation data should be the previously set...",
                Arrays.equals(objectInstance._rawInstrumentationData.get(0), 
                dummyInstrumentationData));        
    }
    
    /**
     * Tests the internal state change of a class definition.
     */
    public void testStateChange() throws UnableToBuildFeatureException
    {
    	_class = new QpidClass(TestConstants.EXCHANGE_CLASS_NAME,TestConstants.HASH,_package)
    	{
    		@Override
    		void requestSchema() throws Exception {
    			_state = _schemaRequestedButNotYetInjected;
    		}
    		
    		@Override
    		void setSchema(List<Map<String, Object>> propertyDefinitions,
    				List<Map<String, Object>> statisticDefinitions,
    				List<MethodOrEventDataTransferObject> methodDefinitions) throws UnableToBuildFeatureException {
    			_state = _schemaInjected;
    		}
    	};
    	
    	assertSame(
    			"Initial state doesn't match.",
    			_class._schemaNotRequested,
    			_class._state);
    	
    	_class.addConfigurationData(TestConstants.OBJECT_ID, TestConstants.TEST_RAW_DATA);
    	_class.addInstrumentationData(TestConstants.OBJECT_ID, TestConstants.TEST_RAW_DATA);

    	assertSame(
    			"Request schema has been requested but not yet injected. The current state is not indicating that!",
    			_class._schemaRequestedButNotYetInjected,
    			_class._state);    	
    	
    	_class.setSchema(
    			TestConstants.EMPTY_PROPERTIES_SCHEMA, 
    			TestConstants.EMPTY_STATISTICS_SCHEMA, 
    			new LinkedList<MethodOrEventDataTransferObject>());
    	
    	assertSame(
    			"Request schema has been injected. The current state is not indicating that!",
    			_class._schemaInjected,
    			_class._state);    	
    }
    
    /**
     * Tests the injection of a valid schema on a QpidClass.
     * 
     * <br>precondition : a valid arguments is injected on the qpid class.
     * <br>postcondition : class definition is built successfully.
     */
    public void testSchemaInjectionOk() throws UnableToBuildFeatureException
    {
    	_class = new QpidClass(TestConstants.EXCHANGE_CLASS_NAME,TestConstants.HASH,_package)
    	{
    		@Override
    		void requestSchema() throws Exception 
    		{
    			// DO NOTHING : QMan is not running and therefore the schema will be manually injected.
    		}
    		
    		 @Override
    		void updateInstanceWithConfigurationData(QpidManagedObject instance, byte[] rawData) 
    		{
    			// DO NOTHING Given raw data is not valid so it cannot be converted.
			}    		
    	};
    	
        // Incoming configuration data : that will fire the schema request and a state change 
    	// from schema-not-requested to schema-requested-but-not-injected
    	_class.addConfigurationData(TestConstants.OBJECT_ID, TestConstants.TEST_RAW_DATA);
    	
        // I must be sure that what is obvious for me it's obvious for QMan... :)
        assertSame(
    			"Request schema has been requested but not yet injected. The current state is not indicating that!",
    			_class._schemaRequestedButNotYetInjected,
    			_class._state);
        
        List<Map<String,Object>> propertyDefinitions = new ArrayList<Map<String,Object>>(2);
        propertyDefinitions.add(
        		createProperty(
	        		TestConstants.AGE_ATTRIBUTE_NAME, 
	        		1, 
	        		TestConstants.YEARS, 
	        		TestConstants.SAMPLE_MIN_VALUE, 
	        		TestConstants.SAMPLE_MAX_VALUE, 
	        		null,
	        		TestConstants.AGE_ATTRIBUTE_DESCRIPTION, 
	        		TestConstants._1,
	        		false, 
	        		TestConstants._0));
      
        propertyDefinitions.add(
        		createProperty(
	        		TestConstants.SURNAME_ATTRIBUTE_NAME, 
	        		TestConstants.SAMPLE_ACCESS_CODE,
	        		null,
	        		null, 
	        		null, 
	        		TestConstants.SAMPLE_MAX_VALUE,
	        		TestConstants.SURNAME_ATTRIBUTE_DESCRIPTION, 
	        		TestConstants._1,
	        		true, 
	        		TestConstants._1));
        
        _class.setSchema(propertyDefinitions, TestConstants.EMPTY_STATISTICS_SCHEMA, TestConstants.EMPTY_METHODS_SCHEMA);
        
        assertEquals(2,_class._properties.size());
        
        QpidProperty property = _class._properties.get(TestConstants.AGE_ATTRIBUTE_NAME);
        assertEquals(TestConstants.AGE_ATTRIBUTE_NAME,property.getName());
        assertEquals(AccessMode.RC,property.getAccessMode());
        assertEquals(TestConstants.YEARS,property.getUnit());
        assertEquals(TestConstants.SAMPLE_MIN_VALUE,property.getMinValue());
        assertEquals(TestConstants.SAMPLE_MAX_VALUE,property.getMaxValue());
        assertEquals(Integer.MIN_VALUE,property.getMaxLength());
        assertEquals(TestConstants.AGE_ATTRIBUTE_DESCRIPTION,property.getDescription());
        assertEquals(Short.class,property.getJavaType());
        assertFalse(property.isOptional());
        
        property = _class._properties.get(TestConstants.SURNAME_ATTRIBUTE_NAME);
        assertEquals(TestConstants.SURNAME_ATTRIBUTE_NAME,property.getName());
        assertEquals(AccessMode.RC,property.getAccessMode());
        assertNull(property.getUnit());
        assertEquals(Integer.MIN_VALUE,property.getMinValue());
        assertEquals(Integer.MIN_VALUE,property.getMaxValue());
        assertEquals(TestConstants.SAMPLE_MAX_VALUE,property.getMaxLength());
        assertEquals(TestConstants.SURNAME_ATTRIBUTE_DESCRIPTION,property.getDescription());
        assertEquals(Short.class,property.getJavaType());
        assertTrue(property.isOptional());
        
        MBeanInfo mbeanInfo = _class._metadata;
        assertEquals(TestConstants.EXCHANGE_CLASS_NAME,mbeanInfo.getClassName());
        
        MBeanAttributeInfo [] attributes = mbeanInfo.getAttributes();
        assertEquals(2,attributes.length);
        
        MBeanAttributeInfo attribute = attributes[0];
        assertEquals(TestConstants.AGE_ATTRIBUTE_NAME,attribute.getName());
        assertEquals(TestConstants.AGE_ATTRIBUTE_DESCRIPTION,attribute.getDescription());
        assertFalse(attribute.isWritable());
        assertTrue(attribute.isReadable());
        assertEquals(Short.class.getName(),attribute.getType());
        
        attribute = attributes[1];
        assertEquals(TestConstants.SURNAME_ATTRIBUTE_NAME,attribute.getName());
        assertEquals(TestConstants.SURNAME_ATTRIBUTE_DESCRIPTION,attribute.getDescription());
        assertFalse(attribute.isWritable());
        assertTrue(attribute.isReadable());
        assertEquals(Short.class.getName(),attribute.getType());
    }
        
    /**
     * Tests the behaviour of the class when a schema request can't be made.
     * 
     * <br>precondition : class must be in "schema-not-requested" state when incoming data arrives.
     * <br>postcondition : no exception is thrown and no state transition happens.
     */
    public void testStateChange_withRequestSchemaFailure()
    {
    	_class= new QpidClass(TestConstants.EXCHANGE_CLASS_NAME,TestConstants.HASH,_package)
    	{
    		@Override
    		void requestSchema() throws Exception {
    			throw new Exception();
    		}
    		
    		@Override
    		void setSchema(
    				List<Map<String, Object>> propertyDefinitions,
    				List<Map<String, Object>> statisticDefinitions,
    				List<MethodOrEventDataTransferObject> methodDefinitions) throws UnableToBuildFeatureException 
    		{
    		}
    	};
    	
    	assertSame(
    			"Initial state must be schema-not-requested.",
    			_class._schemaNotRequested,
    			_class._state);

    	_class.addInstrumentationData(TestConstants.OBJECT_ID, TestConstants.TEST_RAW_DATA);
    	
    	assertSame(
    			"Current state must be still schema-not-requested.",
    			_class._schemaNotRequested,
    			_class._state);
    }

    /**
     * Tests the behaviour of the class when a schema injection fails.
     * 
     * <br>precondition : class must be in "schema-not-requested" state when incoming data arrives.
     * <br>postcondition : an exception is thrown and no state transition happens.
     */
    public void testStateChange_withSchemaInjectionFailure()
    {
    	_class = new QpidClass(TestConstants.EXCHANGE_CLASS_NAME,TestConstants.HASH,_package)
    	{
    		@Override
    		void requestSchema() throws Exception 
    		{
    			// DO NOTHING.
    		}
    		
    		@Override
    		void setSchema(List<Map<String, Object>> propertyDefinitions,
    				List<Map<String, Object>> statisticDefinitions,
    				List<MethodOrEventDataTransferObject> methodDefinitions)
    				throws UnableToBuildFeatureException 
    		{
    			throw new UnableToBuildFeatureException("");
    		}
    	};
    	
    	assertSame(
    			"Initial state must be schema-not-requested.",
    			_class._schemaNotRequested,
    			_class._state);
    	
    	_class.addInstrumentationData(TestConstants.OBJECT_ID, TestConstants.TEST_RAW_DATA);
    	
      	assertSame(
    			"Request schema has been requested but not yet injected. The current state is not indicating that!",
    			_class._schemaRequestedButNotYetInjected,
    			_class._state);
      	
      	try {
			_class.setSchema(
					TestConstants.EMPTY_PROPERTIES_SCHEMA,
					TestConstants.EMPTY_STATISTICS_SCHEMA,
					TestConstants.EMPTY_METHODS_SCHEMA);
			fail("If we are here something was wrong becuase the setSchema() of this event is throwing an exception...");
		} catch (UnableToBuildFeatureException expected) {
	      	assertSame(
	    			"Request schema has been requested but not yet injected. The current state is not indicating that!",
	    			_class._schemaRequestedButNotYetInjected,
	    			_class._state);
		}
    }    
    
    private Map<String,Object> createProperty(
    		String name,
    		Integer accessCode, 
    		String unit,
    		Integer min, 
    		Integer max, 
    		Integer maxLength,
    		String description,
    		Integer type,
    		boolean optional,
    		Integer index)
    {
        Map <String,Object> result = new HashMap<String, Object>();
        result.put(QpidFeatureBuilder.Attribute.name.name(),name);
        result.put(QpidFeatureBuilder.Attribute.access.name(), accessCode);

        if (unit != null)
        {
        	result.put(QpidFeatureBuilder.Attribute.unit.name(),unit);
        }
        
        if (min != null)
        {
        	result.put(QpidFeatureBuilder.Attribute.min.name(), min);
        }
        
        if (max != null) 
        {
            result.put(QpidFeatureBuilder.Attribute.max.name(),max);        	
        }

        if (maxLength != null) 
        {
            result.put(QpidFeatureBuilder.Attribute.maxlen.name(),maxLength);        	
        }
        
        result.put(QpidFeatureBuilder.Attribute.desc.name(), description);
        result.put(QpidFeatureBuilder.Attribute.type.name(), type);
        result.put(QpidFeatureBuilder.Attribute.optional.name(),optional ? 1 : 0);

        if (index != null)
        {
        	result.put(QpidFeatureBuilder.Attribute.index.name(), index);
        }
    	return result;
    }    
}
