/*
* Copyright 2014 - LAit SpA
*				 Realizzato da Icona Management srl su commessa LAit SpA
* Copyright 2015- LAit SpA
*                                                  Aggiornato da LAit SpA
*
* Concesso in licenza a norma dell'EUPL, versione 1.1 o
* successive dell'EUPL (la "Licenza")– non appena saranno
* approvate dalla Commissione europea;
* Non è possibile utilizzare l'opera salvo nel rispetto della Licenza.
* È possibile ottenere una copia della Licenza al seguente indirizzo:
*
* http://ec.europa.eu/idabc/eupl
*
* Salvo diversamente indicato dalla legge applicabile o
* concordato per iscritto, il software distribuito secondo
* i termini della Licenza è distribuito "TAL QUALE",
* SENZA GARANZIE O CONDIZIONI DI ALCUN TIPO,
* esplicite o implicite.
* Si veda la Licenza per la lingua specifica che disciplina
* le autorizzazioni e le limitazioni secondo i termini della
* Licenza.
*/
package it.laitspa.xsd.resolver;

import it.laitspa.cpf.util.log.LogUtils;
import org.apache.log4j.Logger;
import org.apache.xerces.jaxp.validation.XMLSchemaFactory;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import javax.xml.bind.*;
import javax.xml.bind.util.JAXBSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.*;
import java.text.MessageFormat;

public class XmlMarshaller<T>
{

  private boolean                hasErrors = false;
  private StringBuilder          buffer;

  private T                      result;

  public static transient Logger logger;

  static
  {
    logger = Logger.getLogger(XmlMarshaller.class);
  }

  public XmlMarshaller()
  {
    buffer = new StringBuilder();
  }
  public T unmarshal(Class<T> docClass, InputStream inputStream, InputStream xsd)
    throws JAXBException, SAXException
  {
    return unmarshal(docClass, inputStream, xsd, null);
  }

  @SuppressWarnings("unchecked")
  public T
      unmarshal(Class<T> docClass, InputStream inputStream, InputStream xsd, String charsetName)
        throws JAXBException, SAXException
  {
    String packageName = docClass.getPackage().getName();
    JAXBContext jc = JAXBContext.newInstance(packageName);
    Unmarshaller u = jc.createUnmarshaller();

    if(xsd != null) {
            // Setting the Validation
            //
            Schema schema;
            SchemaFactory schemaFactory = new XMLSchemaFactory();

            // associate the schema factory with the resource resolver, which is
            // responsible for resolving the imported XSD's
            //
            schemaFactory.setResourceResolver(new ResourceResolver());

            schema = schemaFactory.newSchema(new StreamSource(xsd));
            u.setSchema(schema);

            hasErrors = false;
            result = null;

            u.setEventHandler(new ValidationEventHandler() {

                @Override
                public boolean handleEvent(ValidationEvent event) {
                    hasErrors = true;
                    MessageFormat mf = new MessageFormat("LINE: {0} COLUMN: {1} ERROR: {2}\n");
                    String error =
                            mf.format(new Object[]{
                                    event.getLocator().getLineNumber(), event.getLocator().getColumnNumber(),
                                    event.getMessage()});
                    buffer.append(error);
                    return true;
                }
            });
    }
    InputStreamReader r = new InputStreamReader(inputStream);
    if(charsetName != null)
    {
      try
      {
        r = new InputStreamReader(inputStream, charsetName);
      }
      catch(UnsupportedEncodingException e)
      {
        LogUtils.error(getClass(), "Unsupported charset encoding. " + charsetName);
      }
    }
    JAXBElement<T> doc = (JAXBElement<T>)u.unmarshal(r);

    if(hasErrors)
    {
      logger.info("ERRORI trovati:");
      logger.info(buffer.toString());
    }

    this.result = doc.getValue();
    return result;
  }

  @SuppressWarnings("unchecked")
  public void writeDocument(JAXBElement<T> document, OutputStream os)
    throws JAXBException, IOException
  {
    Class<T> clazz = (Class<T>)document.getValue().getClass();
    JAXBContext context = JAXBContext.newInstance(clazz.getPackage().getName());
    Marshaller m = context.createMarshaller();
    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    m.marshal(document, os);
  }

  public boolean isHasErrors()
  {
    return hasErrors;
  }

  public StringBuilder getBuffer()
  {
    return buffer;
  }

  public void setHasErrors(boolean hasErrors)
  {
    this.hasErrors = hasErrors;
  }

  public void setBuffer(StringBuilder sb)
  {
    this.buffer = sb;
  }

  public T getResult()
  {
    return result;
  }

  public void setResult(T result)
  {
    this.result = result;
  }

  public static String readText(String s)
  {
    if(s == null || s.trim().length() == 0)
      return null;
    else
      return s;
  }

  public static <K> K readJAXBProperty(JAXBElement<K> element)
  {

    if(element == null)
      return null;

    K value = element.getValue();
    if(value == null)
      return null;
    else
      return value;
  }

  public String validate(Class<T> docClass, T x_object, InputStream xsd)
    throws JAXBException, SAXException, IOException
  {
    String packageName = docClass.getPackage().getName();
    JAXBContext jc = JAXBContext.newInstance(packageName);
    JAXBSource source = new JAXBSource(jc, x_object);

    // Setting the Validation
    //
    Schema schema;
    SchemaFactory schemaFactory = new XMLSchemaFactory();

    // associate the schema factory with the resource resolver, which is
    // responsible for resolving the imported XSD's
    //
    schemaFactory.setResourceResolver(new ResourceResolver());

    schema = schemaFactory.newSchema(new StreamSource(xsd));
    Validator validator = schema.newValidator();
    validator.setErrorHandler(new ErrorHandler()
      {

        @Override
        public void warning(SAXParseException exception)
          throws SAXException
        {
          // Nothing.
          //
          MessageFormat mf = new MessageFormat("LINE: {0} COLUMN: {1} ERROR: {2}\n");
          String error =
              mf.format(new Object[] {
                  exception.getLineNumber(), exception.getColumnNumber(), exception.getMessage() });
          LogUtils.warn(getClass(), error);
        }

        @Override
        public void fatalError(SAXParseException exception)
          throws SAXException
        {
          hasErrors = true;
          MessageFormat mf = new MessageFormat("LINE: {0} COLUMN: {1} ERROR: {2}\n");
          String error =
              mf.format(new Object[] {
                  exception.getLineNumber(), exception.getColumnNumber(), exception.getMessage() });
          buffer.append(error);
        }

        @Override
        public void error(SAXParseException exception)
          throws SAXException
        {
          hasErrors = true;
          MessageFormat mf = new MessageFormat("LINE: {0} COLUMN: {1} ERROR: {2}\n");
          String error =
              mf.format(new Object[] {
                  exception.getLineNumber(), exception.getColumnNumber(), exception.getMessage() });
          buffer.append(error);
        }
      });

    hasErrors = false;
    buffer = new StringBuilder();

    validator.validate(source);

    if(hasErrors)
    {
      logger.info("ERRORI trovati:");
      logger.info(buffer.toString());
      return buffer.toString();
    }
    else
      return null;
  }

}
