/*
 * Copyright (C) 2010 Teleal GmbH, Switzerland
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.teleal.cling.support.contentdirectory.descriptor;

import org.teleal.cling.support.contentdirectory.descriptor.didl.DIDLLite;
import org.teleal.cling.support.contentdirectory.descriptor.didl.Container;
import org.teleal.cling.support.contentdirectory.descriptor.didl.Item;
import org.teleal.cling.support.contentdirectory.descriptor.didl.Desc;
import org.teleal.cling.support.contentdirectory.descriptor.didl.Res;
import java.util.logging.Logger;
import java.util.logging.Level;

import javax.xml.bind.Unmarshaller;
import javax.xml.bind.Marshaller;
import javax.xml.bind.JAXBContext;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Schema;
import java.util.List;
import java.util.ArrayList;
import java.io.StringReader;
import java.io.StringWriter;

/**
 *
 */
public class DIDLParser {

    private static Logger log = Logger.getLogger(DIDLParser.class.getName());

    public static final String DIDL_XSD_RESOURCE = "org/teleal/cling/support/contentdirectory/descriptor/didl-lite-1.0.xsd";

    final protected JAXBContext jaxbContext;
    final protected SchemaFactory schemaFactory = SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI);
    final protected Schema didlSchema;
    protected boolean validating = true, generateFragment = true, generateFormatted = true;

    public DIDLParser() {
        this(true, true, true);
    }

    public DIDLParser(boolean validating) {
        this(validating, true, true);
    }

    public DIDLParser(boolean validating, boolean generateFragment, boolean generateFormatted) {
        this.validating = validating;
        this.generateFragment = generateFragment;
        this.generateFormatted = generateFormatted;

        try {
            jaxbContext = JAXBContext.newInstance(
                    org.teleal.cling.support.contentdirectory.descriptor.didl.ObjectFactory.class,
                    org.teleal.cling.support.contentdirectory.descriptor.dc.ObjectFactory.class,
                    org.teleal.cling.support.contentdirectory.descriptor.upnp.ObjectFactory.class
            );
            didlSchema = schemaFactory.newSchema(
                    Thread.currentThread().getContextClassLoader().getResource(
                            DIDL_XSD_RESOURCE
                    )
            );
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    public JAXBContext getJaxbContext() {
        return jaxbContext;
    }

    public SchemaFactory getSchemaFactory() {
        return schemaFactory;
    }

    public Schema getDidlSchema() {
        return didlSchema;
    }

    public boolean isValidating() {
        return validating;
    }

    public boolean isGenerateFragment() {
        return generateFragment;
    }

    public boolean isGenerateFormatted() {
        return generateFormatted;
    }

    public Marshaller createMarshaller() throws Exception {
        Marshaller marshaller = jaxbContext.createMarshaller();
        marshaller.setProperty("com.sun.xml.internal.bind.namespacePrefixMapper", new DidlNamespacePrefixMapper());
        if (isGenerateFragment()) marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
        if (isGenerateFormatted()) marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        if (isValidating()) marshaller.setSchema(didlSchema);
        return marshaller;
    }

    public Unmarshaller createUnmarshaller() throws Exception {
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        if (isValidating()) unmarshaller.setSchema(didlSchema);
        return unmarshaller;
    }

    public DIDLLite readResource(String resource) throws Exception {
        return (DIDLLite) createUnmarshaller().unmarshal(
                Thread.currentThread().getContextClassLoader().getResourceAsStream(
                        resource
                )
        );
    }

    public DIDLLite readWriter(StringWriter writer) throws Exception {
        return (DIDLLite) createUnmarshaller().unmarshal(new StringReader(writer.toString()));
    }

    public DIDLLite read(String xml) throws Exception {
        return (DIDLLite) createUnmarshaller().unmarshal(new StringReader(xml));
    }

    public void debugXML(StringWriter writer) {
        debugXML(writer.toString());
    }

    public void debugXML(String s) {
        if (log.isLoggable(Level.FINE)) {
            log.fine("-------------------------------------------------------------------------------------");
            log.fine("\n" + s);
            log.fine("-------------------------------------------------------------------------------------");
        }
    }

    // Static utility methods

    public static boolean hasContainers(DIDLLite didl) {
        return didl.getChildren().size() > 0 && didl.getChildren().get(0) instanceof Container;
    }

    public static boolean hasItems(DIDLLite didl) {
        return didl.getChildren().size() > 0 && didl.getChildren().get(0) instanceof Item;
    }

    public static boolean hasDescriptors(DIDLLite didl) {
        return didl.getChildren().size() > 0 && didl.getChildren().get(0) instanceof Desc;
    }

    public static boolean hasContainers(Container container) {
        for (Object child : container.getChildren()) {
            if (child instanceof Container) return true;
        }
        return false;
    }

    public static boolean hasItems(Container container) {
        for (Object child : container.getChildren()) {
            if (child instanceof Item) return true;
        }
        return false;
    }

    public static boolean hasDescriptors(Container container) {
        for (Object child : container.getChildren()) {
            if (child instanceof Desc) return true;
        }
        return false;
    }

    public static boolean hasResources(Container container) {
        for (Object child : container.getChildren()) {
            if (child instanceof Res) return true;
        }
        return false;
    }

    public static <C> List<C> getChildren(DIDLLite didl, Class<C> type) {
        List<C> children = new ArrayList();
        for (Object o : didl.getChildren()) {
            if (type.isAssignableFrom(o.getClass())) {
                children.add((C) o);
            }
        }
        return children;
    }

    public static <C> List<C> getChildren(Container container, Class<C> type) {
        List<C> children = new ArrayList();
        for (Object o : container.getChildren()) {
            if (type.isAssignableFrom(o.getClass())) {
                children.add((C) o);
            }
        }
        return children;
    }

    public static <C> List<C> getChildren(Item item, Class<C> type) {
        List<C> children = new ArrayList();
        for (Object o : item.getChildren()) {
            if (type.isAssignableFrom(o.getClass())) {
                children.add((C) o);
            }
        }
        return children;
    }

    public static <C> C getFirstChild(DIDLLite didl, Class<C> type) {
        for (Object o : didl.getChildren()) {
            if (type.isAssignableFrom(o.getClass())) {
                return (C) o;
            }
        }
        return null;
    }

    public static <C> C getFirstChild(Container container, Class<C> type) {
        for (Object o : container.getChildren()) {
            if (type.isAssignableFrom(o.getClass())) {
                return (C) o;
            }
        }
        return null;
    }

    public static <C> C getFirstChild(Item item, Class<C> type) {
        for (Object o : item.getChildren()) {
            if (type.isAssignableFrom(o.getClass())) {
                return (C) o;
            }
        }
        return null;
    }

}
