package de.fhg.iais.roberta.util.jaxb;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;

import de.fhg.iais.roberta.blockly.generated.Block;
import de.fhg.iais.roberta.blockly.generated.BlockSet;
import de.fhg.iais.roberta.blockly.generated.Hide;
import de.fhg.iais.roberta.util.dbc.DbcException;

public class JaxbHelper {
    private static final Logger LOG = LoggerFactory.getLogger(JaxbHelper.class);

    private static final Schema blockSetSchema;
    private static final JAXBContext jaxbContext;
    static {
        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        InputStream xsdStream = JaxbHelper.class.getResourceAsStream("/blockly.xsd");
        StreamSource xsdSource = new StreamSource(xsdStream);
        try {
            blockSetSchema = sf.newSchema(xsdSource);
            jaxbContext = JAXBContext.newInstance(BlockSet.class);
        } catch ( Exception e ) {
            LOG.error("1. from blockly.xsd no schema could be generated or 2. JAXBContext could not be created", e);
            throw new RuntimeException("from blockly.xsd no schema could be generated");
        }
    }

    private JaxbHelper() {
        // no objects
    }

    /**
     * return the BlockSet generated by a jaxb unmarshaller for a given blockly XML string.
     *
     * @param blocklyXml the blockly XML as String
     * @return the BlockSet instance corresponding to the XML
     */
    public static BlockSet xml2BlockSet(String blocklyXml) throws JAXBException {
        Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
        jaxbUnmarshaller.setSchema(blockSetSchema);

        InputStream stream = new ByteArrayInputStream(blocklyXml.getBytes(StandardCharsets.UTF_8));
        InputSource src = new InputSource(stream);
        return (BlockSet) jaxbUnmarshaller.unmarshal(src);
    }

    /**
     * return the element of type T generated by an jaxb unmarshaller for a given blockly XML string.<br>
     * <b>This call is expensive. Use it not too often. It is ok to use it for the import/export of XML, for example.</b>
     *
     * @param xml the XML as String
     * @param clazz the element class, an object of which should be returned
     * @return the element instance corresponding to the XML
     */
    @SuppressWarnings("unchecked")
    public static <T> T xml2Element(String xml, Class<T> clazz) throws JAXBException {
        JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
        Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
        jaxbUnmarshaller.setSchema(blockSetSchema);
        return (T) jaxbUnmarshaller.unmarshal(new InputSource(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))));
    }

    /**
     * return the XML generated by a jaxb marshaller for a given BlockSet.
     *
     * @param blockSet the BlockSet from which XML has to be generated for
     * @return the XML as a String
     * @throws Exception
     */
    public static String blockSet2xml(BlockSet blockSet) throws Exception {
        Marshaller jaxbmarshaller = jaxbContext.createMarshaller();
        jaxbmarshaller.setProperty("com.sun.xml.bind.xmlDeclaration", Boolean.FALSE);
        jaxbmarshaller.setSchema(blockSetSchema);
        StringWriter writer = new StringWriter();
        jaxbmarshaller.marshal(blockSet, writer);
        return writer.toString();
    }

    /**
     * return the BlockSet generated by a jaxb unmarshaller for a file-path to blockly XML.
     *
     * @param pathToblocklyXml the file path to a blockly XML
     * @return the BlockSet instance corresponding to the XML
     * @throws Exception
     */
    public static BlockSet path2BlockSet(String pathToblocklyXml) throws Exception {
        return xml2BlockSet(IOUtils.toString(JaxbHelper.class.getResourceAsStream(pathToblocklyXml), "UTF-8"));
    }

    public static Hide getHideFromBlock(Block block) {
        List<Hide> hide = block.getHide();
        if ( hide == null || hide.size() == 0 ) {
            return null;
        } else if ( hide.size() == 1 ) {
            return hide.get(0);
        } else {
            throw new DbcException("more than 1 hide element");
        }
    }

}
