/*
 * 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.log4j.chainsaw.receivers;


import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.chainsaw.plugins.PluginClassLoaderFactory;
import org.apache.log4j.plugins.Plugin;
import org.apache.log4j.plugins.PluginRegistry;
import org.apache.log4j.plugins.Receiver;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.LoggerRepositoryEx;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;


/**
 * Helper class to assisit with all the known Receivers.
 * <p>
 * A local resource 'known.receivers' is read in on initialization
 * with each line representing the FQN of the Class that is a recognised Receiver.
 *
 * @author Paul Smith &lt;psmith@apache.org&gt;
 */
public class ReceiversHelper {

    private static final ReceiversHelper instance = new ReceiversHelper();

    private final Logger logger = LogManager.getLogger(ReceiversHelper.class);
    private List<Class> receiverClassList = new ArrayList<>();

    /**
     *
     */
    private ReceiversHelper() {

        URL url = this.getClass().getClassLoader().getResource(
            this.getClass().getPackage().getName().replace('.', '/') + "/known.receivers");
        if (url == null) {
            logger.warn("Failed to locate known.receivers file");
            return;
        }
        LineNumberReader stream = null;
        try {

            stream = new LineNumberReader(new InputStreamReader(url.openStream()));
            String line;
            // we need the special Classloader, because under Web start, optional jars might be local
            // to this workstation
            ClassLoader classLoader = PluginClassLoaderFactory.getInstance().getClassLoader();

            while ((line = stream.readLine()) != null) {

                try {
                    if (line.startsWith("#") || (line.length() == 0)) {
                        continue;
                    }
                    Class receiverClass = classLoader.loadClass(line);
                    receiverClassList.add(receiverClass);
                    logger.debug("Located known Receiver class " + receiverClass.getName());
                } catch (ClassNotFoundException e) {
                    logger.warn("Failed to locate Receiver class:" + line);
                } catch (NoClassDefFoundError e) {
                    logger.error("Failed to locate Receiver class:" + line + ", looks like a dependent class is missing from the classpath", e);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException ioe) {
                    ioe.printStackTrace();
                }
            }
        }
    }


    public static ReceiversHelper getInstance() {
        return instance;
    }


    /**
     * Returns an unmodifiable list of Class objects which represent all the 'known'
     * Receiver classes.
     *
     * @return known receiver classes
     */
    public List getKnownReceiverClasses() {
        return Collections.unmodifiableList(receiverClassList);
    }


    public void saveReceiverConfiguration(File file) {
        LoggerRepository repo = LogManager.getLoggerRepository();
        PluginRegistry pluginRegistry = ((LoggerRepositoryEx) repo).getPluginRegistry();
        List<Plugin> fullPluginList = pluginRegistry.getPlugins();
        List<Plugin> pluginList = new ArrayList<>();
        for (Object aFullPluginList : fullPluginList) {
            Plugin thisPlugin = (Plugin) aFullPluginList;
            if (thisPlugin instanceof Receiver) {
                pluginList.add(thisPlugin);
            }
        }
        //remove everything that isn't a receiver..otherwise, we'd create an empty config file
        try {
            if (pluginList.size() > 0) {
                //we programmatically register the ZeroConf plugin in the plugin registry
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                factory.setNamespaceAware(true);
                DocumentBuilder builder = factory.newDocumentBuilder();
                Document document = builder.newDocument();
                Element rootElement = document.createElementNS("http://jakarta.apache.org/log4j/", "configuration");
                rootElement.setPrefix("log4j");
                rootElement.setAttribute("xmlns:log4j", "http://jakarta.apache.org/log4j/");
                rootElement.setAttribute("debug", "true");

                for (Object aPluginList : pluginList) {
                    Receiver receiver;

                    if (aPluginList instanceof Receiver) {
                        receiver = (Receiver) aPluginList;
                    } else {
                        continue;
                    }

                    Element pluginElement = document.createElement("plugin");
                    pluginElement.setAttribute("name", receiver.getName());
                    pluginElement.setAttribute("class", receiver.getClass().getName());

                    BeanInfo beanInfo = Introspector.getBeanInfo(receiver.getClass());
                    List<PropertyDescriptor> list = new ArrayList<>(Arrays.asList(beanInfo.getPropertyDescriptors()));

                    for (Object aList : list) {
                        PropertyDescriptor d = (PropertyDescriptor) aList;
                        //don't serialize the loggerRepository property for subclasses of componentbase..
                        //easier to change this than tweak componentbase right now..
                        if (d.getReadMethod().getName().equals("getLoggerRepository")) {
                            continue;
                        }
                        Object o = d.getReadMethod().invoke(receiver);
                        if (o != null) {
                            Element paramElement = document.createElement("param");
                            paramElement.setAttribute("name", d.getName());
                            paramElement.setAttribute("value", o.toString());
                            pluginElement.appendChild(paramElement);
                        }
                    }

                    rootElement.appendChild(pluginElement);

                }

                TransformerFactory transformerFactory = TransformerFactory.newInstance();
                Transformer transformer = transformerFactory.newTransformer();
                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
                DOMSource source = new DOMSource(rootElement);
                FileOutputStream stream = new FileOutputStream(file);
                StreamResult result = new StreamResult(stream);
                transformer.transform(source, result);
                stream.close();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
