/**
 * 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.service.report.internal;

import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.ow2.jonas.endpoint.collector.util.Util;
import org.ow2.jonas.event.provider.api.Event;
import org.ow2.jonas.event.provider.api.EventLevel;
import org.ow2.jonas.event.provider.api.IEventProvider;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.service.AbsServiceImpl;
import org.ow2.jonas.report.api.IReport;
import org.ow2.jonas.report.api.IReportExtension;
import org.ow2.jonas.report.generated.DateTimeType;
import org.ow2.jonas.report.generated.EventType;
import org.ow2.jonas.report.generated.EventsType;
import org.ow2.jonas.report.generated.InfoType;
import org.ow2.jonas.service.ServiceException;
import org.ow2.jonas.service.report.util.Utility;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.xml.sax.SAXException;

import javax.management.ObjectName;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.File;
import java.io.InputStream;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Represents a JOnAS report
 * @author Jeremy Cazaux
 */
public class Report extends AbsServiceImpl implements IReport, ReportMBean {

    /**
     * The {@link JmxService}
     */
    private JmxService jmxService;

    /**
     * {@link ObjectName} of the JOnAS report service
     */
    private ObjectName objectName;

    /**
     * The {@link IEventProvider}
     */
    private IEventProvider eventProvider;

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

    /**
     * JOnAS Base directory
     */
    public static final File JONAS_BASE_DIR = new File(System.getProperty("jonas.base"));

    /**
     * JOnAS report directory
     */
    public static final File JONAS_REPORTS_DIR = new File(JONAS_BASE_DIR, REPORTS_DIRNAME);

    /**
     * Dash
     */
    public static final String DASH = "-";

    /**
     * Map between a {@link IReportExtension} and its associated namespace
     */
    private Map<IReportExtension, String> reportExtensions;

    /**
     * Default constructor
     */
    public Report(final BundleContext bundleContext) {
        this.reportExtensions = new HashMap<IReportExtension, String>();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doStart() throws ServiceException {

        // Load the Descriptors
        this.jmxService.loadDescriptors(getClass().getPackage().getName(), getClass().getClassLoader());

        //create a new ObjectName associate to the Model MBean
        this.objectName = Util.getObjectName(this.jmxService.getDomainName() + ":type=service,name=report");

        //register the MBean
        if (this.objectName != null) {
            try {
                this.jmxService.registerModelMBean(this, this.objectName);
            } catch (Exception e) {
                logger.error("Could not register JOnAS report model MBean.", e);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doStop() throws ServiceException {
        //generate report & unregister the Model MBean
        generateReport();

        if (this.jmxService != null) {
            //unregister Model MBeans
            this.jmxService.unregisterModelMBean(this.objectName);
        }
    }

    /**
     * {@inheritDoc}
     */
    public String generateReport() {
        //create reports directory if !exist
        if (!JONAS_REPORTS_DIR.exists()) {
            JONAS_REPORTS_DIR.mkdirs();
        }

        //create the report with a timestamp
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss.SSSZ");
        String timestamp = simpleDateFormat.format(date);
        File logFile = new File(JONAS_REPORTS_DIR, "report" + DASH + timestamp + ".log");

        EventsType eventsType = new EventsType();
        List<EventType> eventTypeList = eventsType.getEvent();
        
        //generate the report        
        if (this.eventProvider != null) {
            List<EventLevel> eventLevels = new ArrayList<EventLevel>();
            eventLevels.add(EventLevel.INFO);
            eventLevels.add(EventLevel.EXCEPTION);
            eventLevels.add(EventLevel.SEVERE);
            eventLevels.add(EventLevel.WARNING);
            List<Event> events = this.eventProvider.getEvents(eventLevels);
            if (events != null) {
                for (Event event: events) {
                    EventType eventType = new EventType();
                    eventType.setType(event.getType());
                    eventType.setValue(event.getValue());
                    eventType.setTimestamp(event.getTimestamp());
                    eventType.setSource(event.getSource());
                    eventTypeList.add(eventType);
                }
            }
        }
        
        InfoType info = new InfoType();

        //set the current time
        DateTimeType currentTime = new DateTimeType();
        currentTime.setDateTime(Utility.date2XMLGregorianCalendar(date));
        currentTime.setTimestamp(BigInteger.valueOf(date.getTime()));
        info.setCurrentTime(currentTime);

        //list of java classes to be recognized by the new {@link JAXBContext}.
        List<Class> classes = new ArrayList<Class>();
        classes.add(org.ow2.jonas.report.generated.Report.class);

        //set reports extensions
        for (IReportExtension reportExtension: this.reportExtensions.keySet()) {
            Object report = reportExtension.generateReport();
            if (report != null) {
                info.getAny().add(report);
                Class clazz = reportExtension.getRootClass();
                if (clazz != null) {
                    classes.add(clazz);
                }
            }
        }

        org.ow2.jonas.report.generated.Report report = new org.ow2.jonas.report.generated.Report();
        report.setInfo(info);
        report.setEvents(eventsType);

        String rootClass = org.ow2.jonas.report.generated.Report.class.getPackage().getName();

        // marshall report
        JAXBContext jaxbContext  = null;
        try {
            jaxbContext = JAXBContext.newInstance(classes.toArray(new Class[classes.size()]));
        } catch (JAXBException e) {
            logger.error("Cannot create a new instance of JAXBContext object with rootClass " + rootClass, e);
        }

        if (jaxbContext != null) {
            Marshaller marshaller = null;
            try {
                marshaller = jaxbContext.createMarshaller();
            } catch (JAXBException e) {
                logger.error("Cannot create instanciate Marshaller from the JAXBContext " + jaxbContext, e);
            }

            if (marshaller != null) {
                List<Source> sources = new ArrayList<Source>();
                InputStream xsd = org.ow2.jonas.report.generated.Report.class.getClassLoader().getResourceAsStream(XSD_PATH);
                Source streamSource = null;
                if (xsd != null) {
                    streamSource = new StreamSource(xsd);
                } else {
                    logger.error("Cannot find XSD " + XSD_PATH);
                }
                sources.add(streamSource);
                for (IReportExtension reportExtension: this.reportExtensions.keySet()) {
                    Source source = reportExtension.getXsd();
                    if (source != null) {
                        sources.add(source);
                    } else {
                        logger.error("Cannot get XSD source of extension " + reportExtension.getClass().getName());
                    }
                }
                
                SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
                Schema schema = null;
                if (streamSource != null) {
                    try {
                        schema = schemaFactory.newSchema(sources.toArray(new Source[sources.size()]));
                    } catch (SAXException e) {
                        logger.error("Cannot create a new instance of Schema object with the given XSD " + XSD_PATH, e);
                    }
                    marshaller.setSchema(schema);
                }

                try {
                    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
                } catch (PropertyException e) {
                    logger.error("Cannot set property " + Marshaller.JAXB_FORMATTED_OUTPUT + " to " + Boolean.TRUE, e);
                }

                try {
                    marshaller.marshal(report, logFile);
                } catch (JAXBException e) {
                    logger.error("Cannot marshall the JOnAS report", e);
                }
            }
        }

        return logFile.getAbsolutePath();
    }

    /**
     * @param jmxService The {@link JmxService} to bind
     */
    public void bindJmxService(final JmxService jmxService) {
        this.jmxService = jmxService;
    }

    /**
     * @param jmxService The {@link JmxService} to unbind
     */
    public void unbindJmxService(final JmxService jmxService) {
        //this.jmxService = null;
    }

    /**
     * @param eventProvider The {@link IEventProvider} to bind
     */
    public void bindEventProvider(final IEventProvider eventProvider) {
        this.eventProvider = eventProvider;
    }

    /**
     * @param reportExtension The {@link IReportExtension} to bind
     * @param serviceReference The associated {@link ServiceReference}
     */
    public void bindReportExtension(final IReportExtension reportExtension, final ServiceReference serviceReference) {
        if (reportExtension != null) {
            String namespace = null;
            Object object = serviceReference.getProperty("namespace"); 
            if (object != null) {
                namespace = String.valueOf(object);
            }
            this.reportExtensions.put(reportExtension, namespace);
        }
    }

    /**
      * @param reportExtension The {@link IReportExtension to unbind}
     */
    public void unbindReportExtension(final IReportExtension reportExtension) {
        this.reportExtensions.remove(reportExtension);
    }
}
