/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2012 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.report.extensions.ipojo.instances.internal;

import org.apache.felix.ipojo.architecture.HandlerDescription;
import org.apache.felix.ipojo.architecture.InstanceDescription;
import org.apache.felix.ipojo.metadata.Attribute;
import org.apache.felix.ipojo.metadata.Element;
import org.ow2.jonas.ipojo.interceptor.IInstanceProvider;
import org.ow2.jonas.ipojo.interceptor.ComponentInstanceState;
import org.ow2.jonas.report.api.IReportExtension;
import org.ow2.jonas.report.extensions.ipojo.instances.generated.DateTimeType;
import org.ow2.jonas.report.extensions.ipojo.instances.generated.IpojoInstanceType;
import org.ow2.jonas.report.extensions.ipojo.instances.generated.IpojoInstances;
import org.ow2.jonas.report.extensions.ipojo.instances.generated.PropertyType;
import org.ow2.jonas.report.extensions.ipojo.instances.generated.ProvideType;
import org.ow2.jonas.report.extensions.ipojo.instances.generated.ProvidesType;
import org.ow2.jonas.report.extensions.ipojo.instances.generated.RequirementType;
import org.ow2.jonas.report.extensions.ipojo.instances.generated.RequirementsType;
import org.ow2.jonas.service.report.util.Utility;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Represents an iPOJO instances report extension
 * @author Jeremy Cazaux
 */
public class IpojoInstancesReportExtension implements IReportExtension {

    /**
     * The logger
     */
    public static final Log logger = LogFactory.getLog(IpojoInstancesReportExtension.class);

    /**
     * Path to the XSD
     */
    public static final String XSD_PATH = "xsd/ipojo-instances-report.xsd";

    /**
     * Required handler name
     */
    public static final String REQUIRED_HANDLER_NAME = "org.apache.felix.ipojo:requires";

    /**
     * Provides handler name
     */
    public static final String PROVIDES_HANDLER_NAME = "org.apache.felix.ipojo:provides";

    /**
     * EOL
     */
    public static final String EOL = "\n";

    /**
     * List of {@link org.ow2.jonas.ipojo.interceptor.IInstanceProvider}
     */
    private List<IInstanceProvider> instanceProviders;

    /**
     * Default constructor
     */
    public IpojoInstancesReportExtension() {
        this.instanceProviders = new ArrayList<IInstanceProvider>();
    }

    /**
     * {@inheritDoc}
     */
    public Object generateReport() {
        IpojoInstances ipojoInstances = new IpojoInstances();
        List<IpojoInstanceType> ipojoServiceTypeList = ipojoInstances.getIpojoInstance();
        for (IInstanceProvider instanceProvider : this.instanceProviders) {
            IpojoInstanceType ipojoServiceType = new IpojoInstanceType();
            ipojoServiceType.setName(instanceProvider.getName());
            ipojoServiceType.setState(instanceProvider.getStateComponent().toString());

            Date date = instanceProvider.getStartTime();
            if (date != null) {
                DateTimeType startTime = new DateTimeType();
                startTime.setTimestamp(BigInteger.valueOf(date.getTime()));
                startTime.setDateTime(Utility.date2XMLGregorianCalendar(date));
                ipojoServiceType.setStartTime(startTime);
            }

            Long upTimeLong = instanceProvider.getUpTime();
            if (upTimeLong != null) {
                date = new Date(upTimeLong);
                if (date != null) {
                    DateTimeType upTime = new DateTimeType();
                    upTime.setDateTime(Utility.date2XMLGregorianCalendar(date));
                    upTime.setTimestamp(BigInteger.valueOf(date.getTime()));
                    ipojoServiceType.setUpTime(upTime);
                }
            }

            date = instanceProvider.getShutdownTime();
            if (date != null) {
                DateTimeType shutdownTime = new DateTimeType();
                shutdownTime.setDateTime(Utility.date2XMLGregorianCalendar(date));
                shutdownTime.setTimestamp(BigInteger.valueOf(date.getTime()));
                ipojoServiceType.setShutdownTime(shutdownTime);
            }

            String source = instanceProvider.getSource();
            if (source != null) {
                ipojoServiceType.setSource(source);
            }
            
            ProvidesType providesType = new ProvidesType();
            List<ProvideType> provides = providesType.getProvide();              
            RequirementsType requirementsType = new RequirementsType();
            List<RequirementType> requirements = requirementsType.getRequirement();

            //update requirements and provides properties
            boolean isInstanceValid = ComponentInstanceState.VALID.toString().equals(ipojoServiceType.getState());
            InstanceDescription instanceDescription = instanceProvider.getInstanceDescription();
            HandlerDescription handlerDescriptions[] = instanceDescription.getHandlers();
            if (handlerDescriptions != null) {
                for (HandlerDescription handlerDescription: handlerDescriptions) {
                    if (handlerDescription != null) {
                        String handlerName = handlerDescription.getHandlerName();

                        if (!isInstanceValid && REQUIRED_HANDLER_NAME.equals(handlerName)) {

                            RequirementType requirement = new RequirementType();

                            Element element = handlerDescription.getHandlerInfo();
                            if (element != null) {
                                Element elements[] = element.getElements("requires");
                                if (elements != null) {
                                    Element requireElt = elements[0];
                                    Attribute[] attributes = requireElt.getAttributes();
                                    for (Attribute attribute: attributes) {
                                        String name = attribute.getName();
                                        String value = attribute.getValue();

                                        if ("id".equals(name)) {
                                            requirement.setId(value);
                                        } else if ("".equals(name)) {
                                            requirement.setAggregate(value);
                                        } else if ("instance.name".equals(name)) {
                                            requirement.setInstanceName(value);
                                        } else if ("optional".equals(name)) {
                                            requirement.setOptional(value);
                                        } else if ("specification".equals(name)) {
                                            requirement.setSpecification(value);
                                        } else if ("optional".equals(name)) {
                                            requirement.setOptional(value);
                                        } else if ("state".equals(name)) {
                                            requirement.setState(value);
                                        } else if ("nullable".equals(name)) {
                                            requirement.setNullable(value);
                                        } else if ("binding-policy".equals(name)) {
                                            requirement.setBindingPolicy(value);
                                        } else if ("proxy".equals(name)) {
                                            requirement.setProxy(value);
                                        }
                                    }

                                }
                            }

                            requirements.add(requirement);
                        } else if (isInstanceValid && PROVIDES_HANDLER_NAME.equals(handlerName)) {
                            ProvideType provide = new ProvideType();

                            Element element = handlerDescription.getHandlerInfo();
                            if (element != null) {

                                Element elements[] = element.getElements("provides");
                                if (elements != null) {
                                    Element provideElt = elements[0];
                                    Attribute[] attributes = provideElt.getAttributes();
                                    for (Attribute attribute: attributes) {
                                        String name = attribute.getName();
                                        String value = attribute.getValue();

                                        if ("specifications".equals(name)) {
                                            provide.setSpecifications(value);
                                        } else if ("state".equals(name)) {
                                            provide.setState(value);
                                        }
                                    }

                                    List<PropertyType> properties = provide.getProperty();
                                    for (Element child: provideElt.getElements()) {

                                        if ("property".equals(child.getName())) {
                                            PropertyType property = new PropertyType();
                                            property.setName(child.getAttribute("name"));
                                            property.setValue(child.getAttribute("value"));
                                            properties.add(property);
                                        }
                                    }
                                }
                            }

                            provides.add(provide);
                        }
                    }
                }
            }

            //set the bundle id associated to the iPOJO instance
            ipojoServiceType.setBundleId(instanceDescription.getBundleId());

            //set the name of the iPOJO object
            Element element = null;
            try {
                element = instanceDescription.getDescription();
            } catch (NullPointerException e) {
                //TODO iPOJO bug - see FELIX-3836
                //do nothing
            }

            if (element != null) {
                Element[] elements = element.getElements("object");
                if (elements != null && elements.length > 0) {
                    String objectName = elements[0].getAttribute("name");
                    if (objectName != null) {
                        ipojoServiceType.setObjectName(objectName);
                    }
                }
            }

            //set provides properties if the instance is valid. Otherwise set requirements
            if (isInstanceValid) {
                ipojoServiceType.setProvides(providesType);
            } else {
                ipojoServiceType.setRequirements(requirementsType);
                ipojoServiceType.setArch(EOL + instanceDescription.getDescription().toString() + EOL);
            }

            ipojoServiceTypeList.add(ipojoServiceType);
        }
        
        return ipojoInstances;
    }

    /**
     * {@inheritDoc}
     */
    public Source getXsd() {
        InputStream xsd = getRootClass().getClassLoader().getResourceAsStream(XSD_PATH);
        Source source = null;
        if (xsd != null) {
            source = new StreamSource(xsd);
        } else {
            logger.error("Cannot find XSD " + XSD_PATH);
        }
        return source;
    }

    /**
     * {@inheritDoc}
     */
    public Class getRootClass() {
        return IpojoInstances.class;
    }

    /**
     * @param instanceProvider The {@link org.ow2.jonas.ipojo.interceptor.IInstanceProvider} to bind
     */
    public void bindServiceProvider(final IInstanceProvider instanceProvider) {
        this.instanceProviders.add(instanceProvider);
    }

    /**
     * @param instanceProvider The {@link org.ow2.jonas.ipojo.interceptor.IInstanceProvider} to unbind
     */
    public void unbindServiceProvider(final IInstanceProvider instanceProvider) {
        this.instanceProviders.remove(instanceProvider);
    }
}
