/*
 * 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.dm.annotation.plugin.bnd;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import aQute.bnd.osgi.Analyzer;
import aQute.bnd.osgi.Clazz;
import aQute.bnd.osgi.EmbeddedResource;
import aQute.bnd.osgi.Resource;
import aQute.bnd.osgi.Clazz.QUERY;
import aQute.bnd.osgi.Descriptors.TypeRef;

/**
 * This helper parses all classes which contain DM annotations, and generates the corresponding component descriptors.
 * 
 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
 */
public class DescriptorGenerator
{
    /**
     * This is the bnd analyzer used to lookup classes containing DM annotations.
     */
    private Analyzer m_analyzer;

    /**
     * This is the generated Dependency Manager descriptors. The hashtable key is the path
     * to a descriptor. The value is a bnd Resource object which contains the content of a
     * descriptor. 
     */
    Map<String, Resource> m_resources = new HashMap<String, Resource>();

    /**
     * This is the generated MetaType XML descriptor, if any Properties/Property annotations have been found.
     */
    private Resource m_metaTypeResource;

    /**
     * Object used to collect logs.
     */
    private final Logger m_logger;

    /**
     * List of imported services found from every ServiceDependency annotations.
     */
    private Set<String> m_importService = new HashSet<String>();

    /**
     * List of exported services found from every service providing components.
     */
    private Set<String> m_exportService = new HashSet<String>();

    /**
     * Creates a new descriptor generator.
     * @param analyzer The bnd analyzer used to lookup classes containing DM annotations.
     * @param debug 
     */
    public DescriptorGenerator(Analyzer analyzer, Logger logger)
    {
        m_analyzer = analyzer;
        m_logger = logger;
    }

    /**
     * Starts the scanning.
     * @return true if some annotations were successfully parsed, false if not. corresponding generated 
     * descriptors can then be retrieved by invoking the getDescriptors/getDescriptorPaths methods.
     */
    public boolean execute() throws Exception
    {
        boolean annotationsFound = false;
        // Try to locate any classes in the wildcarded universe
        // that are annotated with the DependencyManager "Service" annotations.
        Collection<Clazz> expanded = m_analyzer.getClasses("",
                                                           // Parse everything
                                                           QUERY.NAMED.toString(), "*");
            
        for (Clazz c : expanded)
        {
            AnnotationCollector reader = new AnnotationCollector(m_logger, m_analyzer);
            reader.baseClass(true);
            m_logger.debug("scanning class %s", c.getClassName());
            c.parseClassFileWithCollector(reader);

            // parse inherited annotations.
            while (reader.getSuperClass() != null) {
            	Clazz superClazz = m_analyzer.findClass(reader.getSuperClass());
            	if (superClazz == null) {
            		m_logger.error("Can't find super class %s from %s", reader.getSuperClass(), c.getClassName());
            		break;
            	}
            	if (isObject(reader.getSuperClass()) || isScalaObject(reader.getSuperClass())) {
            		/* don't scan java.lang.Object or scala object ! */
            		break;
            	}

            	m_logger.debug("scanning super class %s for class %s", reader.getSuperClass(), c.getClassName());
            	reader.baseClass(false);
            	superClazz.parseClassFileWithCollector(reader);				
            }

            if (reader.finish())
            {
                // And store the generated component descriptors in our resource list.
                String name = c.getFQN();
                Resource resource = createComponentResource(reader);
                m_resources.put("META-INF/dependencymanager/" + name, resource);
                annotationsFound = true;
                
                m_importService.addAll(reader.getImportService());
                m_exportService.addAll(reader.getExportService());
            }
        }

        return annotationsFound;
    }

	/**
     * Returns the path of the descriptor.
     * @return the path of the generated descriptors.
     */
    public String getDescriptorPaths()
    {
        StringBuilder descriptorPaths = new StringBuilder();
        String del = "";
        for (Map.Entry<String, Resource> entry : m_resources.entrySet())
        {
            descriptorPaths.append(del);
            descriptorPaths.append(entry.getKey());
            del = ",";
        }
        return descriptorPaths.toString();
    }

    /**
     * Returns the list of the generated descriptors.
     * @return the list of the generated descriptors.
     */
    public Map<String, Resource> getDescriptors()
    {
        return m_resources;
    }

    /**
     * Returns the MetaType resource.
     */
    public Resource getMetaTypeResource() {
        return m_metaTypeResource;
    }
    
    /**
     * Returns set of all imported services. Imported services are deduced from every
     * @ServiceDependency annotations.
     * @return the list of imported services
     */
    public Set<String> getImportService()
    {
        return m_importService;
    }

    /**
     * Returns set of all exported services. Imported services are deduced from every
     * annotations which provides a service (@Component, etc ...)
     * @return the list of exported services
     */
    public Set<String> getExportService()
    {
        return m_exportService;
    }    
    
    /**
     * Tests if a given type is java.lang.Object
     */
    private boolean isObject(TypeRef typeRef) {
    	return (typeRef.isJava());
	}
    
    /**
     * Tests if a given type is scala object.
     */
    private boolean isScalaObject(TypeRef typeRef) {
    	return (typeRef.getBinary().equals("scala/ScalaObject"));
    }

    /**
     * Creates a bnd resource that contains the generated dm descriptor.
     * @param collector 
     * @return
     * @throws IOException
     */
    private Resource createComponentResource(AnnotationCollector collector) throws IOException
    {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
        collector.writeTo(pw);
        pw.close();
        byte[] data = out.toByteArray();
        out.close();
        return new EmbeddedResource(data, 0);
    }
    
}