package de.ivu.elect.nl.eml;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;

import javax.annotation.Nullable;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Element;

import de.ivu.wahl.wus.xmlsecurity.SHAXmlDigestCreator;


import static de.ivu.elect.nl.eml.LocaleTimeZone.LOCALE;
import static de.ivu.elect.nl.eml.LocaleTimeZone.TIME_ZONE;
import static java.util.Optional.of;

public class XmlHelper {

  public static <E> List<E> getElements(Class<E> clazz, List<Object> objects) {
    List<E> result = new ArrayList<>();

    if (objects != null) {
      for (Object o : objects) {
        if (clazz.isInstance(o)) {
          result.add((E) o);
        } else if (o instanceof JAXBElement) {
          Object value = ((JAXBElement) o).getValue();
          if (clazz.isInstance(value)) {
            result.add((E) value);
          }
        }
      }
    }
    return result;
  }

  public static <E> Optional<E> getElement(Class<E> clazz, List<Object> objects) {
    List<E> elements = getElements(clazz, objects);
    if (elements.isEmpty()) {
      return Optional.empty();
    }
    return of(elements.get(0));
  }

  public static <E> E getOrCreateElement(Class<E> clazz, List<Object> objects, Supplier<JAXBElement<E>> factory) {
    Optional<E> element = getElement(clazz, objects);
    if (element.isPresent()) {
      return element.get();
    }

    objects.add(factory.get());

    element = getElement(clazz, objects);
    if (element.isPresent()) {
      return element.get();
    }
    throw new IllegalStateException("Cannot happen " + clazz + ", " + objects);
  }

  @Nullable
  public static Element geKiesraadElement(List<Object> objects, String tagName) {
    return (Element) objects.stream().filter(object -> object instanceof Element).filter(object -> tagName.equals(((Element) object).getTagName())).findFirst()
        .orElse(null);
  }

  public static Map<String, Object> createMarshallerProperties(boolean prettyPrint) {
    Map<String, Object> result = new HashMap<>();

    result.put(Marshaller.JAXB_FORMATTED_OUTPUT, prettyPrint); // ELECTVAPP-492 und ELECTVAPP-984
    // ELECT-9717 funktioniert nicht WildFly 19 - result.put("com.sun.xml.bind.namespacePrefixMapper", new EmlNamespacePrefixMapper());
    return result;
  }

  public static String createHash(InputStream is) {
    try {
      return new SHAXmlDigestCreator().createDigest(is);
    } catch (Exception e) {
      throw new RuntimeException(e.getMessage(), e);
    }
  }

  public static void conditionalCanonicalizeXml(InputStream is, OutputStream os) {
    try {
      new SHAXmlDigestCreator().conditionalCanonicalizeXml(is, os);
    } catch (Exception e) {
      throw new RuntimeException(e.getMessage(), e);
    }
  }



  @Nullable
  public static XMLGregorianCalendar mappeDateTimeToCalendar(Date date) {
    if (date == null) {
      return null;
    }

    GregorianCalendar c = new GregorianCalendar(TIME_ZONE, LOCALE);
    c.setTime(date);
    try {
      XMLGregorianCalendar xmlGregorianCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(c);
      // ELECTVAPP-740
      xmlGregorianCalendar.setTimezone(DatatypeConstants.FIELD_UNDEFINED);

      return xmlGregorianCalendar;
    } catch (DatatypeConfigurationException e) {
      throw new RuntimeException(e.getMessage(), e);
    }

  }
  @Nullable
  public static XMLGregorianCalendar mappeDateTimeToCalendarDate(Date date) {
    if (date == null) {
      return null;
    }

    GregorianCalendar c = new GregorianCalendar(TIME_ZONE, LOCALE);
    c.setTime(date);
    try {
      XMLGregorianCalendar xmlGregorianCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(c);
      // ELECTVAPP-740
      xmlGregorianCalendar.setTimezone(DatatypeConstants.FIELD_UNDEFINED);
      xmlGregorianCalendar.setTime(DatatypeConstants.FIELD_UNDEFINED, DatatypeConstants.FIELD_UNDEFINED, DatatypeConstants.FIELD_UNDEFINED);

      return xmlGregorianCalendar;
    } catch (DatatypeConfigurationException e) {
      throw new RuntimeException(e.getMessage(), e);
    }

  }

  private static final TransformerFactory TRANSFORMER_FACTORY;

  static {
    TRANSFORMER_FACTORY = TransformerFactory.newInstance();
    try {
      TRANSFORMER_FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); // Compliant
    } catch (Exception ignore) {
      //jaxp 1.5 feature not supported
    }
    try {
      TRANSFORMER_FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); // Compliant
    } catch (Exception ignore) {
      //jaxp 1.5 feature not supported
    }
  }

  public static String elementToString(Element e) {
    try {

      Transformer transformer = TRANSFORMER_FACTORY.newTransformer();
      // Uncomment if you do not require XML declaration
      // transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

      //A character stream that collects its output in a string buffer,
      //which can then be used to construct a string.
      try (StringWriter writer = new StringWriter()) {

        //transform document to string
        transformer.transform(new DOMSource(e), new StreamResult(writer));
        return writer.getBuffer().toString();
      }
    } catch (Exception ex) {
      throw new RuntimeException(ex.getMessage(), ex);
    }
  }


}
